/src/script/index.ts
import gs from '../gamestate';
import { Label } from './label';
import * as nearley from 'nearley';
import 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(): InstructionPromise
}

// TODO: integrate this into script execution state?
export async function executeInstructions(l: ScriptInstruction[]): InstructionPromise {
    for (const i of l) {
        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;
    }

    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(' ');
        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(): InstructionPromise {
        let r = this.instructions[this.pc].execute();
        this.pc++;
        return r;
    }

    async execute() {
        this.pc = 0;
        while (this.pc < this.instructions.length) {
            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);
                }
            }
        }
    }

    dump() {
        console.log(this.instructions);
    }
}