UNPKG

lua-format

Version:

Lua Beautifier & Minifier, written in JavaScript.

1,628 lines (1,447 loc) 149 kB
/* 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,