Browse Source

Merge branch 'master' into themes

# Conflicts:
#	src/mol-model/structure/structure/element.ts
Alexander Rose 6 years ago
parent
commit
a8198c52ca
72 changed files with 2499 additions and 1038 deletions
  1. 2 1
      README.md
  2. 218 218
      package-lock.json
  3. 4 2
      package.json
  4. 8 3
      src/apps/structure-info/model.ts
  5. 4 1
      src/helpers.d.ts
  6. 1 1
      src/mol-app/ui/visualization/sequence-view.tsx
  7. 14 14
      src/mol-data/db/column.ts
  8. 1 1
      src/mol-data/int/sorted-ranges.ts
  9. 7 7
      src/mol-io/common/binary-cif/array-encoder.ts
  10. 173 0
      src/mol-io/common/binary-cif/classifier.ts
  11. 4 4
      src/mol-io/common/binary-cif/encoding.ts
  12. 1 1
      src/mol-io/reader/cif/binary/field.ts
  13. 18 1
      src/mol-io/reader/cif/data-model.ts
  14. 3 3
      src/mol-io/reader/cif/schema.ts
  15. 18 0
      src/mol-io/reader/cif/schema/mmcif-extras.ts
  16. 1 1
      src/mol-io/reader/cif/text/field.ts
  17. 1 1
      src/mol-io/reader/common/text/column/fixed.ts
  18. 1 1
      src/mol-io/reader/common/text/column/token.ts
  19. 70 14
      src/mol-io/reader/common/text/number-parser.ts
  20. 148 0
      src/mol-model-props/pdbe/structure-quality-report.ts
  21. 0 0
      src/mol-model-props/rcsb/graphql/symmetry.gql.ts
  22. 0 0
      src/mol-model-props/rcsb/graphql/types.ts
  23. 13 12
      src/mol-model-props/rcsb/symmetry.ts
  24. 31 3
      src/mol-model/structure/export/categories/atom_site.ts
  25. 5 1
      src/mol-model/structure/export/mmcif.ts
  26. 1 0
      src/mol-model/structure/model/formats/mmcif/bonds/comp.ts
  27. 1 0
      src/mol-model/structure/model/formats/mmcif/bonds/struct_conn.ts
  28. 1 1
      src/mol-model/structure/model/properties/atomic/hierarchy.ts
  29. 3 3
      src/mol-model/structure/model/properties/coarse/hierarchy.ts
  30. 2 1
      src/mol-model/structure/model/properties/custom.ts
  31. 13 3
      src/mol-model/structure/model/properties/custom/descriptor.ts
  32. 94 0
      src/mol-model/structure/model/properties/custom/residue.ts
  33. 4 4
      src/mol-model/structure/model/properties/utils/coarse-keys.ts
  34. 2 2
      src/mol-model/structure/query/context.ts
  35. 2 2
      src/mol-model/structure/query/query.ts
  36. 30 0
      src/mol-model/structure/structure/element.ts
  37. 1 10
      src/mol-model/structure/structure/properties.ts
  38. 17 10
      src/mol-model/structure/structure/structure.ts
  39. 4 4
      src/mol-model/structure/structure/symmetry.ts
  40. 78 79
      src/mol-script/compiler.ts
  41. 7 9
      src/mol-script/language/builder.ts
  42. 0 0
      src/mol-script/language/container.ts
  43. 7 7
      src/mol-script/language/expression-formatter.ts
  44. 6 6
      src/mol-script/language/expression.ts
  45. 5 5
      src/mol-script/language/helpers.ts
  46. 178 0
      src/mol-script/language/parser.ts
  47. 6 6
      src/mol-script/language/symbol-table.ts
  48. 11 2
      src/mol-script/language/symbol-table/core.ts
  49. 0 0
      src/mol-script/language/symbol-table/structure-query.ts
  50. 19 9
      src/mol-script/language/symbol.ts
  51. 1 1
      src/mol-script/language/type.ts
  52. 34 11
      src/mol-script/runtime/environment.ts
  53. 19 19
      src/mol-script/runtime/expression.ts
  54. 81 0
      src/mol-script/runtime/macro.ts
  55. 175 0
      src/mol-script/runtime/query/compiler.ts
  56. 273 0
      src/mol-script/runtime/query/table.ts
  57. 24 24
      src/mol-script/runtime/symbol.ts
  58. 103 103
      src/mol-script/script/mol-script/examples.ts
  59. 34 34
      src/mol-script/script/mol-script/macro.ts
  60. 178 178
      src/mol-script/script/mol-script/parser.ts
  61. 121 76
      src/mol-script/script/mol-script/symbols.ts
  62. 1 1
      src/mol-script/script/parser.ts
  63. 33 0
      src/mol-util/number.ts
  64. 31 0
      src/mol-util/retry-if.ts
  65. 10 0
      src/mol-util/upper-case.ts
  66. 1 1
      src/mol-util/uuid.ts
  67. 88 9
      src/perf-tests/mol-script.ts
  68. 4 4
      src/servers/model/properties.ts
  69. 18 0
      src/servers/model/properties/pdbe.ts
  70. 0 123
      src/servers/model/properties/structure-quality-report.ts
  71. 29 0
      src/servers/model/utils/fetch-retry.ts
  72. 3 1
      tsconfig.json

+ 2 - 1
README.md

