/* parser.tsplus */

import { closecurly, closeround, eof, eq, keyword, numberconst, opencurly, openround, semicolon, type Keyword, type Numberconst, type Token } from './lexer.js';
import { identifier as tokenid } from './lexer.js';
import { identifier as astid, Program } from './ast.js';
import { REX } from './rex.js';
import { ASTfunction, Codeblock, datatype, Etype, numberconstant, Returnstm, SyntaxException, type AST, type Datatype, type Expr, type Identifier, type Numberconstant, type Stm } from './ast.js';

interface Iparser {
    constructor:Function;
    curtokens:Token[];
    savedtokens:Token[];
    fail:(msg:string)=>never;
    parse:()=>AST;
    expression:()=>AST;
    returnstm:()=>AST;
    statement:()=>AST;
    codeblock:()=>AST;
    datatype:()=>AST;
    fun:()=>AST;
    program:()=>AST;
}

class Parser implements Iparser {
    public curtokens:Token[];
    public savedtokens:Token[];

    constructor(ts:Token[]) {
        this.curtokens =
            this.savedtokens = ts;
    }

    public fail(msg:string = 'Parse error'): never {
        this.curtokens = this.savedtokens;
        throw new SyntaxException(msg);

        return void 0 as never;
    }

    public expression(): AST {
        let numconst:Numberconst;
        let node:Numberconstant;
        let t:Token;

        t = this.curtokens.shift()
            || this.fail();
        
        if (eq(t, numberconst()))
            numconst = t.contents as Numberconst;
        else
            return this.fail();

        node = numberconstant(numconst);
        this.savedtokens = this.curtokens;

        return node;
    }

    public returnstm(): AST {
        let kw:Keyword;
        let e:Expr;
        let node:Returnstm;
        let t:Token;

        t = this.curtokens.shift()
            || this.fail();
        
        if ((eq(t, keyword()) && (t.contents === 'return')))
            kw = t.contents;
        else
            return this.fail();

        e = this.expression();
        node = new Returnstm(e);
        this.savedtokens = this.curtokens;

        return node;
    }

    public statement(): AST {
        let r:Returnstm;
        let node:Stm;
        let t:Token;

        r = this.returnstm();
        t = this.curtokens.shift()
            || this.fail();
        
        if (eq(t, semicolon()))
            node = r;
        else
            return this.fail();
        this.savedtokens = this.curtokens;

        return node;
    }

    public codeblock(): AST {
        let t:Token;
        let ts:Token[];
        let ss:Stm[];
        let s:Stm;
        let node:Codeblock;
        let parses:number;
        let err:boolean;
        let fatal:boolean;
        
        parses = 0;
        err = false;
        fatal = false;
        ss = [];

        t = this.curtokens.shift()
            || this.fail();
        
        if (!eq(t, opencurly()))
            this.fail();

        ts = new Array(...this.curtokens);
        while (!err) {
            try {
                ts = new Array(...this.curtokens);
                s = this.statement();
                ss.push(s);

                parses++;
                this.savedtokens = this.curtokens;

            } catch {
                err = true;
                this.curtokens = ts;
            }

            if (fatal)
                this.fail();
        }

        if (!parses)
            return this.fail();

        t = this.curtokens.shift()
            || this.fail();

        if (!eq(t, closecurly()))
            this.fail();

        node = new Codeblock(ss);

        return node;
    }

    public datatype(): AST {
        let node:Datatype;
        let t:Token;
        let kw:Keyword;
        let et:Etype;

        t = this.curtokens.shift()
            || this.fail();
        
        if (((eq(t, keyword())) && t.contents === 'int'))
            kw = t.contents;
        else
            this.fail();

        et = Etype.Int;
        node = datatype(et);
        this.savedtokens = this.curtokens;

        return node;
    }

    public fun(): AST {
        let d:Datatype;
        let i:Identifier;
        let c:Codeblock;
        let node:ASTfunction;
        let t:Token;

        d = this.datatype();
        t = this.curtokens.shift()
            || this.fail();
        
        if (eq(t, tokenid()))
            i = astid(t.contents as string)
        else
            return this.fail();

        t = this.curtokens.shift()
            || this.fail();
        if (!eq(t, openround()))
            return this.fail();

         t = this.curtokens.shift()
            || this.fail();
        if (!eq(t, closeround()))
            return this.fail();

        this.savedtokens = this.curtokens;
        c = this.codeblock();
        this.savedtokens = this.curtokens;
        node = new ASTfunction(d, i, c);

        return node;
    }

    public program(): AST {
        let f:ASTfunction;
        let fs:ASTfunction[];
        let p:Program;
        let parsed:number;
        let err:boolean;
        let t:Token;
        let ts:Token[];

        err = false;
        parsed = 0;
        fs = [];
        ts = new Array(...this.curtokens);

        while (!err) {
            try {
                ts = new Array(...this.curtokens);
                f = this.fun();
                this.savedtokens = this.curtokens;
                fs.push(f);
                parsed++;
            } catch {
                err = true;
                this.curtokens = ts;
            }
        }

        if (!parsed)
            this.fail();

        t = this.curtokens.shift()
            || this.fail();
        
        if ((!eq(t, eof())) || (this.curtokens.length))
            this.fail();
        p = new Program(fs);

        return p;
    }

    public parse(): AST {
        let a:AST;
        a = this.program();

        return a;
    }
}

export { Parser };
