Initial Commit
This commit is contained in:
commit
5767f131a1
1 changed files with 407 additions and 0 deletions
407
spinny.js
Normal file
407
spinny.js
Normal file
|
|
@ -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...] [—] <filename>\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<filename> 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 <file> 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<args.length;i++) {
|
||||
let arg = args[i];a
|
||||
|
||||
if (nextIsOutput) {
|
||||
output = arg;
|
||||
nextIsOutput = false;
|
||||
}
|
||||
if (!noMoreFlags) {
|
||||
// Flags
|
||||
flag = false;
|
||||
letterFlag = false;
|
||||
if (arg.slice(0,1) === "—") {
|
||||
flag = true;
|
||||
arg = arg.slice(1);
|
||||
} else if (arg.slice(0,2) === "--") {
|
||||
flag = true;
|
||||
arg = arg.slice(2);
|
||||
} else if (arg.slice(0,1) === "-") {
|
||||
flag = true;
|
||||
letterFlag = true;
|
||||
arg = arg.slice(1);
|
||||
}
|
||||
if (flag) {
|
||||
if (arg === "compile" || arg === "c") {
|
||||
compileonly = true;
|
||||
} else if (arg === "help" || arg === "h") {
|
||||
showhelp();
|
||||
quit();
|
||||
} else if (arg === "version" || arg === "v") {
|
||||
showversion();
|
||||
quit();
|
||||
} else if (arg === "output" || arg === "o") {
|
||||
nextisoutput = true;
|
||||
} else if (arg === "" && !letterFlag) {
|
||||
nomoreflags = true;
|
||||
} else {
|
||||
if (letterFlag){
|
||||
display.print(`Spinny: Unrecognized flag \`-${arg}'.`);
|
||||
} else {
|
||||
display.print(`Spinny: Unrecognized flag \`—${arg}'.`);
|
||||
}
|
||||
display.print("Spinny: For allowed flags, use `—help'.");
|
||||
io.error([11, "unknown flag"]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
filename = arg;
|
||||
}
|
||||
return {
|
||||
filename: filename,
|
||||
compile_only: compileOnly,
|
||||
output: output
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
class spinnyParser {
|
||||
compile(sourceFileContents) {
|
||||
this.file = sourceFileContents;
|
||||
this.index = 0;
|
||||
this.debugStack = [];
|
||||
return parseFile()
|
||||
}
|
||||
parseFile() {
|
||||
this.debugStack.push(["File", this.index]);
|
||||
let lines = [];
|
||||
|
||||
// A bunch of lines
|
||||
while (this.index+1 < this.file.length) {
|
||||
if (now() == "\n") {
|
||||
next();
|
||||
continue
|
||||
}
|
||||
lines.push(parseLine("F"))
|
||||
}
|
||||
this.debugStack.pop();
|
||||
return lines
|
||||
}
|
||||
|
||||
now() { return this.file[this.index] }
|
||||
next() { return this.file[this.index++] }
|
||||
peek() { return this.file[this.index+1] }
|
||||
|
||||
parseLine(inside) {
|
||||
this.debugStack.push(["Line", this.index])
|
||||
let words = []
|
||||
wordLoop:
|
||||
while (1) {
|
||||
switch (now()) {
|
||||
case " ":
|
||||
case "\t":
|
||||
// advance past whitespace till
|
||||
// next word
|
||||
next();
|
||||
continue
|
||||
case "#":
|
||||
parseComment();
|
||||
continue
|
||||
case "}":
|
||||
if (inside === "L")
|
||||
break wordLoop
|
||||
break
|
||||
case ">":
|
||||
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();
|
||||
Loading…
Add table
Add a link
Reference in a new issue