@@ -1,5 +1,5 @@
 [![License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](./LICENSE)
-[![Build Status](https://travis-ci.org/molstar/molstar-proto.svg?branch=master)](https://travis-ci.org/mol-star/mol-star-proto)
+[![Build Status](https://travis-ci.org/molstar/molstar-proto.svg?branch=master)](https://travis-ci.org/molstar/molstar-proto)
 [![Gitter](https://badges.gitter.im/molstar/Lobby.svg)](https://gitter.im/molstar/Lobby)
 
 # Mol*
@@ -17,6 +17,7 @@ The core of Mol* currently consists of these modules:
 - `mol-math` Math related (loosely) algorithms and data structures.
 - `mol-io` Parsing library. Each format is parsed into an interface that corresponds to the data stored by it. Support for common coordinate, experimental/map, and annotation data formats.
 - `mol-model` Data structures and algorithms (such as querying) for representing molecular data (including coordinate, experimental/map, and annotation data).
+- `mol-model-props` Common "custom properties"
 - `mol-script` A scriting language for creating representations/scenes and querying (includes the [MolQL query language](https://molql.github.io)).
 - `mol-geo` Creating molecular geometries.
 - `mol-gl` A wrapper around WebGL. Uses `mol-geo` to generate geometries.

File diff suppressed because it is too large
+ 218 - 218
package-lock.json


+ 4 - 2
package.json

@@ -43,6 +43,7 @@
       "node_modules",
       "build/node_modules"
     ],
+    "testURL": "http://localhost/",
     "moduleNameMapper": {
       "mol-app($|/.*)": "<rootDir>/src/mol-app$1",
       "mol-data($|/.*)": "<rootDir>/src/mol-data$1",
@@ -51,6 +52,7 @@
       "mol-io($|/.*)": "<rootDir>/src/mol-io$1",
       "mol-math($|/.*)": "<rootDir>/src/mol-math$1",
       "mol-model($|/.*)": "<rootDir>/src/mol-model$1",
+      "mol-model-props($|/.*)": "<rootDir>/src/mol-model-props$1",
       "mol-ql($|/.*)": "<rootDir>/src/mol-ql$1",
       "mol-script($|/.*)": "<rootDir>/src/mol-script$1",
       "mol-task($|/.*)": "<rootDir>/src/mol-task$1",
@@ -83,8 +85,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",

+ 8 - 3
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);
 }
 

+ 4 - 1
src/helpers.d.ts

@@ -9,7 +9,10 @@ declare module Helpers {
     export type Mutable<T> = {
         -readonly [P in keyof T]: T[P]
     }
-    export type TypedArray = Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array
+    export type TypedIntArray = Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array
+    export type TypedFloatArray = Float32Array | Float64Array
+
+    export type TypedArray = TypedIntArray | TypedFloatArray
     export type NumberArray = TypedArray | number[]
     export type UintArray = Uint8Array | Uint16Array | Uint32Array | number[]
     export type ValueOf<T> = T[keyof T]

+ 1 - 1
src/mol-app/ui/visualization/sequence-view.tsx

@@ -17,7 +17,7 @@ export class SequenceView extends View<SequenceViewController, {}, {}> {
         const s = this.controller.latestState.structure;
         if (!s) return <div className='molstar-sequence-view-wrap'>No structure available.</div>;
 
-        const seqs = Structure.getModels(s)[0].sequence.sequences;
+        const seqs = s.models[0].sequence.sequences;
         return <div className='molstar-sequence-view-wrap'>
             {seqs.map((seq, i) => <EntitySequence key={i} ctx={this.controller.context} seq={seq} structure={s} /> )}
         </div>;

+ 14 - 14
src/mol-data/db/column.ts

@@ -10,7 +10,7 @@ import { Tensor as Tensors } from 'mol-math/linear-algebra'
 
 interface Column<T> {
     readonly schema: Column.Schema,
-    readonly '@array': ArrayLike<any> | undefined,
+    readonly __array: ArrayLike<any> | undefined,
 
     readonly isDefined: boolean,
     readonly rowCount: number,
@@ -159,14 +159,14 @@ namespace Column {
 
     /** Makes the column backed by an array. Useful for columns that are accessed often. */
     export function asArrayColumn<T>(c: Column<T>, array?: ArrayCtor<T>): Column<T> {
-        if (c['@array']) return c;
+        if (c.__array) return c;
         if (!c.isDefined) return Undefined(c.rowCount, c.schema) as any as Column<T>;
         return arrayColumn({ array: c.toArray({ array }), schema: c.schema, valueKind: c.valueKind });
     }
 
     export function copyToArray<T extends number>(c: Column<T>, array: { [k: number]: T, length: number }, offset = 0) {
         if (!c.isDefined) return;
-        const cArray = c['@array']
+        const cArray = c.__array
         if (cArray) {
             for (let i = 0, _i = cArray.length; i < _i; i++) array[offset + i] = cArray[i];
         } else {
@@ -200,7 +200,7 @@ function constColumn<T extends Column.Schema>(v: T['T'], rowCount: number, schem
     const value: Column<T['T']>['value'] = row => v;
     return {
         schema: schema,
-        '@array': void 0,
+        __array: void 0,
         isDefined: valueKind === Column.ValueKind.Present,
         rowCount,
         value,
@@ -217,7 +217,7 @@ function constColumn<T extends Column.Schema>(v: T['T'], rowCount: number, schem
 function lambdaColumn<T extends Column.Schema>({ value, valueKind, rowCount, schema }: Column.LambdaSpec<T>): Column<T['T']> {
     return {
         schema: schema,
-        '@array': void 0,
+        __array: void 0,
         isDefined: true,
         rowCount,
         value,
@@ -240,7 +240,7 @@ function arrayColumn<T extends Column.Schema>({ array, schema, valueKind }: Colu
     const isTyped = ColumnHelpers.isTypedArray(array);
     return {
         schema: schema,
-        '@array': array,
+        __array: array,
         isDefined: true,
         rowCount,
         value,
@@ -271,12 +271,12 @@ function arrayColumn<T extends Column.Schema>({ array, schema, valueKind }: Colu
 function windowColumn<T>(column: Column<T>, start: number, end: number) {
     if (!column.isDefined) return Column.Undefined(end - start, column.schema);
     if (start === 0 && end === column.rowCount) return column;
-    if (!!column['@array'] && ColumnHelpers.isTypedArray(column['@array'])) return windowTyped(column, start, end);
+    if (!!column.__array && ColumnHelpers.isTypedArray(column.__array)) return windowTyped(column, start, end);
     return windowFull(column, start, end);
 }
 
 function windowTyped<T>(c: Column<T>, start: number, end: number): Column<T> {
-    const array = ColumnHelpers.typedArrayWindow(c['@array'], { start, end });
+    const array = ColumnHelpers.typedArrayWindow(c.__array, { start, end });
     return arrayColumn({ array, schema: c.schema, valueKind: c.valueKind }) as any;
 }
 
@@ -286,7 +286,7 @@ function windowFull<T>(c: Column<T>, start: number, end: number): Column<T> {
     const rowCount = end - start;
     return {
         schema: c.schema,
-        '@array': void 0,
+        __array: void 0,
         isDefined: c.isDefined,
         rowCount,
         value,
@@ -311,12 +311,12 @@ function isIdentity(map: ArrayLike<number>, rowCount: number) {
 function columnView<T>(c: Column<T>, map: ArrayLike<number>, checkIdentity: boolean): Column<T> {
     if (!c.isDefined) return c;
     if (checkIdentity && isIdentity(map, c.rowCount)) return c;
-    if (!!c['@array']) return arrayView(c, map);
+    if (!!c.__array) return arrayView(c, map);
     return viewFull(c, map);
 }
 
 function arrayView<T>(c: Column<T>, map: ArrayLike<number>): Column<T> {
-    const array = c['@array']!;
+    const array = c.__array!;
     const ret = new (array as any).constructor(map.length);
     for (let i = 0, _i = map.length; i < _i; i++) ret[i] = array[map[i]];
     return arrayColumn({ array: ret, schema: c.schema, valueKind: c.valueKind });
@@ -328,7 +328,7 @@ function viewFull<T>(c: Column<T>, map: ArrayLike<number>): Column<T> {
     const rowCount = map.length;
     return {
         schema: c.schema,
-        '@array': void 0,
+        __array: void 0,
         isDefined: c.isDefined,
         rowCount,
         value,
@@ -351,12 +351,12 @@ function mapToArrayImpl<T, S>(c: Column<T>, f: (v: T) => S, ctor: Column.ArrayCt
 function areColumnsEqual(a: Column<any>, b: Column<any>) {
     if (a === b) return true;
     if (a.rowCount !== b.rowCount || a.isDefined !== b.isDefined || a.schema.valueType !== b.schema.valueType) return false;
-    if (!!a['@array'] && !!b['@array']) return areArraysEqual(a, b);
+    if (!!a.__array && !!b.__array) return areArraysEqual(a, b);
     return areValuesEqual(a, b);
 }
 
 function areArraysEqual(a: Column<any>, b: Column<any>) {
-    const xs = a['@array']!, ys = b['@array']!;
+    const xs = a.__array!, ys = b.__array!;
     for (let i = 0, _i = a.rowCount; i < _i; i++) {
         if (xs[i] !== ys[i]) return false;
     }

+ 1 - 1
src/mol-data/int/sorted-ranges.ts

@@ -70,7 +70,7 @@ namespace SortedRanges {
         constructor(private ranges: SortedRanges<T>, private set: OrderedSet<T>) {
             // TODO cleanup, refactor to make it clearer
             const min = SortedArray.findPredecessorIndex(this.ranges, OrderedSet.min(set))
-            const max = SortedArray.findPredecessorIndex(this.ranges, OrderedSet.max(set))
+            const max = SortedArray.findPredecessorIndex(this.ranges, OrderedSet.max(set) + 1)
             if (ranges.length && min !== max) {
                 this.curIndex = this.getRangeIndex(OrderedSet.min(set))
                 this.maxIndex = Math.min(ranges.length - 2, this.getRangeIndex(OrderedSet.max(set)))

+ 7 - 7
src/mol-io/common/binary-cif/array-encoder.ts

@@ -100,7 +100,7 @@ export namespace ArrayEncoding {
         [Encoding.FloatDataType.Float64]: 8
     }
 
-    export function byteArray(data: Encoding.FloatArray | Encoding.IntArray) {
+    export function byteArray(data: Encoding.TypedFloatArray | Encoding.TypedIntArray) {
         let type = Encoding.getDataType(data);
 
         if (type === Encoding.IntDataType.Int8) return int8(data as Int8Array);
@@ -118,7 +118,7 @@ export namespace ArrayEncoding {
         };
     }
 
-    function _fixedPoint(data: Encoding.FloatArray, factor: number): Result {
+    function _fixedPoint(data: Encoding.TypedFloatArray, factor: number): Result {
         let srcType = Encoding.getDataType(data) as Encoding.FloatDataType;
         let result = new Int32Array(data.length);
         for (let i = 0, n = data.length; i < n; i++) {
@@ -129,9 +129,9 @@ export namespace ArrayEncoding {
             data: result
         };
     }
-    export function fixedPoint(factor: number): Provider { return data => _fixedPoint(data as Encoding.FloatArray, factor); }
+    export function fixedPoint(factor: number): Provider { return data => _fixedPoint(data as Encoding.TypedFloatArray, factor); }
 
-    function _intervalQuantizaiton(data: Encoding.FloatArray, min: number, max: number, numSteps: number, arrayType: new (size: number) => Encoding.IntArray): Result {
+    function _intervalQuantizaiton(data: Encoding.TypedFloatArray, min: number, max: number, numSteps: number, arrayType: new (size: number) => Encoding.TypedIntArray): Result {
         let srcType = Encoding.getDataType(data) as Encoding.FloatDataType;
         if (!data.length) {
             return {
@@ -161,11 +161,11 @@ export namespace ArrayEncoding {
             data: output
         };
     }
-    export function intervalQuantizaiton(min: number, max: number, numSteps: number, arrayType: new (size: number) => Encoding.IntArray = Int32Array): Provider {
-        return data => _intervalQuantizaiton(data as Encoding.FloatArray, min, max, numSteps, arrayType);
+    export function intervalQuantizaiton(min: number, max: number, numSteps: number, arrayType: new (size: number) => Encoding.TypedIntArray = Int32Array): Provider {
+        return data => _intervalQuantizaiton(data as Encoding.TypedFloatArray, min, max, numSteps, arrayType);
     }
 
-    export function runLength(data: Encoding.IntArray): Result {
+    export function runLength(data: Encoding.TypedIntArray): Result {
         let srcType = Encoding.getDataType(data) as Encoding.IntDataType;
         if (srcType === void 0) {
             data = new Int32Array(data);

+ 173 - 0
src/mol-io/common/binary-cif/classifier.ts

@@ -0,0 +1,173 @@
+
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { ArrayEncoder, ArrayEncoding as E } from './array-encoder';
+import { getArrayMantissaMultiplier } from 'mol-util/number';
+
+export function classifyIntArray(xs: ArrayLike<number>) {
+    return IntClassifier.classify(xs as number[]);
+}
+
+export function classifyFloatArray(xs: ArrayLike<number>) {
+    return FloatClassifier.classify(xs as number[]);
+}
+
+namespace IntClassifier {
+    function packSize(value: number, upperLimit: number) {
+        return value >= 0
+            ? Math.ceil((value + 1) / upperLimit)
+            : Math.ceil((value + 1) / (-upperLimit - 1));
+    }
+
+    type IntColumnInfo = { signed: boolean, limit8: number, limit16: number };
+
+    function getInfo(data: number[]): IntColumnInfo {
+        let signed = false;
+        for (let i = 0, n = data.length; i < n; i++) {
+            if (data[i] < 0) {
+                signed = true;
+                break;
+            }
+        }
+        return signed ? { signed, limit8: 0x7F, limit16: 0x7FFF } : { signed, limit8: 0xFF, limit16: 0xFFFF };
+    }
+
+    type SizeInfo = { pack8: number, pack16: number, count: number }
+    function SizeInfo(): SizeInfo { return { pack8: 0, pack16: 0, count: 0 } };
+
+    function incSize({ limit8, limit16 }: IntColumnInfo, info: SizeInfo, value: number) {
+        info.pack8 += packSize(value, limit8);
+        info.pack16 += packSize(value, limit16);
+        info.count += 1;
+    }
+
+    function incSizeSigned(info: SizeInfo, value: number) {
+        info.pack8 += packSize(value, 0x7F);
+        info.pack16 += packSize(value, 0x7FFF);
+        info.count += 1;
+    }
+
+    function byteSize(info: SizeInfo) {
+        if (info.count * 4 < info.pack16 * 2) return { length: info.count * 4, elem: 4 };
+        if (info.pack16 * 2 < info.pack8) return { length: info.pack16 * 2, elem: 2 };
+        return { length: info.pack8, elem: 1 };
+    }
+
+    function packingSize(data: number[], info: IntColumnInfo) {
+        const size = SizeInfo();
+        for (let i = 0, n = data.length; i < n; i++) {
+            incSize(info, size, data[i]);
+        }
+        return { ...byteSize(size), kind: 'pack' };
+    }
+
+    function deltaSize(data: number[], info: IntColumnInfo) {
+        const size = SizeInfo();
+        let prev = data[0];
+        for (let i = 1, n = data.length; i < n; i++) {
+            incSizeSigned(size, data[i] - prev);
+            prev = data[i];
+        }
+        return { ...byteSize(size), kind: 'delta' };
+    }
+
+    function rleSize(data: number[], info: IntColumnInfo) {
+        const size = SizeInfo();
+        let run = 1;
+        for (let i = 1, n = data.length; i < n; i++) {
+            if (data[i - 1] !== data[i]) {
+                incSize(info, size, data[i - 1]);
+                incSize(info, size, run);
+                run = 1;
+            } else {
+                run++;
+            }
+        }
+        incSize(info, size, data[data.length - 1]);
+        incSize(info, size, run);
+
+        return { ...byteSize(size), kind: 'rle' };
+    }
+
+    function deltaRleSize(data: number[], info: IntColumnInfo) {
+        const size = SizeInfo();
+        let run = 1, prev = 0, prevValue = 0;
+        for (let i = 1, n = data.length; i < n; i++) {
+            const v = data[i] - prev;
+            if (prevValue !== v) {
+                incSizeSigned(size, prevValue);
+                incSizeSigned(size, run);
+                run = 1;
+            } else {
+                run++;
+            }
+            prevValue = v;
+            prev = data[i];
+        }
+        incSizeSigned(size, prevValue);
+        incSizeSigned(size, run);
+
+        return { ...byteSize(size), kind: 'delta-rle' };
+    }
+
+    export function getSize(data: number[]) {
+        const info = getInfo(data);
+        const sizes = [packingSize(data, info), rleSize(data, info), deltaSize(data, info), deltaRleSize(data, info)];
+        sizes.sort((a, b) => a.length - b.length);
+        return sizes;
+    }
+
+    export function classify(data: number[]): ArrayEncoder {
+        if (data.length < 2) return E.by(E.byteArray);
+
+        const sizes = getSize(data);
+        const size = sizes[0];
+
+        switch (size.kind) {
+            case 'pack': return E.by(E.integerPacking);
+            case 'rle': return E.by(E.runLength).and(E.integerPacking);
+            case 'delta': return E.by(E.delta).and(E.integerPacking);
+            case 'delta-rle': return E.by(E.delta).and(E.runLength).and(E.integerPacking);
+        }
+
+        throw new Error('should not happen :)');
+    }
+}
+
+namespace FloatClassifier {
+    const delta = 1e-6;
+    export function classify(data: number[]) {
+        const digitCount = getArrayMantissaMultiplier(data, 4, delta);
+        if (digitCount < 0) return { encoder: E.by(E.byteArray), typedArray: Float64Array };
+
+        // TODO: check for overflows here?
+        if (digitCount === 1) return { encoder: IntClassifier.classify(data), typedArray: Int32Array }
+
+        const intArray = new Int32Array(data.length);
+        for (let i = 0, n = data.length; i < n; i++) {
+            const v = digitCount * data[i];
+            intArray[i] = v;
+            // check if the value didn't overflow
+            if (Math.abs(Math.round(v) / digitCount - intArray[i] / digitCount) > delta) {
+                return { encoder: E.by(E.byteArray), typedArray: Float64Array };
+            }
+        }
+
+        const sizes = IntClassifier.getSize(intArray as any);
+        const size = sizes[0];
+
+        const fp = E.by(E.fixedPoint(digitCount));
+        switch (size.kind) {
+            case 'pack': return { encoder: fp.and(E.integerPacking), typedArray: Float32Array };
+            case 'rle': return { encoder: fp.and(E.runLength).and(E.integerPacking), typedArray: Float32Array };
+            case 'delta': return { encoder: fp.and(E.delta).and(E.integerPacking), typedArray: Float32Array };
+            case 'delta-rle': return { encoder: fp.and(E.delta).and(E.runLength).and(E.integerPacking), typedArray: Float32Array };
+        }
+
+        throw new Error('should not happen :)');
+    }
+}

+ 4 - 4
src/mol-io/common/binary-cif/encoding.ts

@@ -71,10 +71,10 @@ export namespace Encoding {
 
     export type DataType = IntDataType | FloatDataType
 
-    export type IntArray = Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array
-    export type FloatArray = Float32Array | Float64Array
+    export type TypedIntArray = Helpers.TypedIntArray
+    export type TypedFloatArray = Helpers.TypedFloatArray
 
-    export function getDataType(data: IntArray | FloatArray): DataType {
+    export function getDataType(data: TypedIntArray | TypedFloatArray): DataType {
         let srcType: DataType;
         if (data instanceof Int8Array) srcType = Encoding.IntDataType.Int8;
         else if (data instanceof Int16Array) srcType = Encoding.IntDataType.Int16;
@@ -88,7 +88,7 @@ export namespace Encoding {
         return srcType;
     }
 
-    export function isSignedIntegerDataType(data: IntArray) {
+    export function isSignedIntegerDataType(data: TypedIntArray) {
         return data instanceof Int8Array || data instanceof Int16Array || data instanceof Int32Array;
     }
 

+ 1 - 1
src/mol-io/reader/cif/binary/field.ts

@@ -38,7 +38,7 @@ export default function Field(column: EncodedColumn): Data.CifField {
     const rowCount = data.length;
 
     return {
-        '@array': data,
+        __array: data,
         isDefined: true,
         rowCount,
         str,

+ 18 - 1
src/mol-io/reader/cif/data-model.ts

@@ -7,6 +7,7 @@
 
 import { Column } from 'mol-data/db'
 import { Tensor } from 'mol-math/linear-algebra'
+import { getNumberType, NumberType } from '../common/text/number-parser';
 
 export interface CifFile {
     readonly name?: string,
@@ -61,7 +62,7 @@ export namespace CifCategory {
  * This is to ensure that the functions can invoked without having to "bind" them.
  */
 export interface CifField {
-    readonly '@array': ArrayLike<any> | undefined
+    readonly __array: ArrayLike<any> | undefined
     readonly isDefined: boolean,
     readonly rowCount: number,
 
@@ -108,4 +109,20 @@ export function getTensor(category: CifCategory, field: string, space: Tensor.Sp
         }
     } else throw new Error('Tensors with rank > 3 or rank 0 are currently not supported.');
     return ret;
+}
+
+export function getCifFieldType(field: CifField): Column.Schema.Int | Column.Schema.Float | Column.Schema.Str {
+    let floatCount = 0, hasString = false;
+    for (let i = 0, _i = field.rowCount; i < _i; i++) {
+        const k = field.valueKind(i);
+        if (k !== Column.ValueKind.Present) continue
+        const type = getNumberType(field.str(i));
+        if (type === NumberType.Int) continue;
+        else if (type === NumberType.Float) floatCount++;
+        else { hasString = true; break; }
+    }
+
+    if (hasString) return Column.Schema.str;
+    if (floatCount > 0) return Column.Schema.float;
+    return Column.Schema.int;
 }

+ 3 - 3
src/mol-io/reader/cif/schema.ts

@@ -41,7 +41,7 @@ function getColumnCtor(t: Column.Schema): ColumnCtor {
 function createColumn<T>(schema: Column.Schema, field: Data.CifField, value: (row: number) => T, toArray: Column<T>['toArray']): Column<T> {
     return {
         schema,
-        '@array': field['@array'],
+        __array: field.__array,
         isDefined: field.isDefined,
         rowCount: field.rowCount,
         value,
@@ -61,7 +61,7 @@ function createListColumn<T extends number|string>(schema: Column.Schema.List<T>
 
     return {
         schema,
-        '@array': void 0,
+        __array: void 0,
         isDefined: !!f,
         rowCount: category.rowCount,
         value,
@@ -89,7 +89,7 @@ function createTensorColumn(schema: Column.Schema.Tensor, category: Data.CifCate
 
     return {
         schema,
-        '@array': void 0,
+        __array: void 0,
         isDefined: first.isDefined,
         rowCount: category.rowCount,
         value,

+ 18 - 0
src/mol-io/reader/cif/schema/mmcif-extras.ts

@@ -0,0 +1,18 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+ import { mmCIF_Schema } from './mmcif';
+
+export const mmCIF_residueId_schema = {
+    label_comp_id: mmCIF_Schema.atom_site.label_comp_id,
+    label_seq_id: mmCIF_Schema.atom_site.label_seq_id,
+    pdbx_PDB_ins_code: mmCIF_Schema.atom_site.pdbx_PDB_ins_code,
+    label_asym_id: mmCIF_Schema.atom_site.label_asym_id,
+    label_entity_id: mmCIF_Schema.atom_site.label_entity_id,
+    auth_comp_id: mmCIF_Schema.atom_site.auth_atom_id,
+    auth_seq_id: mmCIF_Schema.atom_site.auth_seq_id,
+    auth_asym_id: mmCIF_Schema.atom_site.auth_asym_id
+}

+ 1 - 1
src/mol-io/reader/cif/text/field.ts

@@ -38,7 +38,7 @@ export default function CifTextField(tokens: Tokens, rowCount: number): Data.Cif
     };
 
     return {
-        '@array': void 0,
+        __array: void 0,
         isDefined: true,
         rowCount,
         str,

+ 1 - 1
src/mol-io/reader/common/text/column/fixed.ts

@@ -35,7 +35,7 @@ export function FixedColumn<T extends Column.Schema>(lines: Tokens, offset: numb
     };
     return {
         schema: schema,
-        '@array': void 0,
+        __array: void 0,
         isDefined: true,
         rowCount,
         value,

+ 1 - 1
src/mol-io/reader/common/text/column/token.ts

@@ -27,7 +27,7 @@ export function TokenColumn<T extends Column.Schema>(tokens: Tokens, schema: T):
 
     return {
         schema: schema,
-        '@array': void 0,
+        __array: void 0,
         isDefined: true,
         rowCount,
         value,

+ 70 - 14
src/mol-io/reader/common/text/number-parser.ts

@@ -18,10 +18,10 @@ export function parseIntSkipLeadingWhitespace(str: string, start: number, end: n
 }
 
 export function parseInt(str: string, start: number, end: number) {
-    let ret = 0, neg = 1;
-    if (str.charCodeAt(start) === 45 /* - */) { neg = -1; start++; }
-    for (; start < end; start++) {
-        const c = str.charCodeAt(start) - 48;
+    let _start = start, ret = 0, neg = 1;
+    if (str.charCodeAt(_start) === 45 /* - */) { neg = -1; _start++; }
+    for (; _start < end; _start++) {
+        const c = str.charCodeAt(_start) - 48;
         if (c > 9 || c < 0) return (neg * ret) | 0;
         else ret = (10 * ret + c) | 0;
     }
@@ -40,37 +40,93 @@ export function parseFloatSkipLeadingWhitespace(str: string, start: number, end:
 }
 
 export function parseFloat(str: string, start: number, end: number) {
-    let neg = 1.0, ret = 0.0, point = 0.0, div = 1.0;
+    let _start = start, neg = 1.0, ret = 0.0, point = 0.0, div = 1.0;
 
-    if (str.charCodeAt(start) === 45) {
+    if (str.charCodeAt(_start) === 45) {
         neg = -1.0;
+        ++_start;
+    }
+
+    while (_start < end) {
+        let c = str.charCodeAt(_start) - 48;
+        if (c >= 0 && c < 10) {
+            ret = ret * 10 + c;
+            ++_start;
+        } else if (c === -2) { // .
+            ++_start;
+            while (_start < end) {
+                c = str.charCodeAt(_start) - 48;
+                if (c >= 0 && c < 10) {
+                    point = 10.0 * point + c;
+                    div = 10.0 * div;
+                    ++_start;
+                } else if (c === 53 || c === 21) { // 'e'/'E'
+                    return parseScientific(neg * (ret + point / div), str, _start + 1, end);
+                } else {
+                    return neg * (ret + point / div);
+                }
+            }
+            return neg * (ret + point / div);
+        } else if (c === 53 || c === 21) { // 'e'/'E'
+            return parseScientific(neg * ret, str, _start + 1, end);
+        }
+        else break;
+    }
+    return neg * ret;
+}
+
+export const enum NumberType {
+    Int,
+    Float,
+    NaN
+}
+
+function isInt(str: string, start: number, end: number) {
+    if (str.charCodeAt(start) === 45 /* - */) { start++; }
+    for (; start < end; start++) {
+        const c = str.charCodeAt(start) - 48;
+        if (c > 9 || c < 0) return false;
+    }
+    return true;
+}
+
+// TODO: check for "scientific integers?"
+function getNumberTypeScientific(str: string, start: number, end: number) {
+    // handle + in '1e+1' separately.
+    if (str.charCodeAt(start) === 43 /* + */) start++;
+    return isInt(str, start, end) ? NumberType.Float : NumberType.NaN;
+}
+
+/** The whole range must match, otherwise returns NaN */
+export function getNumberType(str: string): NumberType {
+    let start = 0, end = str.length;
+    if (str.charCodeAt(start) === 45) {
         ++start;
     }
 
     while (start < end) {
         let c = str.charCodeAt(start) - 48;
         if (c >= 0 && c < 10) {
-            ret = ret * 10 + c;
             ++start;
         } else if (c === -2) { // .
             ++start;
+            let hasDigit = false;
             while (start < end) {
                 c = str.charCodeAt(start) - 48;
                 if (c >= 0 && c < 10) {
-                    point = 10.0 * point + c;
-                    div = 10.0 * div;
+                    hasDigit = true;
                     ++start;
                 } else if (c === 53 || c === 21) { // 'e'/'E'
-                    return parseScientific(neg * (ret + point / div), str, start + 1, end);
+                    return getNumberTypeScientific(str, start + 1, end);
                 } else {
-                    return neg * (ret + point / div);
+                    return NumberType.NaN;
                 }
             }
-            return neg * (ret + point / div);
+            return hasDigit ? NumberType.Float : NumberType.Int;
         } else if (c === 53 || c === 21) { // 'e'/'E'
-            return parseScientific(neg * ret, str, start + 1, end);
+            return getNumberTypeScientific(str, start + 1, end);
         }
         else break;
     }
-    return neg * ret;
+    return NumberType.Int;
 }

+ 148 - 0
src/mol-model-props/pdbe/structure-quality-report.ts

@@ -0,0 +1,148 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { CifWriter } from 'mol-io/writer/cif';
+import { Model, ModelPropertyDescriptor, ResidueIndex, Unit, ResidueCustomProperty } from 'mol-model/structure';
+import { residueIdFields } from 'mol-model/structure/export/categories/atom_site';
+import CifField = CifWriter.Field;
+import { mmCIF_residueId_schema } from 'mol-io/reader/cif/schema/mmcif-extras';
+import { Column, Table } from 'mol-data/db';
+import { toTable } from 'mol-io/reader/cif/schema';
+import { StructureElement } from 'mol-model/structure/structure';
+
+
+import { QuerySymbolRuntime } from 'mol-script/runtime/query/compiler';
+import { CustomPropSymbol } from 'mol-script/language/symbol';
+import Type from 'mol-script/language/type';
+
+type IssueMap = ResidueCustomProperty<string[]>
+
+const _Descriptor = ModelPropertyDescriptor({
+    isStatic: true,
+    name: 'structure_quality_report',
+    cifExport: {
+        prefix: 'pdbe',
+        categories: [{
+            name: 'pdbe_structure_quality_report',
+            instance() {
+                return { fields: _structure_quality_report_fields, rowCount: 1 }
+            }
+        }, {
+            name: 'pdbe_structure_quality_report_issues',
+            instance(ctx) {
+                const issues = StructureQualityReport.get(ctx.model);
+                if (!issues) return CifWriter.Category.Empty;
+                return ResidueCustomProperty.createCifCategory(ctx, issues, _structure_quality_report_issues_fields);
+            }
+        }]
+    },
+    symbols: {
+        issueCount: QuerySymbolRuntime.Dynamic(CustomPropSymbol('pdbe', 'structure-quality.issue-count', Type.Num),
+            ctx => StructureQualityReport.getIssues(ctx.element).length),
+        // TODO: add (hasIssue :: IssueType(extends string) -> boolean) symbol
+    }
+})
+
+type ExportCtx = ResidueCustomProperty.ExportCtx<string[]>
+const _structure_quality_report_issues_fields: CifField<number, ExportCtx>[] = [
+    CifField.index('id'),
+    ...residueIdFields<number, ExportCtx>((i, d) => d.elements[i]),
+    CifField.str<number, ExportCtx>('issues', (i, d) => d.property(i).join(','))
+];
+
+const _structure_quality_report_fields: CifField<ResidueIndex, ExportCtx>[] = [
+    CifField.str('updated_datetime_utc', () => `${new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '')}`)
+];
+
+function createIssueMapFromJson(modelData: Model, data: any): IssueMap | undefined {
+    const ret = new Map<ResidueIndex, string[]>();
+    if (!data.molecules) return;
+
+    for (const entity of data.molecules) {
+        const entity_id = entity.entity_id.toString();
+        for (const chain of entity.chains) {
+            const asym_id = chain.struct_asym_id.toString();
+            for (const model of chain.models) {
+                const model_id = model.model_id.toString();
+                if (+model_id !== modelData.modelNum) continue;
+
+                for (const residue of model.residues) {
+                    const auth_seq_id = residue.author_residue_number, ins_code = residue.author_insertion_code || '';
+                    const idx = modelData.atomicHierarchy.findResidueKey(entity_id, asym_id, '', auth_seq_id, ins_code);
+                    ret.set(idx, residue.outlier_types);
+                }
+            }
+        }
+    }
+
+    return ResidueCustomProperty.fromMap(ret, Unit.Kind.Atomic);
+}
+
+function createIssueMapFromCif(modelData: Model, data: Table<typeof StructureQualityReport.Schema.pdbe_structure_quality_report_issues>): IssueMap | undefined {
+    const ret = new Map<ResidueIndex, string[]>();
+    const { label_entity_id, label_asym_id, auth_seq_id, pdbx_PDB_ins_code, issues, _rowCount } = data;
+
+    for (let i = 0; i < _rowCount; i++) {
+        const idx = modelData.atomicHierarchy.findResidueKey(label_entity_id.value(i), label_asym_id.value(i), '', auth_seq_id.value(i), pdbx_PDB_ins_code.value(i));
+        ret.set(idx, issues.value(i));
+    }
+
+    return ResidueCustomProperty.fromMap(ret, Unit.Kind.Atomic);
+}
+
+export namespace StructureQualityReport {
+    export const Descriptor = _Descriptor;
+
+    export const Schema = {
+        pdbe_structure_quality_report: {
+            updated_datetime_utc: Column.Schema.str
+        },
+        pdbe_structure_quality_report_issues: {
+            id: Column.Schema.int,
+            ...mmCIF_residueId_schema,
+            issues: Column.Schema.List(',', x => x)
+        }
+    }
+
+    export async function attachFromCifOrApi(model: Model, params: {
+        // provide JSON from api
+        PDBe_apiSourceJson?: (model: Model) => Promise<any>
+    }) {
+        if (model.customProperties.has(Descriptor)) return true;
+
+        let issueMap;
+
+        if (model.sourceData.kind === 'mmCIF' && model.sourceData.frame.categoryNames.includes('pdbe_structure_quality_report')) {
+            const data = toTable(Schema.pdbe_structure_quality_report_issues, model.sourceData.frame.categories.pdbe_structure_quality_report);
+            issueMap = createIssueMapFromCif(model, data);
+        } else if (params.PDBe_apiSourceJson) {
+            const id = model.label.toLowerCase();
+            const json = await params.PDBe_apiSourceJson(model);
+            const data = json[id];
+            if (!data) return false;
+            issueMap = createIssueMapFromJson(model, data);
+        } else {
+            return false;
+        }
+
+        model.customProperties.add(Descriptor);
+        model._staticPropertyData.__StructureQualityReport__ = issueMap;
+        return true;
+    }
+
+    export function get(model: Model): IssueMap | undefined {
+        return model._staticPropertyData.__StructureQualityReport__;
+    }
+
+    const _emptyArray: string[] = [];
+    export function getIssues(e: StructureElement) {
+        if (!Unit.isAtomic(e.unit)) return _emptyArray;
+        const issues = StructureQualityReport.get(e.unit.model);
+        if (!issues) return _emptyArray;
+        const rI = e.unit.residueIndex[e.element];
+        return issues.has(rI) ? issues.get(rI)! : _emptyArray;
+    }
+}

+ 0 - 0
src/servers/model/properties/rcsb/graphql/symmetry.gql.ts → src/mol-model-props/rcsb/graphql/symmetry.gql.ts


+ 0 - 0
src/servers/model/properties/rcsb/graphql/types.ts → src/mol-model-props/rcsb/graphql/types.ts


+ 13 - 12
src/servers/model/properties/rcsb/symmetry.ts → src/mol-model-props/rcsb/symmetry.ts

@@ -32,9 +32,9 @@ function getCategory(name: keyof SymmetryAnnotation.Schema) {
 function createDatabase(assemblies: ReadonlyArray<RcsbSymmetry.Assemblies>): SymmetryAnnotation.Database {
     const Schema = SymmetryAnnotation.Schema
 
-    const featureRows: Table.Row<typeof Schema.symmetry_annotation_feature>[] = []
-    const clusterRows: Table.Row<typeof Schema.symmetry_annotation_cluster>[] = []
-    const axisRows: Table.Row<typeof Schema.symmetry_annotation_axis>[] = []
+    const featureRows: Table.Row<typeof Schema.rcsb_symmetry_annotation_feature>[] = []
+    const clusterRows: Table.Row<typeof Schema.rcsb_symmetry_annotation_cluster>[] = []
+    const axisRows: Table.Row<typeof Schema.rcsb_symmetry_annotation_axis>[] = []
 
     let id = 0
     for (let i = 0, il = assemblies.length; i < il; ++i) {
@@ -83,9 +83,9 @@ function createDatabase(assemblies: ReadonlyArray<RcsbSymmetry.Assemblies>): Sym
     }
 
     return _Database.ofTables('symmetry_annotation', Schema, {
-        symmetry_annotation_feature: Table.ofRows(Schema.symmetry_annotation_feature, featureRows),
-        symmetry_annotation_cluster: Table.ofRows(Schema.symmetry_annotation_cluster, clusterRows),
-        symmetry_annotation_axis: Table.ofRows(Schema.symmetry_annotation_axis, axisRows)
+        symmetry_annotation_feature: Table.ofRows(Schema.rcsb_symmetry_annotation_feature, featureRows),
+        symmetry_annotation_cluster: Table.ofRows(Schema.rcsb_symmetry_annotation_cluster, clusterRows),
+        symmetry_annotation_axis: Table.ofRows(Schema.rcsb_symmetry_annotation_axis, axisRows)
     })
 }
 
@@ -93,10 +93,11 @@ const _Descriptor: ModelPropertyDescriptor = {
     isStatic: true,
     name: 'symmetry_annotation',
     cifExport: {
+        prefix: 'rcsb',
         categories: [
-            getCategory('symmetry_annotation_feature'),
-            getCategory('symmetry_annotation_cluster'),
-            getCategory('symmetry_annotation_axis')
+            getCategory('rcsb_symmetry_annotation_feature'),
+            getCategory('rcsb_symmetry_annotation_cluster'),
+            getCategory('rcsb_symmetry_annotation_axis')
         ]
     }
 }
@@ -105,7 +106,7 @@ const client = new GraphQLClient('http://rest-experimental.rcsb.org/graphql')
 
 export namespace SymmetryAnnotation {
     export const Schema = {
-        symmetry_annotation_feature: {
+        rcsb_symmetry_annotation_feature: {
             id: int,
             assembly_id: str,
             source: str,
@@ -113,12 +114,12 @@ export namespace SymmetryAnnotation {
             stoichiometry_value: List(',', x => x),
             stoichiometry_description: str
         },
-        symmetry_annotation_cluster: {
+        rcsb_symmetry_annotation_cluster: {
             feature_id: int,
             avg_rmsd: float,
             members: List(',', x => x)
         },
-        symmetry_annotation_axis: {
+        rcsb_symmetry_annotation_axis: {
             feature_id: int,
             order: int,
             start: Vector(3),

+ 31 - 3
src/mol-model/structure/export/categories/atom_site.ts

@@ -12,15 +12,21 @@ import CifField = CifWriter.Field
 import CifCategory = CifWriter.Category
 import E = CifWriter.Encodings
 
-const atom_site_fields: CifField<StructureElement>[] = [
+const atom_site_fields: CifField<StructureElement, Structure>[] = [
     CifField.str('group_PDB', P.residue.group_PDB),
     CifField.index('id'),
     CifField.str('type_symbol', P.atom.type_symbol as any),
     CifField.str('label_atom_id', P.atom.label_atom_id),
-    CifField.str('label_alt_id', P.atom.label_alt_id),
 
     CifField.str('label_comp_id', P.residue.label_comp_id),
-    CifField.int('label_seq_id', P.residue.label_seq_id, { encoder: E.deltaRLE }),
+    CifField.int('label_seq_id', P.residue.label_seq_id, {
+        encoder: E.deltaRLE,
+        valueKind: (k, d) => {
+            const m = k.unit.model;
+            return m.atomicHierarchy.residues.label_seq_id.valueKind(m.atomicHierarchy.residueAtomSegments.index[k.element]);
+        }
+    }),
+    CifField.str('label_alt_id', P.atom.label_alt_id),
     CifField.str('pdbx_PDB_ins_code', P.residue.pdbx_PDB_ins_code),
 
     CifField.str('label_asym_id', P.chain.label_asym_id),
@@ -53,4 +59,26 @@ export const _atom_site: CifCategory<CifExportContext> = {
             keys: () => structure.elementLocations()
         };
     }
+}
+
+export function residueIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement): CifField<K, D>[] {
+    return [
+        CifField.str('label_comp_id', (k, d) => P.residue.label_comp_id(getLocation(k, d))),
+        CifField.int('label_seq_id', (k, d) => P.residue.label_seq_id(getLocation(k, d)), {
+            encoder: E.deltaRLE,
+            valueKind: (k, d) => {
+                const e = getLocation(k, d);
+                const m = e.unit.model;
+                return m.atomicHierarchy.residues.label_seq_id.valueKind(m.atomicHierarchy.residueAtomSegments.index[e.element]);
+            }
+        }),
+        CifField.str('pdbx_PDB_ins_code', (k, d) => P.residue.pdbx_PDB_ins_code(getLocation(k, d))),
+
+        CifField.str('label_asym_id', (k, d) => P.chain.label_asym_id(getLocation(k, d))),
+        CifField.str('label_entity_id', (k, d) => P.chain.label_entity_id(getLocation(k, d))),
+
+        CifField.str('auth_comp_id', (k, d) => P.residue.auth_comp_id(getLocation(k, d))),
+        CifField.int('auth_seq_id', (k, d) => P.residue.auth_seq_id(getLocation(k, d)), { encoder: E.deltaRLE }),
+        CifField.str('auth_asym_id', (k, d) => P.chain.auth_asym_id(getLocation(k, d))),
+    ]
 }

+ 5 - 1
src/mol-model/structure/export/mmcif.ts

@@ -88,7 +88,7 @@ export const mmCIF_Export_Filters = {
 
 /** Doesn't start a data block */
 export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structure: Structure) {
-    const models = Structure.getModels(structure);
+    const models = structure.models;
     if (models.length !== 1) throw 'Can\'t export stucture composed from multiple models.';
     const model = models[0];
 
@@ -98,8 +98,12 @@ export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structure: S
         encoder.writeCategory(cat, ctx);
     }
     for (const customProp of model.customProperties.all) {
+        if (!customProp.cifExport || customProp.cifExport.categories.length === 0) continue;
+
+        const prefix = customProp.cifExport.prefix;
         const cats = customProp.cifExport.categories;
         for (const cat of cats) {
+            if (cat.name.indexOf(prefix) !== 0) throw new Error(`Custom category '${cat.name}' name must start with prefix '${prefix}.'`);
             encoder.writeCategory(cat, ctx);
         }
     }

+ 1 - 0
src/mol-model/structure/model/formats/mmcif/bonds/comp.ts

@@ -22,6 +22,7 @@ export namespace ComponentBond {
         isStatic: true,
         name: 'chem_comp_bond',
         cifExport: {
+            prefix: '',
             categories: [{
                 name: 'chem_comp_bond',
                 instance(ctx) {

+ 1 - 0
src/mol-model/structure/model/formats/mmcif/bonds/struct_conn.ts

@@ -27,6 +27,7 @@ export namespace StructConn {
         isStatic: true,
         name: 'struct_conn',
         cifExport: {
+            prefix: '',
             categories: [{
                 name: 'struct_conn',
                 instance(ctx) {

+ 1 - 1
src/mol-model/structure/model/properties/atomic/hierarchy.ts

@@ -29,7 +29,7 @@ export const ResiduesSchema = {
     auth_comp_id: mmCIF.atom_site.auth_comp_id,
     label_seq_id: mmCIF.atom_site.label_seq_id,
     auth_seq_id: mmCIF.atom_site.auth_seq_id,
-    pdbx_PDB_ins_code: mmCIF.atom_site.pdbx_PDB_ins_code
+    pdbx_PDB_ins_code: mmCIF.atom_site.pdbx_PDB_ins_code,
 };
 export type ResiduesSchema = typeof ResiduesSchema
 export interface Residues extends Table<ResiduesSchema> { }

+ 3 - 3
src/mol-model/structure/model/properties/coarse/hierarchy.ts

@@ -7,14 +7,14 @@
 
 import { Column } from 'mol-data/db'
 import { Segmentation } from 'mol-data/int';
-import { ElementIndex, ChainIndex } from '../../indexing';
+import { ElementIndex, ChainIndex, EntityIndex } from '../../indexing';
 import SortedRanges from 'mol-data/int/sorted-ranges';
 
 export interface CoarsedElementKeys {
     // assign a key to each element
-    chainKey: ArrayLike<number>,
+    chainKey: ArrayLike<ChainIndex>,
     // assign a key to each element, index to the Model.entities.data table
-    entityKey: ArrayLike<number>,
+    entityKey: ArrayLike<EntityIndex>,
 
     /** find index of the residue/feature element where seq_id is included */
     findSequenceKey(entityId: string, asym_id: string, seq_id: number): ElementIndex

+ 2 - 1
src/mol-model/structure/model/properties/custom.ts

@@ -5,4 +5,5 @@
  */
 
 export * from './custom/descriptor'
-export * from './custom/collection'
+export * from './custom/collection'
+export * from './custom/residue'

+ 13 - 3
src/mol-model/structure/model/properties/custom/descriptor.ts

@@ -6,14 +6,24 @@
 
 import { CifWriter } from 'mol-io/writer/cif'
 import { CifExportContext } from '../../../export/mmcif';
+import { QuerySymbolRuntime } from 'mol-script/runtime/query/compiler';
 
-interface ModelPropertyDescriptor {
+interface ModelPropertyDescriptor<Symbols extends { [name: string]: QuerySymbolRuntime } = { }> {
     readonly isStatic: boolean,
     readonly name: string,
 
-    cifExport: {
+    cifExport?: {
+        // Prefix enforced during export.
+        prefix: string,
         categories: CifWriter.Category<CifExportContext>[]
-    }
+    },
+
+    // TODO: add aliases when lisp-like mol-script is done
+    symbols?: Symbols
+}
+
+function ModelPropertyDescriptor<Desc extends ModelPropertyDescriptor>(desc: Desc) {
+    return desc;
 }
 
 export { ModelPropertyDescriptor }

+ 94 - 0
src/mol-model/structure/model/properties/custom/residue.ts

@@ -0,0 +1,94 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { ResidueIndex } from '../../indexing';
+import { Unit, Structure, StructureElement } from '../../../structure';
+import { Segmentation } from 'mol-data/int';
+import { CifExportContext } from '../../../export/mmcif';
+import { UUID } from 'mol-util';
+import { CifWriter } from 'mol-io/writer/cif';
+
+export interface ResidueCustomProperty<T = any> {
+    readonly id: UUID,
+    readonly kind: Unit.Kind,
+    has(idx: ResidueIndex): boolean
+    get(idx: ResidueIndex): T | undefined
+}
+
+export namespace ResidueCustomProperty {
+    export interface ExportCtx<T> {
+        exportCtx: CifExportContext,
+        elements: StructureElement[],
+        property(index: number): T
+    };
+
+    function getExportCtx<T>(exportCtx: CifExportContext, prop: ResidueCustomProperty<T>): ExportCtx<T> {
+        if (exportCtx.cache[prop.id]) return exportCtx.cache[prop.id];
+        const residueIndex = exportCtx.model.atomicHierarchy.residueAtomSegments.index;
+        const elements = getStructureElements(exportCtx.structure, prop);
+        return {
+            exportCtx,
+            elements,
+            property: i => prop.get(residueIndex[elements[i].element])!
+        }
+    }
+
+    export function createCifCategory<T>(ctx: CifExportContext, prop: ResidueCustomProperty<T>, fields: CifWriter.Field<number, ExportCtx<T>>[]): CifWriter.Category.Instance {
+        const data = getExportCtx(ctx, prop);
+        return { fields, data, rowCount: data.elements.length };
+    }
+
+    class FromMap<T> implements ResidueCustomProperty<T> {
+        readonly id = UUID.create();
+
+        has(idx: ResidueIndex): boolean {
+            return this.map.has(idx);
+        }
+
+        get(idx: ResidueIndex) {
+            return this.map.get(idx);
+        }
+
+        constructor(private map: Map<ResidueIndex, T>, public kind: Unit.Kind) {
+        }
+    }
+
+    export function fromMap<T>(map: Map<ResidueIndex, T>, kind: Unit.Kind) {
+        return new FromMap(map, kind);
+    }
+
+    /**
+     * Gets all StructureElements that correspond to 1st atoms of residues that have an property assigned.
+     * Only works correctly for structures with a single model.
+     */
+    export function getStructureElements(structure: Structure, property: ResidueCustomProperty) {
+        const models = structure.models;
+        if (models.length !== 1) throw new Error(`Only works on structures with a single model.`);
+
+        const seenResidues = new Set<ResidueIndex>();
+        const unitGroups = structure.unitSymmetryGroups;
+        const loci: StructureElement[] = [];
+
+        for (const unitGroup of unitGroups) {
+            const unit = unitGroup.units[0];
+            if (unit.kind !== property.kind) {
+                continue;
+            }
+
+            const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
+            while (residues.hasNext) {
+                const seg = residues.move();
+                if (!property.has(seg.index) || seenResidues.has(seg.index)) continue;
+
+                seenResidues.add(seg.index);
+                loci[loci.length] = StructureElement.create(unit, unit.elements[seg.start]);
+            }
+        }
+
+        loci.sort((x, y) => x.element - y.element);
+        return loci;
+    }
+}

+ 4 - 4
src/mol-model/structure/model/properties/utils/coarse-keys.ts

@@ -7,7 +7,7 @@
 
 import { Entities } from '../common';
 import { CoarseElementData, CoarsedElementKeys } from '../coarse';
-import { ChainIndex, ElementIndex } from '../../indexing';
+import { ChainIndex, ElementIndex, EntityIndex } from '../../indexing';
 
 function getElementKey(map: Map<string, number>, key: string, counter: { index: number }) {
     if (map.has(key)) return map.get(key)!;
@@ -57,8 +57,8 @@ export function getCoarseKeys(data: CoarseElementData, entities: Entities): Coar
     const seqMaps = new Map<number, Map<number, number>>();
     const chainMaps = new Map<number, Map<string, number>>(), chainCounter = { index: 0 };
 
-    const chainKey = new Int32Array(count);
-    const entityKey = new Int32Array(count);
+    const chainKey = new Int32Array(count) as any as ChainIndex[];
+    const entityKey = new Int32Array(count) as any as EntityIndex[];
 
     for (let i = 0; i < count; i++) {
         entityKey[i] = entities.getEntityIndex(entity_id.value(i));
@@ -68,7 +68,7 @@ export function getCoarseKeys(data: CoarseElementData, entities: Entities): Coar
     for (let cI = 0; cI < chainElementSegments.count; cI++) {
         const start = chainElementSegments.offsets[cI], end = chainElementSegments.offsets[cI + 1];
         const map = getElementSubstructureKeyMap(chainMaps, entityKey[start]);
-        const key = getElementKey(map, asym_id.value(start), chainCounter);
+        const key = getElementKey(map, asym_id.value(start), chainCounter) as ChainIndex;
         for (let i = start; i < end; i++) chainKey[i] = key;
 
         // create seq_id map for the ranges defined by seq_id_begin and seq_id_end

+ 2 - 2
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 }
+export interface QueryPredicate { (ctx: QueryContext): boolean }
+export interface QueryFn<T = any> { (ctx: QueryContext): T }

+ 2 - 2
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));

+ 30 - 0
src/mol-model/structure/structure/element.ts

@@ -7,6 +7,7 @@
 import { OrderedSet, SortedArray } from 'mol-data/int'
 import Unit from './unit'
 import { ElementIndex } from '../model';
+import { ResidueIndex, ChainIndex } from '../model/indexing';
 
 interface StructureElement<U = Unit> {
     readonly kind: 'element-location',
@@ -65,6 +66,35 @@ namespace StructureElement {
     export function isLocation(x: any): x is StructureElement {
         return !!x && x.kind === 'element-location';
     }
+
+    export function residueIndex(e: StructureElement) {
+        if (Unit.isAtomic(e.unit)) {
+            return e.unit.residueIndex[e.element];
+        } else {
+            // TODO: throw error instead?
+            return -1 as ResidueIndex;
+        }
+    }
+
+    export function chainIndex(e: StructureElement) {
+        if (Unit.isAtomic(e.unit)) {
+            return e.unit.chainIndex[e.element];
+        } else {
+            // TODO: throw error instead?
+            return -1 as ChainIndex;
+        }
+    }
+
+    export function entityIndex(l: StructureElement) {
+        switch (l.unit.kind) {
+            case Unit.Kind.Atomic:
+                return l.unit.model.atomicHierarchy.getEntityKey(l.unit.chainIndex[l.element])
+            case Unit.Kind.Spheres:
+                return l.unit.model.coarseHierarchy.spheres.entityKey[l.element]
+            case Unit.Kind.Gaussians:
+                return l.unit.model.coarseHierarchy.gaussians.entityKey[l.element]
+        }
+    }
 }
 
 export default StructureElement

+ 1 - 10
src/mol-model/structure/structure/properties.ts

@@ -89,16 +89,7 @@ const coarse = {
     gaussian_covariance_matrix: StructureElement.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.covariance_matrix[l.element])
 }
 
-function eK(l: StructureElement) {
-    switch (l.unit.kind) {
-        case Unit.Kind.Atomic:
-            return l.unit.model.atomicHierarchy.getEntityKey(l.unit.chainIndex[l.element])
-        case Unit.Kind.Spheres:
-            return l.unit.model.coarseHierarchy.spheres.entityKey[l.element]
-        case Unit.Kind.Gaussians:
-            return l.unit.model.coarseHierarchy.gaussians.entityKey[l.element]
-    }
-}
+const eK = StructureElement.entityIndex
 
 const entity = {
     key: eK,

+ 17 - 10
src/mol-model/structure/structure/structure.ts

@@ -32,8 +32,9 @@ class Structure {
         crossLinkRestraints?: CrossLinkRestraints,
         unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup>,
         carbohydrates?: Carbohydrates,
+        models?: ReadonlyArray<Model>,
         hashCode: number,
-        elementCount: number
+        elementCount: number,
     } = { hashCode: -1, elementCount: 0 };
 
     subsetBuilder(isSorted: boolean) {
@@ -102,6 +103,12 @@ class Structure {
         return this._props.carbohydrates;
     }
 
+    get models(): ReadonlyArray<Model> {
+        if (this._props.models) return this._props.models;
+        this._props.models = getModels(this);
+        return this._props.models;
+    }
+
     constructor(units: ArrayLike<Unit>) {
         const map = IntMap.Mutable<Unit>();
         let elementCount = 0;
@@ -123,6 +130,15 @@ class Structure {
 
 function cmpUnits(units: ArrayLike<Unit>, i: number, j: number) { return units[i].id - units[j].id; }
 
+function getModels(s: Structure) {
+    const { units } = s;
+    const arr = UniqueArray.create<Model['id'], Model>();
+    for (const u of units) {
+        UniqueArray.add(arr, u.model.id, u.model);
+    }
+    return arr.array;
+}
+
 namespace Structure {
     export const Empty = new Structure([]);
 
@@ -199,15 +215,6 @@ namespace Structure {
 
     export function Builder() { return new StructureBuilder(); }
 
-    export function getModels(s: Structure) {
-        const { units } = s;
-        const arr = UniqueArray.create<Model['id'], Model>();
-        for (const u of units) {
-            UniqueArray.add(arr, u.model.id, u.model);
-        }
-        return arr.array;
-    }
-
     export function hashCode(s: Structure) {
         return s.hashCode;
     }

+ 4 - 4
src/mol-model/structure/structure/symmetry.ts

@@ -17,7 +17,7 @@ import { SymmetryOperator, Spacegroup, SpacegroupCell } from 'mol-math/geometry'
 namespace StructureSymmetry {
     export function buildAssembly(structure: Structure, asmName: string) {
         return Task.create('Build Assembly', async ctx => {
-            const models = Structure.getModels(structure);
+            const models = structure.models;
             if (models.length !== 1) throw new Error('Can only build assemblies from structures based on 1 model.');
 
             const assembly = ModelSymmetry.findAssembly(models[0], asmName);
@@ -121,7 +121,7 @@ function assembleOperators(structure: Structure, operators: ReadonlyArray<Symmet
 }
 
 async function _buildNCS(ctx: RuntimeContext, structure: Structure) {
-    const models = Structure.getModels(structure);
+    const models = structure.models;
     if (models.length !== 1) throw new Error('Can only build NCS from structures based on 1 model.');
 
     const operators = models[0].symmetry.ncsOperators;
@@ -130,7 +130,7 @@ async function _buildNCS(ctx: RuntimeContext, structure: Structure) {
 }
 
 async function findSymmetryRange(ctx: RuntimeContext, structure: Structure, ijkMin: Vec3, ijkMax: Vec3) {
-    const models = Structure.getModels(structure);
+    const models = structure.models;
     if (models.length !== 1) throw new Error('Can only build symmetries from structures based on 1 model.');
 
     const { spacegroup } = models[0].symmetry;
@@ -141,7 +141,7 @@ async function findSymmetryRange(ctx: RuntimeContext, structure: Structure, ijkM
 }
 
 async function findMatesRadius(ctx: RuntimeContext, structure: Structure, radius: number) {
-    const models = Structure.getModels(structure);
+    const models = structure.models;
     if (models.length !== 1) throw new Error('Can only build symmetries from structures based on 1 model.');
 
     const symmetry = models[0].symmetry;

+ 78 - 79
src/mol-script/compiler.ts

@@ -1,93 +1,92 @@
-/**
- * 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 './expression'
-import Environment from './runtime/environment'
-import RuntimeExpression from './runtime/expression'
-import SymbolRuntime, { RuntimeArguments } from './runtime/symbol'
+// import Expression from './expression'
+// import Environment from './runtime/environment'
+// import RuntimeExpression from './runtime/expression'
+// import SymbolRuntime, { RuntimeArguments } from './runtime/symbol'
 
-export type CompiledExpression<C, T> = (ctx: C) => T
+// export type CompiledExpression<T> = () => T
 
-type Compiler<C> = <T>(expression: Expression) => CompiledExpression<C, T>
-function Compiler<C>(envProvider: (ctx?: C) => Environment): Compiler<C> {
-    const env = envProvider(void 0);
-    return expression => wrap(envProvider, compile(env, expression).runtime);
-}
+// type Compiler = <T>(expression: Expression) => CompiledExpression<T>
+// function Compiler<C>(env: Environment): Compiler {
+//     return expression => compile(env, expression).runtime;
+// }
 
-type CompileResult = { isConst: boolean, runtime: RuntimeExpression }
+// type CompileResult = { isConst: boolean, runtime: RuntimeExpression }
 
-namespace CompileResult {
-    export function Const(value: any): CompileResult { return { isConst: true, runtime: RuntimeExpression.constant(value) } }
-    export function Dynamic(runtime: RuntimeExpression): CompileResult { return { isConst: false, runtime } }
-}
+// namespace CompileResult {
+//     export function Const(value: any): CompileResult { return { isConst: true, runtime: RuntimeExpression.constant(value) } }
+//     export function Dynamic(runtime: RuntimeExpression): CompileResult { return { isConst: false, runtime } }
+// }
 
-function wrap<C, T>(envProvider: (ctx?: C) => Environment, runtime: RuntimeExpression<C, T>) {
-    return (ctx: C) => runtime(envProvider(ctx));
-}
+// function wrap<T>(envProvider: (ctx?: C) => Environment, runtime: RuntimeExpression<T>) {
+//     return (ctx: C) => runtime(envProvider(ctx));
+// }
 
-function noRuntimeFor(symbol: string) {
-    throw new Error(`Could not find runtime for symbol '${symbol}'.`);
-}
+// function noRuntimeFor(symbol: string) {
+//     throw new Error(`Could not find runtime for symbol '${symbol}'.`);
+// }
 
-function applySymbolStatic(runtime: SymbolRuntime, args: RuntimeArguments) {
-    return CompileResult.Dynamic(env => runtime(env, args))
-}
+// function applySymbolStatic(runtime: SymbolRuntime, args: RuntimeArguments) {
+//     return CompileResult.Dynamic(env => runtime(env, args))
+// }
 
-function applySymbolDynamic(head: RuntimeExpression, args: RuntimeArguments) {
-    return CompileResult.Dynamic(env => {
-        const value = head(env);
-        const symbol = env.symbolTable[value];
-        if (!symbol) noRuntimeFor(value);
-        return symbol.runtime(env, args);
-    })
-}
+// function applySymbolDynamic(head: RuntimeExpression, args: RuntimeArguments) {
+//     return CompileResult.Dynamic(env => {
+//         const value = head(env);
+//         const symbol = env.runtimeTable[value];
+//         if (!symbol) noRuntimeFor(value);
+//         return symbol.runtime(env, args);
+//     })
+// }
 
-function apply(env: Environment, head: CompileResult, args: RuntimeArguments, constArgs: boolean): CompileResult {
-    if (head.isConst) {
-        const value = head.runtime(env);
-        const symbol = env.symbolTable[value];
-        if (!symbol) throw new Error(`Could not find runtime for symbol '${value}'.`);
-        if (symbol.attributes.isStatic && constArgs) return CompileResult.Const(symbol.runtime(env, args));
-        return applySymbolStatic(symbol.runtime, args);
-    }
-    return applySymbolDynamic(head.runtime, args);
-}
+// function apply(env: Environment, head: CompileResult, args: RuntimeArguments, constArgs: boolean): CompileResult {
+//     if (head.isConst) {
+//         const value = head.runtime(env);
+//         const symbol = env.runtimeTable[value];
+//         if (!symbol) throw new Error(`Could not find runtime for symbol '${value}'.`);
+//         if (symbol.attributes.isStatic && constArgs) return CompileResult.Const(symbol.runtime(env, args));
+//         return applySymbolStatic(symbol.runtime, args);
+//     }
+//     return applySymbolDynamic(head.runtime, args);
+// }
 
-function compile(env: Environment, expression: Expression): CompileResult {
-    if (Expression.isLiteral(expression)) {
-        return CompileResult.Const(expression);
-    }
+// function compile(env: Environment, expression: Expression): CompileResult {
+//     if (Expression.isLiteral(expression)) {
+//         return CompileResult.Const(expression);
+//     }
 
-    if (Expression.isSymbol(expression)) {
-        // TOTO: this needs to look up in the symbol table.
-        return 0 as any;
-    }
+//     if (Expression.isSymbol(expression)) {
+//         // TOTO: this needs to look up in the symbol table.
+//         return 0 as any;
+//     }
 
-    const head = compile(env, expression.head);
-    if (!expression.args) {
-        return apply(env, head, [], true);
-    } else if (Expression.isArgumentsArray(expression.args)) {
-        const args = [];
-        let constArgs = false;
-        for (const arg of expression.args) {
-            const compiled = compile(env, arg);
-            constArgs = constArgs && compiled.isConst;
-            args.push(compiled.runtime);
-        }
-        return apply(env, head, args as any, constArgs);
-    } else {
-        const args = Object.create(null);
-        let constArgs = false;
-        for (const key of Object.keys(expression.args)) {
-            const compiled = compile(env, expression.args[key]);
-            constArgs = constArgs && compiled.isConst;
-            args[key] = compiled.runtime;
-        }
-        return apply(env, head, args, constArgs);
-    }
-}
+//     const head = compile(env, expression.head);
+//     if (!expression.args) {
+//         return apply(env, head, [], true);
+//     } else if (Expression.isArgumentsArray(expression.args)) {
+//         const args = [];
+//         let constArgs = false;
+//         for (const arg of expression.args) {
+//             const compiled = compile(env, arg);
+//             constArgs = constArgs && compiled.isConst;
+//             args.push(compiled.runtime);
+//         }
+//         return apply(env, head, args as any, constArgs);
+//     } else {
+//         const args = Object.create(null);
+//         let constArgs = false;
+//         for (const key of Object.keys(expression.args)) {
+//             const compiled = compile(env, expression.args[key]);
+//             constArgs = constArgs && compiled.isConst;
+//             args[key] = compiled.runtime;
+//         }
+//         return apply(env, head, args, constArgs);
+//     }
+// }
 
-export default Compiler
+// export default Compiler

+ 7 - 9
src/mol-script/builder.ts → src/mol-script/language/builder.ts

@@ -5,10 +5,10 @@
  */
 
 import Expression from './expression'
-import Symbol from './symbol'
-import SymbolTable from './symbol-table'
+import { MSymbol } from './symbol'
+import { MolScriptSymbolTable as SymbolTable } from './symbol-table'
 
-namespace Builder {
+export namespace MolScriptBuilder {
     export const core = SymbolTable.core;
     export const struct = SymbolTable.structureQuery;
 
@@ -22,19 +22,17 @@ namespace Builder {
     const _acp = struct.atomProperty.core, _ammp = struct.atomProperty.macromolecular, _atp = struct.atomProperty.topology;
 
     // atom core property
-    export function acp(p: keyof typeof _acp) { return (_acp[p] as Symbol<any>)() };
+    export function acp(p: keyof typeof _acp) { return (_acp[p] as MSymbol<any>)() };
 
     // atom topology property
-    export function atp(p: keyof typeof _atp) { return (_atp[p] as Symbol<any>)() };
+    export function atp(p: keyof typeof _atp) { return (_atp[p] as MSymbol<any>)() };
 
     // atom macromolecular property
-    export function ammp(p: keyof typeof _ammp) { return (_ammp[p] as Symbol<any>)() };
+    export function ammp(p: keyof typeof _ammp) { return (_ammp[p] as MSymbol<any>)() };
 
     // atom property sets
     const _aps = struct.atomSet.propertySet
     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
+}

+ 0 - 0
src/mol-script/container.ts → src/mol-script/language/container.ts


+ 7 - 7
src/mol-script/expression-formatter.ts → src/mol-script/language/expression-formatter.ts

@@ -8,12 +8,6 @@ import Expression from './expression'
 
 const { isLiteral, isSymbol, isArgumentsArray } = Expression;
 
-export default function format(e: Expression) {
-    const writer = new Writer();
-    _format(e, writer);
-    return writer.getStr();
-}
-
 class Writer {
     private value: string[] = [];
     private currentLineLength = 0;
@@ -128,4 +122,10 @@ function _format(e: Expression, writer: Writer) {
         _format(e.args[a], writer);
     }
     writer.pop();
-}
+}
+
+export function formatMolScript(e: Expression) {
+    const writer = new Writer();
+    _format(e, writer);
+    return writer.getStr();
+}

+ 6 - 6
src/mol-script/expression.ts → src/mol-script/language/expression.ts

@@ -11,18 +11,18 @@ type Expression =
 
 namespace Expression {
     export type Literal = string | number | boolean
-    export type Symbol = { kind: 'symbol', name: string }
+    export type Symbol = { name: string }
     export type Arguments = Expression[] | { [name: string]: Expression }
     export interface Apply { readonly head: Expression, readonly args?: Arguments }
 
-    export function Symbol(name: string): Symbol { return { kind: 'symbol', name }; }
+    export function Symbol(name: string): Symbol { return { name }; }
     export function Apply(head: Expression, args?: Arguments): Apply { return args ? { head, args } : { head }; }
 
-    export function isArgumentsArray(e: Arguments): e is Expression[] { return e instanceof Array; }
-    export function isArgumentsMap(e: Arguments): e is { [name: string]: Expression } { return !(e instanceof Array); }
-    export function isLiteral(e: Expression): e is Expression.Literal { return !isApply(e); }
+    export function isArgumentsArray(e?: Arguments): e is Expression[] { return !!e && Array.isArray(e); }
+    export function isArgumentsMap(e?: Arguments): e is { [name: string]: Expression } { return !!e && !Array.isArray(e); }
+    export function isLiteral(e: Expression): e is Expression.Literal { return !isApply(e) && !isSymbol(e); }
     export function isApply(e: Expression): e is Expression.Apply { return !!e && !!(e as Expression.Apply).head && typeof e === 'object'; }
-    export function isSymbol(e: Expression): e is Expression.Symbol { return !!e && (e as any).kind === 'symbol' }
+    export function isSymbol(e: Expression): e is Expression.Symbol { return !!e && typeof (e as any).name === 'string' }
 }
 
 export default Expression

+ 5 - 5
src/mol-script/helpers.ts → src/mol-script/language/helpers.ts

@@ -5,18 +5,18 @@
  */
 
 import Type from './type'
-import Symbol, { Arguments, isSymbol } from './symbol'
+import { MSymbol, Arguments, isSymbol } from './symbol'
 
 export function symbol<A extends Arguments, T extends Type<S>, S>(args: A, type: T, description?: string) {
-    return Symbol('', args, type, description);
+    return MSymbol('', args, type, description);
 }
 
 export function normalizeTable(table: any) {
     _normalizeTable('', '', table);
 }
 
-export function symbolList(table: any): Symbol[] {
-    const list: Symbol[] = [];
+export function symbolList(table: any): MSymbol[] {
+    const list: MSymbol[] = [];
     _symbolList(table, list);
     return list;
 }
@@ -42,7 +42,7 @@ function _normalizeTable(namespace: string, key: string, obj: any) {
     }
 }
 
-function _symbolList(obj: any, list: Symbol[]) {
+function _symbolList(obj: any, list: MSymbol[]) {
     if (isSymbol(obj)) {
         list.push(obj);
         return;

+ 178 - 0
src/mol-script/language/parser.ts

@@ -0,0 +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 './expression'
+import { MolScriptBuilder as B } from './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;
+    }
+}

+ 6 - 6
src/mol-script/symbol-table.ts → src/mol-script/language/symbol-table.ts

@@ -7,18 +7,18 @@
 import core from './symbol-table/core'
 import structureQuery from './symbol-table/structure-query'
 import { normalizeTable, symbolList } from './helpers'
-import Symbol from './symbol'
+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]: Symbol | undefined } = Object.create(null);
+    const map: { [id: string]: MSymbol | undefined } = Object.create(null);
     for (const s of SymbolList) map[s.id] = s;
     return map;
 })();
 
-export default table
+export { MolScriptSymbolTable }

+ 11 - 2
src/mol-script/symbol-table/core.ts → src/mol-script/language/symbol-table/core.ts

@@ -5,7 +5,7 @@
  */
 
 import Type from '../type'
-import Symbol, { Arguments, Argument } from '../symbol'
+import { MSymbol, Arguments, Argument } from '../symbol'
 import { symbol, normalizeTable, symbolList } from '../helpers'
 
 export namespace Types {
@@ -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.'),
@@ -174,7 +183,7 @@ normalizeTable(table);
 export const SymbolList = symbolList(table);
 
 export const SymbolMap = (function() {
-    const map: { [id: string]: Symbol | undefined } = Object.create(null);
+    const map: { [id: string]: MSymbol | undefined } = Object.create(null);
     for (const s of SymbolList) map[s.id] = s;
     return map;
 })();

+ 0 - 0
src/mol-script/symbol-table/structure-query.ts → src/mol-script/language/symbol-table/structure-query.ts


+ 19 - 9
src/mol-script/symbol.ts → 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 };
     }
 
@@ -50,7 +51,7 @@ export namespace Arguments {
 
 export type ExpressionArguments<T> = { [P in keyof T]?: Expression } | { [index: number]: Expression }
 
-interface Symbol<A extends Arguments = Arguments, T extends Type = Type> {
+export interface MSymbol<A extends Arguments = Arguments, T extends Type = Type> {
     (args?: ExpressionArguments<A['@type']>): Expression,
     info: {
         namespace: string,
@@ -62,8 +63,8 @@ interface Symbol<A extends Arguments = Arguments, T extends Type = Type> {
     id: string,
 }
 
-function Symbol<A extends Arguments, T extends Type>(name: string, args: A, type: T, description?: string) {
-    const symbol: Symbol<A, T> = function(args: ExpressionArguments<A['@type']>) {
+export function MSymbol<A extends Arguments, T extends Type>(name: string, args: A, type: T, description?: string) {
+    const symbol: MSymbol<A, T> = function(args: ExpressionArguments<A['@type']>) {
         return Expression.Apply(Expression.Symbol(symbol.id), args as any);
     } as any;
     symbol.info = { namespace: '', name, description };
@@ -73,12 +74,21 @@ function Symbol<A extends Arguments, T extends Type>(name: string, args: A, type
     return symbol;
 }
 
-export function isSymbol(x: any): x is Symbol {
-    const s = x as Symbol;
-    return typeof s === 'function' && !!s.info && !!s.args && typeof s.info.namespace === 'string' && !!s.type;
+export function CustomPropSymbol<T extends Type>(namespace: string, name: string, type: T, description?: string) {
+    const symbol: MSymbol<Arguments<{}>, T> = function(args: ExpressionArguments<Arguments<{}>['@type']>) {
+        return Expression.Apply(Expression.Symbol(symbol.id), args as any);
+    } as any;
+    symbol.info = { namespace, name, description };
+    symbol.id = `${namespace}.${name}`;
+    symbol.args = Arguments.None;
+    symbol.type = type;
+    return symbol;
 }
 
-export type SymbolMap = { [id: string]: Symbol | undefined }
+export function isSymbol(x: any): x is MSymbol {
+    const s = x as MSymbol;
+    return typeof s === 'function' && !!s.info && !!s.args && typeof s.info.namespace === 'string' && !!s.type;
+}
 
-export default Symbol
+export type SymbolMap = { [id: string]: MSymbol | undefined }
 

+ 1 - 1
src/mol-script/type.ts → 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 }

+ 34 - 11
src/mol-script/runtime/environment.ts

@@ -1,14 +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 { SymbolRuntimeTable } from './symbol'
+// import { MSymbol } from '../language/symbol'
+// import { SymbolRuntime } from './symbol'
+// import { Macro } from './macro';
+// import Expression from '../language/expression';
 
-interface Environment<T = any> {
-    readonly symbolTable: SymbolRuntimeTable,
-    readonly context: T
-}
+// class Environment {
+//     readonly runtimeTable: SymbolRuntime.Table;
+//     readonly macroTable: Macro.Table = new Map<string, Macro>();
 
-export default Environment
+//     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);
+//     }
+
+//     addSymbolRuntime(symbol: MSymbol, runtime: SymbolRuntime) {
+//         this.runtimeTable.set(symbol.id, runtime);
+//     }
+
+//     removeSymbolRuntime(symbol: MSymbol) {
+//         this.runtimeTable.delete(symbol.id);
+//     }
+// }
+
+// export default Environment

+ 19 - 19
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<C = any, T = any> = (env: Environment<C>) => T
+// type RuntimeExpression<T = any> = (env: Environment) => T
 
-export interface ExpressionInfo {
-    isConst?: boolean
-}
+// export interface ExpressionInfo {
+//     isConst?: boolean
+// }
 
-namespace RuntimeExpression {
-    export function constant<C, T>(c: T): RuntimeExpression<C, T> {
-        return env => c;
-    }
+// namespace RuntimeExpression {
+//     export function constant<T>(c: T): RuntimeExpression<T> {
+//         return env => c;
+//     }
 
-    export function func<C, T>(f: (env: Environment<C>) => T): RuntimeExpression<C, T> {
-        return env => f(env);
-    }
-}
+//     export function func<T>(f: (env: Environment) => T): RuntimeExpression<T> {
+//         return env => f(env);
+//     }
+// }
 
-export default RuntimeExpression
+// export default RuntimeExpression

+ 81 - 0
src/mol-script/runtime/macro.ts

@@ -0,0 +1,81 @@
+// /**
+//  * 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';
+
+// interface Macro {
+//     readonly argNames: ReadonlyArray<string>,
+//     readonly argIndex: { [name: string]: number },
+//     readonly expression: Expression
+// }
+
+// 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];
+
+//             if (table.has(expr.name)) {
+//                 const macro = table.get(expr.name)!;
+//                 if (macro.argNames.length === 0) return macro.expression;
+//             }
+
+//             return 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;
+
+//         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 (!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;
+
+//             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 { Macro }

+ 175 - 0
src/mol-script/runtime/query/compiler.ts

@@ -0,0 +1,175 @@
+/**
+ * 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) {
+        if (this.map.has(runtime.symbol.id)) {
+            throw new Error(`Symbol '${runtime.symbol.id}' already added.`);
+        }
+        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 QueryRuntimeArguments {
+    export function forEachEval<S extends MSymbol, Ctx>(xs: QueryRuntimeArguments<S>, queryCtx: QueryContext, f: (arg: any, i: number, ctx: Ctx) => void, ctx: Ctx): Ctx {
+        if (typeof xs.length === 'number') {
+            for (let i = 0, _i = xs.length; i < _i; i++) f((xs as any)[i](queryCtx), i, ctx);
+        } else {
+            let i = 0;
+            for (const k of Object.keys(xs)) f((xs as any)[k](queryCtx), i++, ctx);
+        }
+        return ctx;
+    }
+}
+
+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)) {
+        const runtime = ctx.table.getRuntime(expression.name);
+        if (!runtime) return CompiledQueryFn.Const(expression.name);
+
+        return runtime.compile(ctx);
+    }
+
+    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'

+ 273 - 0
src/mol-script/runtime/query/table.ts

@@ -0,0 +1,273 @@
+/**
+ * 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, QueryRuntimeArguments } from './compiler';
+import { Queries, StructureProperties, StructureElement, QueryContext } from 'mol-model/structure';
+import { ElementSymbol } from 'mol-model/structure/model/types';
+import { isSuperset } from 'mol-util/set';
+import toUpperCase from 'mol-util/upper-case';
+import { VdwRadius, AtomWeight, AtomNumber } from 'mol-model/structure/model/properties/atomic';
+import { cantorPairing } from 'mol-data/util';
+import C = QuerySymbolRuntime.Const
+import D = QuerySymbolRuntime.Dynamic
+
+const symbols = [
+    // ============= TYPES =============
+
+    C(MolScript.core.type.bool, (ctx, v) => !!v[0](ctx)),
+    C(MolScript.core.type.num, (ctx, v) => +v[0](ctx)),
+    C(MolScript.core.type.str, (ctx, v) => '' + v[0](ctx)),
+    C(MolScript.core.type.list, (ctx, xs) => QueryRuntimeArguments.forEachEval(xs, ctx, (v, i, list) => list[i] = v, [] as any[])),
+    C(MolScript.core.type.set, (ctx, xs) => QueryRuntimeArguments.forEachEval(xs, ctx, (v, i, set) => set.add(v), new Set<any>())),
+    C(MolScript.core.type.regex, (ctx, v) => new RegExp(v[0](ctx), (v[1] && v[1](ctx)) || '')),
+    C(MolScript.core.type.bitflags, (ctx, v) => +v[0](ctx)),
+    C(MolScript.core.type.compositeKey, (ctx, xs) => QueryRuntimeArguments.forEachEval(xs, ctx, (v, i, list) => list[i] = '' + v, [] as string[]).join('-')),
+
+    // ============= LOGIC ================
+    C(MolScript.core.logic.not, (ctx, v) => !v[0](ctx)),
+    C(MolScript.core.logic.and, (ctx, xs) => {
+        if (typeof xs.length === 'number') {
+            for (let i = 0, _i = xs.length; i < _i; i++) if (!xs[i](ctx)) return false;
+        } else {
+            for (const k of Object.keys(xs)) if (!xs[k](ctx)) return false;
+        }
+        return true;
+    }),
+    C(MolScript.core.logic.or, (ctx, xs) => {
+        if (typeof xs.length === 'number') {
+            for (let i = 0, _i = xs.length; i < _i; i++) if (xs[i](ctx)) return true;
+        } else {
+            for (const k of Object.keys(xs)) if (xs[k](ctx)) return true;
+        }
+        return false;
+    }),
+
+    // ============= 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)),
+    C(MolScript.core.rel.inRange, (ctx, v) => {
+        const x = v[0](ctx);
+        return x >= v[1](ctx) && x <= v[2](ctx);
+    }),
+
+    // ============= ARITHMETIC ================
+    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;
+    }),
+    C(MolScript.core.math.sub, (ctx, xs) => {
+        let ret = 0;
+        if (typeof xs.length === 'number') {
+            if (xs.length === 1) return -xs[0](ctx);
+            ret = xs[0](ctx) || 0;
+            for (let i = 1, _i = xs.length; i < _i; i++) ret -= xs[i](ctx);
+        } else {
+            const keys = Object.keys(xs);
+            if (keys.length === 1)
+            ret = xs[keys[0]](ctx) || 0;
+            for (let i = 1, _i = keys.length; i < _i; i++) ret -= xs[keys[i]](ctx);
+        }
+        return ret;
+    }),
+    C(MolScript.core.math.mult, (ctx, xs) => {
+        let ret = 1;
+        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;
+    }),
+    C(MolScript.core.math.div, (ctx, v) => v[0](ctx) / v[1](ctx)),
+    C(MolScript.core.math.pow, (ctx, v) => Math.pow(v[0](ctx), v[1](ctx))),
+    C(MolScript.core.math.mod, (ctx, v) => v[0](ctx) % v[1](ctx)),
+
+    C(MolScript.core.math.min, (ctx, xs) => {
+        let ret = Number.POSITIVE_INFINITY;
+        if (typeof xs.length === 'number') {
+            for (let i = 0, _i = xs.length; i < _i; i++) ret = Math.min(xs[i](ctx), ret);
+        } else {
+            for (const k of Object.keys(xs)) ret = Math.min(xs[k](ctx), ret)
+        }
+        return ret;
+    }),
+    C(MolScript.core.math.max, (ctx, xs) => {
+        let ret = Number.NEGATIVE_INFINITY;
+        if (typeof xs.length === 'number') {
+            for (let i = 0, _i = xs.length; i < _i; i++) ret = Math.max(xs[i](ctx), ret);
+        } else {
+            for (const k of Object.keys(xs)) ret = Math.max(xs[k](ctx), ret)
+        }
+        return ret;
+    }),
+
+    C(MolScript.core.math.floor, (ctx, v) => Math.floor(v[0](ctx))),
+    C(MolScript.core.math.ceil, (ctx, v) => Math.ceil(v[0](ctx))),
+    C(MolScript.core.math.roundInt, (ctx, v) => Math.round(v[0](ctx))),
+    C(MolScript.core.math.abs, (ctx, v) => Math.abs(v[0](ctx))),
+    C(MolScript.core.math.sqrt, (ctx, v) => Math.sqrt(v[0](ctx))),
+    C(MolScript.core.math.sin, (ctx, v) => Math.sin(v[0](ctx))),
+    C(MolScript.core.math.cos, (ctx, v) => Math.cos(v[0](ctx))),
+    C(MolScript.core.math.tan, (ctx, v) => Math.tan(v[0](ctx))),
+    C(MolScript.core.math.asin, (ctx, v) => Math.asin(v[0](ctx))),
+    C(MolScript.core.math.acos, (ctx, v) => Math.acos(v[0](ctx))),
+    C(MolScript.core.math.atan, (ctx, v) => Math.atan(v[0](ctx))),
+    C(MolScript.core.math.sinh, (ctx, v) => Math.sinh(v[0](ctx))),
+    C(MolScript.core.math.cosh, (ctx, v) => Math.cosh(v[0](ctx))),
+    C(MolScript.core.math.tanh, (ctx, v) => Math.tanh(v[0](ctx))),
+    C(MolScript.core.math.exp, (ctx, v) => Math.exp(v[0](ctx))),
+    C(MolScript.core.math.log, (ctx, v) => Math.log(v[0](ctx))),
+    C(MolScript.core.math.log10, (ctx, v) => Math.log10(v[0](ctx))),
+    C(MolScript.core.math.atan2, (ctx, v) => Math.atan2(v[0](ctx), v[1](ctx))),
+
+    // ============= STRING ================
+    C(MolScript.core.str.match, (ctx, v) => v[0](ctx).test(v[1](ctx))),
+    C(MolScript.core.str.concat, (ctx, xs) => {
+        let ret: string[] = [];
+        if (typeof xs.length === 'number') {
+            for (let i = 0, _i = xs.length; i < _i; i++) ret.push(xs[i](ctx).toString());
+        } else {
+            for (const k of Object.keys(xs)) ret.push(xs[k](ctx).toString());
+        }
+        return ret.join('');
+    }),
+
+    // ============= LIST ================
+    C(MolScript.core.list.getAt, (ctx, v) => v[0](ctx)[v[1](ctx)]),
+
+    // ============= SET ================
+    C(MolScript.core.set.has, (ctx, v) => v[0](ctx).has(v[1](ctx))),
+    C(MolScript.core.set.isSubset, (ctx, v) => isSuperset(v[1](ctx) as Set<any>, v[0](ctx) as Set<any>)),
+
+    // ============= FLAGS ================
+    C(MolScript.core.flags.hasAny, (ctx, v) => {
+        const test = v[1](ctx);
+        const tested = v[0](ctx);
+        if (!test) return !!tested;
+        return (tested & test) !== 0;
+    }),
+    C(MolScript.core.flags.hasAll, (ctx, v) => {
+        const test = v[1](ctx);
+        const tested = v[0](ctx);
+        if (!test) return !tested;
+        return (tested & test) === test;
+    }),
+
+    ////////////////////////////////////
+    // Structure
+
+    // ============= TYPES ================
+    C(MolScript.structureQuery.type.elementSymbol, (ctx, v) => ElementSymbol(v[0](ctx))),
+    C(MolScript.structureQuery.type.atomName, (ctx, v) => toUpperCase(v[0](ctx))),
+
+    // TODO:
+    // C(MolScript.structureQuery.type.bondFlags, (ctx, v) => StructureRuntime.BondProperties.createFlags(env, v)),
+    // C(MolScript.structureQuery.type.secondaryStructureFlags, (ctx, v) => StructureRuntime.AtomProperties.createSecondaryStructureFlags(env, v)),
+    // C(MolScript.structureQuery.type.entityType, (ctx, v) => StructureRuntime.Common.entityType(v[0](ctx))),
+    // C(MolScript.structureQuery.type.ringFingerprint, (ctx, v) => StructureRuntime.Common.ringFingerprint(env, v as any)),
+    // C(MolScript.structureQuery.type.authResidueId, (ctx, v) => ResidueIdentifier.auth(v[0](ctx), v[1](ctx), v[2] && v[2](ctx))),
+    // C(MolScript.structureQuery.type.labelResidueId, (ctx, v) => ResidueIdentifier.label(v[0](ctx), v[1](ctx), v[2](ctx), v[3] && v[3](ctx))),
+
+    // ============= SLOTS ================
+    // TODO: slots might not be needed after all: reducer simply pushes/pops current element
+    C(MolScript.structureQuery.slot.element, (ctx, _) => ctx.element),
+    // C(MolScript.structureQuery.slot.elementSetReduce, (ctx, _) => ctx.element),
+
+    // ============= 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 ================
+
+    // ~~~ CORE ~~~
+    D(MolScript.structureQuery.atomProperty.core.elementSymbol, atomProp(StructureProperties.atom.type_symbol)),
+    D(MolScript.structureQuery.atomProperty.core.vdw, (ctx, _) => VdwRadius(StructureProperties.atom.type_symbol(ctx.element))),
+    D(MolScript.structureQuery.atomProperty.core.mass, (ctx, _) => AtomWeight(StructureProperties.atom.type_symbol(ctx.element))),
+    D(MolScript.structureQuery.atomProperty.core.atomicNumber, (ctx, _) => AtomNumber(StructureProperties.atom.type_symbol(ctx.element))),
+    D(MolScript.structureQuery.atomProperty.core.x, atomProp(StructureProperties.atom.x)),
+    D(MolScript.structureQuery.atomProperty.core.y, atomProp(StructureProperties.atom.y)),
+    D(MolScript.structureQuery.atomProperty.core.z, atomProp(StructureProperties.atom.z)),
+    D(MolScript.structureQuery.atomProperty.core.atomKey, (ctx, _) => cantorPairing(ctx.element.unit.id, ctx.element.element)),
+
+    // TODO:
+    // D(MolScript.structureQuery.atomProperty.core.bondCount, (ctx, _) => ),
+
+    // ~~~ TOPOLOGY ~~~
+
+    // TODO
+
+    // ~~~ MACROMOLECULAR ~~~
+
+    // TODO:
+    // // identifiers
+    // labelResidueId: prop((env, v) => ResidueIdentifier.labelOfResidueIndex(env.context.model, getAddress(env, v).residue)),
+    // authResidueId: prop((env, v) => ResidueIdentifier.authOfResidueIndex(env.context.model, getAddress(env, v).residue)),
+
+    // keys
+    D(MolScript.structureQuery.atomProperty.macromolecular.residueKey, (ctx, _) => StructureElement.residueIndex(ctx.element)),
+    D(MolScript.structureQuery.atomProperty.macromolecular.chainKey, (ctx, _) => StructureElement.chainIndex(ctx.element)),
+    D(MolScript.structureQuery.atomProperty.macromolecular.entityKey, (ctx, _) => StructureElement.entityIndex(ctx.element)),
+
+    // mmCIF
+    D(MolScript.structureQuery.atomProperty.macromolecular.id, atomProp(StructureProperties.atom.id)),
+    D(MolScript.structureQuery.atomProperty.macromolecular.isHet, (ctx, _) => StructureProperties.residue.group_PDB(ctx.element) !== 'ATOM'),
+
+    D(MolScript.structureQuery.atomProperty.macromolecular.label_atom_id, atomProp(StructureProperties.atom.label_atom_id)),
+    D(MolScript.structureQuery.atomProperty.macromolecular.label_alt_id, atomProp(StructureProperties.atom.label_alt_id)),
+    D(MolScript.structureQuery.atomProperty.macromolecular.label_asym_id, atomProp(StructureProperties.chain.label_asym_id)),
+    D(MolScript.structureQuery.atomProperty.macromolecular.label_comp_id, atomProp(StructureProperties.residue.label_comp_id)),
+    D(MolScript.structureQuery.atomProperty.macromolecular.label_seq_id, atomProp(StructureProperties.residue.label_seq_id)),
+    D(MolScript.structureQuery.atomProperty.macromolecular.label_entity_id, atomProp(StructureProperties.entity.id)),
+
+    D(MolScript.structureQuery.atomProperty.macromolecular.auth_atom_id, atomProp(StructureProperties.atom.auth_atom_id)),
+    D(MolScript.structureQuery.atomProperty.macromolecular.auth_asym_id, atomProp(StructureProperties.chain.auth_asym_id)),
+    D(MolScript.structureQuery.atomProperty.macromolecular.auth_comp_id, atomProp(StructureProperties.residue.auth_comp_id)),
+    D(MolScript.structureQuery.atomProperty.macromolecular.auth_seq_id, atomProp(StructureProperties.residue.auth_seq_id)),
+
+    D(MolScript.structureQuery.atomProperty.macromolecular.pdbx_PDB_ins_code, atomProp(StructureProperties.residue.pdbx_PDB_ins_code)),
+    D(MolScript.structureQuery.atomProperty.macromolecular.pdbx_formal_charge, atomProp(StructureProperties.atom.pdbx_formal_charge)),
+    D(MolScript.structureQuery.atomProperty.macromolecular.occupancy, atomProp(StructureProperties.atom.occupancy)),
+    D(MolScript.structureQuery.atomProperty.macromolecular.B_iso_or_equiv, atomProp(StructureProperties.atom.B_iso_or_equiv)),
+
+    D(MolScript.structureQuery.atomProperty.macromolecular.entityType, atomProp(StructureProperties.entity.type)),
+
+    D(MolScript.structureQuery.atomProperty.macromolecular.isModified, (ctx, _) => ctx.element.unit.model.properties.modifiedResidues.parentId.has(StructureProperties.residue.label_comp_id(ctx.element))),
+    D(MolScript.structureQuery.atomProperty.macromolecular.modifiedParentName, (ctx, _) => {
+        const id = StructureProperties.residue.label_comp_id(ctx.element);
+        return ctx.element.unit.model.properties.modifiedResidues.parentId.get(id) || id
+    })
+
+    // TODO
+    // MolScript.structureQuery.atomProperty.macromolecular.secondaryStructureKey
+    // MolScript.structureQuery.atomProperty.macromolecular.secondaryStructureFlags
+
+    // ============= BOND PROPERTIES ================
+];
+
+function atomProp(p: (e: StructureElement) => any): (ctx: QueryContext, _: any) => any {
+    return (ctx, _) => p(ctx.element);
+}
+
+(function () {
+    for (const s of symbols) {
+        DefaultQueryRuntimeTable.addSymbol(s);
+    }
+})();

+ 24 - 24
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 Environment from './environment'
+// import RuntimeExpression from './expression'
+// import Expression from '../language/expression';
 
-export type RuntimeArguments = ArrayLike<RuntimeExpression> | { [name: string]: RuntimeExpression | undefined }
+// type SymbolRuntime = SymbolRuntime.Dynamic | SymbolRuntime.Static
 
-type SymbolRuntime = (env: Environment, args: RuntimeArguments) => any
+// namespace SymbolRuntime {
+//     export interface Static {
+//         kind: 'static',
+//         readonly runtime: (ctx: any, args: Arguments) => any,
+//         readonly attributes: Attributes
+//     }
 
-namespace SymbolRuntime {
-    export interface Info {
-        readonly runtime: SymbolRuntime,
-        readonly attributes: Attributes
-    }
+//     export interface Dynamic {
+//         kind: 'dynamic',
+//         readonly compile: (env: Environment, args: Expression.Arguments) => RuntimeExpression
+//     }
 
-    export interface Attributes { isStatic: boolean }
-}
+//     export interface Attributes { isStatic: boolean }
 
-function SymbolRuntime(symbol: Symbol, attributes: Partial<SymbolRuntime.Attributes> = {}) {
-    const { isStatic = false } = attributes;
-    return (runtime: SymbolRuntime): SymbolRuntime.Info => {
-        return ({ runtime, attributes: { isStatic } });
-    };
-}
+//     export type Table = Map<string, SymbolRuntime>
 
-export type SymbolRuntimeTable = { readonly [id: string]: SymbolRuntime.Info }
+//     export type Arguments = ArrayLike<RuntimeExpression> | { [name: string]: RuntimeExpression | undefined }
+// }
 
-export default SymbolRuntime
+// export { SymbolRuntime }

+ 103 - 103
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))
-  ])`
-}]
+// 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))
+//   ])`
+// }]

+ 34 - 34
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 '../../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 ])
-    });
-}
+// 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 ])
+//     });
+// }

+ 178 - 178
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 '../../expression'
-import B from '../../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;
-    }
-}
+// /**
+//  * 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;
+//     }
+// }

+ 121 - 76
src/mol-script/script/mol-script/symbols.ts

@@ -4,25 +4,23 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import Symbol, { Arguments, Argument } from '../../symbol'
-import B from '../../builder'
-import * as M from './macro'
-import MolScript from '../../symbol-table'
-import Type from '../../type'
-import * as Struct from '../../symbol-table/structure-query'
-import Expression from '../../expression'
-import { UniqueArray } from 'mol-data/generic'
+import { UniqueArray } from 'mol-data/generic';
+import Expression from '../../language/expression';
+import { Argument, MSymbol } from '../../language/symbol';
+//import * as M from './macro'
+import { MolScriptSymbolTable as MolScript } from '../../language/symbol-table';
+import Type from '../../language/type';
 
 export type MolScriptSymbol =
-    | { kind: 'alias', aliases: string[], symbol: Symbol }
-    | { kind: 'macro', aliases: string[], symbol: Symbol, translate: (args: any) => Expression }
-
-function Alias(symbol: Symbol<any>, ...aliases: string[]): MolScriptSymbol { return { kind: 'alias', aliases, symbol }; }
-function Macro(symbol: Symbol<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] };
-}
+    | { 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] };
+// }
 
 export function isMolScriptSymbol(x: any): x is MolScriptSymbol {
     return x.kind === 'alias' || x.kind === 'macro';
@@ -105,26 +103,26 @@ export const SymbolTable = [
             Alias(MolScript.structureQuery.generator.rings, 'sel.atom.rings'),
             Alias(MolScript.structureQuery.generator.empty, 'sel.atom.empty'),
 
-            Macro(Symbol('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(Symbol('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(Symbol('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')
-            })),
+            // 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.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',
@@ -138,15 +136,15 @@ export const SymbolTable = [
             Alias(MolScript.structureQuery.modifier.includeConnected, 'sel.atom.include-connected'),
             Alias(MolScript.structureQuery.modifier.expandProperty, 'sel.atom.expand-property'),
 
-            Macro(Symbol('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)
-            }))
+            // 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',
@@ -169,25 +167,25 @@ export const SymbolTable = [
             Alias(MolScript.structureQuery.atomSet.reduce, 'atom.set.reduce'),
             Alias(MolScript.structureQuery.atomSet.propertySet, 'atom.set.property'),
 
-            Macro(Symbol('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(Symbol('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(Symbol('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(Symbol('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))
+            // 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.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',
@@ -231,16 +229,16 @@ export const SymbolTable = [
             Alias(MolScript.structureQuery.atomProperty.macromolecular.isModified, 'atom.is-modified'),
             Alias(MolScript.structureQuery.atomProperty.macromolecular.modifiedParentName, 'atom.modified-parent'),
 
-            Macro(Symbol('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)])),
+            // 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(Symbol('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('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))])),
         ]
     ]
 ];
@@ -293,8 +291,55 @@ 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];
+function substSymbols(expr: Expression): Expression {
+    if (Expression.isLiteral(expr)) {
+        return expr;
+    }
+    if (Expression.isSymbol(expr)) {
+        if (!SymbolMap[expr.name]) return expr;
+        return Expression.Symbol(SymbolMap[expr.name]!.symbol.id);
+    }
+
+    const head = substSymbols(expr.head);
+    const headChanged = head !== expr.head;
+    if (!expr.args) {
+        return headChanged ? Expression.Apply(head) : expr;
+    }
+
+    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 = substSymbols(oldArg);
+            if (oldArg !== newArg) argsChanged = true;
+            newArgs[newArgs.length] = newArg;
+        }
+        if (!argsChanged) newArgs = expr.args;
+        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 = substSymbols(oldArg);
+            if (oldArg !== newArg) argsChanged = true;
+            newArgs[key] = newArg;
+        }
+        if (!headChanged && !argsChanged) return expr;
+        if (!argsChanged) newArgs = expr.args;
+
+        return Expression.Apply(head, newArgs);
+    }
+}
+
+export function transpileMolScript(expr: Expression) {
+    return substSymbols(expr);
+}
+
+// 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];

+ 1 - 1
src/mol-script/script/parser.ts

@@ -5,7 +5,7 @@
  * @author Alexander Rose <alexanderose@weirdbyte.de>
  */
 
-import Expression from '../expression'
+import Expression from '../language/expression'
 
 type Parser = (source: string) => Expression
 

+ 33 - 0
src/mol-util/number.ts

@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ */
+
+/**
+ * Determine the number of digits in a floating point number
+ * Find a number M such that round(M * v) - M * v <= delta.
+ * If no such M exists, return -1.
+ */
+export function getMantissaMultiplier(v: number, maxDigits: number, delta: number) {
+    let m = 1;
+    for (let i = 0; i < maxDigits; i++) {
+        let mv = m * v;
+        if (Math.abs(Math.round(mv) - mv) <= delta) return m;
+        m *= 10;
+    }
+    return -1;
+}
+
+/**
+ * Determine the maximum number of digits in a floating point array.
+ * Find a number M such that round(M * v) - M * v <= delta.
+ * If no such M exists, return -1.
+ */
+export function getArrayMantissaMultiplier(xs: ArrayLike<number>, maxDigits: number, delta: number) {
+    let m = 1;
+    for (let i = 0, _i = xs.length; i < _i; i++) {
+        const t = getMantissaMultiplier(xs[i], maxDigits, delta);
+        if (t < 0) return -1;
+        if (t > m) m = t;
+    }
+    return m;
+}

+ 31 - 0
src/mol-util/retry-if.ts

@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+export async function retryIf<T>(promiseProvider: () => Promise<T>, params: {
+    retryThenIf?: (result: T) => boolean,
+    retryCatchIf?: (error: any) => boolean,
+    retryCount: number
+}) {
+    let count = 0;
+    while (count <= params.retryCount) {
+        try {
+            const result = await promiseProvider();
+            if (params.retryThenIf && params.retryThenIf(result)) {
+                count++;
+                continue;
+            }
+            return result;
+        } catch (e) {
+            if (!params.retryCatchIf || params.retryCatchIf(e)) {
+                count++;
+                continue;
+            }
+            throw e;
+        }
+    }
+
+    throw new Error('Maximum retry count exceeded.');
+}

+ 10 - 0
src/mol-util/upper-case.ts

@@ -0,0 +1,10 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+export default function toUpperCase(value: any): string {
+    if (!value) return '';
+    return typeof value === 'string' ? value.toUpperCase() : `${value}`.toUpperCase();
+}

+ 1 - 1
src/mol-util/uuid.ts

@@ -6,7 +6,7 @@
 
 import { now } from 'mol-task'
 
-interface UUID extends String { '@type': 'uuid' }
+type UUID = string & { '@type': 'uuid' }
 
 namespace UUID {
     export function create(): UUID {

+ 88 - 9
src/perf-tests/mol-script.ts

@@ -1,12 +1,24 @@
-import Examples from 'mol-script/script/mol-script/examples'
-import { parseMolScript } from 'mol-script/script/mol-script/parser'
+import { MolScriptBuilder } from 'mol-script/language/builder';
+import { compile, QuerySymbolRuntime, DefaultQueryRuntimeTable } from 'mol-script/runtime/query/compiler';
+import { QueryContext, Structure, StructureQuery, ModelPropertyDescriptor } from 'mol-model/structure';
+import { readCifFile, getModelsAndStructure } from '../apps/structure-info/model';
+import { CustomPropSymbol } from 'mol-script/language/symbol';
+import Type from 'mol-script/language/type';
+import { parseMolScript } from 'mol-script/language/parser';
 import * as util from 'util'
-//import { compileAST } from 'mol-script/script/mol-script/compile';
+import { transpileMolScript } from 'mol-script/script/mol-script/symbols';
+import { formatMolScript } from 'mol-script/language/expression-formatter';
+import { StructureQualityReport } from 'mol-model-props/pdbe/structure-quality-report';
+import fetch from 'node-fetch'; 
 
-for (const e of Examples) {
-    const expr = parseMolScript(e.value)[0];
-    console.log(e.name, util.inspect(expr, true, 10, true));
-}
+// import Examples from 'mol-script/script/mol-script/examples'
+// import { parseMolScript } from 'mol-script/script/mol-script/parser'
+// //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
@@ -14,5 +26,72 @@ for (const e of Examples) {
 //     ;; this is a comment
 //     ((hi) (ho))`);
 
-// console.log(util.inspect(exprs, true, 10, true));
-// //console.log(expr);
+//;; :residue-test (= atom.label_comp_id REA)
+const exprs = parseMolScript(`(sel.atom.atom-groups
+    :residue-test (> pdbe.structure-quality.issue-count 0)
+    :atom-test (= atom.el _C))`);
+
+const tsp = transpileMolScript(exprs[0]);
+
+//console.log(util.inspect(exprs, true, 10, true));
+
+console.log(util.inspect(tsp, true, 10, true));
+
+console.log(formatMolScript);
+console.log(formatMolScript(tsp));
+// //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);
+
+const CustomProp = ModelPropertyDescriptor({
+    name: 'test_prop',
+    isStatic: true,
+    cifExport: { prefix: '', categories: [ ]},
+    symbols: {
+        residueIndex: QuerySymbolRuntime.Dynamic(CustomPropSymbol('custom.test-prop', 'residue-index', Type.Num), ctx => {
+            const e = ctx.element;
+            //console.log(e.element, e.unit.model.atomicHierarchy.residueAtomSegments.index[e.element])
+            return e.unit.model.atomicHierarchy.residueAtomSegments.index[e.element];
+        })
+    }
+});
+
+DefaultQueryRuntimeTable.addCustomProp(CustomProp);
+
+DefaultQueryRuntimeTable.addCustomProp(StructureQualityReport.Descriptor);
+
+export async function testQ() {
+    const frame = await readCifFile('e:/test/quick/1cbs_updated.cif');
+    const { structure } = await getModelsAndStructure(frame);
+
+    await StructureQualityReport.attachFromCifOrApi(structure.models[0], {
+        PDBe_apiSourceJson: async model => {
+            const rawData = await fetch(`https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/${model.label.toLowerCase()}`, { timeout: 1500 });
+            return await rawData.json();
+        }
+    })
+
+    let 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'
+        // ])
+        'residue-test': MolScriptBuilder.core.rel.inRange([CustomProp.symbols.residueIndex.symbol(), 1, 5])
+    });
+
+    expr = tsp;
+
+    const compiled = compile<StructureQuery>(expr);
+    const result = compiled(new QueryContext(structure));
+
+    console.log(result);
+}
+
+testQ();

+ 4 - 4
src/servers/model/properties.ts

@@ -6,14 +6,14 @@
  */
 
 import { Model } from 'mol-model/structure';
-import { StructureQualityReport } from './properties/structure-quality-report';
-import { SymmetryAnnotation } from './properties/rcsb/symmetry';
+import { PDBe_structureQualityReport } from './properties/pdbe';
 
 export function attachModelProperties(model: Model): Promise<any>[] {
     // return a list of promises that start attaching the props in parallel
     // (if there are downloads etc.)
     return [
-        StructureQualityReport.attachFromPDBeApi(model),
-        SymmetryAnnotation.attachFromRCSB(model)
+        PDBe_structureQualityReport(model)
+        // removed for now because of schema validation error
+        // SymmetryAnnotation.attachFromRCSB(model)
     ];
 }

+ 18 - 0
src/servers/model/properties/pdbe.ts

@@ -0,0 +1,18 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+ import { Model } from 'mol-model/structure';
+import { StructureQualityReport } from 'mol-model-props/pdbe/structure-quality-report';
+import { fetchRetry } from '../utils/fetch-retry';
+
+export function PDBe_structureQualityReport(model: Model) {
+    return StructureQualityReport.attachFromCifOrApi(model, {
+        PDBe_apiSourceJson: async model => {
+            const rawData = await fetchRetry(`https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/${model.label.toLowerCase()}`, 1500, 5);
+            return await rawData.json();
+        }
+    });
+}

+ 0 - 123
src/servers/model/properties/structure-quality-report.ts

@@ -1,123 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { ResidueIndex, ModelPropertyDescriptor, Model, Structure, Unit, StructureElement, StructureProperties as P  } from 'mol-model/structure';
-import fetch from 'node-fetch';
-import { CifWriter } from 'mol-io/writer/cif';
-import CifField = CifWriter.Field;
-import { Segmentation } from 'mol-data/int';
-
-type IssueMap = Map<ResidueIndex, string[]>
-
-const _Descriptor: ModelPropertyDescriptor = {
-    isStatic: true,
-    name: 'structure_quality_report',
-    cifExport: {
-        categories: [{
-            name: 'structure_quality_report',
-            instance(ctx) {
-                const issues = StructureQualityReport.get(ctx.model);
-                if (!issues) return CifWriter.Category.Empty;
-
-                const residues = getResidueLoci(ctx.structure, issues);
-                return {
-                    fields: _structure_quality_report_fields,
-                    data: <ExportCtx>{ model: ctx.model, residues, residueIndex: ctx.model.atomicHierarchy.residueAtomSegments.index, issues },
-                    rowCount: residues.length
-                };
-            }
-        }]
-    }
-}
-
-type ExportCtx = { model: Model, residueIndex: ArrayLike<ResidueIndex>, residues: StructureElement[], issues: IssueMap };
-
-const _structure_quality_report_fields: CifField<ResidueIndex, ExportCtx>[] = [
-    CifField.str<ResidueIndex, ExportCtx>('label_comp_id', (i, d) => P.residue.label_comp_id(d.residues[i])),
-    CifField.int<ResidueIndex, ExportCtx>('label_seq_id', (i, d) => P.residue.label_seq_id(d.residues[i])),
-    CifField.str<ResidueIndex, ExportCtx>('pdbx_PDB_ins_code', (i, d) => P.residue.pdbx_PDB_ins_code(d.residues[i])),
-    CifField.str<ResidueIndex, ExportCtx>('label_asym_id', (i, d) => P.chain.label_asym_id(d.residues[i])),
-    CifField.str<ResidueIndex, ExportCtx>('label_entity_id', (i, d) => P.entity.id(d.residues[i])),
-
-    CifField.str<ResidueIndex, ExportCtx>('auth_comp_id', (i, d) => P.residue.auth_comp_id(d.residues[i])),
-    CifField.int<ResidueIndex, ExportCtx>('auth_seq_id', (i, d) => P.residue.auth_seq_id(d.residues[i])),
-    CifField.str<ResidueIndex, ExportCtx>('auth_asym_id', (i, d) => P.chain.auth_asym_id(d.residues[i])),
-
-    CifField.str<ResidueIndex, ExportCtx>('issues', (i, d) => d.issues.get(d.residueIndex[d.residues[i].element])!.join(','))
-];
-
-function getResidueLoci(structure: Structure, issues: IssueMap) {
-    const seenResidues = new Set<ResidueIndex>();
-    const unitGroups = structure.unitSymmetryGroups;
-    const loci: StructureElement[] = [];
-
-    for (const unitGroup of unitGroups) {
-        const unit = unitGroup.units[0];
-        if (!Unit.isAtomic(unit)) {
-            continue;
-        }
-
-        const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
-        while (residues.hasNext) {
-            const seg = residues.move();
-            if (!issues.has(seg.index) || seenResidues.has(seg.index)) continue;
-
-            seenResidues.add(seg.index);
-            loci[loci.length] = StructureElement.create(unit, unit.elements[seg.start]);
-        }
-    }
-
-    loci.sort((x, y) => x.element - y.element);
-    return loci;
-}
-
-function createIssueMap(modelData: Model, data: any): IssueMap | undefined {
-    const ret = new Map<ResidueIndex, string[]>();
-    if (!data.molecules) return;
-
-    for (const entity of data.molecules) {
-        const entity_id = entity.entity_id.toString();
-        for (const chain of entity.chains) {
-            const asym_id = chain.struct_asym_id.toString();
-            for (const model of chain.models) {
-                const model_id = model.model_id.toString();
-                if (+model_id !== modelData.modelNum) continue;
-
-                for (const residue of model.residues) {
-                    const auth_seq_id = residue.author_residue_number, ins_code = residue.author_insertion_code || '';
-                    const idx = modelData.atomicHierarchy.findResidueKey(entity_id, asym_id, '', auth_seq_id, ins_code);
-                    ret.set(idx, residue.outlier_types);
-                }
-            }
-        }
-    }
-
-    return ret;
-}
-
-export namespace StructureQualityReport {
-    export const Descriptor = _Descriptor;
-
-    export async function attachFromPDBeApi(model: Model) {
-        if (model.customProperties.has(Descriptor)) return true;
-
-        const id = model.label.toLowerCase();
-        const rawData = await fetch(`https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/${model.label.toLowerCase()}`, { timeout: 1500 });
-        const json = await rawData.json();
-        const data = json[id];
-        if (!data) return false;
-        const issueMap = createIssueMap(model, data);
-        if (!issueMap || issueMap.size === 0) return false;
-
-        model.customProperties.add(Descriptor);
-        model._staticPropertyData.__StructureQualityReport__ = issueMap;
-        return true;
-    }
-
-    export function get(model: Model): IssueMap | undefined {
-        return model._staticPropertyData.__StructureQualityReport__;
-    }
-}

+ 29 - 0
src/servers/model/utils/fetch-retry.ts

@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import fetch from 'node-fetch';
+import { retryIf } from 'mol-util/retry-if';
+
+const RETRIABLE_NETWORK_ERRORS = [
+    'ECONNRESET', 'ENOTFOUND', 'ESOCKETTIMEDOUT', 'ETIMEDOUT',
+    'ECONNREFUSED', 'EHOSTUNREACH', 'EPIPE', 'EAI_AGAIN'
+];
+
+function isRetriableNetworkError(error: any) {
+    return error && RETRIABLE_NETWORK_ERRORS.includes(error.code);
+}
+
+export async function fetchRetry(url: string, timeout: number, retryCount: number) {
+    const result = await retryIf(() => fetch(url, { timeout }), {
+        retryThenIf: r => r.status >= 500 && r.status < 600,
+        // TODO test retryCatchIf
+        retryCatchIf: e => isRetriableNetworkError(e),
+        retryCount
+    });
+
+    if (result.status >= 200 && result.status < 300) return result;
+    throw new Error(result.statusText);
+}

+ 3 - 1
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" ],
@@ -22,6 +23,7 @@
             "mol-io": ["./mol-io"],
             "mol-math": ["./mol-math"],
             "mol-model": ["./mol-model"],
+            "mol-model-props": ["./mol-model-props"],
             "mol-ql": ["./mol-ql"],
             "mol-script": ["./mol-script"],
             "mol-task": ["./mol-task", "./mol-task/index.ts"],

Some files were not shown because too many files changed in this diff