UNPKG

lolcode

Version:

Parse LOLCODE to JavaScript

470 lines (443 loc) 19 kB
let uFunc = []; let errored = false; let error = null; let warn = null; //////////////////////////////// //----------------------------// // Copyright (c) 2017 NullDev // //----------------------------// //////////////////////////////// const noop = () => {}; function _xor(op1, op2){ return !!((op1 || op2) && !(op1 && op2)); } function _char(n){ return String.fromCharCode(n); } function _ord(c){ return c.charCodeAt(0); } function _len(x){ if (x.length !== undefined) return x.length; return 0; } function prettyprintArr(array){ var str = ""; for (var name in array){ var el = array[name]; el.constructor == Array ? str += prettyprintArr(el) + ", " : str += el + ", "; } str = str.replace(/, $/, ""); return "[" + str + "]"; } function castBukkit(val){ if (typeof val == 'string' || typeof val == "String") return val.split(""); return [val]; } function ihasjsErr(errstr){ if (!errored) error = "IHAZJS Error: " + errstr; errored = true; } function isFunc(funcname){ for (var i = 0; i < uFunc.length; i++) if (uFunc[i].name == funcname) return true; return false; } function getFuncArgs(funcname){ for (var i = 0; i < uFunc.length; i++) if (uFunc[i].name == funcname) return uFunc[i].num_args; return false; } function remComments(str){ str = str.replace(/OBTW([\s\S]*?)TLDR/g, ""); str = str.replace(/BTW.*/g, ""); return str; } function parseInit(str){ //IHAZJS doesn't care about them, should we still warn the user?? str = str.replace(/HAI.*/g, ""); str = str.replace(/KTHXB(YE|AI|AY).*/g, ""); /***************************************************/ /* I MIGHT ACTUALLY INCLUDE LIBRARYS LIKE THAT!!!1 */ /***************************************************/ str = str.replace(/CAN HAS .*/g, ""); /***************************************************/ str = str.replace(/[ \t]+/g, " "); str = str.replace(/[,]/g, "\n"); str = str.replace(/\.{3}[ ]*\n/g, ""); str = str.replace(/\r\n|\r|\n/g, "\n"); str = str.replace(/[ ]+\n/g, "\n"); str = str.replace(/(.*) IS NOW A (.*)/g, "$1 R MAEK $1 A $2"); str = str.replace(/(I HAS A (.*)) IT[SZ] (.*)/g, "$1\n$2 R $3"); str = str.replace(/\bWIN\b/g, "true"); str = str.replace(/\bFAIL\b/g, "false"); return str; } function parseStringLiterals(str, strip){ if (strip == true || strip == undefined){ this.aliases = []; this.num_alaises = 0; let i = -1; let last_i = 0; let start; let in_str = false; let str_ = ""; while ((i = str.indexOf('"', i+1)) !== -1){ if (!in_str){ str_ += str.substr(last_i, i - last_i); in_str = true; start = i; } else { let escapes = 0; let j = i - 1; while (j >= 0 && str[j--] == ":") escapes++; if (!(escapes % 2)){ in_str = false; let quoted = str.substr(start + 1, (i - start - 1)); quoted = quoted.replace(/(\\*)(\n)/g, function($0, $1){ if ($1.length % 2) return $0; return "\\n"; }); quoted = quoted.replace(/:([\)>o":]|\(.*?\)|\{.*?\})/g, function($0, $1){ switch($1){ case ")": { return "\\n"; } case ">": { return "\\t"; } case "o": { return "^g"; } case '"': { return '\\"'; } case ":": { return ":"; } } if ($1.charAt(0) == "{") return '"+' + $1.substr(1,$1.length-2) + '+"'; if ($1.charAt(0) == "(") return "\\u" + $1.substr(1,$1.length-2); return $0; }); this.aliases[this.num_alaises] = quoted; str_ += "@" + this.num_alaises++; i++; } } last_i = i; } str_ += str.substr(last_i); return str_; } else { for (let i = this.num_alaises - 1; i >= 0; i--) str = str.replace("@" + i, '"' + this.aliases[i] + '"'); return str; } } function tokenizer(str){ let tokens = []; let string = []; for (let i = 0; i < str.length; i++){ let str_ = str.substr(i), match = null; if ((match = /^A\b/.exec(str_))) tokens.push("A"); else if ((match = (/^I HAS A(?=\s)/.exec(str_)))) tokens.push("DECLARE"); else if ((match = (/^(R|IT[SZ])(?=\s)/.exec(str_)))) tokens.push("ASSIGN"); else if ((match = (/^G[EI]MMEH(?=\s)/.exec(str_)))) tokens.push("PROMPT"); else if ((match = /^MAEK(?=\s)/.exec(str_))) tokens.push("CAST"); else if ((match = /^(NOOB|YARN|NUMBR|NUMBAR|TROOF|BUKKIT)(?=\s)/.exec(str_))) tokens.push("TYPE"); else if ((match = /^(SMALL?E?R|BIGG?E?R) THAN\b/.exec(str_))) tokens.push("CMP_OP"); else if ((match = /^O RLY\?/.exec(str_))) tokens.push("START_IF"); else if ((match = /^YA RLY\b/.exec(str_))) tokens.push("IF"); else if ((match = /^MEBBE\b/.exec(str_))) tokens.push("ELSE_IF"); else if ((match = /^NO WAI\b/.exec(str_))) tokens.push("ELSE"); else if ((match = /^OIC\b/.exec(str_))) tokens.push("END_IF"); else if ((match = /^AN\b/.exec(str_))) tokens.push("COMMA") else if ((match = /^((\-\s*)?\d+(\.\d+)?|true|false|@\d+(!\w+){0,2})\b/.exec(str_))) tokens.push("LITERAL"); else if ((match = (/^(((SUM|DIFF|PRODUKT|MOD|QUOSHUNT|BOTH|EITHER|BIGGR|SMALLR|WON) OF)|BOTH SAEM|DIFFRINT)\b/.exec(str_)))) tokens.push("BINARY_OP"); else if ((match = (/^(ALL|ANY|CHR|ORD|LEN) OF\b/.exec(str_)))) tokens.push("NARY_OP"); else if ((match = (/^GOT\b/.exec(str_)))) tokens.push("NARY_OP"); else if ((match = (/^NOT\b/.exec(str_)))) tokens.push("NARY_OP"); else if ((match = /^IM IN [YU]R \w+$/.exec(str_))) tokens.push("LOOP_INF"); else if ((match = /^IM IN [YU]R [a-zA-Z]\w+/.exec(str_))) tokens.push("LOOP"); else if ((match = /^IM OUTTA [YU]R \w+\b/.exec(str_))) tokens.push("END_LOOP"); else if ((match = /^(WILE|TILL?)\b/.exec(str_))) tokens.push("LOOP_CONDITION"); else if ((match = /^(UPPIN|NERFIN) [YU]R\b/.exec(str_))) tokens.push("LOOP_ACTION"); else if ((match = /^!!*/.exec(str_))) tokens.push("SCREECH"); else if ((match = /^\?/.exec(str_))) tokens.push("QUESTION_MARK"); else if ((match = /^UPZ\b/.exec(str_))) tokens.push("INC_OP"); else if ((match = /^VISIBLE\b/.exec(str_))) tokens.push("STDOUT"); else if ((match = /^IZ\b/.exec(str_))) tokens.push("INLINE_IF"); else if ((match = /^KTHX\b/.exec(str_))) tokens.push("INLINE_IF_END"); else if ((match = /^GTFO\b/.exec(str_))) tokens.push("BREAK"); else if ((match = /^WTF\\?\b/.exec(str_))) tokens.push("SWITCH"); else if ((match = /^OMG\b/.exec(str_))) tokens.push("SWITCH_CASE"); else if ((match = /^OMGWTF\b/.exec(str_))) tokens.push("SWITCH_CASE_DEFAULT"); else if ( match = /^SMOOSH\b/.exec(str_)) tokens.push("NARY_OP"); else if ( match = /^(MKAY|NOTHING?(?: ELSE)?)\b/.exec(str_)) tokens.push("OP_TERM"); else if ((match = (/^HOW DUZ I\b/).exec(str_))) tokens.push("FUNC_DEF"); else if ((match = (/^YR\b/).exec(str_))) tokens.push("VAR"); else if ((match = (/^FOUND\b/).exec(str_))) tokens.push("RETURN"); else if ((match = (/^IF U SAY SO\b/).exec(str_))) tokens.push("FUNC_DEF_END"); else if ((match = (/^[a-zA-Z@][a-zA-Z0-9_]*(\!\w+)*/.exec(str_)))) tokens.push("IDENTIIFER"); if (match){ i += match[0].length - 1; string.push(match[0]); } else if ((match = str_.match(/^\S+/))){ ihasjsErr("Unrecognised sequence while tokenizing: " + str_.match(/^\S+/) + "\n" + str_.substr(i)); i+=match[0].length-1; } } if (tokens.length != string.length) ihasjsErr("Got different number of tokens than matches. This is fatal."); let ret = []; for (let i = 0; i < tokens.length; i++){ ret.push(tokens[i]); ret.push(string[i]); } return ret; } function subOperation(tokens){ let s = tokens[1]; if (s.charAt(0) == "B") return ">" + evalLine(tokens.slice(2)); else if (s.charAt(0) == "S") return "<" + evalLine(tokens.slice(2)); else if (s == "NOT") return "!(" + evalLine(tokens.slice(2)) + ")"; } function getVariable(token){ let value = token[1]; let xc = value.split("!").length-1; if (!xc) return value; value = value.replace(/!/, "["); value = value.replace(/!/g, "]["); value += "]"; return value; } function evalExpression(tokens){ let string = tokens[1]; let op_symbols = { "SUM OF" : { symbol: "+", nary: 2, before: "(", after: ")" }, "DIFF OF" : { symbol: "-", nary: 2, before: "(", after: ")" }, "PRODUKT OF" : { symbol: "*", nary: 2, before: "(", after: ")" }, "MOD OF" : { symbol: "%", nary: 2, before: "(", after: ")" }, "QUOSHUNT OF" : { symbol: "/", nary: 2, before: "(", after: ")" }, "BOTH OF" : { symbol: "&&", nary: 2, before: "(", after: ")" }, "EITHER OF" : { symbol: "||", nary: 2, before: "(", after: ")" }, "BOTH SAEM" : { symbol: "==", nary: 2, before: "(", after: ")" }, "DIFFRINT" : { symbol: "!=", nary: 2, before: "(", after: ")" }, "BIGGR OF" : { symbol: ",", nary: 2, before: "(Math.max(", after: "))" }, "SMALLR OF" : { symbol: ",", nary: 2, before: "(Math.min(", after: "))" }, "WON OF" : { symbol: ",", nary: 2, before: "(_xor(", after: "))" }, "ALL OF" : { symbol: ")&&(", nary: -1, before: "((", after: "))" }, "ANY OF" : { symbol: ")||(", nary: -1, before: "((", after: "))" }, "SMOOSH" : { symbol: "+", nary: -1, before: '""+', after: "" }, "NOT" : { symbol: "", nary: 1, before: "!(", after: ")" }, "GOT" : { symbol: ",", nary: -1, before: "[", after: "]" }, "CHR OF" : { symbol: "", nary: 1, before: "_char(", after: ")" }, "ORD OF" : { symbol: "", nary: 1, before: "_ord(", after: ")" }, "LEN OF" : { symbol: "", nary: 1, before: "_len(", after: ")" } }; let stack = []; let str_ = ""; for (var i = 0; i < tokens.length; i += 2){ let s = tokens[i + 1], t = tokens[i], schedule_pop = false; switch(t){ case "BINARY_OP": case "NARY_OP": { if (stack.length){ let st = stack[stack.length - 1]; str_ += (st.terms) ? st.symbol : ""; } var sym = op_symbols[s]; var sym_copy = { symbol: sym.symbol, nary: sym.nary, before: sym.before, after: sym.after }; sym_copy.terms = 0; str_ += sym_copy.before; stack.push(sym_copy); break; } case "COMMA": { noop(); //tmp break; } case "OP_TERM": { schedule_pop = true; break; } case "IDENTIIFER": { if (stack.length){ var sym = stack[stack.length-1]; str_ += (sym.terms)? sym.symbol : ""; } var args = getFuncArgs(s); if (args !== false){ var sym = { nary:args, terms:0, symbol: ",", before: "", after: ")" }; stack.push(sym); str_ += s + "("; } else{ str_ += getVariable([t,s]); ++sym.terms; } break; } case "LITERAL": { var sym = stack[stack.length-1]; str_ += (sym.terms)? sym.symbol : ""; str_ += getVariable([t,s]); ++sym.terms break; } default: { ihasjsErr("Unexpected token in expression: " + t + "\n" + s); } } if (!stack.length) break; while (1){ var sym = stack[stack.length - 1]; if ((sym.terms >= sym.nary && sym.nary != -1) || schedule_pop){ schedule_pop = false; stack.pop(); str_ += sym.after; if (stack.length) stack[stack.length-1].terms++; else break; } else break; } if (!stack.length) break; } while (stack.length) str_ += stack.pop().after; if (i < tokens.length - 2) str_ += evalLine(tokens.slice(i + 2)); return str_; } function evalIdentifier(tokens){ let id = tokens[1]; if (!isFunc(id)) return getVariable([tokens[0], tokens[1]]) + evalLine(tokens.slice(2)); return evalExpression(tokens); } function evalLine(tokens){ if (!tokens.length) return ""; let t = tokens[0]; switch(t){ case "BINARY_OP": case "NARY_OP": { return evalExpression(tokens); } case "IDENTIIFER": { return evalIdentifier(tokens); } case "ASSIGN": { return "=" + evalLine(tokens.slice(2)); } case "LITERAL": { return evalIdentifier(tokens); } case "CAST": { let func = ""; let type = null; let val = []; let i; for (i = 2; i < tokens.length; i += 2){ if (tokens[i] == "A"){ i += 2; type = tokens[i + 1]; break; } val.push(tokens[i], tokens[i + 1]); } switch(type){ case "YARN": { func = "String"; break; } case "TROOF": { func = "Boolean"; break; } case "NUMBR": { func = "parseInt"; break; } case "NUMBAR": { func = "Number"; break; } case "BUKKIT": { func = "castBukkit"; break; } default: { ihasjsErr("Cast to unknown type: " + type); return; } } return func + "(" + evalLine(val) + ")" + evalLine(tokens.slice(i + 2)); } case "TYPE": { return tokens[1] + evalLine(tokens.slice(2)); } case "QUESTION_MARK": { return evalLine(tokens.slice(2)); } case "CMP_OP": { return subOperation(tokens); } default: { ihasjsErr("Unknown token: " + t); return tokens.slice(2); } } } function parseLoop(tokens){ let action = tokens[5] + (tokens[3].match(/^UPPIN/) ? "++" : "--"); let condition = evalLine(tokens.slice(8)); if (tokens[7].charAt(0) == "T") condition = "!(" + condition + ")"; let init = tokens[5] + "= ((typeof " + tokens[5] + '=="undefined") ? 0 : ' + tokens[5] + ")"; let loop = "for((" + init + ");" + condition + ";" + action + "){"; return loop; } function evalFunctionDefinition(tokens){ let f = "function " + tokens[3] + "(", args = 0; for (var i = 4; i < tokens.length; i += 2){ let s = tokens[i + 1]; let t = tokens[i]; if (t != "IDENTIIFER") continue; f += s; args++; f += ","; } f = f.replace(/,+$/, ""); f += "){\nvar IT = undefined;\n"; uFunc.push( {name: tokens[3], num_args: args }); return f; } function interpreteLine(tokens){ if (!tokens.length) return ""; let t = tokens[0], js = ""; switch(t){ case "BINARY_OP": case "NARY_OP": { return "IT =" + evalExpression(tokens) + ";"; } case "DECLARE": { return ("var " + tokens[3] + " = null;"); } case "START_IF": { return ""; } case "IF": { return "if (IT){"; } case "ELSE_IF": { return "} else if(" + evalLine(tokens.slice(2)) + "){"; } case "ELSE": { return "} else {"; } case "END_IF": case "END_LOOP": { return "}"; } case "IDENTIIFER": case "LITERAL": { if (tokens.length >= 2 && tokens[2] != "ASSIGN") return "IT = " + evalIdentifier(tokens) + ";"; else return evalIdentifier(tokens) + ";"; } case "PROMPT": { return tokens[3] + ' = prompt("' + tokens[3] + '");'; } case "STDOUT": { let newline = !(tokens[tokens.length - 2] == "SCREECH"), t; if (newline) t = tokens.slice(2); else t = tokens.slice(2, tokens.length - 2); let stdouttxt = evalLine(t); return "console.log(" + (stdouttxt.isArray ? prettyprintArr(stdouttxt) : stdouttxt) + ");"; } case "INC_OP": { let v = evalIdentifier([tokens[2], tokens[3]]); if (tokens.length >= 8) v += "+=" + evalLine(tokens.slice(6)) + ";"; else v += "++;"; return v; break; } case "INLINE_IF": { return "if (" + evalLine(tokens.slice(2)) + "){"; } case "INLINE_IF_END": { return "}"; } case "BREAK": { return "break;"; } case "LOOP_INF": { return "while(1){"; } case "LOOP": { return parseLoop(tokens); } case "SWITCH": { return "switch(IT){"; } case "SWITCH_CASE": { return "case " + tokens[3] + ":"; } case "SWITCH_CASE_DEFAULT": { return "default:"; } case "RETURN": { return "return " + evalLine(tokens.slice(4)) + ";"; } case "FUNC_DEF": { return evalFunctionDefinition(tokens); } case "FUNC_DEF_END": { return "return (IT == undefined) ? null : IT;}"; } default: { return evalLine(tokens); } } return js; } function ihasjs(str, callback){ errored = false; uFunc = []; str = remComments(str); str = parseStringLiterals(str, true); str = parseInit(str); let s = str.split("\n"); let js_out = ""; for (let i = 0; i < s.length; i++){ let t = tokenizer(s[i]); let js = interpreteLine(t); js_out += js + "\n"; } js_out = parseStringLiterals(js_out, false); return callback(error, warn, js_out); } module.exports = ihasjs;