Implement finish and goto
-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 });
}
}
+import { ScriptInstruction, InstructionStatus, InstructionPromise } from './';
+
+export class FinishInstruction implements ScriptInstruction {
+ execute(): InstructionPromise {
+ return Promise.resolve({ status: InstructionStatus.Finish });
+ }
+}
+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 });
+ }
+}
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;
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);
+ }
+ }
}
}
console.log(this.instructions);
}
}
-
-export class ScriptEngine {
-
-}
+/* 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;
+ }
+}
-import { ScriptInstruction, executeInstructions } from './';
+import { ScriptInstruction, InstructionPromise, InstructionStatus, executeInstructions } from './';
import gs from '../gamestate';
enum MenuOptionType {
});
}
- execute(): Promise<void> {
+ execute(): InstructionPromise {
return new Promise( (resolve, reject) => {
gs.actions.clear();
for (let o of this.options) {
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;
}
});
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';
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]) %}
-import { ScriptInstruction } from './';
+import { ScriptInstruction, InstructionPromise, InstructionStatus } from './';
import gs from '../gamestate';
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 });
});
});
}
-import { ScriptInstruction } from './';
+import { ScriptInstruction, InstructionPromise, InstructionStatus } from './';
import gs from '../gamestate';
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 });
}
}
-import { ScriptInstruction } from './';
+import { ScriptInstruction, InstructionPromise, InstructionStatus } from './';
export class SleepInstruction implements ScriptInstruction {
duration: number
this.duration = duration;
}
- execute(): Promise<void> {
+ execute(): InstructionPromise {
return new Promise( (resolve, reject) => {
- setTimeout(resolve, this.duration);
+ setTimeout(resolve, this.duration, { status: InstructionStatus.OK });
});
}
}