Compare commits
No commits in common. "maid" and "mistress" have entirely different histories.
6 changed files with 112 additions and 463 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
#! nix-shell -i bash
|
#! nix-shell -i bash
|
||||||
#! nix-shell -p bash nodejs jq
|
#! nix-shell -p bash nodejs jq
|
||||||
|
|
||||||
node itty/utils/thread.js spool spinny.yarn spinny.js s.spin r.spin
|
node itty/utils/thread.js spool spinny.yarn spinny.js
|
||||||
node itty/utils/thread.js metadata-from-json spinny.yarn yarnball-meta.json
|
node itty/utils/thread.js metadata-from-json spinny.yarn yarnball-meta.json
|
||||||
|
|
||||||
#cat spinny.yarn mergefile.json | jq -s '.[0] as $old | .[1] as $merge | .[0].content | fromjson * $merge | tojson as $new | $old + {"content":$new} | tojson' | jq 'fromjson' > spinny.yarn.tmp
|
#cat spinny.yarn mergefile.json | jq -s '.[0] as $old | .[1] as $merge | .[0].content | fromjson * $merge | tojson as $new | $old + {"content":$new} | tojson' | jq 'fromjson' > spinny.yarn.tmp
|
||||||
|
|
|
||||||
107
r.spin
107
r.spin
|
|
@ -1,107 +0,0 @@
|
||||||
out `Small ``Game\'\'\n'
|
|
||||||
use term
|
|
||||||
|
|
||||||
set `score' `0'
|
|
||||||
set `win.maxw' {
|
|
||||||
# ^ win.maxw is just the variable name. The dot is just a character.
|
|
||||||
# the dot doesn't mean anything special here.
|
|
||||||
|
|
||||||
# We doin RPN :O
|
|
||||||
sub `1' \
|
|
||||||
# `term:rows' is the name a command being run. \
|
|
||||||
# since variable names are quite permissive, this is just the name of a \
|
|
||||||
# command defined within the file `term' (imported at the top) \
|
|
||||||
# \
|
|
||||||
# Also note that a \ at the end of a line, even after a comment \
|
|
||||||
# continues a line. meaning, term:rows will be the seconc argument \
|
|
||||||
# of the subtraction above. \
|
|
||||||
term:rows
|
|
||||||
}
|
|
||||||
set `win.maxh' {sub `1' term:cols}
|
|
||||||
|
|
||||||
set `guy.char' `\U0001FBC5'
|
|
||||||
set `guy.x' `1'
|
|
||||||
set `guy.y' `1'
|
|
||||||
|
|
||||||
# Unicode is supported inside quotes:
|
|
||||||
set `star.char' `⭐'
|
|
||||||
|
|
||||||
# also non-ascii is allowed in identifiers:
|
|
||||||
set `\x1b' `hello'
|
|
||||||
# and escapes are allowed in the middle of identifiers:
|
|
||||||
out \x1b # << outputs hello
|
|
||||||
# and interpolation can be done on its own:
|
|
||||||
out <`\x1b'> # << also outputs hello
|
|
||||||
# and ofc, you could use ^[ in the raw code:
|
|
||||||
out # << bad idea, but yes, it still writes hello.
|
|
||||||
# and then we'll undefine it for your sanity:
|
|
||||||
del `\x1b'
|
|
||||||
|
|
||||||
# `def' defines a command
|
|
||||||
def randstarpos {
|
|
||||||
set `star.x' {
|
|
||||||
# Get the real part of the rounded number after adding 1. (
|
|
||||||
# rounding removes decimal parts but leaves the immaginary
|
|
||||||
# part):
|
|
||||||
rea {rnd {add `1' {
|
|
||||||
mul win.maxw rnd
|
|
||||||
# ^ rnd returns a random number
|
|
||||||
# from 0 to 1
|
|
||||||
}}}
|
|
||||||
}
|
|
||||||
set `star.y' {rea {rnd
|
|
||||||
add `1' {mul rnd win.maxh}
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
def w {set `guy.y' {rea {rnd {
|
|
||||||
max `1' {sub `1' guy.y}
|
|
||||||
}}}}
|
|
||||||
def a {set `guy.x' {rea {rnd {max `1' {sub `1' guy.x}}}}}
|
|
||||||
def s {set `guy.y' {rea {rnd {min win.maxh {add `1' guy.y}}}}}
|
|
||||||
def d {set `guy.x' {rea {rnd {min win.maxw {add `1' guy.x}}}}}
|
|
||||||
|
|
||||||
!randstarpos
|
|
||||||
inf !{
|
|
||||||
#^the infinite loop command.
|
|
||||||
#Note that the bang is nessicary as otherwise, the lazy would always
|
|
||||||
#inherit the same state.
|
|
||||||
out `\x1b[<rea {rnd guy.y}>;<rea {rnd guy.x}>H<guy.char>'
|
|
||||||
# ^writes to stdout
|
|
||||||
out `\x1b[<rea {rnd star.y}>;<rea {rnd star.x}>H<star.char>'
|
|
||||||
map inp \
|
|
||||||
`w' !w \
|
|
||||||
`k' !w \
|
|
||||||
`a' !a \
|
|
||||||
`j' !a \
|
|
||||||
`s' !s \
|
|
||||||
`h' !s \
|
|
||||||
`d' !d \
|
|
||||||
`l' !d \
|
|
||||||
defa {}
|
|
||||||
# ^map -- maps a value to a result
|
|
||||||
# The arguments alternate between
|
|
||||||
# things to match against and lazies
|
|
||||||
# to run as a result of those matches.
|
|
||||||
# The result is always a block that
|
|
||||||
# will be ccl'd if the match is found.
|
|
||||||
# The last thing to match against is
|
|
||||||
# always assumed to be the default.
|
|
||||||
# It is convention to make the last
|
|
||||||
# matching argument `defa'.
|
|
||||||
# `inp' gets a single char from stdin.
|
|
||||||
|
|
||||||
ifs {
|
|
||||||
eql `<guy.x>' `<star.x>'
|
|
||||||
# ^ eql will return `f' if they're
|
|
||||||
# not equal and `' if they're equal.
|
|
||||||
eql `<guy.y>' `<star.y>'
|
|
||||||
# Note that since output is combined, this block is effectively
|
|
||||||
# an and operator. The actual and operator is better tho. It
|
|
||||||
# provides short-circuting.
|
|
||||||
} then !{
|
|
||||||
!randstarpos
|
|
||||||
set score {add `1' score}
|
|
||||||
out `\x1b[2J\x1b[HScore: <rea {rnd score}>'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
136
s.spin
136
s.spin
|
|
@ -1,136 +0,0 @@
|
||||||
out `Small ``Game\'\'\n'
|
|
||||||
use term
|
|
||||||
|
|
||||||
# Before we begin, a few notes
|
|
||||||
# - All objects are strings
|
|
||||||
# - There are a few different kinds of objects.
|
|
||||||
# - variables which have a static value they always evaluate to
|
|
||||||
# - lazies (contained by curlies (`{' and `}')) lazilly evaluate the lines
|
|
||||||
# within themselves only when nessicary. They may take arguments the same
|
|
||||||
# way commands do
|
|
||||||
# - commands, which are essentially lazies behind the mask of a variable name
|
|
||||||
# - strings, contained by a backtick to open (``') and a single quote to close
|
|
||||||
# (`\'').
|
|
||||||
# - Each line begins with any object
|
|
||||||
# - builtin commands are always 3 characters long
|
|
||||||
# - the starting object is followed by a space, then the arguments, which are
|
|
||||||
# space seperated objects
|
|
||||||
# - the arguments are passed to the first object
|
|
||||||
# - variables and strings ignore these arguments
|
|
||||||
# - commands and lazies may access these arguments with the `arg' command
|
|
||||||
# - interpolation, either within a variable name, command name, or string, is
|
|
||||||
# done with the `<...>' syntax.
|
|
||||||
# - The arrows, as they're called, evaluate the line inside them, then insert
|
|
||||||
# that value into the variable name/command name/string.
|
|
||||||
# - thus a lone `<varname>' on a line evaluates to the value of the variable
|
|
||||||
# that is named by varname.
|
|
||||||
# - To say that backwards, varname contains the name of a variable, and that
|
|
||||||
# variable has a value, and that value is what we're getting.
|
|
||||||
# - When a object begins with a bang (`!'), if object being evaluated sets any
|
|
||||||
# variables, it sets them within the current scope.
|
|
||||||
# - Variable and function names are stripped of bangs and spaces.
|
|
||||||
# - Booleans don't exist. Instead, any string that is empty (`') is truthy and
|
|
||||||
# all other (non-empty) strings are falsey.
|
|
||||||
# - Numbers are strings in the formats:
|
|
||||||
# - `[+\-]?real(\.dec)?[+\-]imag(\.dec)?i'
|
|
||||||
# - `[+\-]?real(\.dec)?'
|
|
||||||
# - `[+\-]?imag(\.dec)?i'
|
|
||||||
# - where, imag is the immaginary part, real is the real part, and the .dec is
|
|
||||||
# the decimal part of the one it appears to the right of.
|
|
||||||
# - These strings are in any base from 3 to 19.
|
|
||||||
# - bases 20+ are reserved since they would involve the letter i (which
|
|
||||||
# denotes the immaginary part)
|
|
||||||
# - the current base is defined by the 'base' variable, which is always in
|
|
||||||
# base 2.
|
|
||||||
# - base 2 is reserved for no good reason.
|
|
||||||
# - all bases lower than two are reserved because they're inconveinent to
|
|
||||||
# implement.
|
|
||||||
|
|
||||||
set `score' `0'
|
|
||||||
set `win.maxw' {
|
|
||||||
# ^ win.maxw is just the variable name. The dot is just a character.
|
|
||||||
# the dot doesn't mean anything special here.
|
|
||||||
|
|
||||||
# We doin RPN :O
|
|
||||||
sub `1' term:rows
|
|
||||||
# `term:rows' is the name a command being run.
|
|
||||||
# since variable names are quite permissive, this is just the name of a
|
|
||||||
# command defined within the file `term' (imported at the top)
|
|
||||||
}
|
|
||||||
set `win.maxh' {sub `1' term:cols}
|
|
||||||
|
|
||||||
set `guy.char' `\U0001FBC5'
|
|
||||||
set `guy.x' `1'
|
|
||||||
set `guy.y' `1'
|
|
||||||
|
|
||||||
# Unicode is supported inside quotes:
|
|
||||||
set `star.char' `⭐'
|
|
||||||
|
|
||||||
# `def' defines a command
|
|
||||||
def randstarpos {
|
|
||||||
set `star.x' {
|
|
||||||
# Get the real part of the rounded number after adding 1. (
|
|
||||||
# rounding removes decimal parts but leaves the immaginary
|
|
||||||
# part):
|
|
||||||
rea {rnd {add `1' {
|
|
||||||
mul win.maxw rnd
|
|
||||||
# ^ rnd returns a random number
|
|
||||||
# from 0 to 1
|
|
||||||
}}}
|
|
||||||
}
|
|
||||||
set `star.y' {rea {rnd
|
|
||||||
add `1' {mul rnd win.maxh}
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
def w {set `guy.y' {rea {rnd {
|
|
||||||
max `1' {sub `1' guy.y}
|
|
||||||
}}}}
|
|
||||||
def a {set `guy.x' {rea {rnd {max `1' {sub `1' guy.x}}}}}
|
|
||||||
def s {set `guy.y' {rea {rnd {min win.maxh {add `1' guy.y}}}}}
|
|
||||||
def d {set `guy.x' {rea {rnd {min win.maxw {add `1' guy.x}}}}}
|
|
||||||
|
|
||||||
!randstarpos
|
|
||||||
inf !{
|
|
||||||
#^the infinite loop command.
|
|
||||||
#Note that the bang is nessicary as otherwise, the lazy would always
|
|
||||||
#inherit the same state.
|
|
||||||
out `\x1b[<rea {rnd guy.y}>;<rea {rnd guy.x}>H<guy.char>'
|
|
||||||
# ^writes to stdout
|
|
||||||
out `\x1b[<rea {rnd star.y}>;<rea {rnd star.x}>H<star.char>'
|
|
||||||
map inp \
|
|
||||||
`w' !w \
|
|
||||||
`k' !w \
|
|
||||||
`a' !a \
|
|
||||||
`j' !a \
|
|
||||||
`s' !s \
|
|
||||||
`h' !s \
|
|
||||||
`d' !d \
|
|
||||||
`l' !d \
|
|
||||||
defa {}
|
|
||||||
# ^map -- maps a value to a result
|
|
||||||
# The arguments alternate between
|
|
||||||
# things to match against and lazies
|
|
||||||
# to run as a result of those matches.
|
|
||||||
# The result is always a block that
|
|
||||||
# will be ccl'd if the match is found.
|
|
||||||
# The last thing to match against is
|
|
||||||
# always assumed to be the default.
|
|
||||||
# It is convention to make the last
|
|
||||||
# matching argument `defa'.
|
|
||||||
# `inp' gets a single char from stdin.
|
|
||||||
|
|
||||||
ifs {
|
|
||||||
eql `<guy.x>' `<star.x>'
|
|
||||||
# ^ eql will return `f' if they're
|
|
||||||
# not equal and `' if they're equal.
|
|
||||||
eql `<guy.y>' `<star.y>'
|
|
||||||
# Note that since output is combined, this block is effectively
|
|
||||||
# an and operator. The actual and operator is better tho. It
|
|
||||||
# provides short-circuting.
|
|
||||||
} then !{
|
|
||||||
!randstarpos
|
|
||||||
set score {add `1' score}
|
|
||||||
out `\x1b[2J\x1b[HScore: <rea {rnd score}>'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
324
spinny.js
324
spinny.js
|
|
@ -8,48 +8,35 @@
|
||||||
// - Spinny source files (.spin)
|
// - Spinny source files (.spin)
|
||||||
// - Spinny compiled json files (.spic)
|
// - Spinny compiled json files (.spic)
|
||||||
|
|
||||||
try {
|
function onRun() {
|
||||||
async function onRun() {
|
|
||||||
let cliArgs = parseCliArgs();
|
let cliArgs = parseCliArgs();
|
||||||
let fileobj
|
|
||||||
let file
|
|
||||||
|
|
||||||
// File open:
|
try { const fileobj = io.open(fs.resolve(cliArgs.filename), "r")
|
||||||
try {
|
|
||||||
fileobj = io.open(fs.resolve(cliArgs.filename), "r")
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
display.print(`Spinny: Error opening file or resolving path \`${cliArgs.filename}'.`);
|
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"]);
|
io.error([22, "file open failed"]);
|
||||||
}
|
}
|
||||||
try {
|
try { const file = fileobj.read();
|
||||||
file = fileobj.read();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
display.print(`Spinny: Error reading file \`${cliArgs.filename}'.`);
|
display.print(`Spinny: Error reading file \`${cliArgs.filename}'.`);
|
||||||
display.print(`${e.name}@${e.lineNumber}-${e.columnNumber}: ${e.message}`);
|
|
||||||
io.error([23, "file read failed"]);
|
io.error([23, "file read failed"]);
|
||||||
}
|
}
|
||||||
|
const fileSpincVersion = file.split("\n")[1].split("@");
|
||||||
// Spinc Checker:
|
|
||||||
let isSpincFile = false;
|
let isSpincFile = false;
|
||||||
if (file.split("\n").length > 1) {
|
if (fileSpincVersion.slice(0,2).join("\n") === "SPINC\nspinny:itty") {
|
||||||
const fileSpincVersion = file.split("\n")[1].split("@");
|
// This is a spinny source file
|
||||||
if (fileSpincVersion.length > 2 &&
|
if (fileSpincVersion[2] !== "noversion") {
|
||||||
fileSpincVersion.slice(0,2).join("\n") === "SPINC\nspinny:itty") {
|
let niceversion = spileSpincVersion[2].replace(
|
||||||
// This is a spinny source file
|
/[\t %]/g , "")
|
||||||
if (fileSpincVersion[2] !== "noversion") {
|
display.print("Spinny: Incorrect or unrecognized compiled spinc file");
|
||||||
let niceversion = spileSpincVersion[2].replace(
|
display.print(`Spinny: version \`${niceversion}'.`);
|
||||||
/[\t %]/g , "")
|
display.print("Spinny: This version of the spinny interpreter exclusively");
|
||||||
display.print("Spinny: Incorrect or unrecognized compiled spinc file");
|
display.print("Spinny: accepts compiled spinc files of version");
|
||||||
display.print(`Spinny: version \`${niceversion}'.`);
|
display.print("Spinny: `noversion'.");
|
||||||
display.print("Spinny: This version of the spinny interpreter exclusively");
|
display.print("Spinny: Use `—version' or `—help' for more information.");
|
||||||
display.print("Spinny: accepts compiled spinc files of version");
|
io.error([31, "invalid spinc version"]);
|
||||||
display.print("Spinny: `noversion'.");
|
|
||||||
display.print("Spinny: Use `—version' or `—help' for more information.");
|
|
||||||
io.error([31, "invalid spinc version"]);
|
|
||||||
}
|
|
||||||
isSpincFile = true;
|
|
||||||
}
|
}
|
||||||
|
isSpincFile = true;
|
||||||
}
|
}
|
||||||
if (cliArgs.compileOnly && isSpincFile) {
|
if (cliArgs.compileOnly && isSpincFile) {
|
||||||
display.print("Spinny: Cannot compile a spinc file.");
|
display.print("Spinny: Cannot compile a spinc file.");
|
||||||
|
|
@ -57,32 +44,18 @@ async function onRun() {
|
||||||
io.error([32, "spinc already compiled"]);
|
io.error([32, "spinc already compiled"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let compiled
|
|
||||||
if (!isSpincFile) {
|
if (!isSpincFile) {
|
||||||
// We must compile the file
|
// We must compile the file
|
||||||
const parser = new spinnyParser({
|
const parser = new spinnyParser;
|
||||||
parsestep:cliArgs.debugStep
|
const compiled = parser.compile(file)
|
||||||
});
|
|
||||||
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: Running and/or saving files is not currently supported.");
|
||||||
display.print("Spinny: Dumping contents instead.");
|
display.print("Spinny: Dumping contents instead.");
|
||||||
if (isSpincFile)
|
if (isSpincFile)
|
||||||
display.print(file.split("\n").slice(2).join('\n'))
|
display.print(file.split("\n").slice(2).join('\n'))
|
||||||
|
else
|
||||||
|
display.print(compiled)
|
||||||
}
|
}
|
||||||
|
|
||||||
function showHelp(){
|
function showHelp(){
|
||||||
|
|
@ -97,25 +70,25 @@ function showHelp(){
|
||||||
"Syntax:\n" +
|
"Syntax:\n" +
|
||||||
`\t${program.name} [flags...] [—] <filename>\n` +
|
`\t${program.name} [flags...] [—] <filename>\n` +
|
||||||
"Where:\n" +
|
"Where:\n" +
|
||||||
"\t[flags...] are any combination of the flags\n" +
|
"\t[flags...] are any combination of the flags\n" +
|
||||||
"\t available in the `Flags' section\n" +
|
"\t available in the `Flags' section\n" +
|
||||||
"\t below.\n" +
|
"\t below.\n" +
|
||||||
"\t[—] Is an emdash (—) or a double dash (--)\n" +
|
"\t[—] Is an emdash (—) or a double dash (--)\n" +
|
||||||
"\t to mark where the filename is supposed\n" +
|
"\t to mark where the filename is supposed\n" +
|
||||||
"\t to go. (Note that any time you see an\n" +
|
"\t to go. (Note that any time you see an\n" +
|
||||||
"\t em dash in this help page, you may\n" +
|
"\t em dash in this help page, you may\n" +
|
||||||
"\t choose to use a double dash in its\n" +
|
"\t choose to use a double dash in its\n" +
|
||||||
"\t place if that is your preference.)\n" +
|
"\t place if that is your preference.)\n" +
|
||||||
"\t<filename> is the `.spin' or `.spinc' file to\n" +
|
"\t<filename> is the `.spin' or `.spinc' file to\n" +
|
||||||
"\t compile/run\n" +
|
"\t compile/run\n" +
|
||||||
"Flags:\n" +
|
"Flags:\n" +
|
||||||
"\t—help Print this help page and exit.\n" +
|
"\t—help Print this help page and exit.\n" +
|
||||||
"\t—version Print version and exit.\n" +
|
"\t—version Print version and exit.\n" +
|
||||||
"\t—compile Compile the code to a .spinc file.\n" +
|
"\t—compile Compile the code to a .spinc file.\n" +
|
||||||
"\t The .spinc file must be specified\n" +
|
"\t The .spinc file must be specified\n" +
|
||||||
"\t with `—output'.\n" +
|
"\t with `—output'.\n" +
|
||||||
"\t—output <file> Specifies the output file for the\n" +
|
"\t—output <file> Specifies the output file for the\n" +
|
||||||
"\t `—compile' flag.\n"
|
"\t `—compile' flag.\n"
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -132,19 +105,18 @@ function showVersion(){
|
||||||
|
|
||||||
function parseCliArgs() {
|
function parseCliArgs() {
|
||||||
let compileOnly = false;
|
let compileOnly = false;
|
||||||
let filename = null;
|
let filename = "";
|
||||||
let noMoreFlags = false;
|
let noMoreFlags = false;
|
||||||
let nextIsOutput = false
|
let nextIsOutput = false
|
||||||
let output = "";
|
let output = "";
|
||||||
let letterFlag = false
|
let letterFlag = false
|
||||||
let flag = false
|
let flag = false
|
||||||
let debugStep = false
|
|
||||||
for (let i=0;i<args.length;i++) {
|
for (let i=0;i<args.length;i++) {
|
||||||
let arg = args[i];
|
let arg = args[i];
|
||||||
|
|
||||||
if (nextIsOutput) {
|
if (nextIsOutput) {
|
||||||
output = arg;
|
output = arg;
|
||||||
nextIsOutput = false;
|
nextIsOutput = false;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
if (!noMoreFlags) {
|
if (!noMoreFlags) {
|
||||||
// Flags
|
// Flags
|
||||||
|
|
@ -161,31 +133,20 @@ function parseCliArgs() {
|
||||||
letterFlag = true;
|
letterFlag = true;
|
||||||
arg = arg.slice(1);
|
arg = arg.slice(1);
|
||||||
}
|
}
|
||||||
if (flag) { switch (arg) {
|
if (flag) {
|
||||||
case "c":
|
if (arg === "compile" || arg === "c") {
|
||||||
case "compile":
|
|
||||||
compileOnly = true;
|
compileOnly = true;
|
||||||
break;
|
} else if (arg === "help" || arg === "h") {
|
||||||
case "h":
|
|
||||||
case "help":
|
|
||||||
showHelp();
|
showHelp();
|
||||||
quit();
|
quit();
|
||||||
case "v":
|
} else if (arg === "version" || arg === "v") {
|
||||||
case "version":
|
|
||||||
showVersion();
|
showVersion();
|
||||||
quit();
|
quit();
|
||||||
case "o":
|
} else if (arg === "output" || arg === "o") {
|
||||||
case "output":
|
|
||||||
nextIsOutput = true;
|
nextIsOutput = true;
|
||||||
break
|
} else if (arg === "" && !letterFlag) {
|
||||||
case "debugstep":
|
noMoreFlags = true;
|
||||||
debugStep = true;
|
} else {
|
||||||
case "":
|
|
||||||
if (!letterFlag) {
|
|
||||||
noMoreFlags = true;
|
|
||||||
break
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if (letterFlag){
|
if (letterFlag){
|
||||||
display.print(`Spinny: Unrecognized flag \`-${arg}'.`);
|
display.print(`Spinny: Unrecognized flag \`-${arg}'.`);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -193,30 +154,19 @@ function parseCliArgs() {
|
||||||
}
|
}
|
||||||
display.print("Spinny: For allowed flags, use `—help'.");
|
display.print("Spinny: For allowed flags, use `—help'.");
|
||||||
io.error([11, "unknown flag"]);
|
io.error([11, "unknown flag"]);
|
||||||
}; continue; }
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
filename = arg;
|
filename = arg;
|
||||||
}
|
}
|
||||||
if (filename === null) {
|
|
||||||
display.print("Spinny: No filename provided :c");
|
|
||||||
display.print("Spinny: For help, use `—help'.");
|
|
||||||
io.error([13, "am i alone in this world?"]);
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
filename: filename,
|
filename: filename,
|
||||||
compileOnly: compileOnly,
|
compileOnly: compileOnly,
|
||||||
output: output,
|
output: output
|
||||||
debugStep: debugStep
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class spinnyCompileError extends Error {
|
|
||||||
constructor(message, debugStack) {
|
|
||||||
super(message);
|
|
||||||
this.name = "spinnyCompileError";
|
|
||||||
this.debugStack = debugStack;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class spinnyParser {
|
class spinnyParser {
|
||||||
INSIDE = {
|
INSIDE = {
|
||||||
|
|
@ -224,17 +174,14 @@ class spinnyParser {
|
||||||
INTER: 1,
|
INTER: 1,
|
||||||
LAZY: 2
|
LAZY: 2
|
||||||
}
|
}
|
||||||
constructor(modifiers=null) {
|
compile(sourceFileContents) {
|
||||||
this.mods = modifiers
|
|
||||||
}
|
|
||||||
async compile(sourceFileContents) {
|
|
||||||
this.file = sourceFileContents;
|
this.file = sourceFileContents;
|
||||||
this.index = 0;
|
this.index = 0;
|
||||||
this.debugStack = [];
|
this.debugStack = [];
|
||||||
return await this.parseFile()
|
return this.parseFile()
|
||||||
}
|
}
|
||||||
async parseFile() {
|
parseFile() {
|
||||||
await this.tag("File")
|
this.debugStack.push(["File", this.index]);
|
||||||
let lines = [];
|
let lines = [];
|
||||||
|
|
||||||
// A bunch of lines
|
// A bunch of lines
|
||||||
|
|
@ -243,56 +190,18 @@ class spinnyParser {
|
||||||
this.next();
|
this.next();
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
lines.push(await this.parseLine(this.INSIDE.FILE))
|
lines.push(this.parseLine(INSIDE.FILE))
|
||||||
}
|
}
|
||||||
await this.untag();
|
this.debugStack.pop();
|
||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility Functions
|
|
||||||
now() { return this.file[this.index] }
|
now() { return this.file[this.index] }
|
||||||
next() {
|
next() { return this.file[this.index++] }
|
||||||
if (this.index++ === this.file.length)
|
|
||||||
throw new spinnyCompileError("Compilation failed at EOF", this.debugStack)
|
|
||||||
return this.file[this.index]
|
|
||||||
}
|
|
||||||
peek() { return this.file[this.index+1] }
|
peek() { return this.file[this.index+1] }
|
||||||
async tag(tagtype) {
|
|
||||||
let stackupd = {
|
|
||||||
type: tagtype,
|
|
||||||
fpos: this.getFpos(this.index)
|
|
||||||
};
|
|
||||||
if (this.mods.parsestep) {
|
|
||||||
const regex = /\t/g
|
|
||||||
display.print(`Begin ${stackupd.type} @ ${stackupd.fpos[0]}:${stackupd.fpos[1]}`);
|
|
||||||
display.print(` └ ${this.file.split('\n').slice(stackupd.fpos[0]-1)[0].replace(regex,"\u21a3")}`);
|
|
||||||
display.print(" " + "^".padStart(stackupd.fpos[1]));
|
|
||||||
if (await io.read() === "exit")
|
|
||||||
throw new spinnyCompileError("Compilation canceled by user.", this.debugStack)
|
|
||||||
}
|
|
||||||
this.debugStack.push(stackupd);
|
|
||||||
}
|
|
||||||
async untag() {
|
|
||||||
if (this.mods.parsestep) {
|
|
||||||
const regex = /\t/g
|
|
||||||
let stackupd = this.debugStack.slice(-1)[0];
|
|
||||||
let endFpos = this.getFpos(this.index)
|
|
||||||
display.print(`End ${stackupd.type} @ ${endFpos[0]}:${endFpos[1]}`);
|
|
||||||
display.print(` └ ${this.file.split('\n').slice(endFpos[0]-1)[0].replace(regex,"\u21a3")}`);
|
|
||||||
display.print(" " + "^".padStart(endFpos[1]));
|
|
||||||
if (await io.read() === "exit")
|
|
||||||
throw new spinnyCompileError("Compilation canceled by user.", this.debugStack)
|
|
||||||
}
|
|
||||||
this.debugStack.pop();
|
|
||||||
}
|
|
||||||
getFpos(index) {
|
|
||||||
let upto = this.file.substring(0, index).split('\n');
|
|
||||||
return [upto.length, upto.slice(-1)[0].length + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parsing Functions
|
parseLine(inside) {
|
||||||
async parseLine(inside) {
|
this.debugStack.push(["Line", this.index])
|
||||||
await this.tag("Line")
|
|
||||||
let words = []
|
let words = []
|
||||||
wordLoop:
|
wordLoop:
|
||||||
while (1) {
|
while (1) {
|
||||||
|
|
@ -304,19 +213,19 @@ class spinnyParser {
|
||||||
this.next();
|
this.next();
|
||||||
continue
|
continue
|
||||||
case "#":
|
case "#":
|
||||||
await this.parseComment();
|
this.parseComment();
|
||||||
continue
|
continue
|
||||||
case "}":
|
case "}":
|
||||||
if (inside === this.INSIDE.LAZY)
|
if (inside === INSIDE.LAZY)
|
||||||
break wordLoop
|
break wordLoop
|
||||||
break
|
break
|
||||||
case ">":
|
case ">":
|
||||||
if (inside === this.INSIDE.INTER)
|
if (inside === INSIDE.INTER)
|
||||||
break wordLoop
|
break wordLoop
|
||||||
break
|
break
|
||||||
case "\n":
|
case "\n":
|
||||||
if (inside === this.INSIDE.FILE
|
if (inside === INSIDE.FILE
|
||||||
|| inside == this.INSIDE.LAZY)
|
|| inside == INSIDE.LAZY)
|
||||||
break wordLoop
|
break wordLoop
|
||||||
break
|
break
|
||||||
case "\\":
|
case "\\":
|
||||||
|
|
@ -329,14 +238,14 @@ class spinnyParser {
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
words.push(await this.parseWord(inside));
|
words.push(this.parseWord(inside));
|
||||||
}
|
}
|
||||||
await this.untag()
|
this.debugStack.pop()
|
||||||
return words
|
return words
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseComment() {
|
parseComment() {
|
||||||
await this.tag("Comment")
|
this.debugStack.push(["Comment", this.index])
|
||||||
commentLoop:
|
commentLoop:
|
||||||
while (1) {
|
while (1) {
|
||||||
switch (this.now()) {
|
switch (this.now()) {
|
||||||
|
|
@ -349,11 +258,11 @@ class spinnyParser {
|
||||||
this.next();
|
this.next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this.untag()
|
this.debugStack.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseWord(inside) {
|
parseWord(inside) {
|
||||||
await this.tag("Word")
|
this.debugStack.push(["Word", this.index])
|
||||||
let bang = false;
|
let bang = false;
|
||||||
let word = {}
|
let word = {}
|
||||||
word[CMPVER.KEYS.BANG] = false;
|
word[CMPVER.KEYS.BANG] = false;
|
||||||
|
|
@ -364,47 +273,41 @@ class spinnyParser {
|
||||||
switch (this.now()) {
|
switch (this.now()) {
|
||||||
case "{":
|
case "{":
|
||||||
word[CMPVER.KEYS.TYPE] = CMPVER.TYPE.LAZY;
|
word[CMPVER.KEYS.TYPE] = CMPVER.TYPE.LAZY;
|
||||||
word[CMPVER.KEYS.LINES] = await this.parseLazy();
|
word[CMPVER.KEYS.LINES] = this.parseLazy();
|
||||||
break;
|
|
||||||
case "`":
|
case "`":
|
||||||
word[CMPVER.KEYS.TYPE] = CMPVER.TYPE.LIT;
|
word[CMPVER.KEYS.TYPE] = CMPVER.TYPE.LIT;
|
||||||
word[CMPVER.KEYS.PARTS] = await this.parseLiteral();
|
word[CMPVER.KEYS.PARTS] = this.parseLiteral();
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
word[CMPVER.KEYS.TYPE] = CMPVER.TYPE.ID;
|
word[CMPVER.KEYS.TYPE] = CMPVER.TYPE.ID;
|
||||||
word[CMPVER.KEYS.PARTS] = await this.parseIdentifier(inside);
|
word[CMPVER.KEYS.PARTS] = this.parseIdentifier(inside);
|
||||||
}
|
}
|
||||||
await this.untag()
|
this.debugStack.pop()
|
||||||
return word
|
return word
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseLazy() {
|
parseLazy() {
|
||||||
await this.tag("Lazy")
|
this.debugStack.push(["Lazy", this.index])
|
||||||
this.next(); // Skip `{'
|
this.next(); // Skip `{'
|
||||||
let lines = []
|
let lines = []
|
||||||
while (this.now() !== "}") {
|
while (this.now() !== "}") {
|
||||||
lines.push(await this.parseLine(this.INSIDE.LAZY))
|
lines.push(this.parseLine(INSIDE.LAZY))
|
||||||
if (this.now() === "\n")
|
|
||||||
// Continue to next line like a file would
|
|
||||||
this.next();
|
|
||||||
}
|
}
|
||||||
this.next(); // Skip `}'
|
this.debugStack.pop()
|
||||||
await this.untag()
|
|
||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseLiteral() {
|
parseLiteral() {
|
||||||
await this.tag("Literal")
|
this.debugStack.push(["Literal", this.index])
|
||||||
this.next(); // Skip `\`'
|
this.next(); // Skip `\`'
|
||||||
let parts = [""]
|
let parts = [""]
|
||||||
while (this.now() !== "\'") {
|
while (this.now() !== "\'") {
|
||||||
switch (this.now()) {
|
switch (this.now()) {
|
||||||
case "\\":
|
case "\\":
|
||||||
parts[parts.length-1]+=await this.parseEscape();
|
parts[parts.length-1]+=this.parseEscape();
|
||||||
break
|
break
|
||||||
case "<":
|
case "<":
|
||||||
parts.push(
|
parts.push(
|
||||||
await this.parseInterpolation()
|
this.parseInterpolation()
|
||||||
);
|
);
|
||||||
parts.push("")
|
parts.push("")
|
||||||
break
|
break
|
||||||
|
|
@ -414,13 +317,12 @@ class spinnyParser {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.next(); // Skip `\''
|
this.debugStack.pop();
|
||||||
await this.untag();
|
|
||||||
return parts
|
return parts
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseEscape() {
|
parseEscape() {
|
||||||
await this.tag("Escape")
|
this.debugStack.push(["Literal", this.index])
|
||||||
this.next(); // skip `\\'
|
this.next(); // skip `\\'
|
||||||
let out = "";
|
let out = "";
|
||||||
switch (this.now()) {
|
switch (this.now()) {
|
||||||
|
|
@ -464,57 +366,54 @@ class spinnyParser {
|
||||||
out = this.now(); // record the thing after the `\\'
|
out = this.now(); // record the thing after the `\\'
|
||||||
this.next(); // skip over it
|
this.next(); // skip over it
|
||||||
break
|
break
|
||||||
|
|
||||||
}
|
}
|
||||||
await this.untag();
|
this.debugStack.pop();
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseInterpolation() {
|
parseInterpolation() {
|
||||||
await this.tag("Interpolation")
|
this.debugStack.push(["Interpolation", this.index])
|
||||||
this.next(); // skip `\<'
|
this.next(); // skip `\<'
|
||||||
let line
|
let line
|
||||||
while (this.now() !== ">") {
|
while (this.now() !== ">") {
|
||||||
line = await this.parseLine(this.INSIDE.INTER)
|
line = this.parseLine(INSIDE.INTER)
|
||||||
}
|
}
|
||||||
this.next(); // skip `>'
|
this.next(); // skip `>'
|
||||||
await this.untag()
|
this.debugStack.pop()
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseIdentifier(inside) {
|
parseIdentifier(inside) {
|
||||||
await this.tag("Identifier")
|
this.debugStack.push(["Identifier", this.index])
|
||||||
let parts = [""]
|
let parts = [""]
|
||||||
idLoop:
|
idLoop:
|
||||||
while (1) {
|
while (1) {
|
||||||
switch (this.now()) {
|
switch (this.now()) {
|
||||||
case "\\":
|
case "\\":
|
||||||
parts[parts.length-1]+=await this.parseEscape();
|
parts[parts.length-1]+=this.parseEscape();
|
||||||
continue
|
continue
|
||||||
case "<":
|
case "<":
|
||||||
parts.push(
|
parts.push(
|
||||||
await this.parseInterpolation()
|
this.parseInterpolation()
|
||||||
);
|
);
|
||||||
parts.push("")
|
parts.push("")
|
||||||
continue
|
continue
|
||||||
case "}":
|
case "}":
|
||||||
if (inside === this.INSIDE.LAZY)
|
if (inside === INSIDE.LAZY)
|
||||||
break idLoop
|
break idLoop
|
||||||
break
|
break
|
||||||
case ">":
|
case ">":
|
||||||
if (inside === this.INSIDE.INTER)
|
if (inside === INSIDE.INTER)
|
||||||
break idLoop
|
break idLoop
|
||||||
break
|
break
|
||||||
case "#":
|
case "#":
|
||||||
case " ":
|
|
||||||
case "\t":
|
|
||||||
case "\n":
|
|
||||||
break idLoop
|
break idLoop
|
||||||
}
|
}
|
||||||
parts[parts.length-1]+=this.now();
|
parts[parts.length-1]+=this.now();
|
||||||
this.next();
|
this.next();
|
||||||
}
|
}
|
||||||
await this.untag();
|
this.debugStack.pop();
|
||||||
return parts
|
return parts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -538,10 +437,5 @@ const SPIDNEY = {//spinny identifiers
|
||||||
const CMPVER = SPIDNEY["noversion"] //compileversion
|
const CMPVER = SPIDNEY["noversion"] //compileversion
|
||||||
|
|
||||||
// When the program starts:
|
// When the program starts:
|
||||||
await onRun();
|
onRun();
|
||||||
quit();
|
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"]);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -4,8 +4,6 @@
|
||||||
"version": [0,0,3],
|
"version": [0,0,3],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"paths": {
|
"paths": {
|
||||||
"spinny.js": "%B/spinny.js",
|
"spinny.js": "%B/spinny.js"
|
||||||
"s.spin": "/s.spin",
|
|
||||||
"r.spin": "/r.spin"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue