Skip to content
Snippets Groups Projects
compiler.ts 5.00 KiB
/**
 * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
 *
 * @author David Sehnal <david.sehnal@gmail.com>
 */

import Expression from '../../language/expression';
import { QueryContext, QueryFn, Structure, ModelPropertyDescriptor } from 'mol-model/structure';
import { MSymbol } from '../../language/symbol';

export class QueryRuntimeTable {
    private map = new Map<string, QuerySymbolRuntime>();

    addSymbol(runtime: QuerySymbolRuntime) {
        this.map.set(runtime.symbol.id, runtime);
    }

    addCustomProp(desc: ModelPropertyDescriptor) {
        if (!desc.symbols) return;

        for (const k of Object.keys(desc.symbols)) {
            this.addSymbol((desc.symbols as any)[k]);
        }
    }

    getRuntime(id: string) {
        return this.map.get(id);
    }
}

export const DefaultQueryRuntimeTable = new QueryRuntimeTable();

export class QueryCompilerCtx {
    constQueryContext: QueryContext = new QueryContext(Structure.Empty);

    constructor(public table: QueryRuntimeTable) {

    }
}

export type ConstQuerySymbolFn<S extends MSymbol = MSymbol> = (ctx: QueryContext, args: QueryRuntimeArguments<S>) => any
export type QuerySymbolFn<S extends MSymbol = MSymbol> = (ctx: QueryContext, args: QueryRuntimeArguments<S>) => any


export type QueryCompiledSymbolRuntime = { kind: 'const', value: any } | { kind: 'dynamic', runtime: QuerySymbolFn }

export type CompiledQueryFn<T = any> = { isConst: boolean, fn: QueryFn }

export namespace QueryCompiledSymbol {
    export function Const(value: any): QueryCompiledSymbolRuntime  {
        return { kind: 'const', value }
    }

    export function Dynamic(runtime: QuerySymbolFn): QueryCompiledSymbolRuntime {
        return { kind: 'dynamic', runtime };
    }
}

export namespace CompiledQueryFn {
    export function Const(value: any): CompiledQueryFn  {
        return { isConst: true, fn: ctx => value };
    }

    export function Dynamic(fn: QueryFn): CompiledQueryFn {
        return { isConst: false, fn };
    }
}

export interface QuerySymbolRuntime {
    symbol: MSymbol,
    compile(ctx: QueryCompilerCtx, args?: Expression.Arguments): CompiledQueryFn
}

export type QueryRuntimeArguments<S extends MSymbol> =
    { length?: number } & { [P in keyof S['args']['@type']]: QueryFn<S['args']['@type'][P]> }

export namespace QuerySymbolRuntime {
    export function Const<S extends MSymbol<any>>(symbol: S, fn: ConstQuerySymbolFn<S>): QuerySymbolRuntime {
        return new SymbolRuntimeImpl(symbol, fn, true);
    }

    export function Dynamic<S extends MSymbol<any>>(symbol: S, fn: QuerySymbolFn<S>): QuerySymbolRuntime {
        return new SymbolRuntimeImpl(symbol, fn, false);
    }
}

class SymbolRuntimeImpl<S extends MSymbol> implements QuerySymbolRuntime {
    compile(ctx: QueryCompilerCtx, inputArgs?: Expression.Arguments): CompiledQueryFn {
        let args: any, constArgs = false;
        if (!inputArgs) {
            args = void 0;
            constArgs = true;
        } else if (Expression.isArgumentsArray(inputArgs)) {
            args = [];
            constArgs = false;
            for (const arg of inputArgs) {
                const compiled = _compile(ctx, arg);
                constArgs = constArgs && compiled.isConst;
                args.push(compiled.fn);
            }
        } else {
            args = Object.create(null);
            constArgs = false;
            for (const key of Object.keys(inputArgs)) {
                const compiled = _compile(ctx, inputArgs[key]);
                constArgs = constArgs && compiled.isConst;
                args[key] = compiled.fn;
            }
        }

        if (this.isConst) {
            if (this.isConst && constArgs) {
                return CompiledQueryFn.Const(this.fn(ctx.constQueryContext, args))
            }

            return CompiledQueryFn.Dynamic(createDynamicFn(this.fn, args));
        }

        return CompiledQueryFn.Dynamic(createDynamicFn(this.fn, args));
    }

    constructor(public symbol: S, private fn: QuerySymbolFn<S>, private isConst: boolean) {

    }
}

function createDynamicFn<S extends MSymbol>(fn: QuerySymbolFn<S>, args: any): QueryFn {
    return ctx => fn(ctx, args);
}

function _compile(ctx: QueryCompilerCtx, expression: Expression): CompiledQueryFn {
    if (Expression.isLiteral(expression)) {
        return CompiledQueryFn.Const(expression);
    }

    if (Expression.isSymbol(expression)) {
        // TODO: is this ok in case of constants?
        throw new Error('Cannot compile a symbol that is not applied.');
    }
    if (!Expression.isSymbol(expression.head)) {
        throw new Error('Can only apply symbols.');
    }

    const compiler = ctx.table.getRuntime(expression.head.name);

    if (!compiler) {
        throw new Error(`Symbol '${expression.head.name}' is not implemented.`);
    }

    return compiler.compile(ctx, expression.args);
}

export function compile<T = any>(expression: Expression): QueryFn<T> {
    const ctx = new QueryCompilerCtx(DefaultQueryRuntimeTable);
    return _compile(ctx, expression).fn;
}

import './table'