import { Sym } from "./ast.ts";

export type SymMap = { [ident: string]: Sym };

export interface Syms {
    define(ident: string, sym: Sym): void;
    definedLocally(ident: string): boolean;
    get(ident: string): { ok: true; sym: Sym } | { ok: false };
}

export class GlobalSyms implements Syms {
    private syms: SymMap = {};

    public constructor() {}

    public define(ident: string, sym: Sym) {
        if (sym.type === "let") {
            this.define(ident, {
                ...sym,
                type: "let_static",
            });
            return;
        }
        this.syms[ident] = sym;
    }

    public definedLocally(ident: string): boolean {
        return ident in this.syms;
    }

    public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
        if (ident in this.syms) {
            return { ok: true, sym: this.syms[ident] };
        }
        return { ok: false };
    }
}

export class StaticSyms implements Syms {
    private syms: SymMap = {};

    public constructor(private parent: GlobalSyms) {}

    public define(ident: string, sym: Sym) {
        if (sym.type === "let") {
            this.define(ident, {
                ...sym,
                type: "let_static",
            });
            return;
        }
        this.syms[ident] = sym;
    }

    public definedLocally(ident: string): boolean {
        return ident in this.syms;
    }

    public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
        if (ident in this.syms) {
            return { ok: true, sym: this.syms[ident] };
        }
        return this.parent.get(ident);
    }
}

export class FnSyms implements Syms {
    private syms: SymMap = {};

    public constructor(private parent: Syms) {}

    public define(ident: string, sym: Sym) {
        if (sym.type === "let") {
            this.define(ident, {
                ...sym,
                type: "closure",
                inner: sym,
            });
            return;
        }
        this.syms[ident] = sym;
    }

    public definedLocally(ident: string): boolean {
        return ident in this.syms;
    }

    public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
        if (ident in this.syms) {
            return { ok: true, sym: this.syms[ident] };
        }
        return this.parent.get(ident);
    }
}

export class LeafSyms implements Syms {
    private syms: SymMap = {};

    public constructor(private parent: Syms) {}

    public define(ident: string, sym: Sym) {
        this.syms[ident] = sym;
    }

    public definedLocally(ident: string): boolean {
        return ident in this.syms;
    }

    public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
        if (ident in this.syms) {
            return { ok: true, sym: this.syms[ident] };
        }
        return this.parent.get(ident);
    }
}
