//////////////////////////////////// // SPINNY : A Programming Language / //////////////////////////////////// // This is the Spinny compiler and // interpreter for itty. // It accepts // - Spinny source files (.spin) // - Spinny compiled json files (.spic) try { async function onRun() { let cliArgs = parseCliArgs(); let fileobj let file // File open: try { fileobj = io.open(fs.resolve(cliArgs.filename), "r") } catch (e) { display.print(`Spinny: Error opening file or resolving path \`${cliArgs.filename}'.`); display.print(`${e.name}@${e.lineNumber}-${e.columnNumber}: ${e.message}`); io.error([22, "file open failed"]); } try { file = fileobj.read(); } catch (e) { display.print(`Spinny: Error reading file \`${cliArgs.filename}'.`); display.print(`${e.name}@${e.lineNumber}-${e.columnNumber}: ${e.message}`); io.error([23, "file read failed"]); } // Spinc Checker: let isSpincFile = false; if (file.split("\n").length > 1) { const fileSpincVersion = file.split("\n")[1].split("@"); if (fileSpincVersion.length > 2 && 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"]); } let compiled if (!isSpincFile) { // We must compile the file const parser = new spinnyParser({ parsestep:cliArgs.debugStep }); let compiled; try { compiled = await parser.compile(file) } catch (e) { if (!(e instanceof spinnyCompileError)) throw e let debug = e.debugStack let last = e.debugStack.slice(-1)[0] display.print("Spinny: Error compiling file..."); display.print(`Spinny: Unfinished ${last[0]} at ${last[1]}`); io.error([45, "compilation failed"]); } display.print(JSON.stringify(compiled)) quit(); } 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')) } 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 = null; let noMoreFlags = false; let nextIsOutput = false let output = ""; let letterFlag = false let flag = false let debugStep = false for (let i=0;i": if (inside === this.INSIDE.INTER) break wordLoop break case "\n": if (inside === this.INSIDE.FILE || inside == this.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(await this.parseWord(inside)); } await this.untag() return words } async parseComment() { await this.tag("Comment") commentLoop: while (1) { switch (this.now()) { case "\n": break commentLoop case "\\": if (this.peek() == "\n") break commentLoop default: this.next(); } } await this.untag() } async parseWord(inside) { await this.tag("Word") let bang = false; let word = {} word[CMPVER.KEYS.BANG] = false; if (this.now() === "!") { word[CMPVER.KEYS.BANG] = true; this.next(); } switch (this.now()) { case "{": word[CMPVER.KEYS.TYPE] = CMPVER.TYPE.LAZY; word[CMPVER.KEYS.LINES] = await this.parseLazy(); break; case "`": word[CMPVER.KEYS.TYPE] = CMPVER.TYPE.LIT; word[CMPVER.KEYS.PARTS] = await this.parseLiteral(); break; default: word[CMPVER.KEYS.TYPE] = CMPVER.TYPE.ID; word[CMPVER.KEYS.PARTS] = await this.parseIdentifier(inside); } await this.untag() return word } async parseLazy() { await this.tag("Lazy") this.next(); // Skip `{' let lines = [] while (this.now() !== "}") { lines.push(await this.parseLine(this.INSIDE.LAZY)) if (this.now() === "\n") // Continue to next line like a file would this.next(); } this.next(); // Skip `}' await this.untag() return lines } async parseLiteral() { await this.tag("Literal") this.next(); // Skip `\`' let parts = [""] while (this.now() !== "\'") { switch (this.now()) { case "\\": parts[parts.length-1]+=await this.parseEscape(); break case "<": parts.push( await this.parseInterpolation() ); parts.push("") break default: parts[parts.length-1]+=this.now(); this.next(); break } } this.next(); // Skip `\'' await this.untag(); return parts } async parseEscape() { await this.tag("Escape") 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 } await this.untag(); return out; } async parseInterpolation() { await this.tag("Interpolation") this.next(); // skip `\<' let line while (this.now() !== ">") { line = await this.parseLine(this.INSIDE.INTER) } this.next(); // skip `>' await this.untag() return line } async parseIdentifier(inside) { await this.tag("Identifier") let parts = [""] idLoop: while (1) { switch (this.now()) { case "\\": parts[parts.length-1]+=await this.parseEscape(); continue case "<": parts.push( await this.parseInterpolation() ); parts.push("") continue case "}": if (inside === this.INSIDE.LAZY) break idLoop break case ">": if (inside === this.INSIDE.INTER) break idLoop break case "#": case " ": case "\t": case "\n": break idLoop } parts[parts.length-1]+=this.now(); this.next(); } await this.untag(); 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: await onRun(); quit(); } catch (e) { display.print(`Spinny: Something has gone horribly wrong, commander.`); display.print(`${e.name}@${e.lineNumber}-${e.columnNumber}: ${e.message}`); io.error([9, "unknown error"]); }