compiler.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /**
  2. * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import Expression from '../../language/expression';
  7. import { QueryContext, QueryFn, Structure, ModelPropertyDescriptor } from 'mol-model/structure';
  8. import { MSymbol } from '../../language/symbol';
  9. export class QueryRuntimeTable {
  10. private map = new Map<string, QuerySymbolRuntime>();
  11. addSymbol(runtime: QuerySymbolRuntime) {
  12. if (this.map.has(runtime.symbol.id)) {
  13. throw new Error(`Symbol '${runtime.symbol.id}' already added.`);
  14. }
  15. this.map.set(runtime.symbol.id, runtime);
  16. }
  17. addCustomProp(desc: ModelPropertyDescriptor) {
  18. if (!desc.symbols) return;
  19. for (const k of Object.keys(desc.symbols)) {
  20. this.addSymbol((desc.symbols as any)[k]);
  21. }
  22. }
  23. getRuntime(id: string) {
  24. return this.map.get(id);
  25. }
  26. }
  27. export const DefaultQueryRuntimeTable = new QueryRuntimeTable();
  28. export class QueryCompilerCtx {
  29. constQueryContext: QueryContext = new QueryContext(Structure.Empty);
  30. constructor(public table: QueryRuntimeTable) {
  31. }
  32. }
  33. export type ConstQuerySymbolFn<S extends MSymbol = MSymbol> = (ctx: QueryContext, args: QueryRuntimeArguments<S>) => any
  34. export type QuerySymbolFn<S extends MSymbol = MSymbol> = (ctx: QueryContext, args: QueryRuntimeArguments<S>) => any
  35. export type QueryCompiledSymbolRuntime = { kind: 'const', value: any } | { kind: 'dynamic', runtime: QuerySymbolFn }
  36. export type CompiledQueryFn<T = any> = { isConst: boolean, fn: QueryFn }
  37. export namespace QueryCompiledSymbol {
  38. export function Const(value: any): QueryCompiledSymbolRuntime {
  39. return { kind: 'const', value }
  40. }
  41. export function Dynamic(runtime: QuerySymbolFn): QueryCompiledSymbolRuntime {
  42. return { kind: 'dynamic', runtime };
  43. }
  44. }
  45. export namespace CompiledQueryFn {
  46. export function Const(value: any): CompiledQueryFn {
  47. return { isConst: true, fn: ctx => value };
  48. }
  49. export function Dynamic(fn: QueryFn): CompiledQueryFn {
  50. return { isConst: false, fn };
  51. }
  52. }
  53. export interface QuerySymbolRuntime {
  54. symbol: MSymbol,
  55. compile(ctx: QueryCompilerCtx, args?: Expression.Arguments): CompiledQueryFn
  56. }
  57. export type QueryRuntimeArguments<S extends MSymbol> =
  58. { length?: number } & { [P in keyof S['args']['@type']]: QueryFn<S['args']['@type'][P]> }
  59. export namespace QueryRuntimeArguments {
  60. export function forEachEval<S extends MSymbol, Ctx>(xs: QueryRuntimeArguments<S>, queryCtx: QueryContext, f: (arg: any, i: number, ctx: Ctx) => void, ctx: Ctx): Ctx {
  61. if (typeof xs.length === 'number') {
  62. for (let i = 0, _i = xs.length; i < _i; i++) f((xs as any)[i](queryCtx), i, ctx);
  63. } else {
  64. let i = 0;
  65. for (const k of Object.keys(xs)) f((xs as any)[k](queryCtx), i++, ctx);
  66. }
  67. return ctx;
  68. }
  69. }
  70. export namespace QuerySymbolRuntime {
  71. export function Const<S extends MSymbol<any>>(symbol: S, fn: ConstQuerySymbolFn<S>): QuerySymbolRuntime {
  72. return new SymbolRuntimeImpl(symbol, fn, true);
  73. }
  74. export function Dynamic<S extends MSymbol<any>>(symbol: S, fn: QuerySymbolFn<S>): QuerySymbolRuntime {
  75. return new SymbolRuntimeImpl(symbol, fn, false);
  76. }
  77. }
  78. class SymbolRuntimeImpl<S extends MSymbol> implements QuerySymbolRuntime {
  79. compile(ctx: QueryCompilerCtx, inputArgs?: Expression.Arguments): CompiledQueryFn {
  80. let args: any, constArgs = false;
  81. if (!inputArgs) {
  82. args = void 0;
  83. constArgs = true;
  84. } else if (Expression.isArgumentsArray(inputArgs)) {
  85. args = [];
  86. constArgs = false;
  87. for (const arg of inputArgs) {
  88. const compiled = _compile(ctx, arg);
  89. constArgs = constArgs && compiled.isConst;
  90. args.push(compiled.fn);
  91. }
  92. } else {
  93. args = Object.create(null);
  94. constArgs = false;
  95. for (const key of Object.keys(inputArgs)) {
  96. const compiled = _compile(ctx, inputArgs[key]);
  97. constArgs = constArgs && compiled.isConst;
  98. args[key] = compiled.fn;
  99. }
  100. }
  101. if (this.isConst) {
  102. if (this.isConst && constArgs) {
  103. return CompiledQueryFn.Const(this.fn(ctx.constQueryContext, args))
  104. }
  105. return CompiledQueryFn.Dynamic(createDynamicFn(this.fn, args));
  106. }
  107. return CompiledQueryFn.Dynamic(createDynamicFn(this.fn, args));
  108. }
  109. constructor(public symbol: S, private fn: QuerySymbolFn<S>, private isConst: boolean) {
  110. }
  111. }
  112. function createDynamicFn<S extends MSymbol>(fn: QuerySymbolFn<S>, args: any): QueryFn {
  113. return ctx => fn(ctx, args);
  114. }
  115. function _compile(ctx: QueryCompilerCtx, expression: Expression): CompiledQueryFn {
  116. if (Expression.isLiteral(expression)) {
  117. return CompiledQueryFn.Const(expression);
  118. }
  119. if (Expression.isSymbol(expression)) {
  120. const runtime = ctx.table.getRuntime(expression.name);
  121. if (!runtime) return CompiledQueryFn.Const(expression.name);
  122. return runtime.compile(ctx);
  123. }
  124. if (!Expression.isSymbol(expression.head)) {
  125. throw new Error('Can only apply symbols.');
  126. }
  127. const compiler = ctx.table.getRuntime(expression.head.name);
  128. if (!compiler) {
  129. throw new Error(`Symbol '${expression.head.name}' is not implemented.`);
  130. }
  131. return compiler.compile(ctx, expression.args);
  132. }
  133. export function compile<T = any>(expression: Expression): QueryFn<T> {
  134. const ctx = new QueryCompilerCtx(DefaultQueryRuntimeTable);
  135. return _compile(ctx, expression).fn;
  136. }
  137. import './table'