From abc545a99761e32e5f204d87a3646dbd496bba34 Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Wed, 8 Aug 2018 14:34:00 +0200 Subject: [PATCH] wip mol-script, 1st compiled query --- package-lock.json | Bin 411997 -> 418525 bytes package.json | 4 +- src/apps/structure-info/model.ts | 11 +- src/mol-model/structure/query/context.ts | 4 +- src/mol-model/structure/query/query.ts | 4 +- src/mol-script/language/builder.ts | 8 +- src/mol-script/language/symbol-table.ts | 8 +- src/mol-script/language/symbol-table/core.ts | 9 + src/mol-script/language/symbol.ts | 3 +- src/mol-script/language/type.ts | 2 +- src/mol-script/runtime/environment.ts | 60 +- src/mol-script/runtime/expression.ts | 38 +- src/mol-script/runtime/macro.ts | 132 ++--- src/mol-script/runtime/query/compiler.ts | 151 +++++ src/mol-script/runtime/query/table.ts | 65 +++ src/mol-script/runtime/symbol.ts | 48 +- src/mol-script/script/mol-script/examples.ts | 206 +++---- src/mol-script/script/mol-script/macro.ts | 68 +-- src/mol-script/script/mol-script/parser.ts | 356 ++++++------ src/mol-script/script/mol-script/symbols.ts | 546 +++++++++---------- src/perf-tests/mol-script.ts | 53 +- tsconfig.json | 3 +- 22 files changed, 1021 insertions(+), 758 deletions(-) create mode 100644 src/mol-script/runtime/query/compiler.ts create mode 100644 src/mol-script/runtime/query/table.ts diff --git a/package-lock.json b/package-lock.json index 0b0df7edaf6edb49db5b2861d5b6c4b2f321a620..4f137e24d0fb3957776c524681a389410b1a1b40 100644 GIT binary patch delta 31690 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 UHjG&+K?iSY!8C}h<*MZZ0C*9XjsO4v delta 30563 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⁣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 BwjTfh diff --git a/package.json b/package.json index eb8afa253..40e7655c3 100644 --- a/package.json +++ b/package.json @@ -83,8 +83,8 @@ "file-loader": "^1.1.11", "glslify-import": "^3.1.0", "glslify-loader": "^1.0.2", - "graphql-code-generator": "^0.10.6", - "graphql-codegen-typescript-template": "^0.10.6", + "graphql-code-generator": "^0.10.7", + "graphql-codegen-typescript-template": "^0.10.7", "graphql-tag": "^2.9.2", "jest": "^23.4.2", "jest-raw-loader": "^1.0.1", diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts index fbc466845..c01698f91 100644 --- a/src/apps/structure-info/model.ts +++ b/src/apps/structure-info/model.ts @@ -22,7 +22,7 @@ async function downloadFromPdb(pdb: string) { return parsed.blocks[0]; } -async function readPdbFile(path: string) { +export async function readCifFile(path: string) { const parsed = await openCif(path); return parsed.blocks[0]; } @@ -197,9 +197,14 @@ export function printModelStats(models: ReadonlyArray<Model>) { console.log(); } -async function run(frame: CifFrame, args: Args) { +export async function getModelsAndStructure(frame: CifFrame) { const models = await Model.create(Format.mmCIF(frame)).run(); const structure = Structure.ofModel(models[0]); + return { models, structure }; +} + +async function run(frame: CifFrame, args: Args) { + const { models, structure } = await getModelsAndStructure(frame); if (args.models) printModelStats(models); if (args.seq) printSequence(models[0]); @@ -218,7 +223,7 @@ async function runDL(pdb: string, args: Args) { } async function runFile(filename: string, args: Args) { - const mmcif = await readPdbFile(filename); + const mmcif = await readCifFile(filename); run(mmcif, args); } diff --git a/src/mol-model/structure/query/context.ts b/src/mol-model/structure/query/context.ts index 7541c8ac2..188aac0e7 100644 --- a/src/mol-model/structure/query/context.ts +++ b/src/mol-model/structure/query/context.ts @@ -58,5 +58,5 @@ export class QueryContext implements QueryContextView { } } -export interface QueryPredicate { (ctx: QueryContextView): boolean } -export interface QueryFn<T = any> { (ctx: QueryContextView): T } \ No newline at end of file +export interface QueryPredicate { (ctx: QueryContext): boolean } +export interface QueryFn<T = any> { (ctx: QueryContext): T } \ No newline at end of file diff --git a/src/mol-model/structure/query/query.ts b/src/mol-model/structure/query/query.ts index 4283b6b73..f7629182e 100644 --- a/src/mol-model/structure/query/query.ts +++ b/src/mol-model/structure/query/query.ts @@ -6,9 +6,9 @@ import { Structure } from '../structure' import { StructureSelection } from './selection' -import { QueryContext } from './context'; +import { QueryContext, QueryFn } from './context'; -interface StructureQuery { (ctx: QueryContext): StructureSelection } +interface StructureQuery extends QueryFn<StructureSelection> { } namespace StructureQuery { export function run(query: StructureQuery, structure: Structure, timeoutMs = 0) { return query(new QueryContext(structure, timeoutMs)); diff --git a/src/mol-script/language/builder.ts b/src/mol-script/language/builder.ts index 213b47a7a..8c7efa07d 100644 --- a/src/mol-script/language/builder.ts +++ b/src/mol-script/language/builder.ts @@ -6,9 +6,9 @@ import Expression from './expression' import { MSymbol } from './symbol' -import SymbolTable from './symbol-table' +import { MolScriptSymbolTable as SymbolTable } from './symbol-table' -namespace Builder { +export namespace MolScriptBuilder { export const core = SymbolTable.core; export const struct = SymbolTable.structureQuery; @@ -35,6 +35,4 @@ namespace Builder { export function acpSet(p: keyof typeof _acp) { return _aps([ acp(p) ]) }; export function atpSet(p: keyof typeof _atp) { return _aps([ atp(p) ]) }; export function ammpSet(p: keyof typeof _ammp) { return _aps([ ammp(p) ]) }; -} - -export default Builder \ No newline at end of file +} \ No newline at end of file diff --git a/src/mol-script/language/symbol-table.ts b/src/mol-script/language/symbol-table.ts index 6f47d15d9..3dec8616f 100644 --- a/src/mol-script/language/symbol-table.ts +++ b/src/mol-script/language/symbol-table.ts @@ -9,11 +9,11 @@ import structureQuery from './symbol-table/structure-query' import { normalizeTable, symbolList } from './helpers' import { MSymbol } from './symbol' -const table = { core, structureQuery }; +const MolScriptSymbolTable = { core, structureQuery }; -normalizeTable(table); +normalizeTable(MolScriptSymbolTable); -export const SymbolList = symbolList(table); +export const SymbolList = symbolList(MolScriptSymbolTable); export const SymbolMap = (function() { const map: { [id: string]: MSymbol | undefined } = Object.create(null); @@ -21,4 +21,4 @@ export const SymbolMap = (function() { return map; })(); -export default table \ No newline at end of file +export { MolScriptSymbolTable } \ No newline at end of file diff --git a/src/mol-script/language/symbol-table/core.ts b/src/mol-script/language/symbol-table/core.ts index b4164f613..fa0a801d9 100644 --- a/src/mol-script/language/symbol-table/core.ts +++ b/src/mol-script/language/symbol-table/core.ts @@ -41,6 +41,15 @@ function binRel<A extends Type, T extends Type>(src: A, target: T, description?: }), target, description); } +export const TTargs = Arguments.Dictionary({ + 0: Argument(Type.Num), + 1: Argument(Type.Num) +}) + +const XX = { test: Argument(Type.Str) }; +const t: Arguments.PropTypes<typeof XX> = 0 as any; +t.test + const type = { '@header': 'Types', bool: symbol(Arguments.Dictionary({ 0: Argument(Type.AnyValue) }), Type.Bool, 'Convert a value to boolean.'), diff --git a/src/mol-script/language/symbol.ts b/src/mol-script/language/symbol.ts index c928809b9..de9b53665 100644 --- a/src/mol-script/language/symbol.ts +++ b/src/mol-script/language/symbol.ts @@ -31,7 +31,8 @@ export namespace Arguments { map: { [P in keyof T]: Argument<T[P]> }, '@type': T } - export function Dictionary<Map extends { [key: string]: Argument<any> }>(map: Map): Arguments<{ [P in keyof Map]: Map[P]['type']['@type'] }> { + export type PropTypes<Map extends { [key: string]: Argument<any> }> = { [P in keyof Map]: Map[P]['type']['@type'] } + export function Dictionary<Map extends { [key: string]: Argument<any> }>(map: Map): Arguments<PropTypes<Map>> { return { kind: 'dictionary', map, '@type': 0 as any }; } diff --git a/src/mol-script/language/type.ts b/src/mol-script/language/type.ts index 9a50889cb..90465ec4d 100644 --- a/src/mol-script/language/type.ts +++ b/src/mol-script/language/type.ts @@ -9,7 +9,7 @@ type Type<T = any> = | Type.Container<T> | Type.Union<T> | Type.OneOf<T> namespace Type { - export interface Any { kind: 'any', '@type': any } + export interface Any { kind: 'any', '@type': any } export interface Variable<T> { kind: 'variable', name: string, type: Type, isConstraint: boolean, '@type': any } export interface AnyValue { kind: 'any-value', '@type': any } export interface Value<T> { kind: 'value', namespace: string, name: string, parent?: Value<any>, '@type': T } diff --git a/src/mol-script/runtime/environment.ts b/src/mol-script/runtime/environment.ts index 54882d11a..7592071c8 100644 --- a/src/mol-script/runtime/environment.ts +++ b/src/mol-script/runtime/environment.ts @@ -1,37 +1,37 @@ -/** - * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ +// /** +// * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. +// * +// * @author David Sehnal <david.sehnal@gmail.com> +// */ -import { MSymbol } from '../language/symbol' -import { SymbolRuntime } from './symbol' -import { Macro } from './macro'; -import Expression from '../language/expression'; +// import { MSymbol } from '../language/symbol' +// import { SymbolRuntime } from './symbol' +// import { Macro } from './macro'; +// import Expression from '../language/expression'; -class Environment { - readonly runtimeTable: SymbolRuntime.Table; - readonly macroTable: Macro.Table = new Map<string, Macro>(); +// class Environment { +// readonly runtimeTable: SymbolRuntime.Table; +// readonly macroTable: Macro.Table = new Map<string, Macro>(); - addMacro(name: string, expression: Expression, argNames: ReadonlyArray<string>): Macro { - const argIndex: Macro['argIndex'] = {}; - for (let i = 0; i < argNames.length; i++) argIndex[argNames[i]] = i; - const macro: Macro = { expression, argIndex, argNames }; - this.macroTable.set(name, macro); - return macro; - } +// addMacro(name: string, expression: Expression, argNames: ReadonlyArray<string>): Macro { +// const argIndex: Macro['argIndex'] = {}; +// for (let i = 0; i < argNames.length; i++) argIndex[argNames[i]] = i; +// const macro: Macro = { expression, argIndex, argNames }; +// this.macroTable.set(name, macro); +// return macro; +// } - removeMacro(name: string) { - this.macroTable.delete(name); - } +// removeMacro(name: string) { +// this.macroTable.delete(name); +// } - addSymbolRuntime(symbol: MSymbol, runtime: SymbolRuntime) { - this.runtimeTable.set(symbol.id, runtime); - } +// addSymbolRuntime(symbol: MSymbol, runtime: SymbolRuntime) { +// this.runtimeTable.set(symbol.id, runtime); +// } - removeSymbolRuntime(symbol: MSymbol) { - this.runtimeTable.delete(symbol.id); - } -} +// removeSymbolRuntime(symbol: MSymbol) { +// this.runtimeTable.delete(symbol.id); +// } +// } -export default Environment \ No newline at end of file +// export default Environment \ No newline at end of file diff --git a/src/mol-script/runtime/expression.ts b/src/mol-script/runtime/expression.ts index c6d38827a..f12841edf 100644 --- a/src/mol-script/runtime/expression.ts +++ b/src/mol-script/runtime/expression.ts @@ -1,25 +1,25 @@ -/** - * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ +// /** +// * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. +// * +// * @author David Sehnal <david.sehnal@gmail.com> +// */ -import Environment from './environment' +// import Environment from './environment' -type RuntimeExpression<T = any> = (env: Environment) => T +// type RuntimeExpression<T = any> = (env: Environment) => T -export interface ExpressionInfo { - isConst?: boolean -} +// export interface ExpressionInfo { +// isConst?: boolean +// } -namespace RuntimeExpression { - export function constant<T>(c: T): RuntimeExpression<T> { - return env => c; - } +// namespace RuntimeExpression { +// export function constant<T>(c: T): RuntimeExpression<T> { +// return env => c; +// } - export function func<T>(f: (env: Environment) => T): RuntimeExpression<T> { - return env => f(env); - } -} +// export function func<T>(f: (env: Environment) => T): RuntimeExpression<T> { +// return env => f(env); +// } +// } -export default RuntimeExpression \ No newline at end of file +// export default RuntimeExpression \ No newline at end of file diff --git a/src/mol-script/runtime/macro.ts b/src/mol-script/runtime/macro.ts index 4d5128886..d6ef5f71d 100644 --- a/src/mol-script/runtime/macro.ts +++ b/src/mol-script/runtime/macro.ts @@ -1,81 +1,81 @@ -/** - * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ +// /** +// * 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 Expression from '../language/expression'; -interface Macro { - readonly argNames: ReadonlyArray<string>, - readonly argIndex: { [name: string]: number }, - readonly expression: Expression -} +// interface Macro { +// readonly argNames: ReadonlyArray<string>, +// readonly argIndex: { [name: string]: number }, +// readonly expression: Expression +// } -namespace Macro { - export type Table = Map<string, Macro> +// namespace Macro { +// export type Table = Map<string, Macro> - function subst(table: Table, expr: Expression, argIndex: { [name: string]: number }, args: ArrayLike<Expression>): Expression { - if (Expression.isLiteral(expr)) return expr; - if (Expression.isSymbol(expr)) { - const idx = argIndex[expr.name]; - if (typeof idx !== 'undefined') return args[idx]; +// function subst(table: Table, expr: Expression, argIndex: { [name: string]: number }, args: ArrayLike<Expression>): Expression { +// if (Expression.isLiteral(expr)) return expr; +// if (Expression.isSymbol(expr)) { +// const idx = argIndex[expr.name]; +// if (typeof idx !== 'undefined') return args[idx]; - if (table.has(expr.name)) { - const macro = table.get(expr.name)!; - if (macro.argNames.length === 0) return macro.expression; - } +// if (table.has(expr.name)) { +// const macro = table.get(expr.name)!; +// if (macro.argNames.length === 0) return macro.expression; +// } - return expr; - } +// return expr; +// } - const head = subst(table, expr.head, argIndex, args); - const headChanged = head === expr.head; - if (!expr.args) { - return headChanged ? Expression.Apply(head) : expr; - } +// const head = subst(table, expr.head, argIndex, args); +// const headChanged = head === expr.head; +// if (!expr.args) { +// return headChanged ? Expression.Apply(head) : expr; +// } - let argsChanged = false; +// let argsChanged = false; - if (Expression.isArgumentsArray(expr.args)) { - let newArgs: Expression[] = []; - for (let i = 0, _i = expr.args.length; i < _i; i++) { - const oldArg = expr.args[i]; - const newArg = subst(table, oldArg, argIndex, args); - if (oldArg !== newArg) argsChanged = true; - newArgs[newArgs.length] = newArg; - } - if (!argsChanged) newArgs = expr.args; +// if (Expression.isArgumentsArray(expr.args)) { +// let newArgs: Expression[] = []; +// for (let i = 0, _i = expr.args.length; i < _i; i++) { +// const oldArg = expr.args[i]; +// const newArg = subst(table, oldArg, argIndex, args); +// if (oldArg !== newArg) argsChanged = true; +// newArgs[newArgs.length] = newArg; +// } +// if (!argsChanged) newArgs = expr.args; - if (Expression.isSymbol(head) && table.has(head.name)) { - const macro = table.get(head.name)!; - if (macro.argNames.length === newArgs.length) { - return subst(table, macro.expression, macro.argIndex, newArgs); - } - } +// if (Expression.isSymbol(head) && table.has(head.name)) { +// const macro = table.get(head.name)!; +// if (macro.argNames.length === newArgs.length) { +// return subst(table, macro.expression, macro.argIndex, newArgs); +// } +// } - if (!headChanged && !argsChanged) return expr; - return Expression.Apply(head, newArgs); - } else { +// if (!headChanged && !argsChanged) return expr; +// return Expression.Apply(head, newArgs); +// } else { - let newArgs: any = {} - for (const key of Object.keys(expr.args)) { - const oldArg = expr.args[key]; - const newArg = subst(table, oldArg, argIndex, args); - if (oldArg !== newArg) argsChanged = true; - newArgs[key] = newArg; - } - if (!headChanged && !argsChanged) return expr; - if (!argsChanged) newArgs = expr.args; +// let newArgs: any = {} +// for (const key of Object.keys(expr.args)) { +// const oldArg = expr.args[key]; +// const newArg = subst(table, oldArg, argIndex, args); +// if (oldArg !== newArg) argsChanged = true; +// newArgs[key] = newArg; +// } +// if (!headChanged && !argsChanged) return expr; +// if (!argsChanged) newArgs = expr.args; - return Expression.Apply(head, newArgs); - } - } +// return Expression.Apply(head, newArgs); +// } +// } - export function substitute(table: Table, macro: Macro, args: ArrayLike<Expression>) { - if (args.length === 0) return macro.expression; - return subst(table, macro.expression, macro.argIndex, args); - } -} +// export function substitute(table: Table, macro: Macro, args: ArrayLike<Expression>) { +// if (args.length === 0) return macro.expression; +// return subst(table, macro.expression, macro.argIndex, args); +// } +// } -export { Macro } \ No newline at end of file +// export { Macro } \ No newline at end of file diff --git a/src/mol-script/runtime/query/compiler.ts b/src/mol-script/runtime/query/compiler.ts new file mode 100644 index 000000000..63598bb4c --- /dev/null +++ b/src/mol-script/runtime/query/compiler.ts @@ -0,0 +1,151 @@ +/** + * 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 } 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); + } + + 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' \ No newline at end of file diff --git a/src/mol-script/runtime/query/table.ts b/src/mol-script/runtime/query/table.ts new file mode 100644 index 000000000..d5c4217d7 --- /dev/null +++ b/src/mol-script/runtime/query/table.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { MolScriptSymbolTable as MolScript } from '../../language/symbol-table'; +import { DefaultQueryRuntimeTable, QuerySymbolRuntime } from './compiler'; + +import C = QuerySymbolRuntime.Const +import D = QuerySymbolRuntime.Dynamic +import { Queries, StructureProperties } from 'mol-model/structure'; +import { ElementSymbol } from 'mol-model/structure/model/types'; + +const symbols = [ + C(MolScript.core.math.add, (ctx, xs) => { + let ret = 0; + if (typeof xs.length === 'number') { + for (let i = 0, _i = xs.length; i < _i; i++) ret += xs[i](ctx); + } else { + for (const k of Object.keys(xs)) ret += xs[k](ctx); + } + return ret; + }), + + // ============= RELATIONAL ================ + C(MolScript.core.rel.eq, (ctx, v) => v[0](ctx) === v[1](ctx)), + C(MolScript.core.rel.neq, (ctx, v) => v[0](ctx) !== v[1](ctx)), + C(MolScript.core.rel.lt, (ctx, v) => v[0](ctx) < v[1](ctx)), + C(MolScript.core.rel.lte, (ctx, v) => v[0](ctx) <= v[1](ctx)), + C(MolScript.core.rel.gr, (ctx, v) => v[0](ctx) > v[1](ctx)), + C(MolScript.core.rel.gre, (ctx, v) => v[0](ctx) >= v[1](ctx)), + + // ============= TYPES ================ + C(MolScript.structureQuery.type.elementSymbol, (ctx, v) => ElementSymbol(v[0](ctx))), + + // ============= GENERATORS ================ + D(MolScript.structureQuery.generator.atomGroups, (ctx, xs) => Queries.generators.atoms({ + entityTest: xs['entity-test'], + chainTest: xs['chain-test'], + residueTest: xs['residue-test'], + atomTest: xs['atom-test'], + groupBy: xs['group-by'] + })(ctx)), + + // ============= ATOM PROPERTIES ================ + D(MolScript.structureQuery.atomProperty.macromolecular.label_comp_id, (ctx, _) => StructureProperties.residue.label_comp_id(ctx.element)), + D(MolScript.structureQuery.atomProperty.core.elementSymbol, (ctx, _) => StructureProperties.atom.type_symbol(ctx.element)) + + // Symbol(MolQL.core.rel.neq, staticAttr)((env, v) => v[0](env) !== v[1](env)), + // Symbol(MolQL.core.rel.lt, staticAttr)((env, v) => v[0](env) < v[1](env)), + // Symbol(MolQL.core.rel.lte, staticAttr)((env, v) => v[0](env) <= v[1](env)), + // Symbol(MolQL.core.rel.gr, staticAttr)((env, v) => v[0](env) > v[1](env)), + // Symbol(MolQL.core.rel.gre, staticAttr)((env, v) => v[0](env) >= v[1](env)), + // Symbol(MolQL.core.rel.inRange, staticAttr)((env, v) => { + // const x = v[0](env); + // return x >= v[1](env) && x <= v[2](env) + // }), +]; + +(function () { + for (const s of symbols) { + DefaultQueryRuntimeTable.addSymbol(s); + } +})(); \ No newline at end of file diff --git a/src/mol-script/runtime/symbol.ts b/src/mol-script/runtime/symbol.ts index 5a53a4ba8..b70783330 100644 --- a/src/mol-script/runtime/symbol.ts +++ b/src/mol-script/runtime/symbol.ts @@ -1,32 +1,32 @@ -/** - * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ +// /** +// * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. +// * +// * @author David Sehnal <david.sehnal@gmail.com> +// */ -import Environment from './environment' -import RuntimeExpression from './expression' -import Expression from '../language/expression'; +// import Environment from './environment' +// import RuntimeExpression from './expression' +// import Expression from '../language/expression'; -type SymbolRuntime = SymbolRuntime.Dynamic | SymbolRuntime.Static +// type SymbolRuntime = SymbolRuntime.Dynamic | SymbolRuntime.Static -namespace SymbolRuntime { - export interface Static { - kind: 'static', - readonly runtime: (env: Environment, args: Arguments) => any, - readonly attributes: Attributes - } +// namespace SymbolRuntime { +// export interface Static { +// kind: 'static', +// readonly runtime: (ctx: any, args: Arguments) => any, +// readonly attributes: Attributes +// } - export interface Dynamic { - kind: 'dynamic', - readonly compile: (env: Environment, args: Expression.Arguments) => RuntimeExpression - } +// export interface Dynamic { +// kind: 'dynamic', +// readonly compile: (env: Environment, args: Expression.Arguments) => RuntimeExpression +// } - export interface Attributes { isStatic: boolean } +// export interface Attributes { isStatic: boolean } - export type Table = Map<string, SymbolRuntime> +// export type Table = Map<string, SymbolRuntime> - export type Arguments = ArrayLike<RuntimeExpression> | { [name: string]: RuntimeExpression | undefined } -} +// export type Arguments = ArrayLike<RuntimeExpression> | { [name: string]: RuntimeExpression | undefined } +// } -export { SymbolRuntime } \ No newline at end of file +// export { SymbolRuntime } \ No newline at end of file diff --git a/src/mol-script/script/mol-script/examples.ts b/src/mol-script/script/mol-script/examples.ts index 7df280f8b..1f689f1d3 100644 --- a/src/mol-script/script/mol-script/examples.ts +++ b/src/mol-script/script/mol-script/examples.ts @@ -1,104 +1,104 @@ -/** - * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ +// /** +// * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. +// * +// * @author David Sehnal <david.sehnal@gmail.com> +// */ -export default [{ - name: 'Residues connected to HEM', - value: `(sel.atom.is-connected-to - sel.atom.res - :target (sel.atom.res (= atom.label_comp_id HEM)) - ;; default bond test allows only covalent bonds - :bond-test true - :disjunct true)` -}, { - name: 'All C or N atoms in ALA residues', - value: `(sel.atom.atom-groups - :residue-test (= atom.auth_comp_id ALA) - :atom-test (set.has (set _C _N) atom.el))` -}, { - name: 'Residues 130 to 180', - value: `(sel.atom.res (in-range atom.resno 130 180))` -}, { - name: 'All residues within 5 ang from Fe atom', - value: `(sel.atom.include-surroundings - (sel.atom.atoms (= atom.el _Fe)) - :radius 5 - :as-whole-residues true)` -}, { - name: 'Cluster LYS residues within 5 ang', - value: `(sel.atom.cluster - (sel.atom.res (= atom.label_comp_id LYS)) - :max-distance 5)` -}, { - name: 'Residues with max b-factor < 45', - value: `(sel.atom.pick sel.atom.res - :test (< (atom.set.max atom.B_iso_or_equiv) 45))` -}, { - name: 'Atoms between 10 and 15 ang from Fe', - value: `(sel.atom.within sel.atom.atoms - :target (sel.atom.atoms (= atom.el _Fe)) - :min-radius 10 - :max-radius 15)` -}, { - name: 'HEM and 2 layers of connected residues', - value: `(sel.atom.include-connected - (sel.atom.res (= atom.label_comp_id HEM)) - ;; default bond test allows only covalent bonds - ;; another option is to use :bond-test true to allow any connection - :bond-test (bond.is metallic covalent) - :layer-count 2 - :as-whole-residues true)` -}, { - name: 'All rings', - value: `(sel.atom.rings)` -}, { - name: 'CCCCN and CCNCN rings', - value: `(sel.atom.rings - (ringfp _C _N _C _N _C) - ;; the "rotation" of element symbols has no effect - ;; the following is the same as (ringfp _C _C _C _C _N) - (ringfp _C _C _C _N _C))` -}, { - name: 'Sheets', - value: `(sel.atom.atom-groups - :residue-test (atom.sec-struct.is sheet) - :group-by (atom.key.sec-struct))` -}, { - name: 'Helices formed by at least 30 residues', - value: `(sel.atom.pick - (sel.atom.atom-groups - :residue-test (atom.sec-struct.is helix) - :group-by atom.key.sec-struct) - :test (<= 30 (atom.set.count-query sel.atom.res)))` -}, { - name: 'Modified residues', - value: `(sel.atom.res atom.is-modified)` -}, { - name: 'Atoms participating in metallic coordination', - value: `(sel.atom.atoms - (> (atom.bond-count :flags (bond-flags metallic)) 0))` -}, { - name: 'LYS and ALA residues that are between 2 and 5 ang apart', - value: `(sel.atom.dist-cluster - ;; upper triangular matrix are maximum distances of corresponding selections - ;; lower triangular matrix are minumum distances of corresponding selections - :matrix [ - [0 5] - [2 0] - ] - :selections [ - (sel.atom.res (= atom.resname LYS)) - (sel.atom.res (= atom.resname ALA)) - ])` -}, { - name: 'Clusters of 3 LYS residues that are mutually no more than 5 ang apart', - value: `(sel.atom.dist-cluster - :matrix [[0 5 5] [0 0 5] [0 0 0]] - :selections [ - (sel.atom.res (= atom.resname LYS)) - (sel.atom.res (= atom.resname LYS)) - (sel.atom.res (= atom.resname LYS)) - ])` -}] \ No newline at end of file +// export default [{ +// name: 'Residues connected to HEM', +// value: `(sel.atom.is-connected-to +// sel.atom.res +// :target (sel.atom.res (= atom.label_comp_id HEM)) +// ;; default bond test allows only covalent bonds +// :bond-test true +// :disjunct true)` +// }, { +// name: 'All C or N atoms in ALA residues', +// value: `(sel.atom.atom-groups +// :residue-test (= atom.auth_comp_id ALA) +// :atom-test (set.has (set _C _N) atom.el))` +// }, { +// name: 'Residues 130 to 180', +// value: `(sel.atom.res (in-range atom.resno 130 180))` +// }, { +// name: 'All residues within 5 ang from Fe atom', +// value: `(sel.atom.include-surroundings +// (sel.atom.atoms (= atom.el _Fe)) +// :radius 5 +// :as-whole-residues true)` +// }, { +// name: 'Cluster LYS residues within 5 ang', +// value: `(sel.atom.cluster +// (sel.atom.res (= atom.label_comp_id LYS)) +// :max-distance 5)` +// }, { +// name: 'Residues with max b-factor < 45', +// value: `(sel.atom.pick sel.atom.res +// :test (< (atom.set.max atom.B_iso_or_equiv) 45))` +// }, { +// name: 'Atoms between 10 and 15 ang from Fe', +// value: `(sel.atom.within sel.atom.atoms +// :target (sel.atom.atoms (= atom.el _Fe)) +// :min-radius 10 +// :max-radius 15)` +// }, { +// name: 'HEM and 2 layers of connected residues', +// value: `(sel.atom.include-connected +// (sel.atom.res (= atom.label_comp_id HEM)) +// ;; default bond test allows only covalent bonds +// ;; another option is to use :bond-test true to allow any connection +// :bond-test (bond.is metallic covalent) +// :layer-count 2 +// :as-whole-residues true)` +// }, { +// name: 'All rings', +// value: `(sel.atom.rings)` +// }, { +// name: 'CCCCN and CCNCN rings', +// value: `(sel.atom.rings +// (ringfp _C _N _C _N _C) +// ;; the "rotation" of element symbols has no effect +// ;; the following is the same as (ringfp _C _C _C _C _N) +// (ringfp _C _C _C _N _C))` +// }, { +// name: 'Sheets', +// value: `(sel.atom.atom-groups +// :residue-test (atom.sec-struct.is sheet) +// :group-by (atom.key.sec-struct))` +// }, { +// name: 'Helices formed by at least 30 residues', +// value: `(sel.atom.pick +// (sel.atom.atom-groups +// :residue-test (atom.sec-struct.is helix) +// :group-by atom.key.sec-struct) +// :test (<= 30 (atom.set.count-query sel.atom.res)))` +// }, { +// name: 'Modified residues', +// value: `(sel.atom.res atom.is-modified)` +// }, { +// name: 'Atoms participating in metallic coordination', +// value: `(sel.atom.atoms +// (> (atom.bond-count :flags (bond-flags metallic)) 0))` +// }, { +// name: 'LYS and ALA residues that are between 2 and 5 ang apart', +// value: `(sel.atom.dist-cluster +// ;; upper triangular matrix are maximum distances of corresponding selections +// ;; lower triangular matrix are minumum distances of corresponding selections +// :matrix [ +// [0 5] +// [2 0] +// ] +// :selections [ +// (sel.atom.res (= atom.resname LYS)) +// (sel.atom.res (= atom.resname ALA)) +// ])` +// }, { +// name: 'Clusters of 3 LYS residues that are mutually no more than 5 ang apart', +// value: `(sel.atom.dist-cluster +// :matrix [[0 5 5] [0 0 5] [0 0 0]] +// :selections [ +// (sel.atom.res (= atom.resname LYS)) +// (sel.atom.res (= atom.resname LYS)) +// (sel.atom.res (= atom.resname LYS)) +// ])` +// }] \ No newline at end of file diff --git a/src/mol-script/script/mol-script/macro.ts b/src/mol-script/script/mol-script/macro.ts index a2c9c5a7c..436e06bc6 100644 --- a/src/mol-script/script/mol-script/macro.ts +++ b/src/mol-script/script/mol-script/macro.ts @@ -1,39 +1,39 @@ -/** - * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ +// /** +// * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. +// * +// * @author David Sehnal <david.sehnal@gmail.com> +// * @author Alexander Rose <alexander.rose@weirdbyte.de> +// */ -import B from '../../language/builder' +// import B from '../../language/builder' -export function getPositionalArgs(args: any) { - return Object.keys(args) - .filter(k => !isNaN(k as any)) - .map(k => +k) - .sort((a, b) => a - b) - .map(k => args[k]); -} +// export function getPositionalArgs(args: any) { +// return Object.keys(args) +// .filter(k => !isNaN(k as any)) +// .map(k => +k) +// .sort((a, b) => a - b) +// .map(k => args[k]); +// } -export function tryGetArg(args: any, name: string | number, defaultValue?: any) { - return (args && args[name] !== void 0) ? args[name] : defaultValue; -} +// export function tryGetArg(args: any, name: string | number, defaultValue?: any) { +// return (args && args[name] !== void 0) ? args[name] : defaultValue; +// } -export function pickArgs(args: any, ...names: string[]) { - const ret = Object.create(null); - let count = 0; - for (let k of Object.keys(args)) { - if (names.indexOf(k) >= 0) { - ret[k] = args[k]; - count++; - } - } - return count ? ret : void 0; -} +// export function pickArgs(args: any, ...names: string[]) { +// const ret = Object.create(null); +// let count = 0; +// for (let k of Object.keys(args)) { +// if (names.indexOf(k) >= 0) { +// ret[k] = args[k]; +// count++; +// } +// } +// return count ? ret : void 0; +// } -export function aggregate(property: any, fn: any, initial?: any){ - return B.struct.atomSet.reduce({ - initial: initial !== void 0 ? initial : property, - value: fn([ B.struct.slot.elementSetReduce(), property ]) - }); -} \ No newline at end of file +// export function aggregate(property: any, fn: any, initial?: any){ +// return B.struct.atomSet.reduce({ +// initial: initial !== void 0 ? initial : property, +// value: fn([ B.struct.slot.elementSetReduce(), property ]) +// }); +// } \ No newline at end of file diff --git a/src/mol-script/script/mol-script/parser.ts b/src/mol-script/script/mol-script/parser.ts index b313de302..f9452036a 100644 --- a/src/mol-script/script/mol-script/parser.ts +++ b/src/mol-script/script/mol-script/parser.ts @@ -1,178 +1,178 @@ -/** - * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import { MonadicParser as P } from 'mol-util/monadic-parser' -import Expression from '../../language/expression' -import B from '../../language/builder' - -export function parseMolScript(input: string) { - return Language.parse(input); -} - -namespace Language { - type AST = ASTNode.Expression[] - - namespace ASTNode { - export type Expression = Str | Symb | List | Comment - - export interface Str { - kind: 'string', - value: string - } - - export interface Symb { - kind: 'symbol', - value: string - } - - export interface List { - kind: 'list', - bracket: '(' | '[' | '{', - nodes: Expression[] - } - - export interface Comment { - kind: 'comment', - value: string - } - - export function str(value: string): Str { return { kind: 'string', value }; } - export function symb(value: string): Symb { return { kind: 'symbol', value }; } - export function list(bracket: '(' | '[' | '{', nodes: Expression[]): List { return { kind: 'list', bracket, nodes }; } - export function comment(value: string): Comment { return { kind: 'comment', value } } - } - - const ws = P.regexp(/[\n\r\s]*/); - const Expr: P<ASTNode.Expression> = P.lazy(() => (P.alt(Str, List, Symb, Comment).trim(ws))); - const Str = P.takeWhile(c => c !== '`').trim('`').map(ASTNode.str); - const Symb = P.regexp(/[^()\[\]{};`,\n\r\s]+/).map(ASTNode.symb); - const Comment = P.regexp(/\s*;+([^\n\r]*)\n/, 1).map(ASTNode.comment); - const Args = Expr.many(); - const List1 = Args.wrap('(', ')').map(args => ASTNode.list('(', args)); - const List2 = Args.wrap('[', ']').map(args => ASTNode.list('[', args)); - const List3 = Args.wrap('{', '}').map(args => ASTNode.list('{', args)); - const List = P.alt(List1, List2, List3); - - const Expressions: P<AST> = Expr.many(); - - function getAST(input: string) { return Expressions.tryParse(input); } - - function visitExpr(expr: ASTNode.Expression): Expression { - switch (expr.kind) { - case 'string': return expr.value; - case 'symbol': { - const value = expr.value; - if (value.length > 1) { - const fst = value.charAt(0); - switch (fst) { - case '.': return B.atomName(value.substr(1)); - case '_': return B.struct.type.elementSymbol([value.substr(1)]); - } - } - if (value === 'true') return true; - if (value === 'false') return false; - if (isNumber(value)) return +value; - return Expression.Symbol(value); - } - case 'list': { - switch (expr.bracket) { - case '[': return B.core.type.list(withoutComments(expr.nodes).map(visitExpr)); - case '{': return B.core.type.set(withoutComments(expr.nodes).map(visitExpr)); - case '(': { - const head = visitExpr(expr.nodes[0]); - return Expression.Apply(head, getArgs(expr.nodes)); - } - } - return 0 as any; - } - default: { - throw new Error('should not happen'); - } - } - } - - function getArgs(nodes: ASTNode.Expression[]): Expression.Arguments | undefined { - if (nodes.length <= 1) return void 0; - if (!hasNamedArgs(nodes)) { - const args: Expression[] = []; - for (let i = 1, _i = nodes.length; i < _i; i++) { - const n = nodes[i]; - if (n.kind === 'comment') continue; - args[args.length] = visitExpr(n); - } - return args; - } - const args: { [name: string]: Expression } = {}; - let allNumeric = true; - let pos = 0; - for (let i = 1, _i = nodes.length; i < _i; i++) { - const n = nodes[i]; - if (n.kind === 'comment') continue; - if (n.kind === 'symbol' && n.value.length > 1 && n.value.charAt(0) === ':') { - const name = n.value.substr(1); - ++i; - while (i < _i && nodes[i].kind === 'comment') { i++; } - if (i >= _i) throw new Error(`There must be a value foolowed a named arg ':${name}'.`); - args[name] = visitExpr(nodes[i]); - if (isNaN(+name)) allNumeric = false; - } else { - args[pos++] = visitExpr(n); - } - } - if (allNumeric) { - const keys = Object.keys(args).map(a => +a).sort((a, b) => a - b); - let isArray = true; - for (let i = 0, _i = keys.length; i < _i; i++) { - if (keys[i] !== i) { - isArray = false; - break; - } - } - if (isArray) { - const arrayArgs: Expression[] = []; - for (let i = 0, _i = keys.length; i < _i; i++) { - arrayArgs[i] = args[i]; - } - return arrayArgs; - } - } - return args; - } - - function hasNamedArgs(nodes: ASTNode.Expression[]) { - for (let i = 1, _i = nodes.length; i < _i; i++) { - const n = nodes[i]; - if (n.kind === 'symbol' && n.value.length > 1 && n.value.charAt(0) === ':') return true; - } - return false; - } - - function withoutComments(nodes: ASTNode.Expression[]) { - let hasComment = false; - for (let i = 0, _i = nodes.length; i < _i; i++) { - if (nodes[i].kind === 'comment') { - hasComment = true; - break; - } - } - if (!hasComment) return nodes; - return nodes.filter(n => n.kind !== 'comment'); - } - - function isNumber(value: string) { - return /-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/.test(value); - } - - export function parse(input: string): Expression[] { - const ast = getAST(input); - const ret: Expression[] = []; - for (const expr of ast) { - if (expr.kind === 'comment') continue; - ret[ret.length] = visitExpr(expr); - } - return ret; - } -} \ No newline at end of file +// /** +// * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. +// * +// * @author David Sehnal <david.sehnal@gmail.com> +// */ + +// import { MonadicParser as P } from 'mol-util/monadic-parser' +// import Expression from '../../language/expression' +// import B from '../../language/builder' + +// export function parseMolScript(input: string) { +// return Language.parse(input); +// } + +// namespace Language { +// type AST = ASTNode.Expression[] + +// namespace ASTNode { +// export type Expression = Str | Symb | List | Comment + +// export interface Str { +// kind: 'string', +// value: string +// } + +// export interface Symb { +// kind: 'symbol', +// value: string +// } + +// export interface List { +// kind: 'list', +// bracket: '(' | '[' | '{', +// nodes: Expression[] +// } + +// export interface Comment { +// kind: 'comment', +// value: string +// } + +// export function str(value: string): Str { return { kind: 'string', value }; } +// export function symb(value: string): Symb { return { kind: 'symbol', value }; } +// export function list(bracket: '(' | '[' | '{', nodes: Expression[]): List { return { kind: 'list', bracket, nodes }; } +// export function comment(value: string): Comment { return { kind: 'comment', value } } +// } + +// const ws = P.regexp(/[\n\r\s]*/); +// const Expr: P<ASTNode.Expression> = P.lazy(() => (P.alt(Str, List, Symb, Comment).trim(ws))); +// const Str = P.takeWhile(c => c !== '`').trim('`').map(ASTNode.str); +// const Symb = P.regexp(/[^()\[\]{};`,\n\r\s]+/).map(ASTNode.symb); +// const Comment = P.regexp(/\s*;+([^\n\r]*)\n/, 1).map(ASTNode.comment); +// const Args = Expr.many(); +// const List1 = Args.wrap('(', ')').map(args => ASTNode.list('(', args)); +// const List2 = Args.wrap('[', ']').map(args => ASTNode.list('[', args)); +// const List3 = Args.wrap('{', '}').map(args => ASTNode.list('{', args)); +// const List = P.alt(List1, List2, List3); + +// const Expressions: P<AST> = Expr.many(); + +// function getAST(input: string) { return Expressions.tryParse(input); } + +// function visitExpr(expr: ASTNode.Expression): Expression { +// switch (expr.kind) { +// case 'string': return expr.value; +// case 'symbol': { +// const value = expr.value; +// if (value.length > 1) { +// const fst = value.charAt(0); +// switch (fst) { +// case '.': return B.atomName(value.substr(1)); +// case '_': return B.struct.type.elementSymbol([value.substr(1)]); +// } +// } +// if (value === 'true') return true; +// if (value === 'false') return false; +// if (isNumber(value)) return +value; +// return Expression.Symbol(value); +// } +// case 'list': { +// switch (expr.bracket) { +// case '[': return B.core.type.list(withoutComments(expr.nodes).map(visitExpr)); +// case '{': return B.core.type.set(withoutComments(expr.nodes).map(visitExpr)); +// case '(': { +// const head = visitExpr(expr.nodes[0]); +// return Expression.Apply(head, getArgs(expr.nodes)); +// } +// } +// return 0 as any; +// } +// default: { +// throw new Error('should not happen'); +// } +// } +// } + +// function getArgs(nodes: ASTNode.Expression[]): Expression.Arguments | undefined { +// if (nodes.length <= 1) return void 0; +// if (!hasNamedArgs(nodes)) { +// const args: Expression[] = []; +// for (let i = 1, _i = nodes.length; i < _i; i++) { +// const n = nodes[i]; +// if (n.kind === 'comment') continue; +// args[args.length] = visitExpr(n); +// } +// return args; +// } +// const args: { [name: string]: Expression } = {}; +// let allNumeric = true; +// let pos = 0; +// for (let i = 1, _i = nodes.length; i < _i; i++) { +// const n = nodes[i]; +// if (n.kind === 'comment') continue; +// if (n.kind === 'symbol' && n.value.length > 1 && n.value.charAt(0) === ':') { +// const name = n.value.substr(1); +// ++i; +// while (i < _i && nodes[i].kind === 'comment') { i++; } +// if (i >= _i) throw new Error(`There must be a value foolowed a named arg ':${name}'.`); +// args[name] = visitExpr(nodes[i]); +// if (isNaN(+name)) allNumeric = false; +// } else { +// args[pos++] = visitExpr(n); +// } +// } +// if (allNumeric) { +// const keys = Object.keys(args).map(a => +a).sort((a, b) => a - b); +// let isArray = true; +// for (let i = 0, _i = keys.length; i < _i; i++) { +// if (keys[i] !== i) { +// isArray = false; +// break; +// } +// } +// if (isArray) { +// const arrayArgs: Expression[] = []; +// for (let i = 0, _i = keys.length; i < _i; i++) { +// arrayArgs[i] = args[i]; +// } +// return arrayArgs; +// } +// } +// return args; +// } + +// function hasNamedArgs(nodes: ASTNode.Expression[]) { +// for (let i = 1, _i = nodes.length; i < _i; i++) { +// const n = nodes[i]; +// if (n.kind === 'symbol' && n.value.length > 1 && n.value.charAt(0) === ':') return true; +// } +// return false; +// } + +// function withoutComments(nodes: ASTNode.Expression[]) { +// let hasComment = false; +// for (let i = 0, _i = nodes.length; i < _i; i++) { +// if (nodes[i].kind === 'comment') { +// hasComment = true; +// break; +// } +// } +// if (!hasComment) return nodes; +// return nodes.filter(n => n.kind !== 'comment'); +// } + +// function isNumber(value: string) { +// return /-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/.test(value); +// } + +// export function parse(input: string): Expression[] { +// const ast = getAST(input); +// const ret: Expression[] = []; +// for (const expr of ast) { +// if (expr.kind === 'comment') continue; +// ret[ret.length] = visitExpr(expr); +// } +// return ret; +// } +// } \ No newline at end of file diff --git a/src/mol-script/script/mol-script/symbols.ts b/src/mol-script/script/mol-script/symbols.ts index dbbf1c5f8..70b7028af 100644 --- a/src/mol-script/script/mol-script/symbols.ts +++ b/src/mol-script/script/mol-script/symbols.ts @@ -1,300 +1,300 @@ -/** - * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ +// /** +// * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. +// * +// * @author David Sehnal <david.sehnal@gmail.com> +// */ -import { MSymbol, Arguments, Argument } from '../../language/symbol' -import B from '../../language/builder' -import * as M from './macro' -import MolScript from '../../language/symbol-table' -import Type from '../../language/type' -import * as Struct from '../../language/symbol-table/structure-query' -import Expression from '../../language/expression' -import { UniqueArray } from 'mol-data/generic' +// import { MSymbol, Arguments, Argument } from '../../language/symbol' +// import B from '../../language/builder' +// import * as M from './macro' +// import { MolScriptSymbolTable as MolScript } from '../../language/symbol-table' +// import Type from '../../language/type' +// import * as Struct from '../../language/symbol-table/structure-query' +// import Expression from '../../language/expression' +// import { UniqueArray } from 'mol-data/generic' -export type MolScriptSymbol = - | { kind: 'alias', aliases: string[], symbol: MSymbol } - | { kind: 'macro', aliases: string[], symbol: MSymbol, translate: (args: any) => Expression } +// export type MolScriptSymbol = +// | { kind: 'alias', aliases: string[], symbol: MSymbol } +// | { kind: 'macro', aliases: string[], symbol: MSymbol, translate: (args: any) => Expression } -function Alias(symbol: MSymbol<any>, ...aliases: string[]): MolScriptSymbol { return { kind: 'alias', aliases, symbol }; } -function Macro(symbol: MSymbol<any>, translate: (args: any) => Expression, ...aliases: string[]): MolScriptSymbol { - symbol.info.namespace = 'molscript-macro'; - symbol.id = `molscript-macro.${symbol.info.name}`; - return { kind: 'macro', symbol, translate, aliases: [symbol.info.name, ...aliases] }; -} +// function Alias(symbol: MSymbol<any>, ...aliases: string[]): MolScriptSymbol { return { kind: 'alias', aliases, symbol }; } +// function Macro(symbol: MSymbol<any>, translate: (args: any) => Expression, ...aliases: string[]): MolScriptSymbol { +// symbol.info.namespace = 'molscript-macro'; +// symbol.id = `molscript-macro.${symbol.info.name}`; +// return { kind: 'macro', symbol, translate, aliases: [symbol.info.name, ...aliases] }; +// } -export function isMolScriptSymbol(x: any): x is MolScriptSymbol { - return x.kind === 'alias' || x.kind === 'macro'; -} +// export function isMolScriptSymbol(x: any): x is MolScriptSymbol { +// return x.kind === 'alias' || x.kind === 'macro'; +// } -export const SymbolTable = [ - [ - 'Core symbols', - Alias(MolScript.core.type.bool, 'bool'), - Alias(MolScript.core.type.num, 'num'), - Alias(MolScript.core.type.str, 'str'), - Alias(MolScript.core.type.regex, 'regex'), - Alias(MolScript.core.type.list, 'list'), - Alias(MolScript.core.type.set, 'set'), +// export const SymbolTable = [ +// [ +// 'Core symbols', +// Alias(MolScript.core.type.bool, 'bool'), +// Alias(MolScript.core.type.num, 'num'), +// Alias(MolScript.core.type.str, 'str'), +// Alias(MolScript.core.type.regex, 'regex'), +// Alias(MolScript.core.type.list, 'list'), +// Alias(MolScript.core.type.set, 'set'), - Alias(MolScript.core.type.compositeKey, 'composite-key'), - Alias(MolScript.core.logic.not, 'not'), - Alias(MolScript.core.logic.and, 'and'), - Alias(MolScript.core.logic.or, 'or'), - Alias(MolScript.core.ctrl.if, 'if'), - Alias(MolScript.core.ctrl.fn, 'fn'), - Alias(MolScript.core.ctrl.eval, 'eval'), - Alias(MolScript.core.math.add, 'add', '+'), - Alias(MolScript.core.math.sub, 'sub', '-'), - Alias(MolScript.core.math.mult, 'mult', '*'), - Alias(MolScript.core.math.div, 'div', '/'), - Alias(MolScript.core.math.pow, 'pow', '**'), - Alias(MolScript.core.math.mod, 'mod'), - Alias(MolScript.core.math.min, 'min'), - Alias(MolScript.core.math.max, 'max'), - Alias(MolScript.core.math.floor, 'floor'), - Alias(MolScript.core.math.ceil, 'ceil'), - Alias(MolScript.core.math.roundInt, 'round'), - Alias(MolScript.core.math.abs, 'abs'), - Alias(MolScript.core.math.sqrt, 'sqrt'), - Alias(MolScript.core.math.sin, 'sin'), - Alias(MolScript.core.math.cos, 'cos'), - Alias(MolScript.core.math.tan, 'tan'), - Alias(MolScript.core.math.asin, 'asin'), - Alias(MolScript.core.math.acos, 'acos'), - Alias(MolScript.core.math.atan, 'atan'), - Alias(MolScript.core.math.sinh, 'sinh'), - Alias(MolScript.core.math.cosh, 'cosh'), - Alias(MolScript.core.math.tanh, 'tanh'), - Alias(MolScript.core.math.exp, 'exp'), - Alias(MolScript.core.math.log, 'log'), - Alias(MolScript.core.math.log10, 'log10'), - Alias(MolScript.core.math.atan2, 'atan2'), - Alias(MolScript.core.rel.eq, 'eq', '='), - Alias(MolScript.core.rel.neq, 'neq', '!='), - Alias(MolScript.core.rel.lt, 'lt', '<'), - Alias(MolScript.core.rel.lte, 'lte', '<='), - Alias(MolScript.core.rel.gr, 'gr', '>'), - Alias(MolScript.core.rel.gre, 'gre', '>='), - Alias(MolScript.core.rel.inRange, 'in-range'), - Alias(MolScript.core.str.concat, 'concat'), - Alias(MolScript.core.str.match, 'regex.match'), - Alias(MolScript.core.list.getAt, 'list.get'), - Alias(MolScript.core.set.has, 'set.has'), - Alias(MolScript.core.set.isSubset, 'set.subset'), - ], - [ - 'Structure', - [ - 'Types', - Alias(MolScript.structureQuery.type.entityType, 'ent-type'), - Alias(MolScript.structureQuery.type.authResidueId, 'auth-resid'), - Alias(MolScript.structureQuery.type.labelResidueId, 'label-resid'), - Alias(MolScript.structureQuery.type.ringFingerprint, 'ringfp'), - Alias(MolScript.structureQuery.type.bondFlags, 'bond-flags'), - ], - [ - 'Slots', - Alias(MolScript.structureQuery.slot.elementSetReduce, 'atom.set.reduce.value'), - ], - [ - 'Generators', - Alias(MolScript.structureQuery.generator.atomGroups, 'sel.atom.atom-groups'), - Alias(MolScript.structureQuery.generator.queryInSelection, 'sel.atom.query-in-selection'), - Alias(MolScript.structureQuery.generator.rings, 'sel.atom.rings'), - Alias(MolScript.structureQuery.generator.empty, 'sel.atom.empty'), +// Alias(MolScript.core.type.compositeKey, 'composite-key'), +// Alias(MolScript.core.logic.not, 'not'), +// Alias(MolScript.core.logic.and, 'and'), +// Alias(MolScript.core.logic.or, 'or'), +// Alias(MolScript.core.ctrl.if, 'if'), +// Alias(MolScript.core.ctrl.fn, 'fn'), +// Alias(MolScript.core.ctrl.eval, 'eval'), +// Alias(MolScript.core.math.add, 'add', '+'), +// Alias(MolScript.core.math.sub, 'sub', '-'), +// Alias(MolScript.core.math.mult, 'mult', '*'), +// Alias(MolScript.core.math.div, 'div', '/'), +// Alias(MolScript.core.math.pow, 'pow', '**'), +// Alias(MolScript.core.math.mod, 'mod'), +// Alias(MolScript.core.math.min, 'min'), +// Alias(MolScript.core.math.max, 'max'), +// Alias(MolScript.core.math.floor, 'floor'), +// Alias(MolScript.core.math.ceil, 'ceil'), +// Alias(MolScript.core.math.roundInt, 'round'), +// Alias(MolScript.core.math.abs, 'abs'), +// Alias(MolScript.core.math.sqrt, 'sqrt'), +// Alias(MolScript.core.math.sin, 'sin'), +// Alias(MolScript.core.math.cos, 'cos'), +// Alias(MolScript.core.math.tan, 'tan'), +// Alias(MolScript.core.math.asin, 'asin'), +// Alias(MolScript.core.math.acos, 'acos'), +// Alias(MolScript.core.math.atan, 'atan'), +// Alias(MolScript.core.math.sinh, 'sinh'), +// Alias(MolScript.core.math.cosh, 'cosh'), +// Alias(MolScript.core.math.tanh, 'tanh'), +// Alias(MolScript.core.math.exp, 'exp'), +// Alias(MolScript.core.math.log, 'log'), +// Alias(MolScript.core.math.log10, 'log10'), +// Alias(MolScript.core.math.atan2, 'atan2'), +// Alias(MolScript.core.rel.eq, 'eq', '='), +// Alias(MolScript.core.rel.neq, 'neq', '!='), +// Alias(MolScript.core.rel.lt, 'lt', '<'), +// Alias(MolScript.core.rel.lte, 'lte', '<='), +// Alias(MolScript.core.rel.gr, 'gr', '>'), +// Alias(MolScript.core.rel.gre, 'gre', '>='), +// Alias(MolScript.core.rel.inRange, 'in-range'), +// Alias(MolScript.core.str.concat, 'concat'), +// Alias(MolScript.core.str.match, 'regex.match'), +// Alias(MolScript.core.list.getAt, 'list.get'), +// Alias(MolScript.core.set.has, 'set.has'), +// Alias(MolScript.core.set.isSubset, 'set.subset'), +// ], +// [ +// 'Structure', +// [ +// 'Types', +// Alias(MolScript.structureQuery.type.entityType, 'ent-type'), +// Alias(MolScript.structureQuery.type.authResidueId, 'auth-resid'), +// Alias(MolScript.structureQuery.type.labelResidueId, 'label-resid'), +// Alias(MolScript.structureQuery.type.ringFingerprint, 'ringfp'), +// Alias(MolScript.structureQuery.type.bondFlags, 'bond-flags'), +// ], +// [ +// 'Slots', +// Alias(MolScript.structureQuery.slot.elementSetReduce, 'atom.set.reduce.value'), +// ], +// [ +// 'Generators', +// Alias(MolScript.structureQuery.generator.atomGroups, 'sel.atom.atom-groups'), +// Alias(MolScript.structureQuery.generator.queryInSelection, 'sel.atom.query-in-selection'), +// Alias(MolScript.structureQuery.generator.rings, 'sel.atom.rings'), +// Alias(MolScript.structureQuery.generator.empty, 'sel.atom.empty'), - Macro(MSymbol('sel.atom.atoms', Arguments.Dictionary({ - 0: Argument(Type.Bool, { isOptional: true, defaultValue: true, description: 'Test applied to each atom.' }) - }), Struct.Types.ElementSelection, 'A selection of singleton atom sets.'), - args => B.struct.generator.atomGroups({ 'atom-test': M.tryGetArg(args, 0, true) })), +// Macro(MSymbol('sel.atom.atoms', Arguments.Dictionary({ +// 0: Argument(Type.Bool, { isOptional: true, defaultValue: true, description: 'Test applied to each atom.' }) +// }), Struct.Types.ElementSelection, 'A selection of singleton atom sets.'), +// args => B.struct.generator.atomGroups({ 'atom-test': M.tryGetArg(args, 0, true) })), - Macro(MSymbol('sel.atom.res', Arguments.Dictionary({ - 0: Argument(Type.Bool, { isOptional: true, defaultValue: true, description: 'Test applied to the 1st atom of each residue.' }) - }), Struct.Types.ElementSelection, 'A selection of atom sets grouped by residue.'), - args => B.struct.generator.atomGroups({ - 'residue-test': M.tryGetArg(args, 0, true), - 'group-by': B.ammp('residueKey') - })), +// Macro(MSymbol('sel.atom.res', Arguments.Dictionary({ +// 0: Argument(Type.Bool, { isOptional: true, defaultValue: true, description: 'Test applied to the 1st atom of each residue.' }) +// }), Struct.Types.ElementSelection, 'A selection of atom sets grouped by residue.'), +// args => B.struct.generator.atomGroups({ +// 'residue-test': M.tryGetArg(args, 0, true), +// 'group-by': B.ammp('residueKey') +// })), - Macro(MSymbol('sel.atom.chains', Arguments.Dictionary({ - 0: Argument(Type.Bool, { isOptional: true, defaultValue: true, description: 'Test applied to the 1st atom of each chain.' }) - }), Struct.Types.ElementSelection, 'A selection of atom sets grouped by chain.'), - args => B.struct.generator.atomGroups({ - 'chain-test': M.tryGetArg(args, 0, true), - 'group-by': B.ammp('chainKey') - })), - ], - [ - 'Modifiers', - Alias(MolScript.structureQuery.modifier.queryEach, 'sel.atom.query-each'), - Alias(MolScript.structureQuery.modifier.intersectBy, 'sel.atom.intersect-by'), - Alias(MolScript.structureQuery.modifier.exceptBy, 'sel.atom.except-by'), - Alias(MolScript.structureQuery.modifier.unionBy, 'sel.atom.union-by'), - Alias(MolScript.structureQuery.modifier.union, 'sel.atom.union'), - Alias(MolScript.structureQuery.modifier.cluster, 'sel.atom.cluster'), - Alias(MolScript.structureQuery.modifier.includeSurroundings, 'sel.atom.include-surroundings'), - Alias(MolScript.structureQuery.modifier.includeConnected, 'sel.atom.include-connected'), - Alias(MolScript.structureQuery.modifier.expandProperty, 'sel.atom.expand-property'), +// Macro(MSymbol('sel.atom.chains', Arguments.Dictionary({ +// 0: Argument(Type.Bool, { isOptional: true, defaultValue: true, description: 'Test applied to the 1st atom of each chain.' }) +// }), Struct.Types.ElementSelection, 'A selection of atom sets grouped by chain.'), +// args => B.struct.generator.atomGroups({ +// 'chain-test': M.tryGetArg(args, 0, true), +// 'group-by': B.ammp('chainKey') +// })), +// ], +// [ +// 'Modifiers', +// Alias(MolScript.structureQuery.modifier.queryEach, 'sel.atom.query-each'), +// Alias(MolScript.structureQuery.modifier.intersectBy, 'sel.atom.intersect-by'), +// Alias(MolScript.structureQuery.modifier.exceptBy, 'sel.atom.except-by'), +// Alias(MolScript.structureQuery.modifier.unionBy, 'sel.atom.union-by'), +// Alias(MolScript.structureQuery.modifier.union, 'sel.atom.union'), +// Alias(MolScript.structureQuery.modifier.cluster, 'sel.atom.cluster'), +// Alias(MolScript.structureQuery.modifier.includeSurroundings, 'sel.atom.include-surroundings'), +// Alias(MolScript.structureQuery.modifier.includeConnected, 'sel.atom.include-connected'), +// Alias(MolScript.structureQuery.modifier.expandProperty, 'sel.atom.expand-property'), - Macro(MSymbol('sel.atom.around', Arguments.Dictionary({ - 0: Argument(Type.Bool, { isOptional: true, defaultValue: true, description: 'Test applied to the 1st atom of each chain.' }) - }), Struct.Types.ElementSelection, 'A selection of singleton atom sets with centers within "radius" of the center of any atom in the given selection.'), - args => B.struct.modifier.exceptBy({ - '0': B.struct.filter.within({ - '0': B.struct.generator.atomGroups(), target: M.tryGetArg(args, 0), 'max-radius': M.tryGetArg(args, 'radius') - }), - by: M.tryGetArg(args, 0) - })) - ], - [ - 'Filters', - Alias(MolScript.structureQuery.filter.pick, 'sel.atom.pick'), - Alias(MolScript.structureQuery.filter.withSameAtomProperties, 'sel.atom.with-same-atom-properties'), - Alias(MolScript.structureQuery.filter.intersectedBy, 'sel.atom.intersected-by'), - Alias(MolScript.structureQuery.filter.within, 'sel.atom.within'), - Alias(MolScript.structureQuery.filter.isConnectedTo, 'sel.atom.is-connected-to'), - ], - [ - 'Combinators', - Alias(MolScript.structureQuery.combinator.intersect, 'sel.atom.intersect'), - Alias(MolScript.structureQuery.combinator.merge, 'sel.atom.merge'), - Alias(MolScript.structureQuery.combinator.distanceCluster, 'sel.atom.dist-cluster'), - ], - [ - 'Atom Set Properties', - Alias(MolScript.structureQuery.atomSet.atomCount, 'atom.set.atom-count'), - Alias(MolScript.structureQuery.atomSet.countQuery, 'atom.set.count-query'), - Alias(MolScript.structureQuery.atomSet.reduce, 'atom.set.reduce'), - Alias(MolScript.structureQuery.atomSet.propertySet, 'atom.set.property'), +// Macro(MSymbol('sel.atom.around', Arguments.Dictionary({ +// 0: Argument(Type.Bool, { isOptional: true, defaultValue: true, description: 'Test applied to the 1st atom of each chain.' }) +// }), Struct.Types.ElementSelection, 'A selection of singleton atom sets with centers within "radius" of the center of any atom in the given selection.'), +// args => B.struct.modifier.exceptBy({ +// '0': B.struct.filter.within({ +// '0': B.struct.generator.atomGroups(), target: M.tryGetArg(args, 0), 'max-radius': M.tryGetArg(args, 'radius') +// }), +// by: M.tryGetArg(args, 0) +// })) +// ], +// [ +// 'Filters', +// Alias(MolScript.structureQuery.filter.pick, 'sel.atom.pick'), +// Alias(MolScript.structureQuery.filter.withSameAtomProperties, 'sel.atom.with-same-atom-properties'), +// Alias(MolScript.structureQuery.filter.intersectedBy, 'sel.atom.intersected-by'), +// Alias(MolScript.structureQuery.filter.within, 'sel.atom.within'), +// Alias(MolScript.structureQuery.filter.isConnectedTo, 'sel.atom.is-connected-to'), +// ], +// [ +// 'Combinators', +// Alias(MolScript.structureQuery.combinator.intersect, 'sel.atom.intersect'), +// Alias(MolScript.structureQuery.combinator.merge, 'sel.atom.merge'), +// Alias(MolScript.structureQuery.combinator.distanceCluster, 'sel.atom.dist-cluster'), +// ], +// [ +// 'Atom Set Properties', +// Alias(MolScript.structureQuery.atomSet.atomCount, 'atom.set.atom-count'), +// Alias(MolScript.structureQuery.atomSet.countQuery, 'atom.set.count-query'), +// Alias(MolScript.structureQuery.atomSet.reduce, 'atom.set.reduce'), +// Alias(MolScript.structureQuery.atomSet.propertySet, 'atom.set.property'), - Macro(MSymbol('atom.set.max', Arguments.Dictionary({ - 0: Argument(Type.Num, { description: 'Numeric atom property.'}) - }), Type.Num, 'Maximum of the given property in the current atom set.'), - args => M.aggregate(M.tryGetArg(args, 0), B.core.math.max)), +// Macro(MSymbol('atom.set.max', Arguments.Dictionary({ +// 0: Argument(Type.Num, { description: 'Numeric atom property.'}) +// }), Type.Num, 'Maximum of the given property in the current atom set.'), +// args => M.aggregate(M.tryGetArg(args, 0), B.core.math.max)), - Macro(MSymbol('atom.set.sum', Arguments.Dictionary({ - 0: Argument(Type.Num, { description: 'Numeric atom property.'}) - }), Type.Num, 'Sum of the given property in the current atom set.'), - args => M.aggregate(M.tryGetArg(args, 0), B.core.math.add, 0)), +// Macro(MSymbol('atom.set.sum', Arguments.Dictionary({ +// 0: Argument(Type.Num, { description: 'Numeric atom property.'}) +// }), Type.Num, 'Sum of the given property in the current atom set.'), +// args => M.aggregate(M.tryGetArg(args, 0), B.core.math.add, 0)), - Macro(MSymbol('atom.set.avg', Arguments.Dictionary({ - 0: Argument(Type.Num, { description: 'Numeric atom property.'}) - }), Type.Num, 'Average of the given property in the current atom set.'), - args => B.core.math.div([ M.aggregate(M.tryGetArg(args, 0), B.core.math.add, 0), B.struct.atomSet.atomCount() ])), +// Macro(MSymbol('atom.set.avg', Arguments.Dictionary({ +// 0: Argument(Type.Num, { description: 'Numeric atom property.'}) +// }), Type.Num, 'Average of the given property in the current atom set.'), +// args => B.core.math.div([ M.aggregate(M.tryGetArg(args, 0), B.core.math.add, 0), B.struct.atomSet.atomCount() ])), - Macro(MSymbol('atom.set.min', Arguments.Dictionary({ - 0: Argument(Type.Num, { description: 'Numeric atom property.'}) - }), Type.Num, 'Minimum of the given property in the current atom set.'), - args => M.aggregate(M.tryGetArg(args, 0), B.core.math.min)) - ], - [ - 'Atom Properties', - Alias(MolScript.structureQuery.atomProperty.core.elementSymbol, 'atom.el'), - Alias(MolScript.structureQuery.atomProperty.core.vdw, 'atom.vdw'), - Alias(MolScript.structureQuery.atomProperty.core.mass, 'atom.mass'), - Alias(MolScript.structureQuery.atomProperty.core.atomicNumber, 'atom.atomic-number'), - Alias(MolScript.structureQuery.atomProperty.core.x, 'atom.x'), - Alias(MolScript.structureQuery.atomProperty.core.y, 'atom.y'), - Alias(MolScript.structureQuery.atomProperty.core.z, 'atom.z'), - Alias(MolScript.structureQuery.atomProperty.core.atomKey, 'atom.key'), - Alias(MolScript.structureQuery.atomProperty.core.bondCount, 'atom.bond-count'), +// Macro(MSymbol('atom.set.min', Arguments.Dictionary({ +// 0: Argument(Type.Num, { description: 'Numeric atom property.'}) +// }), Type.Num, 'Minimum of the given property in the current atom set.'), +// args => M.aggregate(M.tryGetArg(args, 0), B.core.math.min)) +// ], +// [ +// 'Atom Properties', +// Alias(MolScript.structureQuery.atomProperty.core.elementSymbol, 'atom.el'), +// Alias(MolScript.structureQuery.atomProperty.core.vdw, 'atom.vdw'), +// Alias(MolScript.structureQuery.atomProperty.core.mass, 'atom.mass'), +// Alias(MolScript.structureQuery.atomProperty.core.atomicNumber, 'atom.atomic-number'), +// Alias(MolScript.structureQuery.atomProperty.core.x, 'atom.x'), +// Alias(MolScript.structureQuery.atomProperty.core.y, 'atom.y'), +// Alias(MolScript.structureQuery.atomProperty.core.z, 'atom.z'), +// Alias(MolScript.structureQuery.atomProperty.core.atomKey, 'atom.key'), +// Alias(MolScript.structureQuery.atomProperty.core.bondCount, 'atom.bond-count'), - Alias(MolScript.structureQuery.atomProperty.topology.connectedComponentKey, 'atom.key.molecule'), +// Alias(MolScript.structureQuery.atomProperty.topology.connectedComponentKey, 'atom.key.molecule'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.authResidueId, 'atom.auth-resid'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.labelResidueId, 'atom.label-resid'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.residueKey, 'atom.key.res'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.chainKey, 'atom.key.chain'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.entityKey, 'atom.key.entity'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.isHet, 'atom.is-het'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.id, 'atom.id'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.label_atom_id, 'atom.label_atom_id'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.label_alt_id, 'atom.label_alt_id', 'atom.altloc'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.label_comp_id, 'atom.label_comp_id'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.label_asym_id, 'atom.label_asym_id'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.label_entity_id, 'atom.label_entity_id'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.label_seq_id, 'atom.label_seq_id'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.auth_atom_id, 'atom.auth_atom_id', 'atom.name'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.auth_comp_id, 'atom.auth_comp_id', 'atom.resname'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.auth_asym_id, 'atom.auth_asym_id', 'atom.chain'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.auth_seq_id, 'atom.auth_seq_id', 'atom.resno'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.pdbx_PDB_ins_code, 'atom.pdbx_PDB_ins_code', 'atom.inscode'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.pdbx_formal_charge, 'atom.pdbx_formal_charge'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.occupancy, 'atom.occupancy'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.B_iso_or_equiv, 'atom.B_iso_or_equiv', 'atom.bfactor'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.entityType, 'atom.entity-type'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.authResidueId, 'atom.auth-resid'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.labelResidueId, 'atom.label-resid'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.residueKey, 'atom.key.res'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.chainKey, 'atom.key.chain'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.entityKey, 'atom.key.entity'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.isHet, 'atom.is-het'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.id, 'atom.id'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.label_atom_id, 'atom.label_atom_id'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.label_alt_id, 'atom.label_alt_id', 'atom.altloc'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.label_comp_id, 'atom.label_comp_id'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.label_asym_id, 'atom.label_asym_id'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.label_entity_id, 'atom.label_entity_id'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.label_seq_id, 'atom.label_seq_id'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.auth_atom_id, 'atom.auth_atom_id', 'atom.name'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.auth_comp_id, 'atom.auth_comp_id', 'atom.resname'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.auth_asym_id, 'atom.auth_asym_id', 'atom.chain'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.auth_seq_id, 'atom.auth_seq_id', 'atom.resno'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.pdbx_PDB_ins_code, 'atom.pdbx_PDB_ins_code', 'atom.inscode'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.pdbx_formal_charge, 'atom.pdbx_formal_charge'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.occupancy, 'atom.occupancy'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.B_iso_or_equiv, 'atom.B_iso_or_equiv', 'atom.bfactor'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.entityType, 'atom.entity-type'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.secondaryStructureKey, 'atom.key.sec-struct'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.secondaryStructureKey, 'atom.key.sec-struct'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.isModified, 'atom.is-modified'), - Alias(MolScript.structureQuery.atomProperty.macromolecular.modifiedParentName, 'atom.modified-parent'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.isModified, 'atom.is-modified'), +// Alias(MolScript.structureQuery.atomProperty.macromolecular.modifiedParentName, 'atom.modified-parent'), - Macro(MSymbol('atom.sec-struct.is', Arguments.List(Struct.Types.SecondaryStructureFlag), Type.Bool, - `Test if the current atom is part of an secondary structure. Optionally specify allowed sec. struct. types: ${Type.oneOfValues(Struct.Types.SecondaryStructureFlag).join(', ')}`), - args => B.core.flags.hasAny([B.struct.atomProperty.macromolecular.secondaryStructureFlags(), B.struct.type.secondaryStructureFlags(args)])), - ], - [ - 'Bond Properties', - Alias(MolScript.structureQuery.bondProperty.order, 'bond.order'), - Macro(MSymbol('bond.is', Arguments.List(Struct.Types.BondFlag), Type.Bool, - `Test if the current bond has at least one (or all if partial = false) of the specified flags: ${Type.oneOfValues(Struct.Types.BondFlag).join(', ')}`), - args => B.core.flags.hasAny([B.struct.bondProperty.flags(), B.struct.type.bondFlags(M.getPositionalArgs(args))])), - ] - ] -]; +// Macro(MSymbol('atom.sec-struct.is', Arguments.List(Struct.Types.SecondaryStructureFlag), Type.Bool, +// `Test if the current atom is part of an secondary structure. Optionally specify allowed sec. struct. types: ${Type.oneOfValues(Struct.Types.SecondaryStructureFlag).join(', ')}`), +// args => B.core.flags.hasAny([B.struct.atomProperty.macromolecular.secondaryStructureFlags(), B.struct.type.secondaryStructureFlags(args)])), +// ], +// [ +// 'Bond Properties', +// Alias(MolScript.structureQuery.bondProperty.order, 'bond.order'), +// Macro(MSymbol('bond.is', Arguments.List(Struct.Types.BondFlag), Type.Bool, +// `Test if the current bond has at least one (or all if partial = false) of the specified flags: ${Type.oneOfValues(Struct.Types.BondFlag).join(', ')}`), +// args => B.core.flags.hasAny([B.struct.bondProperty.flags(), B.struct.type.bondFlags(M.getPositionalArgs(args))])), +// ] +// ] +// ]; -const list: MolScriptSymbol[] = []; +// const list: MolScriptSymbol[] = []; -function makeList(xs: any[]) { - for (const x of xs) { - if (isMolScriptSymbol(x)) list.push(x); - else if (x instanceof Array) makeList(x); - } -} +// function makeList(xs: any[]) { +// for (const x of xs) { +// if (isMolScriptSymbol(x)) list.push(x); +// else if (x instanceof Array) makeList(x); +// } +// } -makeList(SymbolTable); +// makeList(SymbolTable); -const normalized = (function () { - const symbolList: [string, MolScriptSymbol][] = []; - const symbolMap: { [id: string]: MolScriptSymbol | undefined } = Object.create(null); - const namedArgs = UniqueArray.create<string, string>(); - const constants = UniqueArray.create<string, string>(); +// const normalized = (function () { +// const symbolList: [string, MolScriptSymbol][] = []; +// const symbolMap: { [id: string]: MolScriptSymbol | undefined } = Object.create(null); +// const namedArgs = UniqueArray.create<string, string>(); +// const constants = UniqueArray.create<string, string>(); - for (const s of list) { - for (const a of s.aliases) { - symbolList.push([a, s]); - if (symbolMap[a]) throw new Error(`Alias '${a}' already in use.`); - symbolMap[a] = s; - } - const args = s.symbol.args; - if (args.kind !== 'dictionary') { - if (args.type.kind === 'oneof') { - Type.oneOfValues(args.type).forEach(v => UniqueArray.add(constants, v, v)); - } - continue; - } - for (const a of Object.keys(args.map)) { - if (isNaN(a as any)) UniqueArray.add(namedArgs, a, a); - const arg = ((args.map as any)[a]) as Argument; - if (arg.type.kind === 'oneof') { - Type.oneOfValues(arg.type).forEach(v => UniqueArray.add(constants, v, v)); - } - } - } +// for (const s of list) { +// for (const a of s.aliases) { +// symbolList.push([a, s]); +// if (symbolMap[a]) throw new Error(`Alias '${a}' already in use.`); +// symbolMap[a] = s; +// } +// const args = s.symbol.args; +// if (args.kind !== 'dictionary') { +// if (args.type.kind === 'oneof') { +// Type.oneOfValues(args.type).forEach(v => UniqueArray.add(constants, v, v)); +// } +// continue; +// } +// for (const a of Object.keys(args.map)) { +// if (isNaN(a as any)) UniqueArray.add(namedArgs, a, a); +// const arg = ((args.map as any)[a]) as Argument; +// if (arg.type.kind === 'oneof') { +// Type.oneOfValues(arg.type).forEach(v => UniqueArray.add(constants, v, v)); +// } +// } +// } - return { symbolList, symbolMap, namedArgs: namedArgs.array, constants: constants.array } -})(); +// return { symbolList, symbolMap, namedArgs: namedArgs.array, constants: constants.array } +// })(); -export const MolScriptSymbols = list; -export const Constants = normalized.constants; -export const NamedArgs = normalized.namedArgs; -export const SymbolMap = normalized.symbolMap; -export const SymbolList = normalized.symbolList; +// export const MolScriptSymbols = list; +// export const Constants = normalized.constants; +// export const NamedArgs = normalized.namedArgs; +// export const SymbolMap = normalized.symbolMap; +// export const SymbolList = normalized.symbolList; -const sortedSymbols = SymbolList.map(s => s[0]).sort((a, b) => { - if (a.length === b.length) return (a < b) as any; - return a.length - b.length; -}); -export default [...sortedSymbols, ...NamedArgs.map(a => ':' + a), ...Constants]; \ No newline at end of file +// const sortedSymbols = SymbolList.map(s => s[0]).sort((a, b) => { +// if (a.length === b.length) return (a < b) as any; +// return a.length - b.length; +// }); +// export default [...sortedSymbols, ...NamedArgs.map(a => ':' + a), ...Constants]; \ No newline at end of file diff --git a/src/perf-tests/mol-script.ts b/src/perf-tests/mol-script.ts index 3b488f5f5..104c6c194 100644 --- a/src/perf-tests/mol-script.ts +++ b/src/perf-tests/mol-script.ts @@ -1,12 +1,17 @@ -import Examples from 'mol-script/script/mol-script/examples' -import { parseMolScript } from 'mol-script/script/mol-script/parser' -import * as util from 'util' -//import { compileAST } from 'mol-script/script/mol-script/compile'; - -for (const e of Examples) { - const expr = parseMolScript(e.value)[0]; - console.log(e.name, util.inspect(expr, true, 10, true)); -} +import { MolScriptBuilder } from 'mol-script/language/builder'; +import { compile } from 'mol-script/runtime/query/compiler'; +import { QueryContext, Structure, StructureQuery } from 'mol-model/structure'; +import { readCifFile, getModelsAndStructure } from '../apps/structure-info/model'; + +// import Examples from 'mol-script/script/mol-script/examples' +// import { parseMolScript } from 'mol-script/script/mol-script/parser' +// import * as util from 'util' +// //import { compileAST } from 'mol-script/script/mol-script/compile'; + +// for (const e of Examples) { +// const expr = parseMolScript(e.value)[0]; +// console.log(e.name, util.inspect(expr, true, 10, true)); +// } // const exprs = parseMolScript(`(sel.atom.atom-groups // :residue-test (= atom.auth_comp_id ALA) // ;; ho ho ho @@ -15,4 +20,32 @@ for (const e of Examples) { // ((hi) (ho))`); // console.log(util.inspect(exprs, true, 10, true)); -// //console.log(expr); \ No newline at end of file +// //console.log(expr); + +const expr = MolScriptBuilder.core.math.add([1, 2, 3]); +const compiled = compile<number>(expr); +const result = compiled(new QueryContext(Structure.Empty)); +console.log(result); + +async function testQ() { + const frame = await readCifFile('e:/test/quick/1cbs_updated.cif'); + const { structure } = await getModelsAndStructure(frame); + + const expr = MolScriptBuilder.struct.generator.atomGroups({ + 'atom-test': MolScriptBuilder.core.rel.eq([ + MolScriptBuilder.struct.atomProperty.core.elementSymbol(), + MolScriptBuilder.es('C') + ]), + 'residue-test': MolScriptBuilder.core.rel.eq([ + MolScriptBuilder.struct.atomProperty.macromolecular.label_comp_id(), + 'REA' + ]) + }); + + const compiled = compile<StructureQuery>(expr); + const result = compiled(new QueryContext(structure)); + + console.log(result); +} + +testQ(); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 8da5b2ddc..48fb8c0dc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,8 @@ "noUnusedLocals": true, "strictNullChecks": true, "strictFunctionTypes": true, - "keyofStringsOnly": true, + //"keyofStringsOnly": true, + // temp fix for immutable package: has(key: string): key is keyof TProps; => has(key: string | number | Symbol): key is keyof TProps; //"downlevelIteration": true, "jsx": "react", "lib": [ "es6", "dom", "esnext.asynciterable", "es2016" ], -- GitLab