//////////////////////////////////// // SPINNY : A Programming Language / //////////////////////////////////// // This is the Spinny compiler and // interpreter for itty. // It accepts // - Spinny source files (.spin) // - Spinny compiled json files (.spic) function onRun() { let cliArgs = parseCliArgs(); try { const fileobj = io.open(fs.resolve(cliArgs.filename), "r") } catch (e) { display.print(`Spinny: Error opening file or resolving path \`${cliArgs.filename}'.`); io.error([22, "file open failed"]); } try { const file = fileobj.read(); } catch (e) { display.print(`Spinny: Error reading file \`${cliArgs.filename}'.`); io.error([23, "file read failed"]); } const fileSpincVersion = file.split("\n")[1].split("@"); let isSpincFile = false; if (fileSpincVersion.slice(0,2).join("\n") === "SPINC\nspinny:itty") { // This is a spinny source file if (fileSpincVersion[2] !== "noversion") { let niceversion = spileSpincVersion[2].replace( /[\t %]/g , "") display.print("Spinny: Incorrect or unrecognized compiled spinc file"); display.print(`Spinny: version \`${niceversion}'.`); display.print("Spinny: This version of the spinny interpreter exclusively"); display.print("Spinny: accepts compiled spinc files of version"); display.print("Spinny: `noversion'."); display.print("Spinny: Use `—version' or `—help' for more information."); io.error([31, "invalid spinc version"]); } isSpincFile = true; } if (cliArgs.compileOnly && isSpincFile) { display.print("Spinny: Cannot compile a spinc file."); display.print("Spinny: The file is already compiled."); io.error([32, "spinc already compiled"]); } if (!isSpincFile) { // We must compile the file const parser = new spinnyParser; const compiled = parser.compile(file) } display.print("Spinny: Running and/or saving files is not currently supported."); display.print("Spinny: Dumping contents instead."); if (isSpincFile) display.print(file.split("\n").slice(2).join('\n')) else display.print(compiled) } function showHelp(){ display.print( // . 1 . 2 . 3 . 4 . 5 . 6 // ^ "This is the Spinny compiler and interpreter for itty.\n" + "It accepts...\n" + " • Spinny source files (.spin)\n" + " • Spinny compiled json files (.spinc)\n" + "\n" + "Syntax:\n" + `\t${program.name} [flags...] [—] \n` + "Where:\n" + "\t[flags...] are any combination of the flags\n" + "\t available in the `Flags' section\n" + "\t below.\n" + "\t[—] Is an emdash (—) or a double dash (--)\n" + "\t to mark where the filename is supposed\n" + "\t to go. (Note that any time you see an\n" + "\t em dash in this help page, you may\n" + "\t choose to use a double dash in its\n" + "\t place if that is your preference.)\n" + "\t is the `.spin' or `.spinc' file to\n" + "\t compile/run\n" + "Flags:\n" + "\t—help Print this help page and exit.\n" "\t—version Print version and exit.\n" "\t—compile Compile the code to a .spinc file.\n" + "\t The .spinc file must be specified\n" + "\t with `—output'.\n" + "\t—output Specifies the output file for the\n" + "\t `—compile' flag.\n" + ) }; function showVersion(){ display.print( // . 1 . 2 . 3 . 4 . 5 . 6 // ^ "Spinny for itty\n" + "Version 0.0.3\n" + "Known spinc versions:\n" " • `noversion'\n" ) }; function parseCliArgs() { let compileOnly = false; let filename = ""; let noMoreFlags = false; let nextIsOutput = false let output = ""; let letterFlag = false let flag = false for (let i=0;i": if (inside === INSIDE.INTER) break wordLoop break case "\n": if (inside === INSIDE.FILE || inside == INSIDE.LAZY) break wordLoop break case "\\": if (this.now() == "\\" && this.peek() == "\n") { // Line continuation // Go to next line this.next(); // skip `\\' this.next(); // skip `\n' continue } break } words.push(this.parseWord(inside)); } this.debugStack.pop() return words } parseComment() { this.debugStack.push(["Comment", this.index]) commentLoop: while (1) { switch (this.now()) { case "\n": break commentLoop case "\\": if (this.peek() == "\n") break commentLoop default: this.next(); } } this.debugStack.pop() } parseWord(inside) { this.debugStack.push(["Word", this.index]) let bang = false; if (this.now() === "!") { bang = true; this.next(); } switch (this.now()) { case "{": return { CMPVER.KEYS.BANG:bang, CMPVER.KEYS.TYPE:CMPVER.TYPE.LAZY, CMPVER.KEYS.LINES:this.parseLazy() } case "`": return { CMPVER.KEYS.BANG:bang, CMPVER.KEYS.TYPE:CMPVER.TYPE.LIT, CMPVER.KEYS.PARTS:this.parseLiteral() } default: return { CMPVER.KEYS.BANG:bang, CMPVER.KEYS.TYPE:CMPVER.TYPE.ID, CMPVER.KEYS.PARTS:this.parseIdentifier(inside) } } this.debugStack.pop() } parseLazy() { this.debugStack.push(["Lazy", this.index]) this.next(); // Skip `{' let lines = [] while (this.now() !== "}") { lines.push(this.parseLine(INSIDE.LAZY)) } this.debugStack.pop() return lines } parseLiteral() { this.debugStack.push(["Literal", this.index]) this.next(); // Skip `\`' let parts = [""] while (this.now() !== "\'") { switch (this.now()) { case "\\": parts[parts.length-1]+=this.parseEscape(); break case "<": parts.push( this.parseInterpolation() ); parts.push("") break default: parts[parts.length-1]+=this.now(); this.next(); break } } this.debugStack.pop(); return parts } parseEscape() { this.debugStack.push(["Literal", this.index]) this.next(); // skip `\\' let out = ""; switch (this.now()) { case "<": this.next(); // skip `\<' out = "<"; break case "x": this.next(); // skip `x' out = String.fromCharCode( parseInt( this.next()+this.next() ,16)); break case "u": this.next(); // skip `u' out = String.fromCharCode( parseInt( this.next()+this.next()+this.next()+this.next() ,16)); break case "U": this.next(); // skip `U' out = String.fromCharCode( parseInt( this.next()+this.next()+this.next()+this.next()+ this.next()+this.next()+this.next()+this.next() ,16)); break case "s": this.next(); // skip `s' out = " "; break case "t": this.next(); // skip `t' out = "\t"; break case "n": this.next(); // skip `n' out = "\n"; break default: // Just send the character: out = this.now(); // record the thing after the `\\' this.next(); // skip over it break } this.debugStack.pop(); return out; } parseInterpolation() { this.debugStack.push(["Interpolation", this.index]) this.next(); // skip `\<' let line while (this.now() !== ">") { line = this.parseLine(INSIDE.INTER) } this.next(); // skip `>' this.debugStack.pop() return line } parseIdentifier(inside) { this.debugStack.push(["Identifier", this.index]) let parts = [""] idLoop: while (1) { switch (this.now()) { case "\\": parts[parts.length-1]+=this.parseEscape(); continue case "<": parts.push( this.parseInterpolation() ); parts.push("") continue case "}": if (inside === INSIDE.LAZY) break idLoop break case ">": if (inside === INSIDE.INTER) break idLoop break case "#": break idLoop } parts[parts.length-1]+=this.now(); this.next(); } this.debugStack.pop(); return parts } } const SPIDNEY = {//spinny identifiers "noversion": { NAME: "noversion", KEYS: { TYPE: "t", BANG: "b", PARTS: "p", LINES: "l" }, TYPE: { LIT: 0, ID: 1, LAZY: 2 } } } const CMPVER = SPIDNEY["noversion"] //compileversion // When the program starts: onRun(); quit();