Made rin tool and added debug mode to scripts.

This commit is contained in:
Kapendev 2025-01-22 15:42:44 +02:00
parent 17c7b00e72
commit 85596d6fd3
7 changed files with 204 additions and 74 deletions

View file

@ -49,7 +49,7 @@ $ 3 i SET
$ on GET i GET col_count GET 7 * + x CAT SET
$ 4 3 i INC RANGE IF LOOP THEN
## Draw the board.
# Draw the board.
$ i INIT
*
$ i GET x CAT item SET
@ -57,3 +57,6 @@ $ col_count GET i GET % 1 col_count GET - = is_last SET
$ item GET GET is_last GET IF ECHO ELSE ECHON THEN
$ off GET i GET x CAT SET
$ count GET i INC < IF LOOP THEN
*
$ DEBUG IF ECHO

19
rin/.gitignore vendored Normal file
View file

@ -0,0 +1,19 @@
.dub
dub.s*
docs.json
__dummy.html
docs/
/rin
rin.so
rin.dylib
rin.dll
rin.a
rin.lib
rin-test-*
*.exe
*.pdb
*.o
*.obj
*.lst
parin*
test.rin

9
rin/README.md Normal file
View file

@ -0,0 +1,9 @@
# Setup
A Parin script interpreter.
Works both with and without DUB.
## Usage
* With DUB: `dub run parin:rin`
* Without DUB: `dmd -run -Ijoka_path _Iparin_path source/app.d`

13
rin/dub.json Normal file
View file

@ -0,0 +1,13 @@
{
"authors": [
"Alexandros F. G. Kapretsos"
],
"copyright": "Copyright © 2024, Alexandros F. G. Kapretsos",
"description": "A Parin script interpreter.",
"license": "MIT",
"name": "rin",
"dependencies": {
"joka": "~main",
"parin": {"path": ".."}
},
}

61
rin/source/app.d Normal file
View file

@ -0,0 +1,61 @@
/// A Parin script interpreter.
import joka;
import parin.story;
IStr path;
Story story;
void printError(Sz index, IStr text) {
printfln("{}({}): {}", path, index, text);
}
Fault prepareStory() {
if (auto fault = story.prepare()) {
auto index = story.faultPrepareIndex + 1;
printError(index, "Invalid character at the beginning of the line.");
return fault;
}
return Fault.none;
}
Fault updateStory() {
if (story.hasText) println(story.text);
if (auto fault = story.update()) {
auto index = story.lineIndex + 1;
switch (fault) with (Fault) {
case invalid: printError(index, "Invalid arguments passed to the `{}` operator.".format(story.faultOp)); break;
case overflow: printError(index, "A word or number is too long."); break;
case cantParse: printError(index, "A word, number, or operator contains invalid characters."); break;
default: break;
}
return fault;
}
return Fault.none;
}
int main(string[] args) {
if (args.length == 1) {
println("Usage: rin [options] script");
println("Options: -debug");
return 0;
}
foreach (arg; args[1 .. $ - 1]) {
if (arg == "-debug") story.debugMode = true;
}
path = args[$ - 1];
if (auto fault = readTextIntoBuffer(path, story.script)) {
switch (fault) {
case Fault.cantOpen: println("Can't find file `{}`.".format(path)); break;
case Fault.cantRead: println("Can't read file `{}`.".format(path)); break;
default: break;
}
return 1;
}
if (prepareStory()) return 1;
if (updateStory()) return 1;
while (story.lineIndex != story.lineCount) {
if (updateStory()) return 1;
}
return 0;
}

View file

@ -13,5 +13,6 @@ public import joka.io;
public import parin.engine;
public import parin.map;
public import parin.sprite;
public import parin.story;
public import parin.timer;
public import parin.ui;

View file

@ -2,9 +2,6 @@
// TODO: toStr char arrays might need some work. It has bad error messages for them.
// TODO: concat and others might need a "intoBuffer" vesion.
// TODO: Look at CAT case and think about how to make it better with joka.
// TODO: Make command line tool for checking a script and for simple one-line expressions.
// TODO: Change the name of StoryLine.
// TODO: Add better error info like line, reason, ...
// NOTE: Remember to update both joka and parin at the same time because there was a evil change.
// NOTE: I will start cleanning and then will add CALL.
@ -17,7 +14,8 @@ import joka.ascii;
import joka.io;
import joka.unions;
enum StoryLine : ubyte {
enum StoryLineKind : ubyte {
empty = ' ',
comment = '#',
label = '*',
text = '|',
@ -49,6 +47,7 @@ enum StoryOp : ubyte {
CAT,
WORD,
NUM,
DEBUG,
END,
ECHO,
ECHON,
@ -126,6 +125,9 @@ struct Story {
StoryNumber lineIndex;
StoryNumber nextLabelIndex;
StoryNumber previousMenuResult;
StoryNumber faultPrepareIndex;
StoryOp faultOp;
bool debugMode;
IStr opIndex(Sz i) {
if (i >= lineCount) {
@ -135,20 +137,32 @@ struct Story {
return script[pair.a .. pair.b];
}
Fault throwOpFault(StoryOp op) {
faultOp = op;
return Fault.invalid;
}
StoryNumber lineCount() {
return cast(StoryNumber) pairs.length;
}
bool hasPause() {
return lineIndex == lineCount || (lineIndex < lineCount && opIndex(lineIndex)[0] == StoryLine.pause);
if (lineIndex == lineCount) return true;
if (lineIndex >= lineCount) return false;
auto line = opIndex(lineIndex);
return line.length && line[0] == StoryLineKind.pause;
}
bool hasMenu() {
return lineIndex < lineCount && opIndex(lineIndex)[0] == StoryLine.menu;
if (lineIndex >= lineCount) return false;
auto line = opIndex(lineIndex);
return line.length && line[0] == StoryLineKind.menu;
}
bool hasText() {
return lineIndex < lineCount && opIndex(lineIndex)[0] == StoryLine.text;
if (lineIndex >= lineCount) return false;
auto line = opIndex(lineIndex);
return line.length && line[0] == StoryLineKind.text;
}
IStr text() {
@ -163,7 +177,7 @@ struct Story {
auto view = hasMenu ? opIndex(lineIndex)[1 .. $].trimStart() : "";
while (view.length) {
if (buffer.length == buffer.capacity) return buffer[];
buffer.append(view.skipValue(StoryLine.menu).trim());
buffer.append(view.skipValue(StoryLineKind.menu).trim());
}
return buffer[];
}
@ -174,23 +188,23 @@ struct Story {
pairs.clear();
labels.clear();
if (script.isEmpty) return Fault.none;
auto start = 0LU;
auto start = 0;
auto prepareIndex = 0;
foreach (i, c; script) {
if (c == '\n') {
auto pair = StoryStartEndPair(cast(uint) start, cast(uint) i);
auto pair = StoryStartEndPair(start, cast(uint) i);
auto line = script[pair.a .. pair.b];
auto trimmedLine = line.trim();
if (trimmedLine.length == 0) continue;
pair.a += line.length - line.trimStart().length;
pair.b -= line.length - line.trimEnd().length;
if (pair.a == pair.b) continue;
auto lineResult = script[pair.a].toStoryLine();
auto lineResult = toStoryLineKind(trimmedLine.length ? script[pair.a] : StoryLineKind.empty);
if (lineResult.isNone) {
pairs.clear();
return Fault.invalid;
faultPrepareIndex = prepareIndex;
return Fault.cantParse;
}
auto kind = lineResult.value;
if (kind == StoryLine.label) {
if (kind == StoryLineKind.label) {
// TODO: Make words easier to use doooood.
auto name = trimmedLine[1 .. $].trimStart();
auto temp = StoryWord.init;
@ -199,7 +213,8 @@ struct Story {
labels.append(StoryVariable(temp, StoryValue(cast(StoryNumber) pairs.length)));
}
pairs.append(pair);
start = i + 1;
start = cast(int) (i + 1);
prepareIndex += 1;
}
}
lineIndex = lineCount;
@ -239,10 +254,10 @@ struct Story {
case LESS:
case GREATER:
case EQUAL:
if (stack.length < 2) return Fault.invalid;
if (stack.length < 2) return throwOpFault(op);
auto db = stack.pop();
auto da = stack.pop();
if (!db.isType!StoryNumber || !da.isType!StoryNumber) return Fault.invalid;
if (!db.isType!StoryNumber || !da.isType!StoryNumber) return throwOpFault(op);
auto a = da.get!StoryNumber;
auto b = db.get!StoryNumber;
auto c = StoryNumber.init;
@ -262,9 +277,9 @@ struct Story {
stack.append(StoryValue(c));
break;
case NOT:
if (stack.length < 1) return Fault.invalid;
if (stack.length < 1) return throwOpFault(op);
auto da = stack.pop();
if (!da.isType!StoryNumber) return Fault.invalid;
if (!da.isType!StoryNumber) return throwOpFault(op);
stack.append(StoryValue(!da.get!StoryNumber));
break;
case POP:
@ -274,31 +289,31 @@ struct Story {
stack.clear();
break;
case SWAP:
if (stack.length < 2) return Fault.invalid;
if (stack.length < 2) return throwOpFault(op);
auto db = stack.pop();
auto da = stack.pop();
stack.append(db);
stack.append(da);
break;
case COPY:
if (stack.length < 1) return Fault.invalid;
if (stack.length < 1) return throwOpFault(op);
stack.append(stack[$ - 1]);
break;
case RANGE:
if (stack.length < 3) return Fault.invalid;
if (stack.length < 3) return throwOpFault(op);
auto dc = stack.pop();
auto db = stack.pop();
auto da = stack.pop();
if (!dc.isType!StoryNumber || !db.isType!StoryNumber || !da.isType!StoryNumber) return Fault.invalid;
if (!dc.isType!StoryNumber || !db.isType!StoryNumber || !da.isType!StoryNumber) return throwOpFault(op);
auto a = da.get!StoryNumber();
auto b = db.get!StoryNumber();
auto c = dc.get!StoryNumber();
stack.append(StoryValue(c >= b && c <= a));
break;
case IF:
if (stack.length < 1) return Fault.invalid;
if (stack.length < 1) return throwOpFault(op);
auto da = stack.pop();
if (!da.isType!StoryNumber) return Fault.invalid;
if (!da.isType!StoryNumber) return throwOpFault(op);
if (!da.get!StoryNumber) ifCounter += 1;
break;
case ELSE:
@ -307,10 +322,10 @@ struct Story {
case THEN:
break;
case CAT:
if (stack.length < 2) return Fault.invalid;
if (stack.length < 2) return throwOpFault(op);
auto db = stack.pop();
auto da = stack.pop();
if (!db.isType!StoryWord) return Fault.invalid;
if (!db.isType!StoryWord) return throwOpFault(op);
StoryWord word;
auto data = concat(concat(db.toStr()), da.toStr());
if (data.length > word.length) return Fault.overflow;
@ -319,15 +334,18 @@ struct Story {
stack.append(StoryValue(word));
break;
case WORD:
if (stack.length < 1) return Fault.invalid;
if (stack.length < 1) return throwOpFault(op);
auto da = stack.pop();
stack.append(StoryValue(da.isType!StoryWord));
break;
case NUM:
if (stack.length < 1) return Fault.invalid;
if (stack.length < 1) return throwOpFault(op);
auto da = stack.pop();
stack.append(StoryValue(da.isType!StoryNumber));
break;
case DEBUG:
stack.append(StoryValue(debugMode));
break;
case END:
return Fault.none;
case ECHO:
@ -388,9 +406,9 @@ struct Story {
}
break;
case HERE:
if (stack.length < 1) return Fault.invalid;
if (stack.length < 1) return throwOpFault(op);
auto da = stack.pop();
if (!da.isType!StoryWord) return Fault.invalid;
if (!da.isType!StoryWord) return throwOpFault(op);
auto a = da.get!StoryWord();
auto isNotThere = true;
foreach (variable; variables) {
@ -402,9 +420,9 @@ struct Story {
stack.append(StoryValue(!isNotThere));
break;
case GET:
if (stack.length < 1) return Fault.invalid;
if (stack.length < 1) return throwOpFault(op);
auto da = stack.pop();
if (!da.isType!StoryWord) return Fault.invalid;
if (!da.isType!StoryWord) return throwOpFault(op);
auto a = da.get!StoryWord();
auto isNotThere = true;
foreach (variable; variables) {
@ -414,13 +432,13 @@ struct Story {
break;
}
}
if (isNotThere) return Fault.invalid;
if (isNotThere) return throwOpFault(op);
break;
case SET:
if (stack.length < 2) return Fault.invalid;
if (stack.length < 2) return throwOpFault(op);
auto db = stack.pop();
auto da = stack.pop();
if (!db.isType!StoryWord) return Fault.invalid;
if (!db.isType!StoryWord) return throwOpFault(op);
auto b = db.get!StoryWord();
auto isNotThere = true;
foreach (ref variable; variables) {
@ -433,9 +451,9 @@ struct Story {
if (isNotThere) variables.append(StoryVariable(b, da));
break;
case INIT:
if (stack.length < 1) return Fault.invalid;
if (stack.length < 1) return throwOpFault(op);
auto da = stack.pop();
if (!da.isType!StoryWord) return Fault.invalid;
if (!da.isType!StoryWord) return throwOpFault(op);
auto a = da.get!StoryWord();
auto isNotThere = true;
foreach (ref variable; variables) {
@ -448,9 +466,9 @@ struct Story {
if (isNotThere) variables.append(StoryVariable(a, StoryValue(0)));
break;
case DROP:
if (stack.length < 1) return Fault.invalid;
if (stack.length < 1) return throwOpFault(op);
auto da = stack.pop();
if (!da.isType!StoryWord) return Fault.invalid;
if (!da.isType!StoryWord) return throwOpFault(op);
auto a = da.get!StoryWord();
auto isNotThere = true;
foreach (i, variable; variables) {
@ -465,9 +483,9 @@ struct Story {
break;
case INC:
case DEC:
if (stack.length < 1) return Fault.invalid;
if (stack.length < 1) return throwOpFault(op);
auto da = stack.pop();
if (!da.isType!StoryWord) return Fault.invalid;
if (!da.isType!StoryWord) return throwOpFault(op);
auto a = da.get!StoryWord();
auto isNotThere = true;
foreach (ref variable; variables) {
@ -476,20 +494,20 @@ struct Story {
variable.value.get!StoryNumber() += (op == INC ? 1 : -1);
stack.append(variable.value);
} else {
return Fault.invalid;
return throwOpFault(op);
}
isNotThere = false;
break;
}
}
if (isNotThere) return Fault.invalid;
if (isNotThere) return throwOpFault(op);
break;
case INCN:
case DECN:
if (stack.length < 2) return Fault.invalid;
if (stack.length < 2) return throwOpFault(op);
auto db = stack.pop();
auto da = stack.pop();
if (!db.isType!StoryWord || !da.isType!StoryNumber) return Fault.invalid;
if (!db.isType!StoryWord || !da.isType!StoryNumber) return throwOpFault(op);
auto b = db.get!StoryWord();
auto a = da.get!StoryNumber();
auto isNotThere = true;
@ -499,18 +517,18 @@ struct Story {
variable.value.get!StoryNumber() += a * (op == INCN ? 1 : -1);
stack.append(variable.value);
} else {
return Fault.invalid;
return throwOpFault(op);
}
isNotThere = false;
break;
}
}
if (isNotThere) return Fault.invalid;
if (isNotThere) return throwOpFault(op);
break;
case TOG:
if (stack.length < 1) return Fault.invalid;
if (stack.length < 1) return throwOpFault(op);
auto da = stack.pop();
if (!da.isType!StoryWord) return Fault.invalid;
if (!da.isType!StoryWord) return throwOpFault(op);
auto a = da.get!StoryWord();
auto isNotThere = true;
foreach (ref variable; variables) {
@ -519,18 +537,19 @@ struct Story {
variable.value.get!StoryNumber() = !variable.value.get!StoryNumber();
stack.append(variable.value);
} else {
return Fault.invalid;
return throwOpFault(op);
}
isNotThere = false;
break;
}
}
if (isNotThere) return Fault.invalid;
if (isNotThere) return throwOpFault(op);
break;
case MENU:
stack.append(StoryValue(previousMenuResult));
break;
case LOOP:
if (debugMode) break;
auto target = nextLabelIndex - 1;
if (target < 0 || target >= labels.length || labels.length == 0) {
lineIndex = lineCount;
@ -541,11 +560,12 @@ struct Story {
}
break;
case SKIP:
if (stack.length < 1) return Fault.invalid;
if (stack.length < 1) return throwOpFault(op);
auto da = stack.pop();
if (!da.isType!StoryNumber) return Fault.invalid;
if (!da.isType!StoryNumber) return throwOpFault(op);
auto a = da.get!StoryNumber();
if (a == 0) break;
if (debugMode) break;
auto target = nextLabelIndex + (a > 0 ? a - 1 : a);
if (target < 0 || target >= labels.length || labels.length == 0) {
lineIndex = lineCount;
@ -558,20 +578,21 @@ struct Story {
case JUMP:
// TODO: Write a find function like a normal person.
// TODO: Might need some error check for -1.
if (stack.length < 1) return Fault.invalid;
if (stack.length < 1) return throwOpFault(op);
auto da = stack.pop();
if (!da.isType!StoryWord) return Fault.invalid;
if (!da.isType!StoryWord) return throwOpFault(op);
auto a = da.get!StoryWord();
auto isNotThere = true;
foreach (i, ref label; labels) {
if (a == label.name) {
isNotThere = false;
if (debugMode) break;
lineIndex = label.value.get!StoryNumber();
nextLabelIndex = cast(StoryNumber) ((i + 1) % (labels.length + 1));
isNotThere = false;
break;
}
}
if (isNotThere) return Fault.invalid;
if (isNotThere) return throwOpFault(op);
break;
case CALL:
println("TODO: ", op);
@ -598,11 +619,13 @@ struct Story {
lineIndex = (lineIndex + 1) % (lineCount + 1);
while (lineIndex < lineCount && !hasPause && !hasMenu && !hasText) {
auto line = opIndex(lineIndex);
if (line[0] == StoryLine.expression) {
auto fault = execute(line[1 .. $].trimStart());
if (fault) return fault;
} else if (line[0] == StoryLine.label) {
nextLabelIndex = cast(StoryNumber) ((nextLabelIndex + 1) % (labels.length + 1));
if (line.length) {
if (line[0] == StoryLineKind.expression) {
auto fault = execute(line[1 .. $].trimStart());
if (fault) return fault;
} else if (line[0] == StoryLineKind.label) {
nextLabelIndex = cast(StoryNumber) ((nextLabelIndex + 1) % (labels.length + 1));
}
}
lineIndex = (lineIndex + 1) % (lineCount + 1);
}
@ -643,15 +666,16 @@ bool isMaybeStoryWord(IStr value) {
return c == '_' || (!c.isUpper && !c.isSymbol);
}
Result!StoryLine toStoryLine(char value) {
switch (value) with (StoryLine) {
case '#': return Result!StoryLine(comment);
case '*': return Result!StoryLine(label);
case '|': return Result!StoryLine(text);
case '.': return Result!StoryLine(pause);
case '^': return Result!StoryLine(menu);
case '$': return Result!StoryLine(expression);
default: return Result!StoryLine(Fault.invalid);
Result!StoryLineKind toStoryLineKind(char value) {
switch (value) with (StoryLineKind) {
case ' ': return Result!StoryLineKind(empty);
case '#': return Result!StoryLineKind(comment);
case '*': return Result!StoryLineKind(label);
case '|': return Result!StoryLineKind(text);
case '.': return Result!StoryLineKind(pause);
case '^': return Result!StoryLineKind(menu);
case '$': return Result!StoryLineKind(expression);
default: return Result!StoryLineKind(Fault.invalid);
}
}