+import gs from './gamestate';
+
+interface ScriptInstruction {
+ execute(): Promise<void>
+}
+
+class SayInstruction implements ScriptInstruction {
+ s: string
+
+ constructor(s: string) {
+ this.s = s;
+ }
+
+ execute(): Promise<void> {
+ gs.textView.print(this.s);
+ return Promise.resolve();
+ }
+}
+
+interface QueryOption {
+ label: string, instruction: ScriptInstruction
+}
+
+class QueryInstruction implements ScriptInstruction {
+ options: QueryOption[]
+
+ constructor(options: QueryOption[]) {
+ this.options = options;
+ }
+
+ execute(): Promise<void> {
+ return new Promise( (resolve, reject) => {
+ gs.actions.clear();
+ for (let o of this.options) {
+ gs.actions.addAction(o.label, () => {
+ gs.actions.clear();
+ resolve(o.instruction.execute());
+ });
+ }
+ });
+ }
+}
+
+class SleepInstruction implements ScriptInstruction {
+ duration: number
+
+ constructor(duration: number) {
+ this.duration = duration;
+ }
+
+ execute(): Promise<void> {
+ return new Promise( (resolve, reject) => {
+ setTimeout(resolve, this.duration * 1000);
+ });
+ }
+}
+
+class PauseInstruction implements ScriptInstruction {
+ text: string
+
+ constructor(text?: string) {
+ this.text = text || 'Continue';
+ }
+
+ execute(): Promise<void> {
+ return new Promise( (resolve, reject) => {
+ gs.actions.clear();
+ gs.actions.addAction(this.text, () => {
+ gs.actions.clear();
+ resolve();
+ });
+ });
+ }
+}
+
+class ClearScreenInstruction implements ScriptInstruction {
+ execute(): Promise<void> {
+ gs.textView.clear();
+ return Promise.resolve();
+ }
+}
+
+export class Script {
+ instructions: ScriptInstruction[]
+ pc: number
+
+ constructor(text?: string) {
+ this.instructions = [];
+ this.pc = 0;
+ if (text)
+ this.parse(text);
+ }
+
+ parse(text: string) {
+ const lines = text.split(/[\r\n]+/);
+
+ let n = 0;
+ var parseQuery = () => {
+ const options: QueryOption[] = [];
+ let tokens;
+ do {
+ n++;
+ tokens = this.parseLine(lines[n]);
+ const arr = tokens.indexOf('=>');
+ if (arr == -1)
+ continue;
+ const label = tokens.slice(0, arr).join(' ');
+ const instruction = parseTokens(tokens.slice(arr + 1));
+ options.push({ label, instruction });
+ } while (tokens[0] != 'endquery');
+ return new QueryInstruction(options);
+ }
+
+ var parseTokens = (tokens: string[]) => {
+ switch (tokens[0]) {
+ case 'say':
+ return new SayInstruction(tokens.slice(1).join(' '));
+ case 'query':
+ return parseQuery();
+ case 'sleep':
+ return new SleepInstruction(parseFloat(tokens[1]));
+ case 'pause':
+ return new PauseInstruction(tokens.slice(1).join(' '));
+ case 'clear':
+ return new ClearScreenInstruction();
+ case undefined:
+ // empty line
+ break;
+ default:
+ this.dump();
+ throw Error('Unrecognized instruction on line ' + n + ': ' + tokens[0]);
+ }
+ }
+
+ while (n < lines.length) {
+ const tokens = this.parseLine(lines[n]);
+ const instruction = parseTokens(tokens);
+ if (instruction) {
+ this.instructions.push(instruction);
+ }
+ n++;
+ }
+ }
+
+ parseLine(line: string): string[] {
+ const tokens: string[] = [];
+ let b = 0;
+ let n = 0;
+ const scan = (test: (c: string) => boolean) => {
+ while (n < line.length && test(line[n]))
+ n++;
+ };
+
+ const pushToken = () => {
+ if (b < n)
+ tokens.push(line.slice(b, n));
+ };
+
+ // fun function fuckery I'll forget in a fortnight
+ const testFor = (r: RegExp) => (c: string) => r.test(c);
+ const testForNot = (r: RegExp) => (c: string) => !r.test(c);
+ const testQuote = testFor(/['"]/);
+ const testNotQuote = testForNot(/['"]/);
+ const testWhitespace = testFor(/\s/);
+
+ scan(testWhitespace);
+ b = n;
+
+ while (n < line.length) {
+ if (testQuote(line[n])) {
+ const q = line[n];
+ n++;
+ b = n;
+ // scan until next matching quote
+ const re = new RegExp(q);
+ scan(testForNot(re));
+ pushToken();
+ n++;
+ //scan(testWhitespace);
+ b = n;
+ } else if (testWhitespace(line[n])) {
+ pushToken();
+ scan(testWhitespace);
+ b = n;
+ } else if (n + 1 == line.length) {
+ n++;
+ pushToken();
+ } else {
+ n++;
+ }
+ }
+
+ return tokens;
+ }
+
+ step(): Promise<void> {
+ 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();
+ }
+ }
+
+ dump() {
+ console.log(this.instructions);
+ }
+}
+
+export class ScriptEngine {
+
+}