mirror of
https://github.com/Kapendev/parin.git
synced 2025-04-29 22:49:54 +03:00
Docs and story cleaning.
This commit is contained in:
parent
3c4ee5e20b
commit
c5ca1b37cf
13 changed files with 244 additions and 621 deletions
|
@ -23,7 +23,7 @@ mixin runGame!(ready, update, finish);
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Immediate Mode UI: Build UIs quickly.
|
* Immediate Mode UI: Build UIs quickly.
|
||||||
* Dialogue System: Create interactive stories using a simple stack-oriented scripting language.
|
* Dialogue System: Create interactive stories using a stack-oriented scripting language.
|
||||||
* Sprite Animation Support: Manage animations for characters and objects.
|
* Sprite Animation Support: Manage animations for characters and objects.
|
||||||
* Tile Map Support: Create and draw tile-based maps.
|
* Tile Map Support: Create and draw tile-based maps.
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ A list of projects made with Parin is available in the [PROJECTS.md](PROJECTS.md
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
This guide shows how to install Parin and its dependencies using DUB.
|
This guide shows how to install Parin and its dependencies using [DUB](https://dub.pm/).
|
||||||
While DUB simplifies the process, Parin itself doesn't require DUB.
|
While DUB simplifies the process, Parin itself doesn't require DUB.
|
||||||
|
|
||||||
Parin has the following dependencies:
|
Parin has the following dependencies:
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
## A script that tests if things work correctly.
|
|
||||||
|
|
||||||
$ line ECHOC result ECHO
|
|
||||||
|
|
||||||
# [ Math Test ]
|
|
||||||
$ LINE ECHOC 10 6 + 16 = ECHO
|
|
||||||
$ LINE ECHOC 10 6 - 4 = ECHO
|
|
||||||
$ LINE ECHOC 6 10 - -4 = ECHO
|
|
||||||
$ LINE ECHOC 10 6 * 60 = ECHO
|
|
||||||
$ LINE ECHOC 10 6 / 1 = ECHO
|
|
||||||
$ LINE ECHOC 6 10 / 0 = ECHO
|
|
||||||
$ LINE ECHOC 6 0 / 0 = ECHO
|
|
||||||
$ LINE ECHOC 10 6 % 4 = ECHO
|
|
||||||
$ LINE ECHOC 6 10 % 6 = ECHO
|
|
||||||
$ LINE ECHOC 6 0 % 0 = ECHO
|
|
||||||
$ LINE ECHOC 0 0 & 0 = ECHO
|
|
||||||
$ LINE ECHOC 0 1 & 0 = ECHO
|
|
||||||
$ LINE ECHOC 1 0 & 0 = ECHO
|
|
||||||
$ LINE ECHOC 1 1 & 1 = ECHO
|
|
||||||
$ LINE ECHOC 0 0 | 0 = ECHO
|
|
||||||
$ LINE ECHOC 0 1 | 1 = ECHO
|
|
||||||
$ LINE ECHOC 1 0 | 1 = ECHO
|
|
||||||
$ LINE ECHOC 1 1 | 1 = ECHO
|
|
||||||
$ LINE ECHOC 6 10 < 1 = ECHO
|
|
||||||
$ LINE ECHOC 6 10 > 0 = ECHO
|
|
||||||
$ LINE ECHOC 6 10 = 0 = ECHO
|
|
||||||
$ LINE ECHOC 6 10 ! 0 = ECHO
|
|
||||||
$ LINE ECHOC 6 0 ! 1 = ECHO
|
|
||||||
$ LINE ECHOC 6 0 ~ 6 = ECHO
|
|
||||||
|
|
||||||
# [ Operator Test ]
|
|
||||||
$ LINE ECHOC 6 10 SWAP 6 = ECHO
|
|
||||||
$ LINE ECHOC 6 10 COPY 10 = ECHO
|
|
||||||
$ LINE ECHOC 6 10 COPYN 10 = IF 6 = IF 1 THEN ELSE 0 THEN ECHO
|
|
||||||
$ LINE ECHOC 5 6 10 RANGE NOT ECHO
|
|
||||||
$ LINE ECHOC 6 6 10 RANGE ECHO
|
|
||||||
$ LINE ECHOC 10 6 10 RANGE ECHO
|
|
||||||
$ LINE ECHOC 11 6 10 RANGE NOT ECHO
|
|
||||||
$ LINE ECHOC x 1 CAT x1 SAME ECHO
|
|
||||||
$ LINE ECHOC x x CAT xx SAME ECHO
|
|
||||||
$ LINE ECHOC 0 WORD NOT ECHO
|
|
||||||
$ LINE ECHOC x WORD ECHO
|
|
||||||
$ LINE ECHOC 0 NUMBER ECHO
|
|
||||||
$ LINE ECHOC x NUMBER NOT ECHO
|
|
||||||
$ LINE ECHOC 1 ECHO 0 END ECHO
|
|
||||||
$ LINE ECHOC _x HERE NOT ECHO
|
|
||||||
$ LINE ECHOC _x INIT _x HERE ECHO
|
|
||||||
$ LINE ECHOC _x GET 0 = ECHO
|
|
||||||
$ LINE ECHOC _x _x GETN 0 = IF 0 = IF 1 THEN ELSE 0 THEN ECHO
|
|
||||||
$ LINE ECHOC _x 6 SET _x GET 6 = ECHO
|
|
||||||
$ LINE ECHOC _x DROP _x HERE NOT ECHO
|
|
||||||
$ LINE ECHOC _x INIT _x INC 1 = _x INIT ECHO
|
|
||||||
$ LINE ECHOC _x INIT _x DEC -1 = _x INIT ECHO
|
|
||||||
$ LINE ECHOC _x 2 INCN 2 = _x INIT ECHO
|
|
||||||
$ LINE ECHOC _x 2 DECN -2 = _x INIT ECHO
|
|
||||||
$ LINE ECHOC _x TOG 1 = _x INIT ECHO
|
|
||||||
|
|
||||||
# [ Loop Test ]
|
|
||||||
$ _x INIT
|
|
||||||
*
|
|
||||||
$ _x INC 10 < IF LOOP THEN
|
|
||||||
$ LINE ECHOC _x GET 10 = _x INIT ECHO
|
|
|
@ -1,16 +1,14 @@
|
||||||
/// This example shows how to use the Parin dialogue system.
|
/// This example shows how to use the Parin dialogue system.
|
||||||
|
|
||||||
import parin;
|
import parin;
|
||||||
import parin.story;
|
|
||||||
|
|
||||||
auto story = Story();
|
auto story = Story();
|
||||||
auto script = "
|
auto script = "
|
||||||
# A comment.
|
# A comment.
|
||||||
* label
|
* label
|
||||||
| Hello world!
|
| Hello world!
|
||||||
| This is a text line.
|
| This is a text line and the next is a menu line.
|
||||||
| The next line will be a menu line.
|
^ Option 1 ^ Option 2 ^ Option 3
|
||||||
^ Option number 1 ^ Option number 2 ^ Go to end
|
|
||||||
$ MENU SKIP
|
$ MENU SKIP
|
||||||
|
|
||||||
*
|
*
|
||||||
|
@ -22,11 +20,9 @@ auto script = "
|
||||||
$ end JUMP
|
$ end JUMP
|
||||||
|
|
||||||
* end
|
* end
|
||||||
| Expression lines are used to go to labels.
|
|
||||||
| JUMP goes to the label with the given name.
|
| JUMP goes to the label with the given name.
|
||||||
| SKIP skips ahead N labels.
|
| SKIP skips ahead N labels.
|
||||||
| MENU returns the selected option.
|
| MENU returns the selected option.
|
||||||
| That's it. I Hope this helps.
|
|
||||||
";
|
";
|
||||||
|
|
||||||
void ready() {
|
void ready() {
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
# Rin
|
# Rin
|
||||||
|
|
||||||
A Parin script interpreter.
|
A script interpreter for Rin.
|
||||||
Works both with and without DUB.
|
It helps with error checking and debugging, making it easier to work with Parin scripts.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
* With DUB: `dub run parin:rin`
|
* With DUB: `dub run parin:rin`
|
||||||
* Without DUB: `dmd -run -Ijoka_path -Iparin_path source/app.d`
|
* Without DUB: `dmd -run -Ijoka_path -Iparin_path source/app.d`
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
* -debug: Runs the script in debug mode. This will change the value of DEBUG.
|
||||||
|
* -linear: Runs the script in linear mode. This will force the interpreter to go over every line.
|
||||||
|
|
||||||
|
## Editor Themes
|
||||||
|
|
||||||
|
Rin syntax highlighting files for various text editors are available in the [assets](./assets/) folder.
|
||||||
|
|
14
rin/assets/rin.nanorc
Normal file
14
rin/assets/rin.nanorc
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
## Rin Scripting Language
|
||||||
|
|
||||||
|
syntax "Rin" "\.rin$"
|
||||||
|
magic "^(Rin) (source|program)"
|
||||||
|
comment "#"
|
||||||
|
|
||||||
|
# Operators
|
||||||
|
color brightblue "\<(ADD|SUB|MUL|DIV|MOD|AND|OR|LESS|GREATER|EQUAL|NOT|POP|CLEAR|SWAP|COPY|COPYN|RANGE|IF|ELSE|THEN|CAT|SAME|WORD|NUMBER|LINE|DEBUG|LINEAR|ASSERT|END|ECHO|ECHON|LEAK|LEAKN|HERE|GET|GETN|SET|INIT|DROP|DROPN|INC|DEC|INCN|DECN|TOG|MENU|LOOP|SKIP|JUMP|CALL)\>"
|
||||||
|
# Lines
|
||||||
|
color cyan "^[ \t]*\*" "^[ \t]*\|" "^[ \t]*\." "^[ \t]*\^" "^[ \t]*\$"
|
||||||
|
# Comments
|
||||||
|
color green "^[ \t]*#.*"
|
||||||
|
# Trailing Whitespace
|
||||||
|
color ,green "[[:space:]]+$"
|
|
@ -3,7 +3,7 @@
|
||||||
"Alexandros F. G. Kapretsos"
|
"Alexandros F. G. Kapretsos"
|
||||||
],
|
],
|
||||||
"copyright": "Copyright © 2024, Alexandros F. G. Kapretsos",
|
"copyright": "Copyright © 2024, Alexandros F. G. Kapretsos",
|
||||||
"description": "A Parin script interpreter.",
|
"description": "A script interpreter for Rin.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"name": "rin",
|
"name": "rin",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
12
rin/examples/hello.rin
Normal file
12
rin/examples/hello.rin
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
## A script that serves as a classic hello-world program, introducing the structure of a Parin script.
|
||||||
|
|
||||||
|
# A comment.
|
||||||
|
* label
|
||||||
|
| Hello world!
|
||||||
|
| This is a text line and the next is an expression line.
|
||||||
|
$ end JUMP
|
||||||
|
|
||||||
|
| You can't see me...
|
||||||
|
|
||||||
|
* end
|
||||||
|
| JUMP goes to the label with the given name.
|
|
@ -3,28 +3,37 @@
|
||||||
import joka;
|
import joka;
|
||||||
import parin.story;
|
import parin.story;
|
||||||
|
|
||||||
IStr path;
|
RinState rinState;
|
||||||
Story story;
|
|
||||||
|
struct RinState {
|
||||||
|
IStr scriptPath;
|
||||||
|
Story story;
|
||||||
|
}
|
||||||
|
|
||||||
void printError(Sz index, IStr text) {
|
void printError(Sz index, IStr text) {
|
||||||
printfln("\n{}({}): {}", path, index, text);
|
printfln("\n{}({}): {}", rinState.scriptPath, index, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
Fault prepareStory() {
|
Fault prepareStory() {
|
||||||
if (auto fault = story.prepare()) {
|
if (auto fault = rinState.story.prepare()) {
|
||||||
auto index = story.faultPrepareIndex + 1;
|
auto index = rinState.story.faultPrepareIndex + 1;
|
||||||
printError(index, "Invalid character at the beginning of the line.");
|
switch (fault) with (Fault) {
|
||||||
|
overflow: printError(index, "Label is too long."); break;
|
||||||
|
cantParse: printError(index, "Invalid character at the beginning of the line."); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
return fault;
|
return fault;
|
||||||
}
|
}
|
||||||
return Fault.none;
|
return Fault.none;
|
||||||
}
|
}
|
||||||
|
|
||||||
Fault updateStory() {
|
Fault updateStory() {
|
||||||
if (story.hasText) println(story.text);
|
if (rinState.story.hasText) println(rinState.story.text);
|
||||||
if (auto fault = story.update()) {
|
if (auto fault = rinState.story.update()) {
|
||||||
auto index = story.lineIndex + 1;
|
auto index = rinState.story.lineIndex + 1;
|
||||||
switch (fault) with (Fault) {
|
switch (fault) with (Fault) {
|
||||||
case invalid: printError(index, "Invalid arguments passed to the `{}` operator.".format(story.faultOp)); break;
|
case some: printError(index, "Assertion failed."); break;
|
||||||
|
case invalid: printError(index, "Invalid arguments passed to the `{}` operator.".format(rinState.story.faultOp)); break;
|
||||||
case overflow: printError(index, "A word or number is too long."); 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;
|
case cantParse: printError(index, "A word, number, or operator contains invalid characters."); break;
|
||||||
default: break;
|
default: break;
|
||||||
|
@ -34,29 +43,33 @@ Fault updateStory() {
|
||||||
return Fault.none;
|
return Fault.none;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(string[] args) {
|
int rinMain(string[] args) {
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
println("Usage: rin [options] script");
|
println("Usage: rin [options] script");
|
||||||
println("Options: -debug -linear");
|
println("Options: -debug -linear");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
foreach (arg; args[1 .. $ - 1]) {
|
foreach (arg; args[1 .. $ - 1]) {
|
||||||
if (arg == "-debug") story.debugMode = true;
|
if (arg == "-debug") rinState.story.debugMode = true;
|
||||||
if (arg == "-linear") story.linearMode = true;
|
if (arg == "-linear") rinState.story.linearMode = true;
|
||||||
}
|
}
|
||||||
path = args[$ - 1];
|
rinState.scriptPath = args[$ - 1];
|
||||||
if (auto fault = readTextIntoBuffer(path, story.script)) {
|
if (auto fault = readTextIntoBuffer(rinState.scriptPath, rinState.story.script)) {
|
||||||
switch (fault) {
|
switch (fault) {
|
||||||
case Fault.cantOpen: println("Can't find file `{}`.".format(path)); break;
|
case Fault.cantOpen: println("Can't find file `{}`.".format(rinState.scriptPath)); break;
|
||||||
case Fault.cantRead: println("Can't read file `{}`.".format(path)); break;
|
case Fault.cantRead: println("Can't read file `{}`.".format(rinState.scriptPath)); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (prepareStory()) return 1;
|
if (prepareStory()) return 1;
|
||||||
if (updateStory()) return 1;
|
if (updateStory()) return 1;
|
||||||
while (story.lineIndex != story.lineCount) {
|
while (rinState.story.lineIndex != rinState.story.lineCount) {
|
||||||
if (updateStory()) return 1;
|
if (updateStory()) return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int main(string[] args) {
|
||||||
|
return rinMain(args);
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Setup
|
# Setup
|
||||||
|
|
||||||
A helper script that automates the project setup.
|
A helper script that automates the project setup.
|
||||||
It creates some files in the current folder and works both with and without DUB.
|
It creates files in the current folder and supports both DUB and non-DUB projects.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|
|
@ -1,358 +0,0 @@
|
||||||
// ---
|
|
||||||
// Copyright 2024 Alexandros F. G. Kapretsos
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
// Email: alexandroskapretsos@gmail.com
|
|
||||||
// Project: https://github.com/Kapendev/parin
|
|
||||||
// Version: v0.0.34
|
|
||||||
// ---
|
|
||||||
|
|
||||||
// TODO: The DialogueCommandRunner should work with gc functions too. Think about how to do it.
|
|
||||||
// TODO: Update all the doc comments here.
|
|
||||||
|
|
||||||
/// The `dialogue` module provides a simple and versatile dialogue system.
|
|
||||||
module parin.dialogue;
|
|
||||||
|
|
||||||
import joka.ascii;
|
|
||||||
import parin.engine;
|
|
||||||
|
|
||||||
@safe:
|
|
||||||
|
|
||||||
enum DialogueUnitKindChars = ".#*@>|^!+-$";
|
|
||||||
|
|
||||||
enum DialogueUnitKind {
|
|
||||||
pause = '.',
|
|
||||||
comment = '#',
|
|
||||||
point = '*',
|
|
||||||
jump = '@',
|
|
||||||
actor = '>',
|
|
||||||
line = '|',
|
|
||||||
menu = '^',
|
|
||||||
variable = '!',
|
|
||||||
plus = '+',
|
|
||||||
minus = '-',
|
|
||||||
command = '$',
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DialogueUnit {
|
|
||||||
LStr text;
|
|
||||||
DialogueUnitKind kind;
|
|
||||||
|
|
||||||
@safe @nogc nothrow:
|
|
||||||
|
|
||||||
void free() {
|
|
||||||
text.free();
|
|
||||||
this = DialogueUnit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DialogueValue {
|
|
||||||
LStr name;
|
|
||||||
long value;
|
|
||||||
|
|
||||||
@safe @nogc nothrow:
|
|
||||||
|
|
||||||
void free() {
|
|
||||||
name.free();
|
|
||||||
this = DialogueValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
alias DialogueCommandRunner = void function(IStr[] args) @trusted;
|
|
||||||
|
|
||||||
struct Dialogue {
|
|
||||||
List!DialogueUnit units;
|
|
||||||
List!DialogueValue values;
|
|
||||||
IStr text;
|
|
||||||
IStr actor;
|
|
||||||
Sz unitIndex;
|
|
||||||
|
|
||||||
@trusted
|
|
||||||
void run(DialogueCommandRunner runner) {
|
|
||||||
runner(args);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
@safe @nogc nothrow:
|
|
||||||
|
|
||||||
bool isEmpty() {
|
|
||||||
return units.length == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasKind(DialogueUnitKind kind) {
|
|
||||||
return unitIndex < units.length && units[unitIndex].kind == kind;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasPause() {
|
|
||||||
return hasKind(DialogueUnitKind.pause);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasChoices() {
|
|
||||||
return hasKind(DialogueUnitKind.menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasArgs() {
|
|
||||||
return hasKind(DialogueUnitKind.command);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool canUpdate() {
|
|
||||||
return !hasPause;
|
|
||||||
}
|
|
||||||
|
|
||||||
IStr[] choices() {
|
|
||||||
static IStr[16] buffer;
|
|
||||||
|
|
||||||
auto length = 0;
|
|
||||||
auto temp = hasChoices ? units[unitIndex].text.items : "";
|
|
||||||
while (temp.length != 0) {
|
|
||||||
buffer[length] = temp.skipValue(DialogueUnitKind.menu).trim();
|
|
||||||
length += 1;
|
|
||||||
}
|
|
||||||
return buffer[0 .. length];
|
|
||||||
}
|
|
||||||
|
|
||||||
IStr[] args() {
|
|
||||||
static IStr[16] buffer;
|
|
||||||
|
|
||||||
auto length = 0;
|
|
||||||
auto temp = hasArgs ? units[unitIndex].text.items : "";
|
|
||||||
while (temp.length != 0) {
|
|
||||||
buffer[length] = temp.skipValue(' ').trim();
|
|
||||||
length += 1;
|
|
||||||
}
|
|
||||||
return buffer[0 .. length];
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset() {
|
|
||||||
text = "";
|
|
||||||
actor = "";
|
|
||||||
unitIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void jump(IStr point) {
|
|
||||||
if (point.length == 0) {
|
|
||||||
foreach (i; unitIndex + 1 .. units.length) {
|
|
||||||
auto unit = units[i];
|
|
||||||
if (unit.kind == DialogueUnitKind.point) {
|
|
||||||
unitIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
foreach (i; 0 .. units.length) {
|
|
||||||
auto unit = units[i];
|
|
||||||
if (unit.kind == DialogueUnitKind.point && unit.text.items == point) {
|
|
||||||
unitIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void jump(Sz i) {
|
|
||||||
auto currPoint = 0;
|
|
||||||
foreach (j, unit; units.items) {
|
|
||||||
if (unit.kind == DialogueUnitKind.point) {
|
|
||||||
if (currPoint == i) {
|
|
||||||
unitIndex = j;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
currPoint += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void skip(Sz count) {
|
|
||||||
foreach (i; 0 .. count) {
|
|
||||||
jump("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void pick(Sz i) {
|
|
||||||
skip(i + 1);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove the asserts!
|
|
||||||
void update() {
|
|
||||||
if (units.length != 0 && unitIndex < units.length - 1) {
|
|
||||||
unitIndex += 1;
|
|
||||||
text = units[unitIndex].text.items;
|
|
||||||
final switch (units[unitIndex].kind) {
|
|
||||||
case DialogueUnitKind.line, DialogueUnitKind.menu, DialogueUnitKind.command, DialogueUnitKind.pause: {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DialogueUnitKind.comment, DialogueUnitKind.point: {
|
|
||||||
update();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DialogueUnitKind.actor: {
|
|
||||||
actor = text;
|
|
||||||
update();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DialogueUnitKind.jump: {
|
|
||||||
jump(text);
|
|
||||||
update();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DialogueUnitKind.variable: {
|
|
||||||
auto variableIndex = -1;
|
|
||||||
auto view = text;
|
|
||||||
auto name = trim(skipValue(view, '='));
|
|
||||||
auto value = trim(skipValue(view, '='));
|
|
||||||
if (name.length == 0) {
|
|
||||||
assert(0, "TODO: A variable without a name is an error for now.");
|
|
||||||
}
|
|
||||||
// Find if variable exists.
|
|
||||||
foreach (i, variable; values.items) {
|
|
||||||
if (variable.name.items == name) {
|
|
||||||
variableIndex = cast(int) i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Create variable if it does not exist.
|
|
||||||
if (variableIndex < 0) {
|
|
||||||
auto variable = DialogueValue();
|
|
||||||
variable.name.append(name);
|
|
||||||
values.append(variable);
|
|
||||||
variableIndex = cast(int) values.length - 1;
|
|
||||||
}
|
|
||||||
// Set variable value.
|
|
||||||
if (value.length != 0) {
|
|
||||||
auto conv = toSigned(value);
|
|
||||||
if (conv.isNone) {
|
|
||||||
auto valueVariableIndex = -1;
|
|
||||||
auto valueName = value;
|
|
||||||
// Find if variable exists.
|
|
||||||
foreach (i, variable; values.items) {
|
|
||||||
if (variable.name.items == valueName) {
|
|
||||||
valueVariableIndex = cast(int) i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (valueVariableIndex < 0) {
|
|
||||||
assert(0, "TODO: A variable that does not exist it an error for now.");
|
|
||||||
} else {
|
|
||||||
values[variableIndex].value = values[valueVariableIndex].value;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
values[variableIndex].value = conv.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
update();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DialogueUnitKind.plus, DialogueUnitKind.minus: {
|
|
||||||
auto variableIndex = -1;
|
|
||||||
auto name = text;
|
|
||||||
// Find if variable exists.
|
|
||||||
foreach (i, variable; values.items) {
|
|
||||||
if (variable.name.items == name) {
|
|
||||||
variableIndex = cast(int) i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add/Remove from variable.
|
|
||||||
if (variableIndex < 0) {
|
|
||||||
assert(0, "TODO: A variable that does not exist it an error for now.");
|
|
||||||
}
|
|
||||||
if (units[unitIndex].kind == DialogueUnitKind.plus) {
|
|
||||||
values[variableIndex].value += 1;
|
|
||||||
} else {
|
|
||||||
values[variableIndex].value -= 1;
|
|
||||||
}
|
|
||||||
update();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Fault parse(IStr script) {
|
|
||||||
clear();
|
|
||||||
if (script.length == 0) {
|
|
||||||
return Fault.invalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
units.append(DialogueUnit(LStr(), DialogueUnitKind.pause));
|
|
||||||
auto isFirstLine = true;
|
|
||||||
auto view = script;
|
|
||||||
while (view.length != 0) {
|
|
||||||
auto line = trim(skipLine(view));
|
|
||||||
if (line.length == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto text = trimStart(line[1 .. $]);
|
|
||||||
auto kind = line[0];
|
|
||||||
if (isFirstLine) {
|
|
||||||
isFirstLine = false;
|
|
||||||
if (kind == DialogueUnitKind.pause) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isValidDialogueUnitKind(kind)) {
|
|
||||||
auto realKind = cast(DialogueUnitKind) kind;
|
|
||||||
units.append(DialogueUnit(LStr(text), realKind));
|
|
||||||
} else {
|
|
||||||
clear();
|
|
||||||
return Fault.invalid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (units.items[$ - 1].kind != DialogueUnitKind.pause) {
|
|
||||||
units.append(DialogueUnit(LStr(), DialogueUnitKind.pause));
|
|
||||||
}
|
|
||||||
return Fault.none;
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear() {
|
|
||||||
foreach (ref unit; units) {
|
|
||||||
unit.free();
|
|
||||||
}
|
|
||||||
units.clear();
|
|
||||||
foreach (ref variable; values) {
|
|
||||||
variable.free();
|
|
||||||
}
|
|
||||||
values.clear();
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void free() {
|
|
||||||
foreach (ref unit; units) {
|
|
||||||
unit.free();
|
|
||||||
}
|
|
||||||
units.free();
|
|
||||||
foreach (ref variable; values) {
|
|
||||||
variable.free();
|
|
||||||
}
|
|
||||||
values.free();
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@safe @nogc nothrow:
|
|
||||||
|
|
||||||
bool isValidDialogueUnitKind(char c) {
|
|
||||||
foreach (kind; DialogueUnitKindChars) {
|
|
||||||
if (c == kind) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result!Dialogue toDialogue(IStr script) {
|
|
||||||
auto value = Dialogue();
|
|
||||||
auto fault = value.parse(script);
|
|
||||||
if (fault) {
|
|
||||||
value.free();
|
|
||||||
}
|
|
||||||
return Result!Dialogue(value, fault);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result!Dialogue loadRawDialogue(IStr path) {
|
|
||||||
auto temp = loadTempText(path);
|
|
||||||
if (temp.isNone) {
|
|
||||||
return Result!Dialogue(temp.fault);
|
|
||||||
}
|
|
||||||
return toDialogue(temp.get());
|
|
||||||
}
|
|
|
@ -1,9 +1,13 @@
|
||||||
// TODO: skipValue might need some work. Not clear how splitting works.
|
// ---
|
||||||
// TODO: toStr char arrays might need some work. It has bad error messages for them.
|
// Copyright 2024 Alexandros F. G. Kapretsos
|
||||||
// TODO: concat and others might need a "intoBuffer" vesion.
|
// SPDX-License-Identifier: MIT
|
||||||
// TODO: Look at CAT case and think about how to make it better with joka.
|
// Email: alexandroskapretsos@gmail.com
|
||||||
// NOTE: Remember to update both joka and parin at the same time because there was a evil change.
|
// Project: https://github.com/Kapendev/parin
|
||||||
// NOTE: I will start cleanning and then will add CALL.
|
// Version: v0.0.34
|
||||||
|
// ---
|
||||||
|
|
||||||
|
// TODO: Update all the doc comments here.
|
||||||
|
// TODO: Think about lineIndex and nextLineIndex updating.
|
||||||
|
|
||||||
/// The `story` module provides a simple and versatile dialogue system.
|
/// The `story` module provides a simple and versatile dialogue system.
|
||||||
module parin.story;
|
module parin.story;
|
||||||
|
@ -14,6 +18,8 @@ import joka.ascii;
|
||||||
import joka.io;
|
import joka.io;
|
||||||
import joka.unions;
|
import joka.unions;
|
||||||
|
|
||||||
|
@safe @nogc nothrow:
|
||||||
|
|
||||||
enum StoryLineKind : ubyte {
|
enum StoryLineKind : ubyte {
|
||||||
empty = ' ',
|
empty = ' ',
|
||||||
comment = '#',
|
comment = '#',
|
||||||
|
@ -52,10 +58,10 @@ enum StoryOp : ubyte {
|
||||||
LINE,
|
LINE,
|
||||||
DEBUG,
|
DEBUG,
|
||||||
LINEAR,
|
LINEAR,
|
||||||
|
ASSERT,
|
||||||
END,
|
END,
|
||||||
ECHO,
|
ECHO,
|
||||||
ECHON,
|
ECHON,
|
||||||
ECHOC,
|
|
||||||
LEAK,
|
LEAK,
|
||||||
LEAKN,
|
LEAKN,
|
||||||
HERE,
|
HERE,
|
||||||
|
@ -86,6 +92,8 @@ struct StoryValue {
|
||||||
|
|
||||||
alias data this;
|
alias data this;
|
||||||
|
|
||||||
|
@safe @nogc nothrow:
|
||||||
|
|
||||||
static foreach (Type; StoryValueData.Types) {
|
static foreach (Type; StoryValueData.Types) {
|
||||||
this(Type value) {
|
this(Type value) {
|
||||||
data = value;
|
data = value;
|
||||||
|
@ -97,7 +105,7 @@ struct StoryValue {
|
||||||
|
|
||||||
auto result = buffer[];
|
auto result = buffer[];
|
||||||
if (data.isType!StoryNumber) {
|
if (data.isType!StoryNumber) {
|
||||||
result.copy(data.get!StoryNumber().toStr());
|
result.copyStr(data.get!StoryNumber().toStr());
|
||||||
} else {
|
} else {
|
||||||
auto temp = data.get!(StoryWord)()[];
|
auto temp = data.get!(StoryWord)()[];
|
||||||
foreach (i, c; temp) {
|
foreach (i, c; temp) {
|
||||||
|
@ -106,7 +114,7 @@ struct StoryValue {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.copy(temp);
|
result.copyStr(temp);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -136,25 +144,23 @@ struct Story {
|
||||||
bool debugMode;
|
bool debugMode;
|
||||||
bool linearMode;
|
bool linearMode;
|
||||||
|
|
||||||
IStr opIndex(Sz i) {
|
@safe @nogc nothrow:
|
||||||
if (i >= lineCount) {
|
|
||||||
assert(0, "Index `{}` does not exist.".format(i));
|
|
||||||
}
|
|
||||||
auto pair = pairs[i];
|
|
||||||
return script[pair.a .. pair.b];
|
|
||||||
}
|
|
||||||
|
|
||||||
Fault throwOpFault(StoryOp op) {
|
IStr opIndex(Sz i) {
|
||||||
faultOp = op;
|
if (i >= lineCount) assert(0, "Index `{}` does not exist.".format(i));
|
||||||
return Fault.invalid;
|
return script[pairs[i].a .. pairs[i].b];
|
||||||
}
|
}
|
||||||
|
|
||||||
StoryNumber lineCount() {
|
StoryNumber lineCount() {
|
||||||
return cast(StoryNumber) pairs.length;
|
return cast(StoryNumber) pairs.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hasEnd() {
|
||||||
|
return lineIndex == lineCount;
|
||||||
|
}
|
||||||
|
|
||||||
bool hasPause() {
|
bool hasPause() {
|
||||||
if (lineIndex == lineCount) return true;
|
if (hasEnd) return true;
|
||||||
if (lineIndex >= lineCount) return false;
|
if (lineIndex >= lineCount) return false;
|
||||||
auto line = opIndex(lineIndex);
|
auto line = opIndex(lineIndex);
|
||||||
return line.length && line[0] == StoryLineKind.pause;
|
return line.length && line[0] == StoryLineKind.pause;
|
||||||
|
@ -172,16 +178,12 @@ struct Story {
|
||||||
return line.length && line[0] == StoryLineKind.text;
|
return line.length && line[0] == StoryLineKind.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
IStr text() {
|
|
||||||
if (hasText) return opIndex(lineIndex)[1 .. $].trimStart();
|
|
||||||
else return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
IStr[] menu() {
|
IStr[] menu() {
|
||||||
static FixedList!(IStr, 16) buffer;
|
static FixedList!(IStr, 32) buffer;
|
||||||
|
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
auto view = hasMenu ? opIndex(lineIndex)[1 .. $].trimStart() : "";
|
if (!hasMenu) return [];
|
||||||
|
auto view = opIndex(lineIndex)[1 .. $].trimStart();
|
||||||
while (view.length) {
|
while (view.length) {
|
||||||
if (buffer.length == buffer.capacity) return buffer[];
|
if (buffer.length == buffer.capacity) return buffer[];
|
||||||
buffer.append(view.skipValue(StoryLineKind.menu).trim());
|
buffer.append(view.skipValue(StoryLineKind.menu).trim());
|
||||||
|
@ -189,49 +191,85 @@ struct Story {
|
||||||
return buffer[];
|
return buffer[];
|
||||||
}
|
}
|
||||||
|
|
||||||
Fault prepare() {
|
IStr text() {
|
||||||
lineIndex = 0;
|
if (!hasText) return "";
|
||||||
|
return opIndex(lineIndex)[1 .. $].trimStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
Fault throwOpFault(StoryOp op) {
|
||||||
|
faultOp = op;
|
||||||
|
return Fault.invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
StoryNumber findVariable(StoryWord name) {
|
||||||
|
foreach (i, variable; variables) {
|
||||||
|
if (name == variable.name) return cast(StoryNumber) i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
StoryNumber findLabel(StoryWord name) {
|
||||||
|
foreach (i, label; labels) {
|
||||||
|
if (name == label.name) return cast(StoryNumber) i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetLineIndex() {
|
||||||
|
lineIndex = lineCount;
|
||||||
|
nextLabelIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
previousMenuResult = 0;
|
previousMenuResult = 0;
|
||||||
pairs.clear();
|
pairs.clear();
|
||||||
labels.clear();
|
labels.clear();
|
||||||
|
variables.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Fault prepare() {
|
||||||
|
resetLineIndex();
|
||||||
|
clear();
|
||||||
if (script.isEmpty) return Fault.none;
|
if (script.isEmpty) return Fault.none;
|
||||||
auto start = 0;
|
auto startIndex = StoryNumber.init;
|
||||||
auto prepareIndex = 0;
|
auto prepareIndex = StoryNumber.init;
|
||||||
foreach (i, c; script) {
|
foreach (i, c; script) {
|
||||||
if (c == '\n') {
|
if (c == '\n') {
|
||||||
auto pair = StoryStartEndPair(start, cast(uint) i);
|
auto pair = StoryStartEndPair(cast(uint) startIndex, cast(uint) i);
|
||||||
auto line = script[pair.a .. pair.b];
|
auto line = script[pair.a .. pair.b];
|
||||||
auto trimmedLine = line.trim();
|
auto trimmedLine = line.trim();
|
||||||
pair.a += line.length - line.trimStart().length;
|
pair.a += line.length - line.trimStart().length;
|
||||||
pair.b -= line.length - line.trimEnd().length;
|
pair.b -= line.length - line.trimEnd().length;
|
||||||
auto lineResult = toStoryLineKind(trimmedLine.length ? script[pair.a] : StoryLineKind.empty);
|
auto kind = toStoryLineKind(trimmedLine.length ? script[pair.a] : StoryLineKind.empty);
|
||||||
if (lineResult.isNone) {
|
if (kind.isNone) {
|
||||||
pairs.clear();
|
clear();
|
||||||
faultPrepareIndex = prepareIndex;
|
faultPrepareIndex = prepareIndex;
|
||||||
return Fault.cantParse;
|
return kind.fault;
|
||||||
}
|
}
|
||||||
auto kind = lineResult.value;
|
if (kind.value == StoryLineKind.label) {
|
||||||
if (kind == StoryLineKind.label) {
|
|
||||||
// TODO: Make words easier to use doooood.
|
|
||||||
auto name = trimmedLine[1 .. $].trimStart();
|
auto name = trimmedLine[1 .. $].trimStart();
|
||||||
auto temp = StoryWord.init;
|
auto word = StoryWord.init;
|
||||||
auto tempRef = temp[];
|
auto wordRef = word[];
|
||||||
tempRef.copyChars(name); // TODO: Should maybe return a fault if it can't.
|
if (auto fault = wordRef.copyChars(name)) {
|
||||||
labels.append(StoryVariable(temp, StoryValue(cast(StoryNumber) pairs.length)));
|
clear();
|
||||||
|
faultPrepareIndex = prepareIndex;
|
||||||
|
return fault;
|
||||||
|
}
|
||||||
|
labels.append(StoryVariable(word, StoryValue(cast(StoryNumber) pairs.length)));
|
||||||
}
|
}
|
||||||
pairs.append(pair);
|
pairs.append(pair);
|
||||||
start = cast(int) (i + 1);
|
|
||||||
prepareIndex += 1;
|
prepareIndex += 1;
|
||||||
|
startIndex = cast(StoryNumber) (i + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lineIndex = lineCount;
|
resetLineIndex();
|
||||||
return Fault.none;
|
return Fault.none;
|
||||||
}
|
}
|
||||||
|
|
||||||
void parse(IStr text) {
|
Fault parse(IStr text) {
|
||||||
script.clear();
|
script.clear();
|
||||||
script.append(text);
|
script.append(text);
|
||||||
prepare();
|
return prepare();
|
||||||
}
|
}
|
||||||
|
|
||||||
Fault execute(IStr expression) {
|
Fault execute(IStr expression) {
|
||||||
|
@ -240,6 +278,7 @@ struct Story {
|
||||||
while (true) with (StoryOp) {
|
while (true) with (StoryOp) {
|
||||||
if (expression.length == 0) break;
|
if (expression.length == 0) break;
|
||||||
auto token = expression.skipValue(' ');
|
auto token = expression.skipValue(' ');
|
||||||
|
expression = expression.trimStart();
|
||||||
if (token.length == 0) continue;
|
if (token.length == 0) continue;
|
||||||
if (ifCounter > 0) {
|
if (ifCounter > 0) {
|
||||||
if (token == IF.toStr()) ifCounter += 1;
|
if (token == IF.toStr()) ifCounter += 1;
|
||||||
|
@ -247,9 +286,9 @@ struct Story {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (token.isMaybeStoryOp) {
|
if (token.isMaybeStoryOp) {
|
||||||
auto tempResult = token.toStoryOp();
|
auto tempOp = token.toStoryOp();
|
||||||
if (tempResult.isNone) return Fault.cantParse;
|
if (tempOp.isNone) return tempOp.fault;
|
||||||
auto op = tempResult.value;
|
auto op = tempOp.value;
|
||||||
final switch (op) {
|
final switch (op) {
|
||||||
case ADD:
|
case ADD:
|
||||||
case SUB:
|
case SUB:
|
||||||
|
@ -340,9 +379,8 @@ struct Story {
|
||||||
if (!da.isType!StoryWord) return throwOpFault(op);
|
if (!da.isType!StoryWord) return throwOpFault(op);
|
||||||
StoryWord word;
|
StoryWord word;
|
||||||
auto data = concat(concat(da.toStr()), db.toStr());
|
auto data = concat(concat(da.toStr()), db.toStr());
|
||||||
if (data.length > word.length) return Fault.overflow;
|
|
||||||
auto tempWordRef = word[];
|
auto tempWordRef = word[];
|
||||||
tempWordRef.copy(data);
|
if (auto fault = tempWordRef.copyChars(data)) return fault;
|
||||||
stack.append(StoryValue(word));
|
stack.append(StoryValue(word));
|
||||||
break;
|
break;
|
||||||
case SAME:
|
case SAME:
|
||||||
|
@ -372,14 +410,20 @@ struct Story {
|
||||||
case LINEAR:
|
case LINEAR:
|
||||||
stack.append(StoryValue(linearMode));
|
stack.append(StoryValue(linearMode));
|
||||||
break;
|
break;
|
||||||
|
case ASSERT:
|
||||||
|
if (stack.length) {
|
||||||
|
auto da = stack.pop();
|
||||||
|
if (da.isType!StoryWord || (da.isType!StoryNumber && !da.get!StoryNumber())) return Fault.some;
|
||||||
|
} else {
|
||||||
|
return Fault.some;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case END:
|
case END:
|
||||||
return Fault.none;
|
return Fault.none;
|
||||||
case ECHO:
|
case ECHO:
|
||||||
case ECHON:
|
case ECHON:
|
||||||
case ECHOC:
|
|
||||||
auto space = "\n";
|
auto space = "\n";
|
||||||
if (op == ECHON) space = " ";
|
if (op == ECHON) space = " ";
|
||||||
if (op == ECHOC) space = ",";
|
|
||||||
if (stack.length) print(stack.pop(), space);
|
if (stack.length) print(stack.pop(), space);
|
||||||
else print(space);
|
else print(space);
|
||||||
break;
|
break;
|
||||||
|
@ -415,29 +459,19 @@ struct Story {
|
||||||
auto da = stack.pop();
|
auto da = stack.pop();
|
||||||
if (!da.isType!StoryWord) return throwOpFault(op);
|
if (!da.isType!StoryWord) return throwOpFault(op);
|
||||||
auto a = da.get!StoryWord();
|
auto a = da.get!StoryWord();
|
||||||
auto isNotThere = true;
|
stack.append(StoryValue(findVariable(a) != -1));
|
||||||
foreach (variable; variables) {
|
|
||||||
if (a == variable.name) {
|
|
||||||
isNotThere = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stack.append(StoryValue(!isNotThere));
|
|
||||||
break;
|
break;
|
||||||
case GET:
|
case GET:
|
||||||
if (stack.length < 1) return throwOpFault(op);
|
if (stack.length < 1) return throwOpFault(op);
|
||||||
auto da = stack.pop();
|
auto da = stack.pop();
|
||||||
if (!da.isType!StoryWord) return throwOpFault(op);
|
if (!da.isType!StoryWord) return throwOpFault(op);
|
||||||
auto a = da.get!StoryWord();
|
auto a = da.get!StoryWord();
|
||||||
auto isNotThere = true;
|
auto aIndex = findVariable(a);
|
||||||
foreach (variable; variables) {
|
if (aIndex != -1) {
|
||||||
if (a == variable.name) {
|
stack.append(variables[aIndex].value);
|
||||||
stack.append(variable.value);
|
} else {
|
||||||
isNotThere = false;
|
return throwOpFault(op);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (isNotThere) return throwOpFault(op);
|
|
||||||
break;
|
break;
|
||||||
case GETN:
|
case GETN:
|
||||||
if (stack.length < 2) return throwOpFault(op);
|
if (stack.length < 2) return throwOpFault(op);
|
||||||
|
@ -446,24 +480,14 @@ struct Story {
|
||||||
if (!da.isType!StoryWord || !db.isType!StoryWord) return throwOpFault(op);
|
if (!da.isType!StoryWord || !db.isType!StoryWord) return throwOpFault(op);
|
||||||
auto a = da.get!StoryWord();
|
auto a = da.get!StoryWord();
|
||||||
auto b = db.get!StoryWord();
|
auto b = db.get!StoryWord();
|
||||||
auto isNotThere = true;
|
auto aIndex = findVariable(a);
|
||||||
foreach (variable; variables) {
|
auto bIndex = findVariable(b);
|
||||||
if (a == variable.name) {
|
if (aIndex != -1 && bIndex != -1) {
|
||||||
stack.append(variable.value);
|
stack.append(variables[aIndex].value);
|
||||||
isNotThere = false;
|
stack.append(variables[bIndex].value);
|
||||||
break;
|
} else {
|
||||||
|
return throwOpFault(op);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (isNotThere) return throwOpFault(op);
|
|
||||||
isNotThere = true;
|
|
||||||
foreach (variable; variables) {
|
|
||||||
if (b == variable.name) {
|
|
||||||
stack.append(variable.value);
|
|
||||||
isNotThere = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isNotThere) return throwOpFault(op);
|
|
||||||
break;
|
break;
|
||||||
case SET:
|
case SET:
|
||||||
if (stack.length < 2) return throwOpFault(op);
|
if (stack.length < 2) return throwOpFault(op);
|
||||||
|
@ -471,42 +495,33 @@ struct Story {
|
||||||
auto da = stack.pop();
|
auto da = stack.pop();
|
||||||
if (!da.isType!StoryWord) return throwOpFault(op);
|
if (!da.isType!StoryWord) return throwOpFault(op);
|
||||||
auto a = da.get!StoryWord();
|
auto a = da.get!StoryWord();
|
||||||
auto isNotThere = true;
|
auto aIndex = findVariable(a);
|
||||||
foreach (ref variable; variables) {
|
if (aIndex != -1) {
|
||||||
if (a == variable.name) {
|
variables[aIndex].value = db;
|
||||||
variable.value = db;
|
} else {
|
||||||
isNotThere = false;
|
variables.append(StoryVariable(a, db));
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (isNotThere) variables.append(StoryVariable(a, db));
|
|
||||||
break;
|
break;
|
||||||
case INIT:
|
case INIT:
|
||||||
if (stack.length < 1) return throwOpFault(op);
|
if (stack.length < 1) return throwOpFault(op);
|
||||||
auto da = stack.pop();
|
auto da = stack.pop();
|
||||||
if (!da.isType!StoryWord) return throwOpFault(op);
|
if (!da.isType!StoryWord) return throwOpFault(op);
|
||||||
auto a = da.get!StoryWord();
|
auto a = da.get!StoryWord();
|
||||||
auto isNotThere = true;
|
auto aIndex = findVariable(a);
|
||||||
foreach (ref variable; variables) {
|
if (aIndex != -1) {
|
||||||
if (a == variable.name) {
|
variables[aIndex].value = StoryValue(0);
|
||||||
variable.value = StoryValue(0);
|
} else {
|
||||||
isNotThere = false;
|
variables.append(StoryVariable(a, StoryValue(0)));
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (isNotThere) variables.append(StoryVariable(a, StoryValue(0)));
|
|
||||||
break;
|
break;
|
||||||
case DROP:
|
case DROP:
|
||||||
if (stack.length < 1) return throwOpFault(op);
|
if (stack.length < 1) return throwOpFault(op);
|
||||||
auto da = stack.pop();
|
auto da = stack.pop();
|
||||||
if (!da.isType!StoryWord) return throwOpFault(op);
|
if (!da.isType!StoryWord) return throwOpFault(op);
|
||||||
auto a = da.get!StoryWord();
|
auto a = da.get!StoryWord();
|
||||||
auto isNotThere = true;
|
auto aIndex = findVariable(a);
|
||||||
foreach (i, variable; variables) {
|
if (aIndex != -1) {
|
||||||
if (a == variable.name) {
|
variables.remove(aIndex);
|
||||||
variables.remove(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DROPN:
|
case DROPN:
|
||||||
|
@ -518,20 +533,17 @@ struct Story {
|
||||||
auto da = stack.pop();
|
auto da = stack.pop();
|
||||||
if (!da.isType!StoryWord) return throwOpFault(op);
|
if (!da.isType!StoryWord) return throwOpFault(op);
|
||||||
auto a = da.get!StoryWord();
|
auto a = da.get!StoryWord();
|
||||||
auto isNotThere = true;
|
auto aIndex = findVariable(a);
|
||||||
foreach (ref variable; variables) {
|
if (aIndex != -1) {
|
||||||
if (a == variable.name) {
|
if (variables[aIndex].value.isType!StoryNumber) {
|
||||||
if (variable.value.isType!StoryNumber) {
|
variables[aIndex].value.get!StoryNumber() += (op == INC ? 1 : -1);
|
||||||
variable.value.get!StoryNumber() += (op == INC ? 1 : -1);
|
stack.append(variables[aIndex].value);
|
||||||
stack.append(variable.value);
|
|
||||||
} else {
|
} else {
|
||||||
return throwOpFault(op);
|
return throwOpFault(op);
|
||||||
}
|
}
|
||||||
isNotThere = false;
|
} else {
|
||||||
break;
|
return throwOpFault(op);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (isNotThere) return throwOpFault(op);
|
|
||||||
break;
|
break;
|
||||||
case INCN:
|
case INCN:
|
||||||
case DECN:
|
case DECN:
|
||||||
|
@ -541,40 +553,34 @@ struct Story {
|
||||||
if (!da.isType!StoryWord || !db.isType!StoryNumber) return throwOpFault(op);
|
if (!da.isType!StoryWord || !db.isType!StoryNumber) return throwOpFault(op);
|
||||||
auto a = da.get!StoryWord();
|
auto a = da.get!StoryWord();
|
||||||
auto b = db.get!StoryNumber();
|
auto b = db.get!StoryNumber();
|
||||||
auto isNotThere = true;
|
auto aIndex = findVariable(a);
|
||||||
foreach (ref variable; variables) {
|
if (aIndex != -1) {
|
||||||
if (a == variable.name) {
|
if (variables[aIndex].value.isType!StoryNumber) {
|
||||||
if (variable.value.isType!StoryNumber) {
|
variables[aIndex].value.get!StoryNumber() += b * (op == INCN ? 1 : -1);
|
||||||
variable.value.get!StoryNumber() += b * (op == INCN ? 1 : -1);
|
stack.append(variables[aIndex].value);
|
||||||
stack.append(variable.value);
|
|
||||||
} else {
|
} else {
|
||||||
return throwOpFault(op);
|
return throwOpFault(op);
|
||||||
}
|
}
|
||||||
isNotThere = false;
|
} else {
|
||||||
break;
|
return throwOpFault(op);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (isNotThere) return throwOpFault(op);
|
|
||||||
break;
|
break;
|
||||||
case TOG:
|
case TOG:
|
||||||
if (stack.length < 1) return throwOpFault(op);
|
if (stack.length < 1) return throwOpFault(op);
|
||||||
auto da = stack.pop();
|
auto da = stack.pop();
|
||||||
if (!da.isType!StoryWord) return throwOpFault(op);
|
if (!da.isType!StoryWord) return throwOpFault(op);
|
||||||
auto a = da.get!StoryWord();
|
auto a = da.get!StoryWord();
|
||||||
auto isNotThere = true;
|
auto aIndex = findVariable(a);
|
||||||
foreach (ref variable; variables) {
|
if (aIndex != -1) {
|
||||||
if (a == variable.name) {
|
if (variables[aIndex].value.isType!StoryNumber) {
|
||||||
if (variable.value.isType!StoryNumber) {
|
variables[aIndex].value.get!StoryNumber() = !variables[aIndex].value.get!StoryNumber();
|
||||||
variable.value.get!StoryNumber() = !variable.value.get!StoryNumber();
|
stack.append(variables[aIndex].value);
|
||||||
stack.append(variable.value);
|
|
||||||
} else {
|
} else {
|
||||||
return throwOpFault(op);
|
return throwOpFault(op);
|
||||||
}
|
}
|
||||||
isNotThere = false;
|
} else {
|
||||||
break;
|
return throwOpFault(op);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (isNotThere) return throwOpFault(op);
|
|
||||||
break;
|
break;
|
||||||
case MENU:
|
case MENU:
|
||||||
stack.append(StoryValue(previousMenuResult));
|
stack.append(StoryValue(previousMenuResult));
|
||||||
|
@ -583,8 +589,7 @@ struct Story {
|
||||||
if (linearMode) break;
|
if (linearMode) break;
|
||||||
auto target = nextLabelIndex - 1;
|
auto target = nextLabelIndex - 1;
|
||||||
if (target < 0 || target >= labels.length || labels.length == 0) {
|
if (target < 0 || target >= labels.length || labels.length == 0) {
|
||||||
lineIndex = lineCount;
|
resetLineIndex();
|
||||||
nextLabelIndex = 0;
|
|
||||||
} else {
|
} else {
|
||||||
lineIndex = labels[target].value.get!StoryNumber();
|
lineIndex = labels[target].value.get!StoryNumber();
|
||||||
nextLabelIndex = cast(StoryNumber) ((target + 1) % (labels.length + 1));
|
nextLabelIndex = cast(StoryNumber) ((target + 1) % (labels.length + 1));
|
||||||
|
@ -599,45 +604,39 @@ struct Story {
|
||||||
if (linearMode) break;
|
if (linearMode) break;
|
||||||
auto target = nextLabelIndex + (a > 0 ? a - 1 : a);
|
auto target = nextLabelIndex + (a > 0 ? a - 1 : a);
|
||||||
if (target < 0 || target >= labels.length || labels.length == 0) {
|
if (target < 0 || target >= labels.length || labels.length == 0) {
|
||||||
lineIndex = lineCount;
|
resetLineIndex();
|
||||||
nextLabelIndex = 0;
|
|
||||||
} else {
|
} else {
|
||||||
lineIndex = labels[target].value.get!StoryNumber();
|
lineIndex = labels[target].value.get!StoryNumber();
|
||||||
nextLabelIndex = cast(StoryNumber) ((target + 1) % (labels.length + 1));
|
nextLabelIndex = cast(StoryNumber) ((target + 1) % (labels.length + 1));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case JUMP:
|
case JUMP:
|
||||||
// TODO: Write a find function like a normal person.
|
|
||||||
// TODO: Might need some error check for -1.
|
|
||||||
if (stack.length < 1) return throwOpFault(op);
|
if (stack.length < 1) return throwOpFault(op);
|
||||||
auto da = stack.pop();
|
auto da = stack.pop();
|
||||||
if (!da.isType!StoryWord) return throwOpFault(op);
|
if (!da.isType!StoryWord) return throwOpFault(op);
|
||||||
auto a = da.get!StoryWord();
|
auto a = da.get!StoryWord();
|
||||||
auto isNotThere = true;
|
auto aIndex = findLabel(a);
|
||||||
foreach (i, ref label; labels) {
|
if (aIndex != -1) {
|
||||||
if (a == label.name) {
|
|
||||||
isNotThere = false;
|
|
||||||
if (linearMode) break;
|
if (linearMode) break;
|
||||||
lineIndex = label.value.get!StoryNumber();
|
lineIndex = labels[aIndex].value.get!StoryNumber();
|
||||||
nextLabelIndex = cast(StoryNumber) ((i + 1) % (labels.length + 1));
|
nextLabelIndex = cast(StoryNumber) ((aIndex + 1) % (labels.length + 1));
|
||||||
break;
|
} else {
|
||||||
|
return throwOpFault(op);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (isNotThere) return throwOpFault(op);
|
|
||||||
break;
|
break;
|
||||||
case CALL:
|
case CALL:
|
||||||
println("TODO: ", op);
|
println("TODO: ", op);
|
||||||
return Fault.none;
|
return Fault.none;
|
||||||
}
|
}
|
||||||
} else if (token.isMaybeStoryNumber) {
|
} else if (token.isMaybeStoryNumber) {
|
||||||
auto tempResult = token.toSigned();
|
auto number = token.toSigned();
|
||||||
if (tempResult.isNone) return tempResult.fault;
|
if (number.isNone) return number.fault;
|
||||||
stack.append(StoryValue(cast(StoryNumber) tempResult.value));
|
stack.append(StoryValue(cast(StoryNumber) number.value));
|
||||||
} else if (token.isMaybeStoryWord) {
|
} else if (token.isMaybeStoryWord) {
|
||||||
if (token.length > StoryWord.length) return Fault.overflow;
|
auto word = StoryWord.init;
|
||||||
StoryWord temp;
|
auto wordRef = word[];
|
||||||
foreach (i, c; token) temp[i] = c;
|
if (auto fault = wordRef.copyChars(token)) return fault;
|
||||||
stack.append(StoryValue(temp));
|
stack.append(StoryValue(word));
|
||||||
} else {
|
} else {
|
||||||
return Fault.cantParse;
|
return Fault.cantParse;
|
||||||
}
|
}
|
||||||
|
@ -660,6 +659,7 @@ struct Story {
|
||||||
}
|
}
|
||||||
lineIndex = (lineIndex + 1) % (lineCount + 1);
|
lineIndex = (lineIndex + 1) % (lineCount + 1);
|
||||||
}
|
}
|
||||||
|
if (hasPause && lineIndex == lineCount) resetLineIndex();
|
||||||
return Fault.none;
|
return Fault.none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -706,7 +706,7 @@ Result!StoryLineKind toStoryLineKind(char value) {
|
||||||
case '.': return Result!StoryLineKind(pause);
|
case '.': return Result!StoryLineKind(pause);
|
||||||
case '^': return Result!StoryLineKind(menu);
|
case '^': return Result!StoryLineKind(menu);
|
||||||
case '$': return Result!StoryLineKind(expression);
|
case '$': return Result!StoryLineKind(expression);
|
||||||
default: return Result!StoryLineKind(Fault.invalid);
|
default: return Result!StoryLineKind(Fault.cantParse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
# Web
|
# Web
|
||||||
|
|
||||||
A helper script to assist with the web export process.
|
A helper script to assist with the web export process.
|
||||||
Download the latest version of Emscripten before using this script.
|
It supports both DUB and non-DUB projects and requires Emscripten.
|
||||||
It works both with and without DUB.
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue