commit:07a61245db7a05144699f52d99c5c5b469b3459e
author:Chip Black
committer:Chip Black
date:Sun Oct 21 01:07:38 2018 -0500
parents:ab79b14f69d5cffc6c2c3eb466c3b502e14856ac
Implement finish and goto
diff --git a/src/script/clear.ts b/src/script/clear.ts
line changes: +3/-3
index fc5b80f..fcfd2b6
--- a/src/script/clear.ts
+++ b/src/script/clear.ts
@@ -1,9 +1,9 @@
-import { ScriptInstruction } from './';
+import { ScriptInstruction, InstructionPromise, InstructionStatus } from './';
 import gs from '../gamestate';
 
 export class ClearScreenInstruction implements ScriptInstruction {
-    execute(): Promise<void> {
+    execute(): InstructionPromise {
         gs.textView.clear();
-        return Promise.resolve();
+        return Promise.resolve({ status: InstructionStatus.OK });
     }
 }

diff --git a/src/script/finish.ts b/src/script/finish.ts
line changes: +7/-0
index 0000000..eee2cfb
--- /dev/null
+++ b/src/script/finish.ts
@@ -0,0 +1,7 @@
+import { ScriptInstruction, InstructionStatus, InstructionPromise } from './';
+
+export class FinishInstruction implements ScriptInstruction {
+    execute(): InstructionPromise {
+        return Promise.resolve({ status: InstructionStatus.Finish });
+    }
+}

diff --git a/src/script/goto.ts b/src/script/goto.ts
line changes: +13/-0
index 0000000..42881cc
--- /dev/null
+++ b/src/script/goto.ts
@@ -0,0 +1,13 @@
+import { ScriptInstruction, InstructionStatus, InstructionPromise } from './';
+
+export class GotoInstruction implements ScriptInstruction {
+    label: string
+
+    constructor(label: string) {
+        this.label = label;
+    }
+
+    execute(): InstructionPromise {
+        return Promise.resolve({ status: InstructionStatus.Goto, label: this.label });
+    }
+}

diff --git a/src/script/index.ts b/src/script/index.ts
line changes: +59/-12
index 8400989..27e311e
--- a/src/script/index.ts
+++ b/src/script/index.ts
@@ -1,38 +1,80 @@
 import gs from '../gamestate';
+import { Label } from './label';
 import * as nearley from 'nearley';
 import * as parserGrammar from './parser';
 
+export enum InstructionStatus {
+    OK,
+    Finish,
+    Goto,
+}
+
+export interface InstructionResult {
+    status: InstructionStatus
+    label?: string
+}
+
+export type InstructionPromise = Promise<InstructionResult>
+export const InstructionPromise = Promise;
+
 export interface ScriptInstruction {
-    execute(): Promise<void>
+    execute(): InstructionPromise
 }
 
 // TODO: integrate this into script execution state?
-export async function executeInstructions(l: ScriptInstruction[]) {
+export async function executeInstructions(l: ScriptInstruction[]): InstructionPromise {
     for (const i of l) {
-        await i.execute()
+        const r = await i.execute();
+        if (r.status != InstructionStatus.OK) {
+            return r;
+        }
     }
+    return { status: InstructionStatus.OK }
 }
 
 export class Script {
     instructions: ScriptInstruction[]
+    labels: Map<string, number>
     pc: number
 
     constructor(text?: string) {
+        if (text) {
+            this.parse(text);
+        } else {
+            this.reset();
+        }
+    }
+
+    reset() {
         this.instructions = [];
+        this.labels = new Map();
         this.pc = 0;
-        if (text)
-            this.parse(text);
     }
 
     parse(text: string) {
+        this.reset();
+
+        // parse script source (TODO: keep parsed script objects?)
         const parser = new nearley.Parser(nearley.Grammar.fromCompiled(parserGrammar));
         parser.feed(text);
         // Feed in an extra space because our grammar requires whitespace at the end of a statement
         parser.feed(' ');
-        this.instructions = parser.results[0];
+        const instructions = parser.results[0]
+
+        // scan for labels
+        for (let i = 0; i < instructions.length; i++) {
+            const instr = instructions[i];
+            if (instr instanceof Label) {
+                this.labels.set(instr.name, i);
+                instructions.splice(i, 1);
+                i--;
+            }
+        }
+
+        this.instructions = instructions;
     }
 
-    step(): Promise<void> {
+    step(): InstructionPromise {
         let r = this.instructions[this.pc].execute();
         this.pc++;
         return r;
@@ -41,7 +83,16 @@ export class Script {
     async execute() {
         this.pc = 0;
         while (this.pc < this.instructions.length) {
-            await this.step();
+            const r = await this.step();
+            if (r.status == InstructionStatus.Finish) {
+                break;
+            } else if (r.status == InstructionStatus.Goto) {
+                if (this.labels.has(r.label)) {
+                    this.pc = this.labels.get(r.label);
+                } else {
+                    throw new Error('Invalid label: ' + r.label);
+                }
+            }
         }
     }
 
@@ -49,7 +100,3 @@ export class Script {
         console.log(this.instructions);
     }
 }
-
-export class ScriptEngine {
-    
-}

diff --git a/src/script/label.ts b/src/script/label.ts
line changes: +9/-0
index 0000000..dfc1bdf
--- /dev/null
+++ b/src/script/label.ts
@@ -0,0 +1,9 @@
+/* A label is not an instruction.  It is a marker which the script engine
+   scans for, removes, and stores in a jump table for future goto instructions. */
+export class Label {
+    name: string
+
+    constructor(name: string) {
+        this.name = name;
+    }
+}

diff --git a/src/script/menu.ts b/src/script/menu.ts
line changes: +4/-4
index 1fb4ae5..9e6f993
--- a/src/script/menu.ts
+++ b/src/script/menu.ts
@@ -1,4 +1,4 @@
-import { ScriptInstruction, executeInstructions } from './';
+import { ScriptInstruction, InstructionPromise, InstructionStatus, executeInstructions } from './';
 import gs from '../gamestate';
 
 enum MenuOptionType {
@@ -59,7 +59,7 @@ export class MenuInstruction implements ScriptInstruction {
         });
     }
 
-    execute(): Promise<void> {
+    execute(): InstructionPromise {
         return new Promise( (resolve, reject) => {
             gs.actions.clear();
             for (let o of this.options) {
@@ -71,10 +71,10 @@ export class MenuInstruction implements ScriptInstruction {
                             break;
                         case MenuOptionType.Buy:
                             console.log('Buy', o.object, 'for', o.value);
-                            resolve();
+                            resolve({ status: InstructionStatus.OK });
                             break;
                         case MenuOptionType.Exit:
-                            resolve();
+                            resolve({ status: InstructionStatus.OK });
                             break;
                     }
                 });

diff --git a/src/script/parser.ne b/src/script/parser.ne
line changes: +6/-3
index f102fe9..29d815d
--- a/src/script/parser.ne
+++ b/src/script/parser.ne
@@ -3,6 +3,9 @@
 import * as moo from 'moo';
 import { SayInstruction } from './say';
 import { MenuInstruction } from './menu';
+import { Label } from './label';
+import { GotoInstruction } from './goto';
+import { FinishInstruction } from './finish';
 import { ClearScreenInstruction } from './clear';
 import { SleepInstruction } from './sleep';
 import { PauseInstruction } from './pause';
@@ -35,9 +38,9 @@ program -> __:? statement:* {% data => data[1] || data[0] %}
 
 statement -> "say" __ string __             {% data => new SayInstruction(data[2]) %}
 statement -> "menu" __ menuitem:* "end" __  {% data => new MenuInstruction(data[2]) %}
-statement -> %label __
-statement -> "goto" __ %word __
-statement -> "finish" __
+statement -> %label __                      {% data => new Label(data[0].value.slice(0, -1)) %}
+statement -> "goto" __ %word __             {% data => new GotoInstruction(data[2].value) %}
+statement -> "finish" __                    {% data => new FinishInstruction() %}
 statement -> "clear" __                     {% data => new ClearScreenInstruction() %}
 statement -> "sleep" __ %number __          {% data => new SleepInstruction(data[2]) %}
 statement -> "pause" __ string __           {% data => new PauseInstruction(data[2]) %}

diff --git a/src/script/pause.ts b/src/script/pause.ts
line changes: +3/-3
index a3cf1b9..4a8c554
--- a/src/script/pause.ts
+++ b/src/script/pause.ts
@@ -1,4 +1,4 @@
-import { ScriptInstruction } from './';
+import { ScriptInstruction, InstructionPromise, InstructionStatus } from './';
 import gs from '../gamestate';
 
 export class PauseInstruction implements ScriptInstruction {
@@ -8,12 +8,12 @@ export class PauseInstruction implements ScriptInstruction {
         this.text = text || 'Continue';
     }
 
-    execute(): Promise<void> {
+    execute(): InstructionPromise {
         return new Promise( (resolve, reject) => {
             gs.actions.clear();
             gs.actions.addAction(this.text, () => {
                 gs.actions.clear();
-                resolve();
+                resolve({ status: InstructionStatus.OK });
             });
         });
     }

diff --git a/src/script/say.ts b/src/script/say.ts
line changes: +3/-3
index 3b58ee0..a79e49e
--- a/src/script/say.ts
+++ b/src/script/say.ts
@@ -1,4 +1,4 @@
-import { ScriptInstruction } from './';
+import { ScriptInstruction, InstructionPromise, InstructionStatus } from './';
 import gs from '../gamestate';
 
 export class SayInstruction implements ScriptInstruction {
@@ -8,9 +8,9 @@ export class SayInstruction implements ScriptInstruction {
         this.s = s;
     }
 
-    execute(): Promise<void> {
+    execute(): InstructionPromise {
         gs.textView.print(this.s);
-        return Promise.resolve();
+        return Promise.resolve({ status: InstructionStatus.OK });
     }
 }
 

diff --git a/src/script/sleep.ts b/src/script/sleep.ts
line changes: +3/-3
index 5d02ef8..650b7aa
--- a/src/script/sleep.ts
+++ b/src/script/sleep.ts
@@ -1,4 +1,4 @@
-import { ScriptInstruction } from './';
+import { ScriptInstruction, InstructionPromise, InstructionStatus } from './';
 
 export class SleepInstruction implements ScriptInstruction {
     duration: number
@@ -7,9 +7,9 @@ export class SleepInstruction implements ScriptInstruction {
         this.duration = duration;
     }
 
-    execute(): Promise<void> {
+    execute(): InstructionPromise {
         return new Promise( (resolve, reject) => {
-            setTimeout(resolve, this.duration);
+            setTimeout(resolve, this.duration, { status: InstructionStatus.OK });
         });
     }
 }