lua-format
Version:
Lua Beautifier & Minifier, written in JavaScript.
1,628 lines (1,447 loc) • 149 kB
JavaScript
/*
discord.gg/boronide
Luamin.js | beautify or minify your Lua scripts!
*/
// This project is old, stop dming me about bad coding practice. Thanks.
function hashString(key) {
var hash = 0, i = key.length;
while (i--) {
hash += key.charCodeAt(i);
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
}
const print = console.log
const error = console.error
const assert = function(a,b) {
if (!a) {
throw b
}
}
function parseFloat(str, radix) { // Thanks stackoverflow (hex numbers with decimal)
if (!str) return 0;
var parts = str.toString().split(".");
if (parts.length > 1) {
return parseInt(parts[0], radix) + parseInt(parts[1], radix) / Math.pow(radix, parts[1].length);
}
return parseInt(parts[0], radix);
}
/**
*
* regex to make arr : (arr)\[(\S*)\]
* replace value : $1.includes($2)
*
*/
let WhiteChars = [
' ',
'\n',
'\t',
'\r'
]
//unused
/* let EscapeForCharacter = {
'\r': '\\r',
'\n': '\\n',
'\t': '\\t',
'"': '\\"',
"'": "\\'",
'\\': '\\'
} */
let Main_CharacterForEscape = {
'r': '\r',
'n': '\n',
't': '\t',
'"': '"',
"'": "'",
'\\': '\\',
}
const CharacterForEscape = new Proxy(Main_CharacterForEscape, {
get(a, b) { return parseFloat(b) }
})
let AllIdentStartChars = [
'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', '_', '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'
]
let AllIdentChars = [
'0', '1', '2', '3',
'4', '5', '6', '7',
'8', '9',
'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',
'_', 'a', 'b', 'c',
'd', 'e', 'f', 'g',
't', 'u', 'v', 'w',
'h', 'i', 'j', 'k',
'l', 'm', 'n', 'o',
'p', 'q', 'r', 's',
'x', 'y', 'z', // this was actually fucking retarded to add, pls dont do this to me
]
let Digits = [
'0','1','2','3',
'4','5','6','7',
'8','9',
]
let HexDigits = [
//digits
'0','1','2','3',
'4','5','6','7',
'8','9',
//letters
'a','b','c','d','e','f',
'A','B','C','D','E','F',
]
let BinaryDigits = [
'0', '1' // lol
]
let Symbols = [
'+', '-', '*', ')', ';',
'/', '^', '%', '#',
',', '{', '}', ':',
'[', ']', '(','.', '`'
]
let EqualSymbols = [
'~', '=', '>', '<'
]
let CompoundSymbols = [
'+', '-', '*', '/', '^', '..', '%', '//'
]
let Compounds = [
'+=', '-=', '*=', '/=', '^=', '..=', '%=', '//='
]
let Keywords = [
'and', 'break', 'do', 'else',
'elseif', 'end', 'false','for',
'function', 'goto', 'if', 'in',
'local', 'nil', 'not', 'or',
'repeat', 'return', 'then', 'true',
'until', 'while', 'continue'
]
let BlockFollowKeyword = [
'else', 'elseif',
'until', 'end'
]
let UnopSet = [
'-', 'not', '#', '~'
]
let BinopSet = [
'+', '-', '*', '/', '%', '^', '#', '//', //algorithmic
'&', '|', '~', '<<', '>>', // bitops
'..', '.', ':', //dots / colons
'>', '<', '<=', '>=', '~=', '==', //arrows / conditional
'+=', '-=', '*=', '/=', '%=', '^=', '..=', '//=', // compounds
'and', 'or' // conditional
]
let UnaryPriority = 11
let BinaryPriority = {
'^': [13, 12],
'%': [10, 10],
'//': [10, 10],
'/': [10, 10],
'*': [10, 10],
'+': [9, 9],
'-': [9, 9],
'..': [8, 7],
'>>': [7, 7],
'<<': [7, 7],
'&': [6, 6],
'~': [5, 5],
'|': [4, 4],
'==': [3, 3],
'~=': [3, 3],
'>=': [3, 3],
'<=': [3, 3],
'>': [3, 3],
'<': [3, 3],
'+=': [3, 3],
'-=': [3, 3],
'*=': [3, 3],
'/=': [3, 3],
'^=': [3, 3],
'%=': [3, 3],
'..=': [3, 3],
'//=': [3, 3],
'and': [2, 2],
'or': [1, 1],
}
// Eof, Ident, KeyWord, Number, String, Symbol
function CreateLuaTokenStream(text) {
// Tracking for the current position in the buffer, and
// the current line / character we are on
let p = 0
let length = text.length
// Output buffer for tokens
let tokenBuffer = []
// Get a character or '' if at eof
function look(n) {
n = n || 0
if (p <= length) {
return text.substr(p + n, 1)
} else {
return ''
}
}
function get() {
if (p <= length) {
let c = text.substr(p, 1)
p++
return c
} else {
return ''
}
}
// Error
function error(str) {
let q = 0
let line = 1
let char = 1
while (q <= p) {
if (text.substr(q,1) == '\n') {
line++
char = 1
} else {
char++
}
}
let i_;
for (i_ = 0; i_ < tokenBuffer.length; i_++) {
let token = tokenBuffer[i_]
print(`${token.Type}<${token.Source}>`)
}
throw `file<${line}:${char}>: ${str}`
}
// Consume a long data with equals count of `eqcount`
function longdata(eqcount) {
while (true) {
let c = get()
if (c == '') {
error("Unfinished long string.")
} else if(c == ']') {
let done = true // Until contested
let i;
for (i=1; i<=eqcount; i++) {
if (look() == '=') {
p++
} else {
done = false
break
}
}
if (done && get() == ']') {
return
}
}
}
}
// Get the opening part for a long data `[` `=`` * `[`
// Precondition: The first `[` has been consumed
// Return: nil or the equals count
function getopen() {
let startp = p
while (look() == '=') {
p++
}
if (look() == '[') {
p++
return p - startp - 1
} else {
p = startp
return
}
}
// Add token
let whiteStart = 0
let tokenStart = 0
let tokens = 0
function token(type) {
tokens++
let src = text.substr(tokenStart, (p - tokenStart))
let ntype = null
if (type == "Number") {
if (src.substr(0,2).toLowerCase() == "0x") {
ntype = 'hex'
if (parseInt(src, 16) < 999999999999)
src = parseInt(src, 16)
} else if(src.substr(0,2).toLowerCase() == "0b") {
ntype = 'bin'
if (parseInt(src.substr(2), 2) < 999999999999)
src = parseInt(src.substr(2), 2)
}
}
let tk = {
'Type': type,
'LeadingWhite': text.substr(whiteStart, (tokenStart - whiteStart)),
'Source': src
}
if (ntype !== null) {
tk.NType = ntype
}
tokenBuffer.push(tk)
whiteStart = p
tokenStart = p
return tk
}
// Parse tokens loop
while (true) {
// Mark the whitespace start
whiteStart = p
while (true) { // Whitespaces
let c = look()
if (c == '') {
break
} else if(c == '-') {
if (look(1) == "-") {
p += 2
// Consume comment body
if (look() == "[") {
p++
let eqcount = getopen()
if (eqcount != null) {
// Long comment body
longdata(eqcount)
whiteStart = p
} else {
// Normal comment body
while (true) {
let c2 = get()
if (c2 == "" || c2 == "\n") {
//whiteStart = p
break
}
}
}
} else {
// Normal comment body
while (true) {
let c2 = get()
if (c2 == "" || c2 == "\n") {
//whiteStart = p
break
}
}
}
} else {
break
}
} else if(WhiteChars.includes(c)) {
p++
} else {
break
}
}
let leadingWhite = text.substr(whiteStart, (p - whiteStart))
// Mark the token start
tokenStart = p
// Switch on token type
let c1 = get()
if (c1 == '') {
// End of file
token('Eof')
break
} else if(c1 == '\'' || c1 == '\"') {
// String constant
while (true) {
let c2 = get()
if (c2 == '\\') {
let c3 = get()
let esc = CharacterForEscape[c3]
if (esc == null) {
throw (`Invalid Escape Sequence \`${c3}\`.`)
}
} else if(c2 == c1) {
break
} else if(c2 == "") {
throw ("Unfinished string!")
}
}
token('String')
} else if(c1 == '`') {
// Hash string
while (true) {
let c2 = get()
if (c2 == '\\') {
let c3 = get()
let esc = CharacterForEscape[c3]
if (esc == null) {
throw (`Invalid Escape Sequence \`${c3}\`.`)
}
} else if(c2 == c1) {
break
} else if(c2 == "") {
throw ("Unfinished string!")
}
}
token('Hash')
} else if(AllIdentStartChars.includes(c1)) {
// Ident or keyword
while (AllIdentChars.includes(look())) {
p++
}
if (Keywords.includes(text.substr(tokenStart, (p - tokenStart)))) {
token("Keyword")
} else {
token("Ident")
}
} else if(Digits.includes(c1) || (c1 == '.' && Digits.includes(look()))) {
// Number
if (c1 == '0' && look().toLowerCase() == 'x') {
p++
// Hex number
while (HexDigits.includes(look()) || look() === '_') {
p++
}
} else if (c1 == '0' && look().toLowerCase() == 'b') {
p++
// Binary number
while (BinaryDigits.includes(look()) || look() === '_') {
p++
}
} else {
// Normal number
while (Digits.includes(look()) || look() === '_') {
p++
}
if (look() == '.') {
// With decimal point
p++
while (Digits.includes(look())) {
p++
}
}
if (look() == 'e' || look() == 'E') {
// With exponent
p++
if (look() == '-' || look() == '+') {
p++
}
while (Digits.includes(look())) {
p++
}
}
}
token("Number")
} else if(c1 == '[') {
// Symbol or Long String
let eqCount = getopen()
if (eqCount != null) {
// Long String
longdata(eqCount)
token("String")
} else {
// Symbol
token("Symbol")
}
} else if(c1 == '.') {
// Greedily consume up to 3 `.` for . / .. / ... tokens / ..= compound
if (look() == '.') {
get()
if (look() == '.') {
get()
} else if(look() == '=') {
get()
}
}
token("Symbol")
} else if((c1 + look()) == '//') {
get()
if (look() == '=')
get()
token('Symbol')
} else if(BinopSet.includes(c1 + look())) {
get()
token("Symbol")
} else if(EqualSymbols.includes(c1)) {
if (look() == "=") {
p++
}
token("Symbol")
} else if(CompoundSymbols.includes(c1) && look() == '=') {
get()
token('Symbol')
} else if(Symbols.includes(c1)) {
token("Symbol")
} else {
throw(`Bad symbol \`${c1}\` in source. ${p}`)
}
}
return tokenBuffer
}
//Removelater
function CreateLuaParser(text) {
// Token stream and pointer into it
let tokens = CreateLuaTokenStream(text)
let p = 0
function get() {
let tok = tokens[p]
if (p < tokens.length) {
p++
}
return tok
}
function peek(n) {
n = p + (n || 0)
return tokens[n] || tokens[tokens.length - 1]
}
function getTokenStartPosition(token) {
let line = 1
let char = 0
let tkNum = 0
while (true) {
let tk = tokens[tkNum]
let text
if (tk == token) {
text = tk.LeadingWhite
} else {
text = tk.LeadingWhite + tk.Source
}
let i
for (i=0; i<=text.length; i++) {
let c = text.substr(i, 1)
if (c == '\n') {
line++
char = 0
} else {
char++
}
}
if (tk == token) {
break
}
tkNum++
}
return `${line}:${char+1}`
}
function debugMark() {
let tk = peek()
return `<${tk.Type} \`${tk.Source}\`> at: ${getTokenStartPosition(tk)}`
}
function isBlockFollow() {
let tok = peek()
return tok.Type == 'Eof' || (tok.Type == 'Keyword' && BlockFollowKeyword.includes(tok.Source))
}
function isUnop() {
return UnopSet.includes(peek().Source) || false
}
function isBinop() {
return BinopSet.includes(peek().Source) || false
}
function expect(type, source) {
let tk = peek()
if (tk.Type == type && (source == null || tk.Source == source)) {
return get()
} else {
let i
for (i=-3; i<=3; i++) {
print(`Tokens[${i}] = \`${peek(i).Source}\``)
}
if (source) {
let a = `${getTokenStartPosition(tk)}: \`${source}\` expected.`
throw a
} else {
let a = `${getTokenStartPosition(tk)}: ${type} expected.`
throw a
}
}
}
function MkNode(node) {
let getf = node.GetFirstToken
let getl = node.GetLastToken
let self = node
node.GetFirstToken = function() {
let t = getf(self)
assert(t)
return t
}
node.GetLastToken = function() {
let t = getl(self)
assert(t)
return t
}
return node
}
let block
let expr
function exprlist(locals, upvals) {
let exprList = [expr(locals, upvals)]
let commaList = []
while (peek().Source == ",") {
commaList.push(get())
exprList.push(expr(locals, upvals))
}
return [exprList, commaList]
}
function prefixexpr(locals, upvals) {
let tk = peek()
if (tk.Source == '(') {
let oparenTk = get()
let inner = expr(locals, upvals)
let cparenTk = expect('Symbol', ')')
let node
node = MkNode({
'Type': 'ParenExpr',
'Expression': inner,
'Token_OpenParen': oparenTk,
'Token_CloseParen': cparenTk,
'GetFirstToken': () => node.Token_OpenParen,
'GetLastToken': () => node.Token_CloseParen,
})
return node
} else if(tk.Type == "Ident") {
let node
node = MkNode({
'Type': 'VariableExpr',
'Token': get(),
'GetFirstToken': () => node.Token,
'GetLastToken': () => node.Token,
})
if (locals[node.Token.Source] != null && locals[node.Token.Source]?.Tokens?.push != null) {
locals[node.Token.Source].Tokens.push(node.Token)
locals[node.Token.Source].UseCountIncrease()
} else if(upvals[node.Token.Source] != null && upvals[node.Token.Source]?.Tokens?.push != null) {
upvals[node.Token.Source].Tokens.push(node.Token)
upvals[node.Token.Source].UseCountIncrease()
}
return node
} else {
print(debugMark())
let a = (`${getTokenStartPosition(tk)}: Unexpected symbol. ${tk.Type} ${tk.Source}`)
throw a
}
}
function tableexpr(locals, upvals) {
let obrace = expect("Symbol", "{")
let entries = []
let seperators = []
let length = 0
let lastIndex
let valLen = 0
while (peek().Source != "}") {
let indx
let val
if (peek().Source == '[') {
// Index
let obrac = get()
let index = expr(locals, upvals)
let cbrac = expect("Symbol", "]")
let eq = expect("Symbol", "=")
let value = expr(locals, upvals)
indx = index.Token && index.Token.Source
val = value
entries.push({
"EntryType": "Index",
"Index": index,
"Value": value,
"Token_OpenBracket": obrac,
"Token_CloseBracket": cbrac,
"Token_Equals": eq,
})
} else if(peek().Type == "Ident" && peek(1).Source == "=") {
// Field
let field = get()
let eq = get()
let value = expr(locals, upvals)
indx = field
val = value
entries.push({
"EntryType": "Field",
"Field": field,
"Value": value,
"Token_Equals": eq,
})
} else {
// Value
let value = expr(locals, upvals)
entries.push({
"EntryType": "Value",
"Value": value,
})
}
if (peek().Source == "," || peek().Source == ";") {
seperators.push(get())
} else {
break
}
}
let cbrace = expect("Symbol", "}")
let node
node = MkNode({
"Type": "TableLiteral",
"EntryList": entries,
"Token_SeperatorList": seperators,
"Token_OpenBrace": obrace,
"Token_CloseBrace": cbrace,
"GetFirstToken": () => node.Token_OpenBrace,
"GetLastToken": () => node.Token_CloseBrace,
})
return node
}
function varlist(acceptVarg, localdecl) {
let varList = []
let commaList = []
if (peek().Type == "Ident") {
let idn = get()
if (localdecl) {
if (peek().Source == '<' && peek(2).Source == '>') {
let attrb = peek(1).Source
idn.Attribute = { LeadingWhite: peek().LeadingWhite, Source: `<${attrb}>` }
get();
get();
get();
}
}
varList.push(idn)
} else if(peek().Source == "..." && acceptVarg) {
return [varList, commaList, get()]
}
while (peek().Source == ",") {
commaList.push(get())
if (peek().Source == "..." && acceptVarg) {
return [varList, commaList, get()]
} else {
let id = expect("Ident")
if (localdecl) {
if (peek().Source == '<' && peek(2).Source == '>') {
let attrb = peek(1).Source
id.Attribute = { LeadingWhite: peek().LeadingWhite, Source: `<${attrb}>` }
get();
get();
get();
}
}
varList.push(id)
}
}
return [varList, commaList ]
}
function blockbody(terminator, locals, upvals) {
let body = block(locals, upvals)
let after = peek()
if (after.Type == "Keyword" && after.Source == terminator) {
get()
return [body, after]
} else {
print(after.Type, after.Source)
throw `${getTokenStartPosition(after)}: ${terminator} expected.`
}
}
function funcdecl(isAnonymous, locals, upvals, local) {
let functionKw = get()
let nameChain
let nameChainSeperator
if (!isAnonymous) {
nameChain = []
nameChainSeperator = []
let token = expect("Ident")
nameChain.push(token)
while (peek().Source == ".") {
nameChainSeperator.push(get())
nameChain.push(expect("Ident"))
}
if (peek().Source == ":") {
nameChainSeperator.push(get())
nameChain.push(expect("Ident"))
}
}
let oparenTk = expect("Symbol", "(")
let [argList, argCommaList, vargToken] = varlist(true)
let cparenTk = expect("Symbol", ")")
let [fbody, enTk] = blockbody("end", locals, upvals)
let node
node = MkNode({
"Type": (isAnonymous == true ? "FunctionLiteral" : "FunctionStat"),
"NameChain": nameChain,
"ArgList": argList,
"Body": fbody,
"Token_Function": functionKw,
"Token_NameChainSeperator": nameChainSeperator,
"Token_OpenParen": oparenTk,
"Token_Varg": vargToken,
"Token_ArgCommaList": argCommaList,
"Token_CloseParen": cparenTk,
"Token_End": enTk,
"GetFirstToken": () => node.Token_Function,
"GetLastToken": () => node.Token_End,
})
return node
}
function functionargs(locals, upvals) {
let tk = peek()
if (tk.Source == "(") {
let oparenTk = get()
let argList = []
let argCommaList = []
while (peek().Source != ")") {
argList.push(expr(locals, upvals));
if (peek().Source == ",") {
argCommaList.push(get())
} else {
break
}
}
let cparenTk = expect("Symbol", ")")
let node
node = MkNode({
"CallType": "ArgCall",
"ArgList": argList,
"Token_CommaList": argCommaList,
"Token_OpenParen": oparenTk,
"Token_CloseParen": cparenTk,
"GetFirstToken": () => node.Token_OpenParen,
"GetLastToken": () => node.Token_CloseParen,
})
return node
} else if(tk.Source == "{") {
let node
node = MkNode({
"CallType": "TableCall",
"TableExpr": expr(locals, upvals),
"GetFirstToken": () => node.TableExpr.GetFirstToken(),
"GetLastToken": () => node.TableExpr.GetLastToken(),
})
return node
} else if(tk.Type == "String") {
let node
node = MkNode({
"CallType": "StringCall",
"Token": get(),
"GetFirstToken": () => node.Token,
"GetLastToken": () => node.Token,
})
return node
} else {
throw "Function arguments expected."
}
}
function primaryexpr(locals, upvals) {
let base = prefixexpr(locals, upvals)
assert(base, "nil prefixexpr")
while (true) {
let tk = peek()
if (tk.Source == ".") {
let dotTk = get()
let fieldName = expect("Ident")
let node
node = MkNode({
"Type": "FieldExpr",
"Base": base,
"Field": fieldName,
"Token_Dot": dotTk,
"GetFirstToken": () => node.Base.GetFirstToken(),
"GetLastToken": () => node.Field,
})
base = node
} else if(tk.Source == ":") {
let colonTk = get()
let methodName = expect("Ident")
let fargs = functionargs(locals, upvals)
let node
node = MkNode({
"Type": "MethodExpr",
"Base": base,
"Method": methodName,
"FunctionArguments": fargs,
"Token_Colon": colonTk,
"GetFirstToken": () => node.Base.GetFirstToken(),
"GetLastToken": () => node.FunctionArguments.GetLastToken(),
})
base = node
} else if(tk.Source == "[") {
let obrac = get()
let index = expr(locals, upvals)
let cbrac = expect("Symbol", "]")
let node
node = MkNode({
"Type": "IndexExpr",
"Base": base,
"Index": index,
"Token_OpenBracket": obrac,
"Token_CloseBracket": cbrac,
"GetFirstToken": () => node.Base.GetFirstToken(),
"GetLastToken": () => node.Token_CloseBracket,
})
base = node
} else if(tk.Source == "{" || tk.Source == "(" || tk.Type == "String") {
let node
node = MkNode({
"Type": "CallExpr",
"Base": base,
"FunctionArguments": functionargs(locals, upvals),
"GetFirstToken": () => node.Base.GetFirstToken(),
"GetLastToken": () => node.FunctionArguments.GetLastToken(),
})
base = node
} else if(Compounds.includes(tk.Source)) {
let compoundTk = get()
let rhsExpr = expr(locals, upvals)
let node
node = MkNode({
"Type": "CompoundStat",
"Base": base,
"Token_Compound": compoundTk,
"Rhs": rhsExpr,
"Lhs": base,
"GetFirstToken": () => node.Base.GetFirstToken(),
"GetLastToken": () => node.Rhs.GetLastToken(),
})
base = node
} else {
return base
}
}
}
function simpleexpr(locals, upvals) {
let tk = peek()
if (tk.Type == "Number") {
let node
node = MkNode({
"Type": "NumberLiteral",
"Token": get(),
"GetFirstToken": () => node.Token,
"GetLastToken": () => node.Token
})
return node
} else if(tk.Type == "String") {
let node
node = MkNode({
"Type": "StringLiteral",
"Token": get(),
"GetFirstToken": () => node.Token,
"GetLastToken": () => node.Token,
})
return node
} else if (tk.Type == "Hash") {
let node
node = MkNode({
"Type": "HashLiteral",
"Token": get(),
"GetFirstToken": () => node.Token,
"GetLastToken": () => node.Token,
})
return node
} else if(tk.Source == "nil") {
let node
node = MkNode({
"Type": "NilLiteral",
"Token": get(),
"GetFirstToken": () => node.Token,
"GetLastToken": () => node.Token,
})
return node
} else if(tk.Source == "true" || tk.Source == "false") {
let node
node = MkNode({
"Type": "BooleanLiteral",
"Token": get(),
"GetFirstToken": () => node.Token,
"GetLastToken": () => node.Token,
})
return node
} else if(tk.Source == "...") {
let node
node = MkNode({
"Type": "VargLiteral",
"Token": get(),
"GetFirstToken": () => node.Token,
"GetLastToken": () => node.Token,
})
return node
} else if(tk.Source == "{") {
return tableexpr(locals, upvals)
} else if(tk.Source == "function") {
return funcdecl(true, locals, upvals)
} else {
return primaryexpr(locals, upvals)
}
}
function subexpr(limit, locals, upvals) {
let curNode
if (isUnop()) {
let opTk = get()
let ex = subexpr(UnaryPriority, locals, upvals)
let node
node = MkNode({
"Type": "UnopExpr",
"Token_Op": opTk,
"Rhs": ex,
"GetFirstToken": () => node.Token_Op,
"GetLastToken": () => node.Rhs.GetLastToken(),
})
curNode = node
} else {
curNode = simpleexpr(locals, upvals)
assert(curNode, "nil sipleexpr")
}
while (isBinop() && BinaryPriority[peek().Source] != undefined && BinaryPriority[peek().Source][0] > limit) {
let opTk = get()
let rhs = subexpr(BinaryPriority[opTk.Source][1], locals, upvals)
assert(rhs, "RhsNeeded")
let node
node = MkNode({
"Type": "BinopExpr",
"Lhs": curNode,
"Rhs": rhs,
"Token_Op": opTk,
"GetFirstToken": () => node.Lhs.GetFirstToken(),
"GetLastToken": () => node.Rhs.GetLastToken(),
})
curNode = node
}
return curNode
}
expr = (locals, upvals) => subexpr(0, locals, upvals)
function exprstat(locals, upvals) {
let ex = primaryexpr(locals, upvals)
if (ex.Type == "MethodExpr" || ex.Type == "CallExpr") {
let node
node = MkNode({
"Type": "CallExprStat",
"Expression": ex,
"GetFirstToken": () => node.Expression.GetFirstToken(),
"GetLastToken": () => node.Expression.GetLastToken(),
})
return node
} else if(ex.Type == "CompoundStat") {
return ex
} else {
let lhs = [ex]
let lhsSeperator = []
while (peek().Source == ",") {
lhsSeperator.push(get())
let lhsPart = primaryexpr(locals, upvals)
if (lhsPart.Type == "MethodExpr" || lhsPart.Type == "CallExpr") {
throw "Bad left hand side of asignment"
}
lhs.push(lhsPart)
}
let eq = expect("Symbol", "=")
let rhs = [expr(locals, upvals)]
let rhsSeperator = []
while (peek().Source == ",") {
rhsSeperator.push(get())
rhs.push(expr(locals, upvals))
}
let node
node = MkNode({
"Type": "AssignmentStat",
"Rhs": rhs,
"Lhs": lhs,
"Token_Equals": eq,
"Token_LhsSeperatorList": lhsSeperator,
"Token_RhsSeperatorList": rhsSeperator,
"GetFirstToken": () => node.Lhs[0].GetFirstToken(),
"GetLastToken": () => node.Rhs[node.Rhs.length - 1].GetLastToken(),
})
return node
}
}
function ifstat(locals, upvals) {
let ifKw = get()
let condition = expr(locals, upvals)
let thenKw = expect("Keyword", "then")
let ifBody = block(locals, upvals)
let elseClauses = []
while (peek().Source == "elseif" || peek().Source == "else") {
let elseifKw = get()
let elseifCondition
let elseifThenKw
if (elseifKw.Source == "elseif") {
elseifCondition = expr(locals, upvals)
elseifThenKw = expect("Keyword", "then")
}
let elseifBody = block(locals, upvals)
elseClauses.push({
"Condition": elseifCondition,
"Body": elseifBody,
"ClauseType": elseifKw.Source,
"Token": elseifKw,
"Token_Then": elseifThenKw,
})
if (elseifKw.Source == "else") {
break
}
}
let enKw = expect("Keyword", "end")
let node
node = MkNode({
"Type": "IfStat",
"Condition": condition,
"Body": ifBody,
"ElseClauseList": elseClauses,
"Token_If": ifKw,
"Token_Then": thenKw,
"Token_End": enKw,
"GetFirstToken": () => node.Token_If,
"GetLastToken": () => node.Token_End,
})
return node
}
function dostat(locals, upvals) {
let doKw = get()
let [body, enKw] = blockbody("end", locals, upvals)
let node
node = MkNode({
"Type": "DoStat",
"Body": body,
"Token_Do": doKw,
"Token_End": enKw,
"GetFirstToken": () => node.Token_Do,
"GetLastToken": () => node.Token_End,
})
return node
}
function whilestat(locals, upvals) {
let whileKw = get()
let condition = expr(locals, upvals)
let doKw = expect("Keyword", "do")
let [body, enKw] = blockbody("end", locals, upvals)
let node
node = MkNode({
"Type": "WhileStat",
"Condition": condition,
"Body": body,
"Token_While": whileKw,
"Token_Do": doKw,
"Token_End": enKw,
"GetFirstToken": () => node.Token_While,
"GetLastToken": () => node.Token_End,
})
return node
}
function forstat(locals, upvals) {
let forKw = get()
let [loopVars, loopVarCommas] = varlist()
let node = []
if (peek().Source == "=") {
let eqTk = get()
let [exprList, exprCommaList] = exprlist(locals, upvals)
if (exprList.length < 2 || exprList.length > 3) {
throw "Expected 2 or 3 values for range bounds"
}
let doTk = expect("Keyword", "do")
let [body, enTk] = blockbody("end", locals, upvals)
let node
node = MkNode({
"Type": "NumericForStat",
"VarList": loopVars,
"RangeList": exprList,
"Body": body,
"Token_For": forKw,
"Token_VarCommaList": loopVarCommas,
"Token_Equals": eqTk,
"Token_RangeCommaList": exprCommaList,
"Token_Do": doTk,
"Token_End": enTk,
"GetFirstToken": () => node.Token_For,
"GetLastToken": () => node.Token_End,
})
return node
} else if(peek().Source == "in") {
let inTk = get()
let [exprList, exprCommaList] = exprlist(locals, upvals)
let doTk = expect("Keyword", "do")
let [body, enTk] = blockbody("end", locals, upvals)
let node
node = MkNode({
"Type": "GenericForStat",
"VarList": loopVars,
"GeneratorList": exprList,
"Body": body,
"Token_For": forKw,
"Token_VarCommaList": loopVarCommas,
"Token_In": inTk,
"Token_GeneratorCommaList": exprCommaList,
"Token_Do": doTk,
"Token_End": enTk,
"GetFirstToken": () => node.Token_For,
"GetLastToken": () => node.Token_End
})
return node
}
}
function repeatstat(locals, upvals) {
let repeatKw = get()
let [body, untilTk] = blockbody("until", locals)
let condition = expr(locals, upvals)
let node
node = MkNode({
"Type": "RepeatStat",
"Body": body,
"Condition": condition,
"Token_Repeat": repeatKw,
"Token_Until": untilTk,
"GetFirstToken": () => node.Token_Repeat,
"GetLastToken": () => node.Condition.GetLastToken(),
})
return node
}
function localdecl(locals, upvals) {
let localKw = get()
if (peek().Source == "function") {
let funcStat = funcdecl(false, locals, upvals, true)
if (funcStat.NameChain.length > 1) {
throw getTokenStartPosition(funcStat.Token_NameChainSeperator[0]) + ": `(` expected."
}
let node
node = MkNode({
"Type": "LocalFunctionStat",
"FunctionStat": funcStat,
"Token_Local": localKw,
"GetFirstToken": () => node.Token_Local,
"GetLastToken": () => node.FunctionStat.GetLastToken(),
})
return node
} else if(peek().Type == "Ident") {
let [varList, varCommaList ] = varlist(false, true)
let exprList = []
let exprCommaList = []
let eqToken
if (peek().Source == "=") {
eqToken = get()
let [exprList1, exprCommaList1] = exprlist(locals, upvals)
exprList = exprList1
exprCommaList = exprCommaList1
}
let node
node = MkNode({
"Type": "LocalVarStat",
"VarList": varList,
"ExprList": exprList,
"Token_Local": localKw,
"Token_Equals": eqToken,
"Token_VarCommaList": varCommaList,
"Token_ExprCommaList": exprCommaList,
"GetFirstToken": () => node.Token_Local,
"GetLastToken": function() {
if (node.ExprList.length > 0) {
return node.ExprList[node.ExprList.length - 1].GetLastToken()
} else {
return node.VarList[node.VarList.length - 1]
}
},
})
return node
} else {
throw "`function` or ident expected"
}
}
function retstat(locals, upvals) {
let returnKw = get()
let exprList
let commaList
if (isBlockFollow() || peek().Source == ";") {
exprList = []
commaList = []
} else {
[exprList, commaList] = exprlist(locals, upvals)
}
let self
self = {
"Type": "ReturnStat",
"ExprList": exprList,
"Token_Return": returnKw,
"Token_CommaList": commaList,
"GetFirstToken": () => self.Token_Return,
"GetLastToken": function() {
if (self.ExprList.length > 0) {
return self.ExprList[self.ExprList.length- 1].GetLastToken()
} else {
return self.Token_Return
}
},
}
return self
}
function breakstat(locals, upvals) {
let breakKw = get()
let self
self = {
"Type": "BreakStat",
"Token_Break": breakKw,
"GetFirstToken": () => self.Token_Break,
"GetLastToken": () => self.Token_Break,
}
return self
}
function continuestat(locals, upvals) {
let continueKw = get()
let self
self = {
"Type": "ContinueStat",
"Token_Continue": continueKw,
"GetFirstToken": () => self.Token_Continue,
"GetLastToken": () => self.Token_Continue,
}
return self
}
function statement(locals, upvals) {
let tok = peek()
if (tok.Source == "if") {
return [false, ifstat(locals, upvals)]
} else if(tok.Source == "while") {
return [false, whilestat(locals, upvals)]
} else if(tok.Source == "do") {
return [false, dostat(locals, upvals)]
} else if(tok.Source == "for") {
return [false, forstat(locals, upvals)]
} else if(tok.Source == "repeat") {
return [false, repeatstat(locals, upvals)]
} else if(tok.Source == "function") {
return [false, funcdecl(false, locals, upvals)]
} else if(tok.Source == "local") {
return [false, localdecl(locals, upvals)]
} else if(tok.Source == "return") {
return [true, retstat(locals, upvals)]
} else if(tok.Source == "break") {
return [true, breakstat(locals, upvals)]
} else if(tok.Source == "continue") {
return [true, continuestat(locals, upvals)]
} else {
return [false, exprstat(locals, upvals)]
}
}
let blocks = 1
block = function(a, b) {
let myblocknum = blocks++
let statements = []
let semicolons = []
let isLast = false
let locals = {}
let upvals = {}
if (b != null) {
for (let [i, v] of Object.entries(b)) {
upvals[i] = v
}
}
if (a != null) {
for (let [i, v] of Object.entries(a)) {
upvals[i] = v
}
}
let thing
let i = 0
while (!isLast && !isBlockFollow()) {
if (thing && thing == peek()) {
print(`INFINITE LOOP POSSIBLE ON STATEMENT ${thing.Source} :`,thing)
}
thing = peek()
let [isLast, stat] = statement(locals, upvals)
if (stat) {
statements.push(stat);
switch (stat.Type) {
case "LocalVarStat":
stat.VarList.forEach(token => {
token.UseCount = 0
token.Number = i++
locals[token.Source] = token
let tokens = []
function lol() {
token.UseCount++
tokens.forEach(t => {
t.UseCount = token.UseCount
})
}
token.Tokens = {}
token.Tokens.push = (t) => {
t.UseCountIncrease = lol
t.UseCount = token.UseCount
t.Tokens = token.Tokens
tokens.push(t)
}
token.Tokens.get = () => tokens
token.UseCountIncrease = lol
})
break
case "LocalFunctionStat":
let nameChain = stat.FunctionStat.NameChain
if (nameChain.length === 1) {
let token = nameChain[0]
token.UseCount = 0
token.Number = i++
locals[token.Source] = token
let tokens = []
function lol() {
token.UseCount++
tokens.forEach(t => {
t.UseCount = token.UseCount
})
}
token.Tokens = {}
token.Tokens.push = (t) => {
t.UseCountIncrease = lol
t.UseCount = token.UseCount
t.Tokens = token.Tokens
tokens.push(t)
}
token.Tokens.get = () => tokens
token.UseCountIncrease = lol
}
break
default:
break
}
}
let next = peek()
if (next.Type == "Symbol" && next.Source == ";") {
semicolons[statements.length - 1] = get()
}
}
let node
node = {
"Type": "StatList",
"StatementList": statements,
"SemicolonList": semicolons,
"GetFirstToken": function() {
if (node.StatementList.length == 0) {
return
} else {
return node.StatementList[0]?.GetFirstToken()
}
},
"GetLastToken": function() {
if (node.StatementList.length == 0) {
return
} else if(node.SemicolonList[node.StatementList.length - 1]) {
return node.SemicolonList[node.StatementList.length - 1]
} else {
return node.StatementList[node.StatementList.length - 1].GetLastToken()
}
},
}
return node
}
return block([], [])
}
function VisitAst(ast, visitors) {
let ExprType = {
'BinopExpr': true, 'UnopExpr': true,
'NumberLiteral': true, 'StringLiteral': true, 'NilLiteral': true, 'BooleanLiteral': true, 'VargLiteral': true, "HashLiteral": true,
'FieldExpr': true, 'IndexExpr': true,
'MethodExpr': true, 'CallExpr': true,
'FunctionLiteral': true,
'VariableExpr': true,
'ParenExpr': true,
'TableLiteral': true,
}
let StatType = {
'StatList': true,
'BreakStat': true,