mirror of
https://github.com/Kapendev/parin.git
synced 2025-04-27 13:39: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
|
||||
|
||||
* 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.
|
||||
* 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
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
import parin;
|
||||
import parin.story;
|
||||
|
||||
auto story = Story();
|
||||
auto script = "
|
||||
# A comment.
|
||||
* label
|
||||
| Hello world!
|
||||
| This is a text line.
|
||||
| The next line will be a menu line.
|
||||
^ Option number 1 ^ Option number 2 ^ Go to end
|
||||
| This is a text line and the next is a menu line.
|
||||
^ Option 1 ^ Option 2 ^ Option 3
|
||||
$ MENU SKIP
|
||||
|
||||
*
|
||||
|
@ -22,11 +20,9 @@ auto script = "
|
|||
$ end JUMP
|
||||
|
||||
* end
|
||||
| Expression lines are used to go to labels.
|
||||
| JUMP goes to the label with the given name.
|
||||
| SKIP skips ahead N labels.
|
||||
| MENU returns the selected option.
|
||||
| That's it. I Hope this helps.
|
||||
";
|
||||
|
||||
void ready() {
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
# Rin
|
||||
|
||||
A Parin script interpreter.
|
||||
Works both with and without DUB.
|
||||
A script interpreter for Rin.
|
||||
It helps with error checking and debugging, making it easier to work with Parin scripts.
|
||||
|
||||
## Usage
|
||||
|
||||
* With DUB: `dub run parin:rin`
|
||||
* 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"
|
||||
],
|
||||
"copyright": "Copyright © 2024, Alexandros F. G. Kapretsos",
|
||||
"description": "A Parin script interpreter.",
|
||||
"description": "A script interpreter for Rin.",
|
||||
"license": "MIT",
|
||||
"name": "rin",
|
||||
"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 parin.story;
|
||||
|
||||
IStr path;
|
||||
Story story;
|
||||
RinState rinState;
|
||||
|
||||
struct RinState {
|
||||
IStr scriptPath;
|
||||
Story story;
|
||||
}
|
||||
|
||||
void printError(Sz index, IStr text) {
|
||||
printfln("\n{}({}): {}", path, index, text);
|
||||
printfln("\n{}({}): {}", rinState.scriptPath, index, text);
|
||||
}
|
||||
|
||||
Fault prepareStory() {
|
||||
if (auto fault = story.prepare()) {
|
||||
auto index = story.faultPrepareIndex + 1;
|
||||
printError(index, "Invalid character at the beginning of the line.");
|
||||
if (auto fault = rinState.story.prepare()) {
|
||||
auto index = rinState.story.faultPrepareIndex + 1;
|
||||
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.none;
|
||||
}
|
||||
|
||||
Fault updateStory() {
|
||||
if (story.hasText) println(story.text);
|
||||
if (auto fault = story.update()) {
|
||||
auto index = story.lineIndex + 1;
|
||||
if (rinState.story.hasText) println(rinState.story.text);
|
||||
if (auto fault = rinState.story.update()) {
|
||||
auto index = rinState.story.lineIndex + 1;
|
||||
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 cantParse: printError(index, "A word, number, or operator contains invalid characters."); break;
|
||||
default: break;
|
||||
|
@ -34,29 +43,33 @@ Fault updateStory() {
|
|||
return Fault.none;
|
||||
}
|
||||
|
||||
int main(string[] args) {
|
||||
int rinMain(string[] args) {
|
||||
if (args.length == 1) {
|
||||
println("Usage: rin [options] script");
|
||||
println("Options: -debug -linear");
|
||||
return 0;
|
||||
}
|
||||
foreach (arg; args[1 .. $ - 1]) {
|
||||
if (arg == "-debug") story.debugMode = true;
|
||||
if (arg == "-linear") story.linearMode = true;
|
||||
if (arg == "-debug") rinState.story.debugMode = true;
|
||||
if (arg == "-linear") rinState.story.linearMode = true;
|
||||
}
|
||||
path = args[$ - 1];
|
||||
if (auto fault = readTextIntoBuffer(path, story.script)) {
|
||||
rinState.scriptPath = args[$ - 1];
|
||||
if (auto fault = readTextIntoBuffer(rinState.scriptPath, rinState.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;
|
||||
case Fault.cantOpen: println("Can't find file `{}`.".format(rinState.scriptPath)); break;
|
||||
case Fault.cantRead: println("Can't read file `{}`.".format(rinState.scriptPath)); break;
|
||||
default: break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (prepareStory()) return 1;
|
||||
if (updateStory()) return 1;
|
||||
while (story.lineIndex != story.lineCount) {
|
||||
while (rinState.story.lineIndex != rinState.story.lineCount) {
|
||||
if (updateStory()) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(string[] args) {
|
||||
return rinMain(args);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# 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
|
||||
|
||||
|
|
|
@ -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.
|
||||
// TODO: concat and others might need a "intoBuffer" vesion.
|
||||
// TODO: Look at CAT case and think about how to make it better with joka.
|
||||
// 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.
|
||||
// ---
|
||||
// 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: Update all the doc comments here.
|
||||
// TODO: Think about lineIndex and nextLineIndex updating.
|
||||
|
||||
/// The `story` module provides a simple and versatile dialogue system.
|
||||
module parin.story;
|
||||
|
@ -14,6 +18,8 @@ import joka.ascii;
|
|||
import joka.io;
|
||||
import joka.unions;
|
||||
|
||||
@safe @nogc nothrow:
|
||||
|
||||
enum StoryLineKind : ubyte {
|
||||
empty = ' ',
|
||||
comment = '#',
|
||||
|
@ -52,10 +58,10 @@ enum StoryOp : ubyte {
|
|||
LINE,
|
||||
DEBUG,
|
||||
LINEAR,
|
||||
ASSERT,
|
||||
END,
|
||||
ECHO,
|
||||
ECHON,
|
||||
ECHOC,
|
||||
LEAK,
|
||||
LEAKN,
|
||||
HERE,
|
||||
|
@ -86,6 +92,8 @@ struct StoryValue {
|
|||
|
||||
alias data this;
|
||||
|
||||
@safe @nogc nothrow:
|
||||
|
||||
static foreach (Type; StoryValueData.Types) {
|
||||
this(Type value) {
|
||||
data = value;
|
||||
|
@ -97,7 +105,7 @@ struct StoryValue {
|
|||
|
||||
auto result = buffer[];
|
||||
if (data.isType!StoryNumber) {
|
||||
result.copy(data.get!StoryNumber().toStr());
|
||||
result.copyStr(data.get!StoryNumber().toStr());
|
||||
} else {
|
||||
auto temp = data.get!(StoryWord)()[];
|
||||
foreach (i, c; temp) {
|
||||
|
@ -106,7 +114,7 @@ struct StoryValue {
|
|||
break;
|
||||
}
|
||||
}
|
||||
result.copy(temp);
|
||||
result.copyStr(temp);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -136,25 +144,23 @@ struct Story {
|
|||
bool debugMode;
|
||||
bool linearMode;
|
||||
|
||||
IStr opIndex(Sz i) {
|
||||
if (i >= lineCount) {
|
||||
assert(0, "Index `{}` does not exist.".format(i));
|
||||
}
|
||||
auto pair = pairs[i];
|
||||
return script[pair.a .. pair.b];
|
||||
}
|
||||
@safe @nogc nothrow:
|
||||
|
||||
Fault throwOpFault(StoryOp op) {
|
||||
faultOp = op;
|
||||
return Fault.invalid;
|
||||
IStr opIndex(Sz i) {
|
||||
if (i >= lineCount) assert(0, "Index `{}` does not exist.".format(i));
|
||||
return script[pairs[i].a .. pairs[i].b];
|
||||
}
|
||||
|
||||
StoryNumber lineCount() {
|
||||
return cast(StoryNumber) pairs.length;
|
||||
}
|
||||
|
||||
bool hasEnd() {
|
||||
return lineIndex == lineCount;
|
||||
}
|
||||
|
||||
bool hasPause() {
|
||||
if (lineIndex == lineCount) return true;
|
||||
if (hasEnd) return true;
|
||||
if (lineIndex >= lineCount) return false;
|
||||
auto line = opIndex(lineIndex);
|
||||
return line.length && line[0] == StoryLineKind.pause;
|
||||
|
@ -172,16 +178,12 @@ struct Story {
|
|||
return line.length && line[0] == StoryLineKind.text;
|
||||
}
|
||||
|
||||
IStr text() {
|
||||
if (hasText) return opIndex(lineIndex)[1 .. $].trimStart();
|
||||
else return "";
|
||||
}
|
||||
|
||||
IStr[] menu() {
|
||||
static FixedList!(IStr, 16) buffer;
|
||||
static FixedList!(IStr, 32) buffer;
|
||||
|
||||
buffer.clear();
|
||||
auto view = hasMenu ? opIndex(lineIndex)[1 .. $].trimStart() : "";
|
||||
if (!hasMenu) return [];
|
||||
auto view = opIndex(lineIndex)[1 .. $].trimStart();
|
||||
while (view.length) {
|
||||
if (buffer.length == buffer.capacity) return buffer[];
|
||||
buffer.append(view.skipValue(StoryLineKind.menu).trim());
|
||||
|
@ -189,49 +191,85 @@ struct Story {
|
|||
return buffer[];
|
||||
}
|
||||
|
||||
Fault prepare() {
|
||||
lineIndex = 0;
|
||||
IStr text() {
|
||||
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;
|
||||
pairs.clear();
|
||||
labels.clear();
|
||||
variables.clear();
|
||||
}
|
||||
|
||||
Fault prepare() {
|
||||
resetLineIndex();
|
||||
clear();
|
||||
if (script.isEmpty) return Fault.none;
|
||||
auto start = 0;
|
||||
auto prepareIndex = 0;
|
||||
auto startIndex = StoryNumber.init;
|
||||
auto prepareIndex = StoryNumber.init;
|
||||
foreach (i, c; script) {
|
||||
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 trimmedLine = line.trim();
|
||||
pair.a += line.length - line.trimStart().length;
|
||||
pair.b -= line.length - line.trimEnd().length;
|
||||
auto lineResult = toStoryLineKind(trimmedLine.length ? script[pair.a] : StoryLineKind.empty);
|
||||
if (lineResult.isNone) {
|
||||
pairs.clear();
|
||||
auto kind = toStoryLineKind(trimmedLine.length ? script[pair.a] : StoryLineKind.empty);
|
||||
if (kind.isNone) {
|
||||
clear();
|
||||
faultPrepareIndex = prepareIndex;
|
||||
return Fault.cantParse;
|
||||
return kind.fault;
|
||||
}
|
||||
auto kind = lineResult.value;
|
||||
if (kind == StoryLineKind.label) {
|
||||
// TODO: Make words easier to use doooood.
|
||||
if (kind.value == StoryLineKind.label) {
|
||||
auto name = trimmedLine[1 .. $].trimStart();
|
||||
auto temp = StoryWord.init;
|
||||
auto tempRef = temp[];
|
||||
tempRef.copyChars(name); // TODO: Should maybe return a fault if it can't.
|
||||
labels.append(StoryVariable(temp, StoryValue(cast(StoryNumber) pairs.length)));
|
||||
auto word = StoryWord.init;
|
||||
auto wordRef = word[];
|
||||
if (auto fault = wordRef.copyChars(name)) {
|
||||
clear();
|
||||
faultPrepareIndex = prepareIndex;
|
||||
return fault;
|
||||
}
|
||||
labels.append(StoryVariable(word, StoryValue(cast(StoryNumber) pairs.length)));
|
||||
}
|
||||
pairs.append(pair);
|
||||
start = cast(int) (i + 1);
|
||||
prepareIndex += 1;
|
||||
startIndex = cast(StoryNumber) (i + 1);
|
||||
}
|
||||
}
|
||||
lineIndex = lineCount;
|
||||
resetLineIndex();
|
||||
return Fault.none;
|
||||
}
|
||||
|
||||
void parse(IStr text) {
|
||||
Fault parse(IStr text) {
|
||||
script.clear();
|
||||
script.append(text);
|
||||
prepare();
|
||||
return prepare();
|
||||
}
|
||||
|
||||
Fault execute(IStr expression) {
|
||||
|
@ -240,6 +278,7 @@ struct Story {
|
|||
while (true) with (StoryOp) {
|
||||
if (expression.length == 0) break;
|
||||
auto token = expression.skipValue(' ');
|
||||
expression = expression.trimStart();
|
||||
if (token.length == 0) continue;
|
||||
if (ifCounter > 0) {
|
||||
if (token == IF.toStr()) ifCounter += 1;
|
||||
|
@ -247,9 +286,9 @@ struct Story {
|
|||
continue;
|
||||
}
|
||||
if (token.isMaybeStoryOp) {
|
||||
auto tempResult = token.toStoryOp();
|
||||
if (tempResult.isNone) return Fault.cantParse;
|
||||
auto op = tempResult.value;
|
||||
auto tempOp = token.toStoryOp();
|
||||
if (tempOp.isNone) return tempOp.fault;
|
||||
auto op = tempOp.value;
|
||||
final switch (op) {
|
||||
case ADD:
|
||||
case SUB:
|
||||
|
@ -340,9 +379,8 @@ struct Story {
|
|||
if (!da.isType!StoryWord) return throwOpFault(op);
|
||||
StoryWord word;
|
||||
auto data = concat(concat(da.toStr()), db.toStr());
|
||||
if (data.length > word.length) return Fault.overflow;
|
||||
auto tempWordRef = word[];
|
||||
tempWordRef.copy(data);
|
||||
if (auto fault = tempWordRef.copyChars(data)) return fault;
|
||||
stack.append(StoryValue(word));
|
||||
break;
|
||||
case SAME:
|
||||
|
@ -372,14 +410,20 @@ struct Story {
|
|||
case LINEAR:
|
||||
stack.append(StoryValue(linearMode));
|
||||
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:
|
||||
return Fault.none;
|
||||
case ECHO:
|
||||
case ECHON:
|
||||
case ECHOC:
|
||||
auto space = "\n";
|
||||
if (op == ECHON) space = " ";
|
||||
if (op == ECHOC) space = ",";
|
||||
if (stack.length) print(stack.pop(), space);
|
||||
else print(space);
|
||||
break;
|
||||
|
@ -415,29 +459,19 @@ struct Story {
|
|||
auto da = stack.pop();
|
||||
if (!da.isType!StoryWord) return throwOpFault(op);
|
||||
auto a = da.get!StoryWord();
|
||||
auto isNotThere = true;
|
||||
foreach (variable; variables) {
|
||||
if (a == variable.name) {
|
||||
isNotThere = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
stack.append(StoryValue(!isNotThere));
|
||||
stack.append(StoryValue(findVariable(a) != -1));
|
||||
break;
|
||||
case GET:
|
||||
if (stack.length < 1) return throwOpFault(op);
|
||||
auto da = stack.pop();
|
||||
if (!da.isType!StoryWord) return throwOpFault(op);
|
||||
auto a = da.get!StoryWord();
|
||||
auto isNotThere = true;
|
||||
foreach (variable; variables) {
|
||||
if (a == variable.name) {
|
||||
stack.append(variable.value);
|
||||
isNotThere = false;
|
||||
break;
|
||||
}
|
||||
auto aIndex = findVariable(a);
|
||||
if (aIndex != -1) {
|
||||
stack.append(variables[aIndex].value);
|
||||
} else {
|
||||
return throwOpFault(op);
|
||||
}
|
||||
if (isNotThere) return throwOpFault(op);
|
||||
break;
|
||||
case GETN:
|
||||
if (stack.length < 2) return throwOpFault(op);
|
||||
|
@ -446,24 +480,14 @@ struct Story {
|
|||
if (!da.isType!StoryWord || !db.isType!StoryWord) return throwOpFault(op);
|
||||
auto a = da.get!StoryWord();
|
||||
auto b = db.get!StoryWord();
|
||||
auto isNotThere = true;
|
||||
foreach (variable; variables) {
|
||||
if (a == variable.name) {
|
||||
stack.append(variable.value);
|
||||
isNotThere = false;
|
||||
break;
|
||||
}
|
||||
auto aIndex = findVariable(a);
|
||||
auto bIndex = findVariable(b);
|
||||
if (aIndex != -1 && bIndex != -1) {
|
||||
stack.append(variables[aIndex].value);
|
||||
stack.append(variables[bIndex].value);
|
||||
} 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;
|
||||
case SET:
|
||||
if (stack.length < 2) return throwOpFault(op);
|
||||
|
@ -471,42 +495,33 @@ struct Story {
|
|||
auto da = stack.pop();
|
||||
if (!da.isType!StoryWord) return throwOpFault(op);
|
||||
auto a = da.get!StoryWord();
|
||||
auto isNotThere = true;
|
||||
foreach (ref variable; variables) {
|
||||
if (a == variable.name) {
|
||||
variable.value = db;
|
||||
isNotThere = false;
|
||||
break;
|
||||
}
|
||||
auto aIndex = findVariable(a);
|
||||
if (aIndex != -1) {
|
||||
variables[aIndex].value = db;
|
||||
} else {
|
||||
variables.append(StoryVariable(a, db));
|
||||
}
|
||||
if (isNotThere) variables.append(StoryVariable(a, db));
|
||||
break;
|
||||
case INIT:
|
||||
if (stack.length < 1) return throwOpFault(op);
|
||||
auto da = stack.pop();
|
||||
if (!da.isType!StoryWord) return throwOpFault(op);
|
||||
auto a = da.get!StoryWord();
|
||||
auto isNotThere = true;
|
||||
foreach (ref variable; variables) {
|
||||
if (a == variable.name) {
|
||||
variable.value = StoryValue(0);
|
||||
isNotThere = false;
|
||||
break;
|
||||
}
|
||||
auto aIndex = findVariable(a);
|
||||
if (aIndex != -1) {
|
||||
variables[aIndex].value = StoryValue(0);
|
||||
} else {
|
||||
variables.append(StoryVariable(a, StoryValue(0)));
|
||||
}
|
||||
if (isNotThere) variables.append(StoryVariable(a, StoryValue(0)));
|
||||
break;
|
||||
case DROP:
|
||||
if (stack.length < 1) return throwOpFault(op);
|
||||
auto da = stack.pop();
|
||||
if (!da.isType!StoryWord) return throwOpFault(op);
|
||||
auto a = da.get!StoryWord();
|
||||
auto isNotThere = true;
|
||||
foreach (i, variable; variables) {
|
||||
if (a == variable.name) {
|
||||
variables.remove(i);
|
||||
break;
|
||||
}
|
||||
auto aIndex = findVariable(a);
|
||||
if (aIndex != -1) {
|
||||
variables.remove(aIndex);
|
||||
}
|
||||
break;
|
||||
case DROPN:
|
||||
|
@ -518,20 +533,17 @@ struct Story {
|
|||
auto da = stack.pop();
|
||||
if (!da.isType!StoryWord) return throwOpFault(op);
|
||||
auto a = da.get!StoryWord();
|
||||
auto isNotThere = true;
|
||||
foreach (ref variable; variables) {
|
||||
if (a == variable.name) {
|
||||
if (variable.value.isType!StoryNumber) {
|
||||
variable.value.get!StoryNumber() += (op == INC ? 1 : -1);
|
||||
stack.append(variable.value);
|
||||
} else {
|
||||
return throwOpFault(op);
|
||||
}
|
||||
isNotThere = false;
|
||||
break;
|
||||
auto aIndex = findVariable(a);
|
||||
if (aIndex != -1) {
|
||||
if (variables[aIndex].value.isType!StoryNumber) {
|
||||
variables[aIndex].value.get!StoryNumber() += (op == INC ? 1 : -1);
|
||||
stack.append(variables[aIndex].value);
|
||||
} else {
|
||||
return throwOpFault(op);
|
||||
}
|
||||
} else {
|
||||
return throwOpFault(op);
|
||||
}
|
||||
if (isNotThere) return throwOpFault(op);
|
||||
break;
|
||||
case INCN:
|
||||
case DECN:
|
||||
|
@ -541,40 +553,34 @@ struct Story {
|
|||
if (!da.isType!StoryWord || !db.isType!StoryNumber) return throwOpFault(op);
|
||||
auto a = da.get!StoryWord();
|
||||
auto b = db.get!StoryNumber();
|
||||
auto isNotThere = true;
|
||||
foreach (ref variable; variables) {
|
||||
if (a == variable.name) {
|
||||
if (variable.value.isType!StoryNumber) {
|
||||
variable.value.get!StoryNumber() += b * (op == INCN ? 1 : -1);
|
||||
stack.append(variable.value);
|
||||
} else {
|
||||
return throwOpFault(op);
|
||||
}
|
||||
isNotThere = false;
|
||||
break;
|
||||
auto aIndex = findVariable(a);
|
||||
if (aIndex != -1) {
|
||||
if (variables[aIndex].value.isType!StoryNumber) {
|
||||
variables[aIndex].value.get!StoryNumber() += b * (op == INCN ? 1 : -1);
|
||||
stack.append(variables[aIndex].value);
|
||||
} else {
|
||||
return throwOpFault(op);
|
||||
}
|
||||
} else {
|
||||
return throwOpFault(op);
|
||||
}
|
||||
if (isNotThere) return throwOpFault(op);
|
||||
break;
|
||||
case TOG:
|
||||
if (stack.length < 1) return throwOpFault(op);
|
||||
auto da = stack.pop();
|
||||
if (!da.isType!StoryWord) return throwOpFault(op);
|
||||
auto a = da.get!StoryWord();
|
||||
auto isNotThere = true;
|
||||
foreach (ref variable; variables) {
|
||||
if (a == variable.name) {
|
||||
if (variable.value.isType!StoryNumber) {
|
||||
variable.value.get!StoryNumber() = !variable.value.get!StoryNumber();
|
||||
stack.append(variable.value);
|
||||
} else {
|
||||
return throwOpFault(op);
|
||||
}
|
||||
isNotThere = false;
|
||||
break;
|
||||
auto aIndex = findVariable(a);
|
||||
if (aIndex != -1) {
|
||||
if (variables[aIndex].value.isType!StoryNumber) {
|
||||
variables[aIndex].value.get!StoryNumber() = !variables[aIndex].value.get!StoryNumber();
|
||||
stack.append(variables[aIndex].value);
|
||||
} else {
|
||||
return throwOpFault(op);
|
||||
}
|
||||
} else {
|
||||
return throwOpFault(op);
|
||||
}
|
||||
if (isNotThere) return throwOpFault(op);
|
||||
break;
|
||||
case MENU:
|
||||
stack.append(StoryValue(previousMenuResult));
|
||||
|
@ -583,8 +589,7 @@ struct Story {
|
|||
if (linearMode) break;
|
||||
auto target = nextLabelIndex - 1;
|
||||
if (target < 0 || target >= labels.length || labels.length == 0) {
|
||||
lineIndex = lineCount;
|
||||
nextLabelIndex = 0;
|
||||
resetLineIndex();
|
||||
} else {
|
||||
lineIndex = labels[target].value.get!StoryNumber();
|
||||
nextLabelIndex = cast(StoryNumber) ((target + 1) % (labels.length + 1));
|
||||
|
@ -599,45 +604,39 @@ struct Story {
|
|||
if (linearMode) break;
|
||||
auto target = nextLabelIndex + (a > 0 ? a - 1 : a);
|
||||
if (target < 0 || target >= labels.length || labels.length == 0) {
|
||||
lineIndex = lineCount;
|
||||
nextLabelIndex = 0;
|
||||
resetLineIndex();
|
||||
} else {
|
||||
lineIndex = labels[target].value.get!StoryNumber();
|
||||
nextLabelIndex = cast(StoryNumber) ((target + 1) % (labels.length + 1));
|
||||
}
|
||||
break;
|
||||
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);
|
||||
auto da = stack.pop();
|
||||
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 (linearMode) break;
|
||||
lineIndex = label.value.get!StoryNumber();
|
||||
nextLabelIndex = cast(StoryNumber) ((i + 1) % (labels.length + 1));
|
||||
break;
|
||||
}
|
||||
auto aIndex = findLabel(a);
|
||||
if (aIndex != -1) {
|
||||
if (linearMode) break;
|
||||
lineIndex = labels[aIndex].value.get!StoryNumber();
|
||||
nextLabelIndex = cast(StoryNumber) ((aIndex + 1) % (labels.length + 1));
|
||||
} else {
|
||||
return throwOpFault(op);
|
||||
}
|
||||
if (isNotThere) return throwOpFault(op);
|
||||
break;
|
||||
case CALL:
|
||||
println("TODO: ", op);
|
||||
return Fault.none;
|
||||
}
|
||||
} else if (token.isMaybeStoryNumber) {
|
||||
auto tempResult = token.toSigned();
|
||||
if (tempResult.isNone) return tempResult.fault;
|
||||
stack.append(StoryValue(cast(StoryNumber) tempResult.value));
|
||||
auto number = token.toSigned();
|
||||
if (number.isNone) return number.fault;
|
||||
stack.append(StoryValue(cast(StoryNumber) number.value));
|
||||
} else if (token.isMaybeStoryWord) {
|
||||
if (token.length > StoryWord.length) return Fault.overflow;
|
||||
StoryWord temp;
|
||||
foreach (i, c; token) temp[i] = c;
|
||||
stack.append(StoryValue(temp));
|
||||
auto word = StoryWord.init;
|
||||
auto wordRef = word[];
|
||||
if (auto fault = wordRef.copyChars(token)) return fault;
|
||||
stack.append(StoryValue(word));
|
||||
} else {
|
||||
return Fault.cantParse;
|
||||
}
|
||||
|
@ -660,6 +659,7 @@ struct Story {
|
|||
}
|
||||
lineIndex = (lineIndex + 1) % (lineCount + 1);
|
||||
}
|
||||
if (hasPause && lineIndex == lineCount) resetLineIndex();
|
||||
return Fault.none;
|
||||
}
|
||||
|
||||
|
@ -706,7 +706,7 @@ Result!StoryLineKind toStoryLineKind(char value) {
|
|||
case '.': return Result!StoryLineKind(pause);
|
||||
case '^': return Result!StoryLineKind(menu);
|
||||
case '$': return Result!StoryLineKind(expression);
|
||||
default: return Result!StoryLineKind(Fault.invalid);
|
||||
default: return Result!StoryLineKind(Fault.cantParse);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# Web
|
||||
|
||||
A helper script to assist with the web export process.
|
||||
Download the latest version of Emscripten before using this script.
|
||||
It works both with and without DUB.
|
||||
It supports both DUB and non-DUB projects and requires Emscripten.
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue