commit 5767f131a196ab47d88055b9543baedc7a68f5ba Author: mewrrythekibby Date: Wed Dec 31 15:27:41 2025 -0600 Initial Commit diff --git a/spinny.js b/spinny.js new file mode 100644 index 0000000..367b753 --- /dev/null +++ b/spinny.js @@ -0,0 +1,407 @@ +//////////////////////////////////// +// 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) === ["SPINC","spinny: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.1\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 === "I") + break wordLoop + break + case "\n": + if (inside === "F") + break wordLoop + break + case "\\": + if (now() == "\\" && peek() == "\n") { + // Line continuation + // Go to next line + next(); // skip `\\' + next(); // skip `\n' + continue + } + break + } + words.push(parseWord(inside)); + } + this.debugStack.pop() + return words + } + + parseComment() { + this.debugStack.push(["Comment", this.index]) + commentLoop: + while (1) { + switch (now()) { + case "\n": + break commentLoop + case "\\": + if (peek() == "\n") + break commentLoop + default: + next(); + } + } + this.debugStack.pop() + } + + parseWord(inside) { + this.debugStack.push(["Word", this.index]) + switch (now()) { + case "{": + return {type:"lazy",lines:parseLazy()} + case "`": + return {type:"literal",parts:parseLiteral()} + default: + return {type:"identifier",parts:parseIdentifier(inside)} + } + this.debugStack.pop() + + } + + parseLazy() { + this.debugStack.push(["Lazy", this.index]) + next(); // Skip `{' + let lines = [] + while (now() !== "}") { + lines.push(parseLine("L")) + } + this.debugStack.pop() + return lines + } + + parseLiteral() { + this.debugStack.push(["Literal", this.index]) + next(); // Skip `\`' + let parts = [] + while (now() !== "\'") { + switch (now()) { + case "\\": + parts[parts.length-1]+=parseEscape(); + break + case "<": + parts.push( + parseInterpolation() + ); + parts.push("") + break + default: + parts[parts.length-1]+=now(); + next(); + break + } + } + this.debugStack.pop(); + return parts + } + + parseEscape() { + this.debugStack.push(["Literal", this.index]) + next(); // skip `\\' + let out = ""; + switch (now()) { + case "<": + next(); // skip `\<' + out = "<"; + break + case "x": + next(); // skip `x' + out = String.fromCharCode(next()+next()); + break + case "u": + next(); // skip `u' + out = String.fromCharCode( + next()+next()+next()+next() + ); + break + case "U": + next(); // skip `U' + out = String.fromCharCode( + next()+next()+next()+next()+ + next()+next()+next()+next() + ); + break + case "s": + next(); // skip `s' + out = " "; + break + case "t": + next(); // skip `t' + out = "\t"; + break + case "n": + next(); // skip `n' + out = "\n"; + break + default: + // Just send the character: + out = now(); // record the thing after the `\\' + next(); // skip over it + break + + } + this.debugStack.pop(); + return out; + } + + parseInterpolation() { + this.debugStack.push(["Interpolation", this.index]) + next(); // skip `\<' + let line + while (now() !== ">") { + line = parseLine("I") + } + next(); // skip `>' + this.debugStack.pop() + return line + } + + parseIdentifier(inside) { + this.debugStack.push(["Identifier", this.index]) + let parts = [] + idLoop: + while (1) { + switch (now()) { + case "\\": + parts[parts.length-1]+=parseEscape(); + continue + case "<": + parts.push( + parseInterpolation() + ); + parts.push("") + continue + case "}": + if (inside === "L") + break idLoop + break + case ">": + if (inside === "I") + break idLoop + break + case "#": + break idLoop + } + parts[parts.length-1]+=now(); + next(); + } + this.debugStack.pop(); + return parts + } + +} + + +// When the program starts: +onRun(); +quit();