pax_global_header 0000666 0000000 0000000 00000000064 14056157201 0014513 g ustar 00root root 0000000 0000000 52 comment=fa4ba476841b9ca4891848dfe96e42336f54f932
luaparse-0.3.1/ 0000775 0000000 0000000 00000000000 14056157201 0013330 5 ustar 00root root 0000000 0000000 luaparse-0.3.1/.editorconfig 0000664 0000000 0000000 00000000521 14056157201 0016003 0 ustar 00root root 0000000 0000000 # EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[**.{js,json,md,html}]
indent_style = space
indent_size = 2
[Makefile]
indent_style = tab
luaparse-0.3.1/.gitattributes 0000664 0000000 0000000 00000000013 14056157201 0016215 0 ustar 00root root 0000000 0000000 * text=auto luaparse-0.3.1/.github/ 0000775 0000000 0000000 00000000000 14056157201 0014670 5 ustar 00root root 0000000 0000000 luaparse-0.3.1/.github/workflows/ 0000775 0000000 0000000 00000000000 14056157201 0016725 5 ustar 00root root 0000000 0000000 luaparse-0.3.1/.github/workflows/ci.yml 0000664 0000000 0000000 00000012173 14056157201 0020047 0 ustar 00root root 0000000 0000000 name: CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 15.x
uses: actions/setup-node@v2
with:
node-version: 15.x
- run: npm ci
- run: make lint complexity-analysis
run-tests-node:
name: Test in Node.js
needs: lint
runs-on: ubuntu-latest
strategy:
matrix:
version: [10.x, 12.x, 14.x, 15.x]
steps:
- uses: actions/checkout@v2
- name: Install Node.js ${{ matrix.version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.version }}
- run: node test/runner.js --console
run-tests-rhino:
name: Test in Rhino
needs: lint
runs-on: ubuntu-latest
strategy:
matrix:
version: [1.7.13]
steps:
- uses: actions/checkout@v2
- name: Download Rhino ${{ matrix.version }}
run: |
VERSION="${{ matrix.version }}"
wget "https://github.com/mozilla/rhino/releases/download/Rhino${VERSION//./_}_Release/rhino-$VERSION.jar" -O /opt/rhino.jar
- name: Run tests
run: cd test; java -jar /opt/rhino.jar -opt -1 runner.js
run-tests-ringojs:
name: Test in RingoJS
needs: lint
runs-on: ubuntu-latest
strategy:
matrix:
version: [2.0.0]
steps:
- uses: actions/checkout@v2
- name: Download and unpack RingoJS ${{ matrix.version }}
run: |
VERSION="${{ matrix.version }}"
wget "https://github.com/ringo/ringojs/releases/download/v${VERSION}/ringojs-${VERSION}.zip"
unzip "ringojs-${VERSION}.zip" -d /opt/
rm "ringojs-${VERSION}.zip"
- name: Install RingoJS ${{ matrix.version }}
run: |
VERSION="${{ matrix.version }}"
sed -i -e '/-jar/ i \ -Dringo.classpath="$RINGO_HOME/lib/**" \\' "/opt/ringojs-${VERSION}/bin/ringo"
sudo ln -s "/opt/ringojs-${VERSION}/bin/ringo" /usr/local/bin/ringo
sudo chmod +x /usr/local/bin/ringo
- name: Run tests
run: ringo -o -1 test/runner.js 2>&1
run-tests-quickjs:
name: Test in QuickJS
needs: lint
runs-on: ubuntu-latest
strategy:
matrix:
version: ['2020-09-06', '2020-11-08', '2021-03-27']
steps:
- uses: actions/checkout@v2
- name: Install QuickJS ${{ matrix.version }}
run: |
wget "https://bellard.org/quickjs/binary_releases/quickjs-linux-x86_64-${{ matrix.version }}.zip" -O quickjs.zip
unzip quickjs.zip qjs
sudo install -Dm755 qjs /usr/local/bin/qjs
- name: Run tests
# XXX: The default QuickJS stack size limit is 256 KiB;
# QuickJS likes to throw stack overflow errors when running the test suite at that limit,
# so we increase it to 8 MiB. The issue is intermittent and non-deterministic;
# it may or may not be an engine bug
run: cd test; qjs --stack-size 8388608 --std runner.js
run-tests-duktape:
name: Test in Duktape
needs: lint
runs-on: ubuntu-latest
strategy:
matrix:
version: [2.4.0, 2.5.0, 2.6.0]
steps:
- uses: actions/checkout@v2
- name: Build and install Duktape ${{ matrix.version }}
run: |
PACKAGE="duktape-${{ matrix.version }}"
cd /opt
wget "https://duktape.org/$PACKAGE.tar.xz"
tar xf "$PACKAGE.tar.xz"
make -C "$PACKAGE/" -f Makefile.cmdline DEFINES=-DDUK_CMDLINE_FILEIO
sudo install -Dm755 "$PACKAGE/duk" /usr/local/bin/duk
rm -rf "$PACKAGE.tar.xz" "$PACKAGE/"
- name: Run tests
run: cd test; duk runner.js
check-coverage:
name: Coverage analysis
needs: [run-tests-node, run-tests-rhino, run-tests-ringojs, run-tests-quickjs, run-tests-duktape]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm ci
- run: make coverage-analysis
update-docs:
name: Update documentation
if: ${{ github.event_name == 'push' && github.event.ref == 'refs/heads/master' }}
needs: check-coverage
runs-on: ubuntu-latest
steps:
- name: Check out GitHub Pages
uses: actions/checkout@v2
with:
ref: refs/heads/gh-pages
path: .
- name: Check out luaparse
uses: actions/checkout@v2
with:
path: luaparse
- name: Rebuild documentation
run: |
rm -rf coverage examples lib benchmarks
sed -i -e 's/\.\.\///g' luaparse/docs/layout/head.html luaparse/docs/*.md
make -C luaparse install docs
mv luaparse/docs/* .
mv luaparse/examples .
mv luaparse/luaparse.js .
mv luaparse/benchmarks .
cp -rf luaparse/test .
rm -rf *.md layout luaparse public
- name: Upload updated documentation to GitHub Pages
run: |
git add .
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "GitHub Actions"
git commit --message "Rebuild documentation"
git push origin gh-pages
luaparse-0.3.1/.github/workflows/release.yml 0000664 0000000 0000000 00000001764 14056157201 0021100 0 ustar 00root root 0000000 0000000 name: Upload release
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 12
- run: npm ci
- run: npm test
publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 12
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
publish-gpr:
needs: build
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 12
registry-url: https://npm.pkg.github.com/
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
luaparse-0.3.1/.gitignore 0000664 0000000 0000000 00000000132 14056157201 0015314 0 ustar 00root root 0000000 0000000 node_modules
docs/*
!docs/*.md
!docs/*.1
!docs/layout/
dist
logs
components
npm-debug.log
luaparse-0.3.1/.jshintrc 0000664 0000000 0000000 00000000465 14056157201 0015162 0 ustar 00root root 0000000 0000000 {
"curly": false,
"eqeqeq": true,
"forin": true,
"immed": true,
"latedef": false,
"newcap": true,
"noarg": true,
"noempty": true,
"nonew": true,
"regexp": true,
"undef": true,
"trailing": true,
"sub": true,
"boss": true,
"eqnull": true,
"laxcomma": true,
"loopfunc": false
}
luaparse-0.3.1/LICENSE 0000664 0000000 0000000 00000002054 14056157201 0014336 0 ustar 00root root 0000000 0000000 Copyright (c) Oskar Schöldström 2012-2014
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, 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.
luaparse-0.3.1/Makefile 0000664 0000000 0000000 00000004575 14056157201 0015003 0 ustar 00root root 0000000 0000000 DOCS := docs/*.md
PROCESSOR ?= "/opt/v8/tools/linux-tick-processor"
LIB := ./node_modules
BIN := $(LIB)/.bin
all: build
# Main tasks
# ----------
build:
$(BIN)/gulp build
lint:
$(BIN)/gulp lint
.PHONY: build lint all
# Install and internal updates
# ----------------------------
install:
npm install
# This is required if mocha, expect or benchmark is updated.
update:
cp -v $(LIB)/spec/lib/* test/lib/
cp -v $(LIB)/benchmark/benchmark.js test/lib/
version-bump:
$(BIN)/gulp version-bump
git add luaparse.js
.PHONY: install update version-bump
# Tests
# -----
test-node:
node test/runner.js --console
test:
$(BIN)/testem ci
testem-engines:
$(BIN)/testem -l node,ringo,rhino,rhino1.7R5
# Scaffold all test files in the scaffolding dir.
scaffold-tests:
$(foreach file,\
$(notdir $(wildcard test/scaffolding/*)),\
$(MAKE) scaffold-test FILE=$(file);\
)
scaffold-test:
./scripts/scaffold-test --name=$(FILE) \
test/scaffolding/$(FILE) \
> test/spec/$(FILE).js
.PHONY: test-node test testem-engines scaffold-tests scaffold-test
# Documentation
# -------------
docs: coverage docs-test docs-md
docs-index:
$(BIN)/marked README.md --gfm \
| cat docs/layout/head.html - docs/layout/foot.html \
> docs/index.html
docs-md: docs-index $(patsubst %.md,%.html, $(wildcard docs/*.md))
%.html: %.md
echo $<
$(BIN)/marked $< --gfm \
| cat docs/layout/head.html - docs/layout/foot.html \
> $@
.PHONY: docs docs-test docs-index
# Coverage
# --------
coverage:
rm -rf html-report docs/coverage
$(BIN)/nyc --reporter=html --report-dir=docs/coverage node test/runner.js --console >/dev/null
.PHONY: coverage
# Benchmark
# ---------
benchmark:
./scripts/benchmark -v benchmarks/lib/ParseLua.lua
profile:
bash benchmarks/run.sh -v --processor $(PROCESSOR) --profile HEAD
benchmark-previous:
bash benchmarks/run.sh --js HEAD HEAD~1
.PHONY: benchmark profile benchmark-previous
# Quality Assurance
# -----------------
complexity-analysis:
@echo "===================== Complexity analysis ============================"
./scripts/complexity 10
$(BIN)/cr -lws --maxcc 22 luaparse.js
coverage-analysis: coverage
$(BIN)/nyc check-coverage --statements 100 --branches 100 --functions 100
qa:
$(MAKE) test lint complexity-analysis coverage-analysis
clean:
rm -f docs/*.html
rm -rf lib-cov coverage html-report docs/coverage/
.PHONY: complexity-analysis coverage-analysis qa clean
luaparse-0.3.1/README.md 0000664 0000000 0000000 00000023207 14056157201 0014613 0 ustar 00root root 0000000 0000000 # luaparse
A Lua parser written in JavaScript, originally written by Oskar Schöldström for his bachelor's thesis at Arcada.
## Installation
Install through `npm install luaparse`.
## Usage
CommonJS
```js
var parser = require('luaparse');
var ast = parser.parse('i = 0');
console.log(JSON.stringify(ast));
```
AMD
```js
require(['luaparse'], function(parser) {
var ast = parser.parse('i = 0');
console.log(JSON.stringify(ast));
});
```
Browser
```html
```
## Parser Interface
Basic usage:
```js
luaparse.parse(code, options);
```
The output of the parser is an Abstract Syntax Tree (AST) formatted in JSON.
The available options are:
- `wait: false` Explicitly tell the parser when the input ends.
- `comments: true` Store comments as an array in the chunk object.
- `scope: false` Track identifier scopes.
- `locations: false` Store location information on each syntax node.
- `ranges: false` Store the start and end character locations on each syntax
node.
- `onCreateNode: null` A callback which will be invoked when a syntax node
has been completed. The node which has been created will be passed as the
only parameter.
- `onCreateScope: null` A callback which will be invoked when a new scope is
created.
- `onDestroyScope: null` A callback which will be invoked when the current
scope is destroyed.
- `onLocalDeclaration: null` A callback which will be invoked when a local
variable is declared. The identifier will be passed as the only parameter.
- `luaVersion: '5.1'` The version of Lua the parser will target; supported
values are `'5.1'`, `'5.2'`, `'5.3'` and `'LuaJIT'`.
- `extendedIdentifiers: false` Whether to allow code points ≥ U+0080 in
identifiers, like LuaJIT does. **Note:** setting `luaVersion: 'LuaJIT'`
currently does *not* enable this option; this may change in the future.
- `encodingMode: 'none'` Defines the relation between code points ≥ U+0080
appearing in parser input and raw bytes in source code, and how Lua escape
sequences in JavaScript strings should be interpreted. See the
[Encoding modes](#encoding-modes) section below for more information.
The default options are also exposed through `luaparse.defaultOptions` where
they can be overriden globally.
There is a second interface which might be preferable when using the `wait`
option.
```js
var parser = luaparse.parse({ wait: true });
parser.write('foo = "');
parser.write('bar');
var ast = parser.end('"');
```
This would be identical to:
```js
var ast = luaparse.parse('foo = "bar"');
```
### AST format
If the following code is executed:
```js
luaparse.parse('foo = "bar"');
```
then the returned value will be:
```js
{
"type": "Chunk",
"body": [
{
"type": "AssignmentStatement",
"variables": [
{
"type": "Identifier",
"name": "foo"
}
],
"init": [
{
"type": "StringLiteral",
"value": "bar",
"raw": "\"bar\""
}
]
}
],
"comments": []
}
```
### Encoding modes
Unlike strings in JavaScript, Lua strings are not Unicode strings, but
bytestrings (sequences of 8-bit values); likewise, implementations of Lua
parse the source code as a sequence of octets. However, the input to this
parser is a JavaScript string, i.e. a sequence of 16-bit code units (not
necessarily well-formed UTF-16). This poses a problem of how those code
units should be interpreted, particularly if they are outside the Basic
Latin block ('ASCII').
The `encodingMode` option specifies how these issues should be handled.
Possible values are as follows:
- `'none'`: Source code characters all pass through as-is and string
literals are not interpreted at all; the string literal nodes contain
the value `null`. This is the default mode.
- `'x-user-defined'`: Source code has been decoded with the WHATWG
`x-user-defined` encoding; escapes of bytes in the range \[0x80, 0xff]
are mapped to the Unicode range \[U+F780, U+F7FF].
- `'pseudo-latin1'`: Source code has been decoded with the IANA
`iso-8859-1` encoding; escapes of bytes in the range \[0x80, 0xff]
are mapped to Unicode range \[U+0080, U+00FF]. Note that this is
**not** the same as how WHATWG standards define the `iso-8859-1`
encoding, which is to say, as a synonym of `windows-1252`.
### Custom AST
The default AST structure is somewhat inspired by the Mozilla Parser API but
can easily be overriden to customize the structure or to inject custom logic.
`luaparse.ast` is an object containing all functions used to create the AST, if
you for example wanted to trigger an event on node creations you could use the
following:
```js
var luaparse = require('luaparse'),
events = new (require('events').EventEmitter);
Object.keys(luaparse.ast).forEach(function(type) {
var original = luaparse.ast[type];
luaparse.ast[type] = function() {
var node = original.apply(null, arguments);
events.emit(node.type, node);
return node;
};
});
events.on('Identifier', function(node) { console.log(node); });
luaparse.parse('i = "foo"');
```
_this is only an example to illustrate what is possible and this particular
example might not suit your needs as the end location of the node has not been
determined yet. If you desire events you should use the `onCreateNode` callback
instead)._
### Lexer
The lexer used by luaparse can be used independently of the recursive descent
parser. The lex function is exposed as `luaparse.lex()` and it will return the
next token up until `EOF` is reached.
Each token consists of:
- `type` expressed as an enum flag which can be matched with `luaparse.tokenTypes`.
- `value`
- `line`, `lineStart`
- `range` can be used to slice out raw values, eg. `foo = "bar"` will return a
`StringLiteral` token with the value `bar`. Slicing out the range on the other
hand will return `"bar"`.
```js
var parser = luaparse.parse('foo = "bar"', { wait: true });
parser.lex(); // { type: 8, value: "foo", line: 1, lineStart: 0, range: [0, 3] }
parser.lex(); // { type: 32, value: "=", line: 1, lineStart: 0, range: [4, 5]}
parser.lex(); // { type: 2, value: "bar", line: 1, lineStart: 0, range: [6, 11] }
parser.lex(); // { type: 1, value: "", line: 1, lineStart: 0, range: [11 11] }
parser.lex(); // { type: 1, value: "", line: 1, lineStart: 0, range: [11 11] }
```
## Examples
Have a look in the [examples directory](https://github.com/fstirlitz/luaparse/tree/master/examples)
of the repository for some code examples or check them out [live](https://fstirlitz.github.io/luaparse/examples.html).
## luaparse(1)
The `luaparse` executable can be used in your shell by installing `luaparse` globally using npm:
```bash
$ npm install -g luaparse
$ luaparse --help
Usage: luaparse [option]... [file|code]...
Options:
-c|--code [code] parse code snippet
-f|--file [file] parse from file
-b|--beautify output an indenteted AST
--[no]-comments store comments. defaults to true
--[no]-scope store variable scope. defaults to false
--[no]-locations store location data on syntax nodes. defaults to false
--[no]-ranges store start and end character locations. defaults to false
-q|--quiet suppress output
-h|--help
-v|--version
--verbose
Examples:
luaparse --no-comments -c "locale foo = \"bar\""
luaparse foo.lua bar.lua
```
Example usage
```bash
$ luaparse "i = 0"
{"type":"Chunk","body":[{"type":"AssignmentStatement","variables":[{"type":"Identifier","name":"i"}],"init":[{"type":"NumericLiteral","value":0,"raw":"0"}]}],"comments":[]}
```
## Support
Has been tested in at least IE6+, Firefox 3+, Safari 4+, Chrome 10+, Opera 10+,
Node 0.4.0+, RingoJS 0.8-0.9, Rhino 1.7R4-1.7R5, Nashorn 1.8.0.
## Quality Assurance
_TL;DR simply run `make qa`. This will run all quality assurance scripts but
assumes you have it set up correctly._
Begin by cloning the repository and installing the development dependencies
with `npm install`.
The luaparse test suite uses [testem](https://github.com/airportyh/testem) as a
test runner, and because of this it's very easy to run the tests using
different javascript engines or even on locally installed browsers.
### Test runners
- `make test` uses node.
- `make testem-engines` uses node, ringo and rhino
1.7R5. This requires that you have the engines installed.
- `make test-node` uses a custom command line reporter to make the output
easier on the eyes while practicing TDD.
- By installing `testem` globally you can also run the tests in a locally
installed browser.
### Other quality assurance measures
- You can check the function complexity using [complexity-report](https://github.com/philbooth/complexityReport.js)
using `make complexity-analysis`
- Running `make coverage` will generate the [coverage report](https://fstirlitz.github.io/luaparse/coverage.html).
To simply check that all code has coverage you can run `make coverage-analysis`.
- `make lint`, `make benchmark`, `make profile`.
### Documentation
By running `make docs` all [documentation](https://fstirlitz.github.io/luaparse/)
will be generated.
## Projects using/extending luaparse
- [luamin](http://mths.be/luamin), a Lua minifier written by Mathias Bynens.
- [Ace](https://github.com/ajaxorg/ace), an online code editor.
## Acknowledgements
* Initial tests are scaffolded from [yueliang][yueliang] and then manually checked for error.
* Much of the code is based on [LuaMinify][luaminify], the [Lua][lua] source and [Esprima][esprima]. All awesome projects.
## License
MIT
[luaminify]: https://github.com/stravant/LuaMinify
[yueliang]: http://yueliang.luaforge.net/
[lua]: https://www.lua.org
[esprima]: http://esprima.org
[wtf8]: https://simonsapin.github.io/wtf-8/
luaparse-0.3.1/benchmarks/ 0000775 0000000 0000000 00000000000 14056157201 0015445 5 ustar 00root root 0000000 0000000 luaparse-0.3.1/benchmarks/lib/ 0000775 0000000 0000000 00000000000 14056157201 0016213 5 ustar 00root root 0000000 0000000 luaparse-0.3.1/benchmarks/lib/ParseLua.lua 0000664 0000000 0000000 00000075310 14056157201 0020440 0 ustar 00root root 0000000 0000000 -- LuaMinify (Mark Langen) https://github.com/stravant/LuaMinify
--
-- ParseLua.lua
--
-- The main lua parser and lexer.
-- LexLua returns a Lua token stream, with tokens that preserve
-- all whitespace formatting information.
-- ParseLua returns an AST, internally relying on LexLua.
--
function lookupify(tb)
for _, v in pairs(tb) do
tb[v] = true
end
return tb
end
function CountTable(tb)
local c = 0
for _ in pairs(tb) do c = c + 1 end
return c
end
function PrintTable(tb, atIndent)
if tb.Print then
return tb.Print()
end
atIndent = atIndent or 0
local useNewlines = (CountTable(tb) > 1)
local baseIndent = string.rep(' ', atIndent+1)
local out = "{"..(useNewlines and '\n' or '')
for k, v in pairs(tb) do
if type(v) ~= 'function' then
out = out..(useNewlines and baseIndent or '')
if type(k) == 'number' then
--nothing to do
elseif type(k) == 'string' and k:match("^[A-Za-z_][A-Za-z0-9_]*$") then
out = out..k.." = "
elseif type(k) == 'string' then
out = out.."[\""..k.."\"] = "
else
out = out.."["..tostring(k).."] = "
end
if type(v) == 'string' then
out = out.."\""..v.."\""
elseif type(v) == 'number' then
out = out..v
elseif type(v) == 'table' then
out = out..PrintTable(v, atIndent+(useNewlines and 1 or 0))
else
out = out..tostring(v)
end
if next(tb, k) then
out = out..","
end
if useNewlines then
out = out..'\n'
end
end
end
out = out..(useNewlines and string.rep(' ', atIndent) or '').."}"
return out
end
local WhiteChars = lookupify{' ', '\n', '\t', '\r'}
local EscapeLookup = {['\r'] = '\\r', ['\n'] = '\\n', ['\t'] = '\\t', ['"'] = '\\"', ["'"] = "\\'"}
local LowerChars = lookupify{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}
local UpperChars = lookupify{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}
local Digits = lookupify{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
local HexDigits = lookupify{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f'}
local Symbols = lookupify{'+', '-', '*', '/', '^', '%', ',', '{', '}', '[', ']', '(', ')', ';', '#'}
local Keywords = lookupify{
'and', 'break', 'do', 'else', 'elseif',
'end', 'false', 'for', 'function', 'goto', 'if',
'in', 'local', 'nil', 'not', 'or', 'repeat',
'return', 'then', 'true', 'until', 'while',
};
function LexLua(src)
--token dump
local tokens = {}
local st, err = pcall(function()
--line / char / pointer tracking
local p = 1
local line = 1
local char = 1
--get / peek functions
local function get()
local c = src:sub(p,p)
if c == '\n' then
char = 1
line = line + 1
else
char = char + 1
end
p = p + 1
return c
end
local function peek(n)
n = n or 0
return src:sub(p+n,p+n)
end
local function consume(chars)
local c = peek()
for i = 1, #chars do
if c == chars:sub(i,i) then return get() end
end
end
--shared stuff
local function generateError(err)
return error(">> :"..line..":"..char..": "..err, 0)
end
local function tryGetLongString()
local start = p
if peek() == '[' then
local equalsCount = 0
while peek(equalsCount+1) == '=' do
equalsCount = equalsCount + 1
end
if peek(equalsCount+1) == '[' then
--start parsing the string. Strip the starting bit
for _ = 0, equalsCount+1 do get() end
--get the contents
local contentStart = p
while true do
--check for eof
if peek() == '' then
generateError("Expected `]"..string.rep('=', equalsCount).."]` near .", 3)
end
--check for the end
local foundEnd = true
if peek() == ']' then
for i = 1, equalsCount do
if peek(i) ~= '=' then foundEnd = false end
end
if peek(equalsCount+1) ~= ']' then
foundEnd = false
end
else
foundEnd = false
end
--
if foundEnd then
break
else
get()
end
end
--get the interior string
local contentString = src:sub(contentStart, p-1)
--found the end. Get rid of the trailing bit
for i = 0, equalsCount+1 do get() end
--get the exterior string
local longString = src:sub(start, p-1)
--return the stuff
return contentString, longString
else
return nil
end
else
return nil
end
end
--main token emitting loop
while true do
--get leading whitespace. The leading whitespace will include any comments
--preceding the token. This prevents the parser needing to deal with comments
--separately.
local leadingWhite = ''
while true do
local c = peek()
if WhiteChars[c] then
--whitespace
leadingWhite = leadingWhite..get()
elseif c == '-' and peek(1) == '-' then
--comment
get();get()
leadingWhite = leadingWhite..'--'
local _, wholeText = tryGetLongString()
if wholeText then
leadingWhite = leadingWhite..wholeText
else
while peek() ~= '\n' and peek() ~= '' do
leadingWhite = leadingWhite..get()
end
end
else
break
end
end
--get the initial char
local thisLine = line
local thisChar = char
local errorAt = ":"..line..":"..char..":> "
local c = peek()
--symbol to emit
local toEmit = nil
--branch on type
if c == '' then
--eof
toEmit = {Type = 'Eof'}
elseif UpperChars[c] or LowerChars[c] or c == '_' then
--ident or keyword
local start = p
repeat
get()
c = peek()
until not (UpperChars[c] or LowerChars[c] or Digits[c] or c == '_')
local dat = src:sub(start, p-1)
if Keywords[dat] then
toEmit = {Type = 'Keyword', Data = dat}
else
toEmit = {Type = 'Ident', Data = dat}
end
elseif Digits[c] or (peek() == '.' and Digits[peek(1)]) then
--number const
local start = p
if c == '0' and peek(1) == 'x' then
get();get()
while HexDigits[peek()] do get() end
if consume('Pp') then
consume('+-')
while Digits[peek()] do get() end
end
else
while Digits[peek()] do get() end
if consume('.') then
while Digits[peek()] do get() end
end
if consume('Ee') then
consume('+-')
while Digits[peek()] do get() end
end
end
toEmit = {Type = 'Number', Data = src:sub(start, p-1)}
elseif c == '\'' or c == '\"' then
local start = p
--string const
local delim = get()
local contentStart = p
while true do
local c = get()
if c == '\\' then
get() --get the escape char
elseif c == delim then
break
elseif c == '' then
generateError("Unfinished string near ")
end
end
local content = src:sub(contentStart, p-2)
local constant = src:sub(start, p-1)
toEmit = {Type = 'String', Data = constant, Constant = content}
elseif c == '[' then
local content, wholetext = tryGetLongString()
if wholetext then
toEmit = {Type = 'String', Data = wholetext, Constant = content}
else
get()
toEmit = {Type = 'Symbol', Data = '['}
end
elseif consume('>=<') then
if consume('=') then
toEmit = {Type = 'Symbol', Data = c..'='}
else
toEmit = {Type = 'Symbol', Data = c}
end
elseif consume('~') then
if consume('=') then
toEmit = {Type = 'Symbol', Data = '~='}
else
generateError("Unexpected symbol `~` in source.", 2)
end
elseif consume('.') then
if consume('.') then
if consume('.') then
toEmit = {Type = 'Symbol', Data = '...'}
else
toEmit = {Type = 'Symbol', Data = '..'}
end
else
toEmit = {Type = 'Symbol', Data = '.'}
end
elseif consume(':') then
if consume(':') then
toEmit = {Type = 'Symbol', Data = '::'}
else
toEmit = {Type = 'Symbol', Data = ':'}
end
elseif Symbols[c] then
get()
toEmit = {Type = 'Symbol', Data = c}
else
local contents, all = tryGetLongString()
if contents then
toEmit = {Type = 'String', Data = all, Constant = contents}
else
generateError("Unexpected Symbol `"..c.."` in source.", 2)
end
end
--add the emitted symbol, after adding some common data
toEmit.LeadingWhite = leadingWhite
toEmit.Line = thisLine
toEmit.Char = thisChar
toEmit.Print = function()
return "<"..(toEmit.Type..string.rep(' ', 7-#toEmit.Type)).." "..(toEmit.Data or '').." >"
end
tokens[#tokens+1] = toEmit
--halt after eof has been emitted
if toEmit.Type == 'Eof' then break end
end
end)
if not st then
return false, err
end
--public interface:
local tok = {}
local savedP = {}
local p = 1
--getters
function tok:Peek(n)
n = n or 0
return tokens[math.min(#tokens, p+n)]
end
function tok:Get()
local t = tokens[p]
p = math.min(p + 1, #tokens)
return t
end
function tok:Is(t)
return tok:Peek().Type == t
end
--save / restore points in the stream
function tok:Save()
savedP[#savedP+1] = p
end
function tok:Commit()
savedP[#savedP] = nil
end
function tok:Restore()
p = savedP[#savedP]
savedP[#savedP] = nil
end
--either return a symbol if there is one, or return true if the requested
--symbol was gotten.
function tok:ConsumeSymbol(symb)
local t = self:Peek()
if t.Type == 'Symbol' then
if symb then
if t.Data == symb then
self:Get()
return true
else
return nil
end
else
self:Get()
return t
end
else
return nil
end
end
function tok:ConsumeKeyword(kw)
local t = self:Peek()
if t.Type == 'Keyword' and t.Data == kw then
self:Get()
return true
else
return nil
end
end
function tok:IsKeyword(kw)
local t = tok:Peek()
return t.Type == 'Keyword' and t.Data == kw
end
function tok:IsSymbol(s)
local t = tok:Peek()
return t.Type == 'Symbol' and t.Data == s
end
function tok:IsEof()
return tok:Peek().Type == 'Eof'
end
return true, tok
end
function ParseLua(src)
local st, tok = LexLua(src)
if not st then
return false, tok
end
--
local function GenerateError(msg)
local err = ">> :"..tok:Peek().Line..":"..tok:Peek().Char..": "..msg.."\n"
--find the line
local lineNum = 0
for line in src:gmatch("[^\n]*\n?") do
if line:sub(-1,-1) == '\n' then line = line:sub(1,-2) end
lineNum = lineNum+1
if lineNum == tok:Peek().Line then
err = err..">> `"..line:gsub('\t',' ').."`\n"
for i = 1, tok:Peek().Char do
local c = line:sub(i,i)
if c == '\t' then
err = err..' '
else
err = err..' '
end
end
err = err.." ^---"
break
end
end
return err
end
--
local VarUid = 0
local GlobalVarGetMap = {}
local VarDigits = {'_', 'a', 'b', 'c', 'd'}
local function CreateScope(parent)
local scope = {}
scope.Parent = parent
scope.LocalList = {}
scope.LocalMap = {}
function scope:RenameVars()
for _, var in pairs(scope.LocalList) do
local id;
VarUid = 0
repeat
VarUid = VarUid + 1
local varToUse = VarUid
id = ''
while varToUse > 0 do
local d = varToUse % #VarDigits
varToUse = (varToUse - d) / #VarDigits
id = id..VarDigits[d+1]
end
until not GlobalVarGetMap[id] and not parent:GetLocal(id) and not scope.LocalMap[id]
var.Name = id
scope.LocalMap[id] = var
end
end
function scope:GetLocal(name)
--first, try to get my variable
local my = scope.LocalMap[name]
if my then return my end
--next, try parent
if scope.Parent then
local par = scope.Parent:GetLocal(name)
if par then return par end
end
return nil
end
function scope:CreateLocal(name)
--create my own var
local my = {}
my.Scope = scope
my.Name = name
my.CanRename = true
--
scope.LocalList[#scope.LocalList+1] = my
scope.LocalMap[name] = my
--
return my
end
scope.Print = function() return "" end
return scope
end
local ParseExpr;
local ParseStatementList;
local function ParseFunctionArgsAndBody(scope)
local funcScope = CreateScope(scope)
if not tok:ConsumeSymbol('(') then
return false, GenerateError("`(` expected.")
end
--arg list
local argList = {}
local isVarArg = false
while not tok:ConsumeSymbol(')') do
if tok:Is('Ident') then
local arg = funcScope:CreateLocal(tok:Get().Data)
argList[#argList+1] = arg
if not tok:ConsumeSymbol(',') then
if tok:ConsumeSymbol(')') then
break
else
return false, GenerateError("`)` expected.")
end
end
elseif tok:ConsumeSymbol('...') then
isVarArg = true
if not tok:ConsumeSymbol(')') then
return false, GenerateError("`...` must be the last argument of a function.")
end
break
else
return false, GenerateError("Argument name or `...` expected")
end
end
--body
local st, body = ParseStatementList(funcScope)
if not st then return false, body end
--end
if not tok:ConsumeKeyword('end') then
return false, GenerateError("`end` expected after function body")
end
local nodeFunc = {}
nodeFunc.AstType = 'Function'
nodeFunc.Scope = funcScope
nodeFunc.Arguments = argList
nodeFunc.Body = body
nodeFunc.VarArg = isVarArg
--
return true, nodeFunc
end
local function ParsePrimaryExpr(scope)
if tok:ConsumeSymbol('(') then
local st, ex = ParseExpr(scope)
if not st then return false, ex end
if not tok:ConsumeSymbol(')') then
return false, GenerateError("`)` Expected.")
end
--save the information about parenthesized expressions somewhere
ex.ParenCount = (ex.ParenCount or 0) + 1
return true, ex
elseif tok:Is('Ident') then
local id = tok:Get()
local var = scope:GetLocal(id.Data)
if not var then
GlobalVarGetMap[id.Data] = true
end
--
local nodePrimExp = {}
nodePrimExp.AstType = 'VarExpr'
nodePrimExp.Name = id.Data
nodePrimExp.Local = var
--
return true, nodePrimExp
else
return false, GenerateError("primary expression expected")
end
end
local function ParseSuffixedExpr(scope, onlyDotColon)
--base primary expression
local st, prim = ParsePrimaryExpr(scope)
if not st then return false, prim end
--
while true do
if tok:IsSymbol('.') or tok:IsSymbol(':') then
local symb = tok:Get().Data
if not tok:Is('Ident') then
return false, GenerateError(" expected.")
end
local id = tok:Get()
local nodeIndex = {}
nodeIndex.AstType = 'MemberExpr'
nodeIndex.Base = prim
nodeIndex.Indexer = symb
nodeIndex.Ident = id
--
prim = nodeIndex
elseif not onlyDotColon and tok:ConsumeSymbol('[') then
local st, ex = ParseExpr(scope)
if not st then return false, ex end
if not tok:ConsumeSymbol(']') then
return false, GenerateError("`]` expected.")
end
local nodeIndex = {}
nodeIndex.AstType = 'IndexExpr'
nodeIndex.Base = prim
nodeIndex.Index = ex
--
prim = nodeIndex
elseif not onlyDotColon and tok:ConsumeSymbol('(') then
local args = {}
while not tok:ConsumeSymbol(')') do
local st, ex = ParseExpr(scope)
if not st then return false, ex end
args[#args+1] = ex
if not tok:ConsumeSymbol(',') then
if tok:ConsumeSymbol(')') then
break
else
return false, GenerateError("`)` Expected.")
end
end
end
local nodeCall = {}
nodeCall.AstType = 'CallExpr'
nodeCall.Base = prim
nodeCall.Arguments = args
--
prim = nodeCall
elseif not onlyDotColon and tok:Is('String') then
--string call
local nodeCall = {}
nodeCall.AstType = 'StringCallExpr'
nodeCall.Base = prim
nodeCall.Arguments = {tok:Get()}
--
prim = nodeCall
elseif not onlyDotColon and tok:IsSymbol('{') then
--table call
local st, ex = ParseExpr(scope)
if not st then return false, ex end
local nodeCall = {}
nodeCall.AstType = 'TableCallExpr'
nodeCall.Base = prim
nodeCall.Arguments = {ex}
--
prim = nodeCall
else
break
end
end
return true, prim
end
local function ParseSimpleExpr(scope)
if tok:Is('Number') then
local nodeNum = {}
nodeNum.AstType = 'NumberExpr'
nodeNum.Value = tok:Get()
return true, nodeNum
elseif tok:Is('String') then
local nodeStr = {}
nodeStr.AstType = 'StringExpr'
nodeStr.Value = tok:Get()
return true, nodeStr
elseif tok:ConsumeKeyword('nil') then
local nodeNil = {}
nodeNil.AstType = 'NilExpr'
return true, nodeNil
elseif tok:IsKeyword('false') or tok:IsKeyword('true') then
local nodeBoolean = {}
nodeBoolean.AstType = 'BooleanExpr'
nodeBoolean.Value = (tok:Get().Data == 'true')
return true, nodeBoolean
elseif tok:ConsumeSymbol('...') then
local nodeDots = {}
nodeDots.AstType = 'DotsExpr'
return true, nodeDots
elseif tok:ConsumeSymbol('{') then
local v = {}
v.AstType = 'ConstructorExpr'
v.EntryList = {}
--
while true do
if tok:IsSymbol('[') then
--key
tok:Get()
local st, key = ParseExpr(scope)
if not st then
return false, GenerateError("Key Expression Expected")
end
if not tok:ConsumeSymbol(']') then
return false, GenerateError("`]` Expected")
end
if not tok:ConsumeSymbol('=') then
return false, GenerateError("`=` Expected")
end
local st, value = ParseExpr(scope)
if not st then
return false, GenerateError("Value Expression Expected")
end
v.EntryList[#v.EntryList+1] = {
Type = 'Key';
Key = key;
Value = value;
}
elseif tok:Is('Ident') then
--value or key
local lookahead = tok:Peek(1)
if lookahead.Type == 'Symbol' and lookahead.Data == '=' then
--we are a key
local key = tok:Get()
if not tok:ConsumeSymbol('=') then
return false, GenerateError("`=` Expected")
end
local st, value = ParseExpr(scope)
if not st then
return false, GenerateError("Value Expression Expected")
end
v.EntryList[#v.EntryList+1] = {
Type = 'KeyString';
Key = key.Data;
Value = value;
}
else
--we are a value
local st, value = ParseExpr(scope)
if not st then
return false, GenerateError("Value Exected")
end
v.EntryList[#v.EntryList+1] = {
Type = 'Value';
Value = value;
}
end
elseif tok:ConsumeSymbol('}') then
break
else
--value
local st, value = ParseExpr(scope)
v.EntryList[#v.EntryList+1] = {
Type = 'Value';
Value = value;
}
if not st then
return false, GenerateError("Value Expected")
end
end
if tok:ConsumeSymbol(';') or tok:ConsumeSymbol(',') then
--all is good
elseif tok:ConsumeSymbol('}') then
break
else
return false, GenerateError("`}` or table entry Expected")
end
end
return true, v
elseif tok:ConsumeKeyword('function') then
local st, func = ParseFunctionArgsAndBody(scope)
if not st then return false, func end
--
func.IsLocal = true
return true, func
else
return ParseSuffixedExpr(scope)
end
end
local unops = lookupify{'-', 'not', '#'}
local unopprio = 8
local priority = {
['+'] = {6,6};
['-'] = {6,6};
['%'] = {7,7};
['/'] = {7,7};
['*'] = {7,7};
['^'] = {10,9};
['..'] = {5,4};
['=='] = {3,3};
['<'] = {3,3};
['<='] = {3,3};
['~='] = {3,3};
['>'] = {3,3};
['>='] = {3,3};
['and'] = {2,2};
['or'] = {1,1};
}
local function ParseSubExpr(scope, level)
--base item, possibly with unop prefix
local st, exp
if unops[tok:Peek().Data] then
local op = tok:Get().Data
st, exp = ParseSubExpr(scope, unopprio)
if not st then return false, exp end
local nodeEx = {}
nodeEx.AstType = 'UnopExpr'
nodeEx.Rhs = exp
nodeEx.Op = op
exp = nodeEx
else
st, exp = ParseSimpleExpr(scope)
if not st then return false, exp end
end
--next items in chain
while true do
local prio = priority[tok:Peek().Data]
if prio and prio[1] > level then
local op = tok:Get().Data
local st, rhs = ParseSubExpr(scope, prio[2])
if not st then return false, rhs end
local nodeEx = {}
nodeEx.AstType = 'BinopExpr'
nodeEx.Lhs = exp
nodeEx.Op = op
nodeEx.Rhs = rhs
--
exp = nodeEx
else
break
end
end
return true, exp
end
ParseExpr = function(scope)
return ParseSubExpr(scope, 0)
end
local function ParseStatement(scope)
local stat = nil
if tok:ConsumeKeyword('if') then
--setup
local nodeIfStat = {}
nodeIfStat.AstType = 'IfStatement'
nodeIfStat.Clauses = {}
--clauses
repeat
local st, nodeCond = ParseExpr(scope)
if not st then return false, nodeCond end
if not tok:ConsumeKeyword('then') then
return false, GenerateError("`then` expected.")
end
local st, nodeBody = ParseStatementList(scope)
if not st then return false, nodeBody end
nodeIfStat.Clauses[#nodeIfStat.Clauses+1] = {
Condition = nodeCond;
Body = nodeBody;
}
until not tok:ConsumeKeyword('elseif')
--else clause
if tok:ConsumeKeyword('else') then
local st, nodeBody = ParseStatementList(scope)
if not st then return false, nodeBody end
nodeIfStat.Clauses[#nodeIfStat.Clauses+1] = {
Body = nodeBody;
}
end
--end
if not tok:ConsumeKeyword('end') then
return false, GenerateError("`end` expected.")
end
stat = nodeIfStat
elseif tok:ConsumeKeyword('while') then
--setup
local nodeWhileStat = {}
nodeWhileStat.AstType = 'WhileStatement'
--condition
local st, nodeCond = ParseExpr(scope)
if not st then return false, nodeCond end
--do
if not tok:ConsumeKeyword('do') then
return false, GenerateError("`do` expected.")
end
--body
local st, nodeBody = ParseStatementList(scope)
if not st then return false, nodeBody end
--end
if not tok:ConsumeKeyword('end') then
return false, GenerateError("`end` expected.")
end
--return
nodeWhileStat.Condition = nodeCond
nodeWhileStat.Body = nodeBody
stat = nodeWhileStat
elseif tok:ConsumeKeyword('do') then
--do block
local st, nodeBlock = ParseStatementList(scope)
if not st then return false, nodeBlock end
if not tok:ConsumeKeyword('end') then
return false, GenerateError("`end` expected.")
end
local nodeDoStat = {}
nodeDoStat.AstType = 'DoStatement'
nodeDoStat.Body = nodeBlock
stat = nodeDoStat
elseif tok:ConsumeKeyword('for') then
--for block
if not tok:Is('Ident') then
return false, GenerateError(" expected.")
end
local baseVarName = tok:Get()
if tok:ConsumeSymbol('=') then
--numeric for
local forScope = CreateScope(scope)
local forVar = forScope:CreateLocal(baseVarName.Data)
--
local st, startEx = ParseExpr(scope)
if not st then return false, startEx end
if not tok:ConsumeSymbol(',') then
return false, GenerateError("`,` Expected")
end
local st, endEx = ParseExpr(scope)
if not st then return false, endEx end
local st, stepEx;
if tok:ConsumeSymbol(',') then
st, stepEx = ParseExpr(scope)
if not st then return false, stepEx end
end
if not tok:ConsumeKeyword('do') then
return false, GenerateError("`do` expected")
end
--
local st, body = ParseStatementList(forScope)
if not st then return false, body end
if not tok:ConsumeKeyword('end') then
return false, GenerateError("`end` expected")
end
--
local nodeFor = {}
nodeFor.AstType = 'NumericForStatement'
nodeFor.Scope = forScope
nodeFor.Variable = forVar
nodeFor.Start = startEx
nodeFor.End = endEx
nodeFor.Step = stepEx
nodeFor.Body = body
stat = nodeFor
else
--generic for
local forScope = CreateScope(scope)
--
local varList = {forScope:CreateLocal(baseVarName.Data)}
while tok:ConsumeSymbol(',') do
if not tok:Is('Ident') then
return false, GenerateError("for variable expected.")
end
varList[#varList+1] = forScope:CreateLocal(tok:Get().Data)
end
if not tok:ConsumeKeyword('in') then
return false, GenerateError("`in` expected.")
end
local generators = {}
local st, firstGenerator = ParseExpr(scope)
if not st then return false, firstGenerator end
generators[#generators+1] = firstGenerator
while tok:ConsumeSymbol(',') do
local st, gen = ParseExpr(scope)
if not st then return false, gen end
generators[#generators+1] = gen
end
if not tok:ConsumeKeyword('do') then
return false, GenerateError("`do` expected.")
end
local st, body = ParseStatementList(forScope)
if not st then return false, body end
if not tok:ConsumeKeyword('end') then
return false, GenerateError("`end` expected.")
end
--
local nodeFor = {}
nodeFor.AstType = 'GenericForStatement'
nodeFor.Scope = forScope
nodeFor.VariableList = varList
nodeFor.Generators = generators
nodeFor.Body = body
stat = nodeFor
end
elseif tok:ConsumeKeyword('repeat') then
local st, body = ParseStatementList(scope)
if not st then return false, body end
--
if not tok:ConsumeKeyword('until') then
return false, GenerateError("`until` expected.")
end
--
local st, cond = ParseExpr(scope)
if not st then return false, cond end
--
local nodeRepeat = {}
nodeRepeat.AstType = 'RepeatStatement'
nodeRepeat.Condition = cond
nodeRepeat.Body = body
stat = nodeRepeat
elseif tok:ConsumeKeyword('function') then
if not tok:Is('Ident') then
return false, GenerateError("Function name expected")
end
local st, name = ParseSuffixedExpr(scope, true) --true => only dots and colons
if not st then return false, name end
--
local st, func = ParseFunctionArgsAndBody(scope)
if not st then return false, func end
--
func.IsLocal = false
func.Name = name
stat = func
elseif tok:ConsumeKeyword('local') then
if tok:Is('Ident') then
local varList = {tok:Get().Data}
while tok:ConsumeSymbol(',') do
if not tok:Is('Ident') then
return false, GenerateError("local var name expected")
end
varList[#varList+1] = tok:Get().Data
end
local initList = {}
if tok:ConsumeSymbol('=') then
repeat
local st, ex = ParseExpr(scope)
if not st then return false, ex end
initList[#initList+1] = ex
until not tok:ConsumeSymbol(',')
end
--now patch var list
--we can't do this before getting the init list, because the init list does not
--have the locals themselves in scope.
for i, v in pairs(varList) do
varList[i] = scope:CreateLocal(v)
end
local nodeLocal = {}
nodeLocal.AstType = 'LocalStatement'
nodeLocal.LocalList = varList
nodeLocal.InitList = initList
--
stat = nodeLocal
elseif tok:ConsumeKeyword('function') then
if not tok:Is('Ident') then
return false, GenerateError("Function name expected")
end
local name = tok:Get().Data
local localVar = scope:CreateLocal(name)
--
local st, func = ParseFunctionArgsAndBody(scope)
if not st then return false, func end
--
func.Name = localVar
func.IsLocal = true
stat = func
else
return false, GenerateError("local var or function def expected")
end
elseif tok:ConsumeSymbol('::') then
if not tok:Is('Ident') then
return false, GenerateError('Label name expected')
end
local label = tok:Get().Data
if not tok:ConsumeSymbol('::') then
return false, GenerateError("`::` expected")
end
local nodeLabel = {}
nodeLabel.AstType = 'LabelStatement'
nodeLabel.Label = label
stat = nodeLabel
elseif tok:ConsumeKeyword('return') then
local exList = {}
if not tok:IsKeyword('end') then
local st, firstEx = ParseExpr(scope)
if st then
exList[1] = firstEx
while tok:ConsumeSymbol(',') do
local st, ex = ParseExpr(scope)
if not st then return false, ex end
exList[#exList+1] = ex
end
end
end
local nodeReturn = {}
nodeReturn.AstType = 'ReturnStatement'
nodeReturn.Arguments = exList
stat = nodeReturn
elseif tok:ConsumeKeyword('break') then
local nodeBreak = {}
nodeBreak.AstType = 'BreakStatement'
stat = nodeBreak
elseif tok:IsKeyword('goto') then
if not tok:Is('Ident') then
return false, GenerateError("Label expected")
end
local label = tok:Get().Data
local nodeGoto = {}
nodeGoto.AstType = 'GotoStatement'
nodeGoto.Label = label
stat = nodeGoto
else
--statementParseExpr
local st, suffixed = ParseSuffixedExpr(scope)
if not st then return false, suffixed end
--assignment or call?
if tok:IsSymbol(',') or tok:IsSymbol('=') then
--check that it was not parenthesized, making it not an lvalue
if (suffixed.ParenCount or 0) > 0 then
return false, GenerateError("Can not assign to parenthesized expression, is not an lvalue")
end
--more processing needed
local lhs = {suffixed}
while tok:ConsumeSymbol(',') do
local st, lhsPart = ParseSuffixedExpr(scope)
if not st then return false, lhsPart end
lhs[#lhs+1] = lhsPart
end
--equals
if not tok:ConsumeSymbol('=') then
return false, GenerateError("`=` Expected.")
end
--rhs
local rhs = {}
local st, firstRhs = ParseExpr(scope)
if not st then return false, firstRhs end
rhs[1] = firstRhs
while tok:ConsumeSymbol(',') do
local st, rhsPart = ParseExpr(scope)
if not st then return false, rhsPart end
rhs[#rhs+1] = rhsPart
end
--done
local nodeAssign = {}
nodeAssign.AstType = 'AssignmentStatement'
nodeAssign.Lhs = lhs
nodeAssign.Rhs = rhs
stat = nodeAssign
elseif suffixed.AstType == 'CallExpr' or
suffixed.AstType == 'TableCallExpr' or
suffixed.AstType == 'StringCallExpr'
then
--it's a call statement
local nodeCall = {}
nodeCall.AstType = 'CallStatement'
nodeCall.Expression = suffixed
stat = nodeCall
else
return false, GenerateError("Assignment Statement Expected")
end
end
stat.HasSemicolon = tok:ConsumeSymbol(';')
return true, stat
end
local statListCloseKeywords = lookupify{'end', 'else', 'elseif', 'until'}
ParseStatementList = function(scope)
local nodeStatlist = {}
nodeStatlist.Scope = CreateScope(scope)
nodeStatlist.AstType = 'Statlist'
--
local stats = {}
--
while not statListCloseKeywords[tok:Peek().Data] and not tok:IsEof() do
local st, nodeStatement = ParseStatement(nodeStatlist.Scope)
if not st then return false, nodeStatement end
stats[#stats+1] = nodeStatement
end
--
nodeStatlist.Body = stats
return true, nodeStatlist
end
local function mainfunc()
local topScope = CreateScope()
return ParseStatementList(topScope)
end
local st, main = mainfunc()
--print("Last Token: "..PrintTable(tok:Peek()))
return st, main
end
luaparse-0.3.1/benchmarks/lib/transform.lua 0000664 0000000 0000000 00000110664 14056157201 0020741 0 ustar 00root root 0000000 0000000 -- MoonScript http://moonscript.org
-- https://github.com/leafo/moonscript
local types = require("moonscript.types")
local util = require("moonscript.util")
local data = require("moonscript.data")
local reversed = util.reversed
local 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 = table.insert
local NameProxy, LocalName
do
local _table_0 = require("moonscript.transform.names")
NameProxy, LocalName = _table_0.NameProxy, _table_0.LocalName
end
local destructure = require("moonscript.transform.destructure")
local implicitly_return
local Run
do
local _parent_0 = nil
local _base_0 = {
call = function(self, state)
return self.fn(state)
end
}
_base_0.__index = _base_0
if _parent_0 then
setmetatable(_base_0, _parent_0.__base)
end
local _class_0 = setmetatable({
__init = function(self, fn)
self.fn = fn
self[1] = "run"
end,
__base = _base_0,
__name = "Run",
__parent = _parent_0
}, {
__index = function(cls, name)
local val = rawget(_base_0, name)
if val == nil and _parent_0 then
return _parent_0[name]
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 and _parent_0.__inherited then
_parent_0.__inherited(_parent_0, _class_0)
end
Run = _class_0
end
local apply_to_last
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 = 0
for i, stm in ipairs(stms) do
local _value_0
if i == last_exp_id then
_value_0 = fn(stm)
else
_value_0 = stm
end
if _value_0 ~= nil then
_len_0 = _len_0 + 1
_accum_0[_len_0] = _value_0
end
end
return _accum_0
end)()
end
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 true
end
end
local find_assigns
find_assigns = function(body, out)
if out == nil then
out = { }
end
local _list_0 = body
for _index_0 = 1, #_list_0 do
local thing = _list_0[_index_0]
local _exp_0 = thing[1]
if "group" == _exp_0 then
find_assigns(thing[2], out)
elseif "assign" == _exp_0 then
table.insert(out, thing[2])
end
end
return out
end
local hoist_declarations
hoist_declarations = function(body)
local assigns = { }
local _list_0 = find_assigns(body)
for _index_0 = 1, #_list_0 do
local names = _list_0[_index_0]
local _list_1 = names
for _index_1 = 1, #_list_1 do
local name = _list_1[_index_1]
if type(name) == "string" then
table.insert(assigns, name)
end
end
end
local idx = 1
while mtype(body[idx]) == Run do
idx = idx + 1
end
return table.insert(body, idx, {
"declare",
assigns
})
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
local constructor_name = "new"
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
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
local Transformer
do
local _parent_0 = nil
local _base_0 = {
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
if _parent_0 then
setmetatable(_base_0, _parent_0.__base)
end
local _class_0 = setmetatable({
__init = function(self, transformers)
self.transformers = transformers
self.seen_nodes = setmetatable({ }, {
__mode = "k"
})
end,
__base = _base_0,
__name = "Transformer",
__parent = _parent_0
}, {
__index = function(cls, name)
local val = rawget(_base_0, name)
if val == nil and _parent_0 then
return _parent_0[name]
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 and _parent_0.__inherited then
_parent_0.__inherited(_parent_0, _class_0)
end
Transformer = _class_0
end
local construct_comprehension
construct_comprehension = function(inner, clauses)
local current_stms = inner
for _, clause in reversed(clauses) do
local t = clause[1]
if t == "for" then
local names, iter
_, names, iter = unpack(clause)
current_stms = {
"foreach",
names,
{
iter
},
current_stms
}
elseif t == "when" then
local cond
_, cond = unpack(clause)
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 Statement = Transformer({
root_stms = function(self, body)
return apply_to_last(body, implicitly_return(self))
end,
assign = function(self, node)
local names, values = unpack(node, 2)
local transformed
if #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(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({
node,
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 stubs = (function()
local _accum_0 = { }
local _len_0 = 0
local _list_0 = names
for _index_0 = 1, #_list_0 do
local name = _list_0[_index_0]
local _value_0
if type(name) == "table" then
_value_0 = name
else
_value_0 = {
"dot",
name
}
end
if _value_0 ~= nil then
_len_0 = _len_0 + 1
_accum_0[_len_0] = _value_0
end
end
return _accum_0
end)()
local real_names = (function()
local _accum_0 = { }
local _len_0 = 0
local _list_0 = names
for _index_0 = 1, #_list_0 do
local name = _list_0[_index_0]
local _value_0 = type(name) == "table" and name[2] or name
if _value_0 ~= nil then
_len_0 = _len_0 + 1
_accum_0[_len_0] = _value_0
end
end
return _accum_0
end)()
if type(source) == "string" then
return build.assign({
names = real_names,
values = (function()
local _accum_0 = { }
local _len_0 = 0
local _list_0 = stubs
for _index_0 = 1, #_list_0 do
local stub = _list_0[_index_0]
_len_0 = _len_0 + 1
_accum_0[_len_0] = build.chain({
base = source,
stub
})
end
return _accum_0
end)()
})
else
local source_name = NameProxy("table")
return build.group({
{
"declare",
real_names
},
build["do"]({
build.assign_one(source_name, source),
build.assign({
names = real_names,
values = (function()
local _accum_0 = { }
local _len_0 = 0
local _list_0 = stubs
for _index_0 = 1, #_list_0 do
local stub = _list_0[_index_0]
_len_0 = _len_0 + 1
_accum_0[_len_0] = build.chain({
base = source_name,
stub
})
end
return _accum_0
end)()
})
})
})
end
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 = 0
local _list_0 = stm[2]
for _index_0 = 1, #_list_0 do
local name = _list_0[_index_0]
if type(name) == "string" then
_len_0 = _len_0 + 1
_accum_0[_len_0] = name
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(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)
local scope_name = NameProxy("with")
local named_assign
if ntype(exp) == "assign" then
local names, values = unpack(exp, 2)
local assign_name = names[1]
exp = values[1]
values[1] = scope_name
named_assign = {
"assign",
names,
values
}
end
return build["do"]({
Run(function(self)
return self:set("scope_var", scope_name)
end),
build.assign_one(scope_name, exp),
build.group({
named_assign
}),
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 = { }
node.names = (function()
local _accum_0 = { }
local _len_0 = 0
for i, name in ipairs(node.names) do
local _value_0
if ntype(name) == "table" then
do
local _with_0 = NameProxy("des")
local proxy = _with_0
insert(destructures, destructure.build_assign(name, proxy))
_value_0 = _with_0
end
else
_value_0 = name
end
if _value_0 ~= nil then
_len_0 = _len_0 + 1
_accum_0[_len_0] = _value_0
end
end
return _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 = 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 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({
build.assign_one(list_name, list),
slice_var,
build["for"]({
name = index_name,
bounds = bounds,
body = {
{
"assign",
node.names,
{
list_name:index(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"
}
local _list_0 = conds
for _index_0 = 1, #_list_0 do
local cond = _list_0[_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)
local statements = { }
local properties = { }
local _list_0 = body
for _index_0 = 1, #_list_0 do
local item = _list_0[_index_0]
local _exp_0 = item[1]
if "stm" == _exp_0 then
insert(statements, item[2])
elseif "props" == _exp_0 then
local _list_1 = item
for _index_1 = 2, #_list_1 do
local tuple = _list_1[_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 = nil
properties = (function()
local _accum_0 = { }
local _len_0 = 0
local _list_1 = properties
for _index_0 = 1, #_list_1 do
local tuple = _list_1[_index_0]
local key = tuple[1]
local _value_0
if key[1] == "key_literal" and key[2] == constructor_name then
constructor = tuple[2]
_value_0 = nil
else
_value_0 = tuple
end
if _value_0 ~= nil then
_len_0 = _len_0 + 1
_accum_0[_len_0] = _value_0
end
end
return _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
constructor = build.fndef({
args = {
{
"..."
}
},
arrow = "fat",
body = {
build["if"]({
cond = parent_cls_name,
["then"] = {
build.chain({
base = "super",
{
"call",
{
"..."
}
}
})
}
})
}
})
else
smart_node(constructor)
constructor.arrow = "fat"
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
real_name = {
"string",
'"',
real_name
}
end
local cls = build.table({
{
"__init",
constructor
},
{
"__base",
base_name
},
{
"__name",
real_name
},
{
"__parent",
parent_cls_name
}
})
local class_lookup = build["if"]({
cond = {
"exp",
"val",
"==",
"nil",
"and",
parent_cls_name
},
["then"] = {
parent_cls_name:index("name")
}
})
insert(class_lookup, {
"else",
{
"val"
}
})
local cls_mt = build.table({
{
"__index",
build.fndef({
args = {
{
"cls"
},
{
"name"
}
},
body = {
build.assign_one(LocalName("val"), build.chain({
base = "rawget",
{
"call",
{
base_name,
"name"
}
}
})),
class_lookup
}
})
},
{
"__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 _with_0 = build
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 = (function()
local _accum_0 = { }
local _len_0 = 0
local _list_1 = chain
for _index_0 = 3, #_list_1 do
local item = _list_1[_index_0]
_len_0 = _len_0 + 1
_accum_0[_len_0] = item
end
return _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
local _list_1 = slice
for _index_0 = 1, #_list_1 do
local item = _list_1[_index_0]
insert(new_chain, item)
end
return new_chain
else
return parent_cls_name
end
end)
end),
_with_0.assign_one(parent_cls_name, parent_val == "" and "nil" or parent_val),
_with_0.assign_one(base_name, {
"table",
properties
}),
_with_0.assign_one(base_name:chain("__index"), base_name),
_with_0["if"]({
cond = parent_cls_name,
["then"] = {
_with_0.chain({
base = "setmetatable",
{
"call",
{
base_name,
_with_0.chain({
base = parent_cls_name,
{
"dot",
"__base"
}
})
}
}
})
}
}),
_with_0.assign_one(cls_name, cls),
_with_0.assign_one(base_name:chain("__class"), cls_name),
_with_0.group((function()
if #statements > 0 then
return {
_with_0.assign_one(LocalName("self"), cls_name),
_with_0.group(statements)
}
end
end)()),
_with_0["if"]({
cond = {
"exp",
parent_cls_name,
"and",
parent_cls_name:chain("__inherited")
},
["then"] = {
parent_cls_name:chain("__inherited", {
"call",
{
parent_cls_name,
cls_name
}
})
}
}),
_with_0.group((function()
if name then
return {
_with_0.assign_one(name, cls_name)
}
end
end)()),
(function()
if ret then
return ret(cls_name)
end
end)()
}
hoist_declarations(out_body)
value = _with_0.group({
_with_0.group((function()
if ntype(name) == "value" then
return {
_with_0.declare({
names = {
name
}
})
}
end
end)()),
_with_0["do"](out_body)
})
end
return value
end
})
local Accumulator
do
local _parent_0 = nil
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)
return build.block_exp({
build.assign_one(self.accum_name, build.table()),
build.assign_one(self.len_name, 0),
node,
self.accum_name
})
end,
mutate_body = function(self, body, skip_nil)
if skip_nil == nil then
skip_nil = true
end
local val
if not skip_nil and is_singular(body) then
do
local _with_0 = body[1]
body = { }
val = _with_0
end
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 = {
{
"update",
self.len_name,
"+=",
1
},
build.assign_one(self.accum_name:index(self.len_name), val)
}
if skip_nil then
table.insert(body, build["if"]({
cond = {
"exp",
self.value_name,
"!=",
"nil"
},
["then"] = update
}))
else
table.insert(body, build.group(update))
end
return body
end
}
_base_0.__index = _base_0
if _parent_0 then
setmetatable(_base_0, _parent_0.__base)
end
local _class_0 = setmetatable({
__init = function(self)
self.accum_name = NameProxy("accum")
self.value_name = NameProxy("value")
self.len_name = NameProxy("len")
end,
__base = _base_0,
__name = "Accumulator",
__parent = _parent_0
}, {
__index = function(cls, name)
local val = rawget(_base_0, name)
if val == nil and _parent_0 then
return _parent_0[name]
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 and _parent_0.__inherited then
_parent_0.__inherited(_parent_0, _class_0)
end
Accumulator = _class_0
end
local default_accumulator
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 nil
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
local 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
}, false)
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 = node[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
}
luaparse-0.3.1/benchmarks/run.sh 0000775 0000000 0000000 00000011334 14056157201 0016612 0 ustar 00root root 0000000 0000000 DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
REMOTE="$DIR/.." # Clone local copy to get access to HEAD
DEST="$DIR/bench-luaparse"
GIT_FLAGS="--git-dir=$DEST/.git --work-tree=$DEST/"
# Variables
D8=$(which d8 2>/dev/null)
processor="/opt/v8/tools/linux-tick-processor"
[[ "$D8" ]] && processor="$(realpath $(dirname $(readlink $D8))/../../tools/)/linux-tick-processor"
minTime=0
samples=5
js=0
profile=0
verbose=0
usage() {
echo -n "$0 [COMMIT]...
Benchmark luaparse at the the specified commit stages.
While running the profiling tool you have to make sure your specified
tick-processor matches the V8-version used in the running node-version.
Examples:
$0 --js HEAD~1 HEAD
$0 --js --samples 1000 HEAD
$0 -v --profile d2f038
Options:
--js Benchmark js scripts
--profile Profile luaparse.js with d8
-m, --minTime Specify the min time. Defaults to 10 (node specific)
-s, --samples Specify the samples. Defaults to 10
-p, --processor Specify the path to the tick-processor. Defaults to
$processor
-v, --verbose Display verbose messages
-h, --help Display this help and exit
"
}
# Helpers
out() { printf '%b\n' "$@"; }
err() { out "\033[1;31m✖\033[0m $@"; } >&2
success() { (($verbose)) && out " \033[1;32m✔\033[0m $@"; }
log() { (($verbose)) && out "$@"; }
die() { err "$@"; exit 1; } >&2
confirm() {
read -p "$1 [Y/n] " -n 1;
echo
[[ $REPLY =~ ^[Yy]$ ]];
}
g() {
log "Running git $GIT_FLAGS $@"
git $GIT_FLAGS "$@" > /dev/null 2>&1
}
runNodeBenchmark() {
local file=$1
local commit=$2
echo "Commit $commit:"
$REMOTE/scripts/benchmark -v \
--luaparse="$file" --samples="$samples" --minTime="$minTime" \
"benchmarks/lib/ParseLua.lua"
echo
}
# Profile luaparse together with D8.
profileCommit() {
local commit=$1
local output="logs/$commit"
local benchmark="scripts/benchmark benchmarks/lib/ParseLua.lua"
# This relies on the commit having the required files.
[[ ! -f "scripts/benchmark" ]] && die "The commit is outdated for profiling."
[[ ! -f "benchmarks/lib/ParseLua.lua" ]] && die "The commit is outdated for profiling."
[[ ! -f $processor ]] && die "tick-processor not found at $processor"
[[ -d "$output" ]] && rm -rf $output && success "Deleted old: $output"
mkdir -p $output
success "Created log directory: $output"
node --prof $benchmark > /dev/null
$processor > $output/ticks
success "Created $output/ticks ($(wc -c < $output/ticks) bytes)"
mv v8.log $output/v8.log
success "Created $output/v8.log ($(wc -c < $output/v8.log) bytes)"
for fn in inlining bailout gc; do
node --trace_$fn --code_comments $benchmark > $output/$fn
success "Created $output/$fn ($(wc -c < $output/$fn) bytes)"
done
}
# Main benchmark loop
benchmark() {
local commit=$(git rev-parse $1)
commit=${commit:0:6}
g checkout $commit
if [[ $? -ne 0 ]]; then err "Could not checkout $commit"
else
success "Checked out $commit"
if ((js)); then
log "Benchmarking commit $commit"
runNodeBenchmark "$DEST/index" "$commit"
[[ $? -ne 0 ]] && die "Benchmark failed"
fi
if ((profile)); then
profileCommit $commit
fi
fi
}
# Initialize
main() {
# Clone a copy where we can benchmark
[[ ! -d $DEST/.git ]] && git clone $REMOTE $DEST > /dev/null 2>&1
for commit in "$@"; do
benchmark "$commit"
done
# Clean up
rm -rf $DEST
}
# Parse options
# Iterate over options breaking -ab into -a -b when needed and --foo=bar into
# --foo bar
optstring=h
unset options
while (($#)); do
case $1 in
# If option is of type -ab
-[!-]?*)
# Loop over each character starting with the second
for ((i=1; i < ${#1}; i++)); do
c=${1:i:1}
# Add current char to options
options+=("-$c")
# If option takes a required argument, and it's not the last char make
# the rest of the string its argument
if [[ $optstring = *"$c:"* && ${1:i+1} ]]; then
options+=("${1:i+1}")
break
fi
done
;;
# If option is of type --foo=bar
--?*=*) options+=("${1%%=*}" "${1#*=}") ;;
# add --endopts for --
--) options+=(--endopts) ;;
# Otherwise, nothing special
*) options+=("$1") ;;
esac
shift
done
set -- "${options[@]}"
unset options
# Print help if no arguments were passed.
[[ $# -eq 0 ]] && set -- "--help"
# Read the options
while [[ $1 = -?* ]]; do
case $1 in
-h|--help) usage >&2; exit ;;
--js) js=1 ;;
--profile) profile=1 ;;
-s|--samples) shift; samples=$1 ;;
-m|--minTime) shift; minTime=$1 ;;
-p|--processor) shift; processor=$1 ;;
-v|--verbose) verbose=1 ;;
--endopts) shift; break ;;
*) die "invalid option: $1" ;;
esac
shift
done
main "$@"
luaparse-0.3.1/bin/ 0000775 0000000 0000000 00000000000 14056157201 0014100 5 ustar 00root root 0000000 0000000 luaparse-0.3.1/bin/luaparse 0000775 0000000 0000000 00000007062 14056157201 0015647 0 ustar 00root root 0000000 0000000 #!/usr/bin/env node
/*jshint node:true*/
/*globals console:true */
'use strict';
var fs = require('fs')
, util = require('util')
, luaparse = require('../luaparse')
, args = process.argv.splice(2)
, stdin = process.stdin
, isTTY = stdin.isTTY
, beautify = false
, quiet = false
, verbose = false
, input = ''
, options = {}
, snippets = [];
function usage() {
console.log([
"Usage: luaparse [option]... [file|code]..."
, "\nOptions:"
, " -c|--code [code] parse code snippet"
, " -f|--file [file] parse from file"
, " -b|--beautify output an indenteted AST"
, " --[no]-comments store comments. defaults to true"
, " --[no]-scope store variable scope. defaults to false"
, " --[no]-locations store location data on syntax nodes. defaults to false"
, " --[no]-ranges store start and end character locations. defaults to false"
, " -q|--quiet suppress output"
, " -h|--help"
, " -v|--version"
, " --verbose"
, "\nExamples:"
, " luaparse --no-comments -c \"locale foo = \\\"bar\\\"\""
, " luaparse foo.lua bar.lua"
].join("\n"));
}
if (isTTY && !args.length) {
usage();
process.exit(0);
}
for (var i = 0, l = args.length; i < l; i++) {
var arg = args[i], match, flag, bool, snippet;
if (match = /^(?:-|--)(?:(no)-)?(\w+)$/.exec(arg)) {
bool = 'no' !== match[1];
flag = match[2];
switch (flag) {
case 'b': case 'beautify':
beautify = true;
continue;
case 'q': case 'quiet':
quiet = true;
continue;
case 'verbose':
verbose = true;
continue;
case 'c': case 'code':
snippets.push({ name: args[++i], code: args[i] });
continue;
case 'f': case 'file':
snippets.push({ name: args[++i], code: fs.readFileSync(args[i], 'utf-8') });
continue;
case 'comments': case 'scope': case 'locations': case 'ranges':
options[flag] = bool;
continue;
case 'L': case 'luaversion':
options.luaVersion = args[++i];
continue;
case 'X': case 'extendedidentifiers':
options.extendedIdentifiers = bool;
continue;
case 'v': case 'version':
console.log("luaparse v%s", luaparse.version);
process.exit(0);
break;
case 'h': case 'help':
usage();
process.exit(0);
break;
default:
if (!quiet) console.log("Unknown option: %s", match[0]);
process.exit(2);
}
}
// Default to autodetecting code or file.
snippet = fs.existsSync(arg) ?
{ name: arg, code: fs.readFileSync(arg, 'utf-8') } :
{ name: arg, code: arg };
snippets.push(snippet);
}
function done() {
var success = true;
snippets.forEach(function(snippet) {
var message, ast;
try {
ast = luaparse.parse(snippet.code, options);
message = beautify ? JSON.stringify(ast, null, ' ')
: JSON.stringify(ast);
} catch(e) {
if (!(e instanceof luaparse.SyntaxError))
throw e;
message = util.format("%s: %s", snippet.name, e.message);
if (verbose) console.log(e.stack);
success = false;
}
if (!quiet) console.log(message);
});
if (!success) process.exit(1);
}
if (stdin.isTTY) done();
else {
// @TODO doesn't work in node 0.9.4-0.9.11 because `end` is never emitted.
stdin.setEncoding('utf8');
stdin.on('data', function(chunk) { input += chunk; });
stdin.on('end', function() { snippets.unshift({ code: input }); done(); });
stdin.resume();
}
/* vim: set sw=2 ts=2 et tw=80 ft=javascript : */
luaparse-0.3.1/component.json 0000664 0000000 0000000 00000000402 14056157201 0016221 0 ustar 00root root 0000000 0000000 {
"name": "luaparse",
"repository": "fstirlitz/luaparse",
"description": "A Lua parser in JavaScript",
"version": "0.2.1",
"keywords": ["ast", "lua", "parser", "parsing"],
"main": "luaparse.js",
"scripts": ["luaparse.js"],
"license": "MIT"
}
luaparse-0.3.1/docs/ 0000775 0000000 0000000 00000000000 14056157201 0014260 5 ustar 00root root 0000000 0000000 luaparse-0.3.1/docs/coverage.md 0000664 0000000 0000000 00000000233 14056157201 0016373 0 ustar 00root root 0000000 0000000 Coverage is reported using [nyc](https://github.com/istanbuljs/nyc).
luaparse-0.3.1/docs/examples.md 0000664 0000000 0000000 00000000127 14056157201 0016420 0 ustar 00root root 0000000 0000000 * [Code parser](../examples/treeview.html)
* [Code statistics](../examples/stats.html)
luaparse-0.3.1/docs/layout/ 0000775 0000000 0000000 00000000000 14056157201 0015575 5 ustar 00root root 0000000 0000000 luaparse-0.3.1/docs/layout/foot.html 0000664 0000000 0000000 00000000552 14056157201 0017434 0 ustar 00root root 0000000 0000000