Bladeren bron

Added Mol-script

David Sehnal 7 jaren geleden
bovenliggende
commit
3fff4c0bf5

+ 88 - 0
src/mol-script/compiler.ts

@@ -0,0 +1,88 @@
+/*
+ * 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'
+
+export type CompiledExpression<C, T> = (ctx: C) => 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 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 } }
+}
+
+function wrap<C, T>(envProvider: (ctx?: C) => Environment, runtime: RuntimeExpression<C, T>) {
+    return (ctx: C) => runtime(envProvider(ctx));
+}
+
+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 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 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 compile(env: Environment, expression: Expression): CompileResult {
+    if (Expression.isLiteral(expression)) {
+        return CompileResult.Const(expression);
+    }
+
+    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

+ 15 - 0
src/mol-script/container.ts

@@ -0,0 +1,15 @@
+/*
+ * 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'
+
+interface Container {
+    source?: string,
+    version: string,
+    expression: Expression
+}
+
+export default Container

+ 184 - 0
src/mol-script/core-symbols.ts

@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import Type from './type'
+import Symbol, { Arguments, Argument } from './symbol'
+import { symbol, normalizeTable, symbolList } from './helpers'
+
+export namespace Types {
+    export type List<T = any> = ArrayLike<T>
+    export type Set<T = any> = { has(e: T): boolean }
+
+    export const AnyVar = Type.Variable('a', Type.Any);
+    export const AnyValueVar = Type.Variable('a', Type.Any);
+    export const ConstrainedVar = Type.Variable('a', Type.Any, true);
+
+    export const Regex = Type.Value<RegExp>('Core', 'Regex');
+
+    export const Set = <T extends Type>(t?: T) => Type.Container<Set<T['@type']>>('Core', 'Set', t || AnyValueVar);
+    export const List = <T extends Type>(t?: T) => Type.Container<List<T['@type']>>('Core', 'List', t || AnyVar);
+    export const Fn = <T extends Type>(t?: T, alias?: string) => Type.Container<(env: any) => T['@type']>('Core', 'Fn', t || AnyVar, alias);
+    export const Flags = <T extends Type>(t: T, alias?: string) => Type.Container<number>('Core', 'Flags', t, alias);
+
+    export const BitFlags = Flags(Type.Num, 'BitFlags');
+}
+
+function unaryOp<T extends Type>(type: T, description?: string) {
+    return symbol(Arguments.Dictionary({ 0: Argument(type) }), type, description);
+}
+
+function binOp<T extends Type>(type: T, description?: string) {
+    return symbol(Arguments.List(type, { nonEmpty: true }), type, description);
+}
+
+function binRel<A extends Type, T extends Type>(src: A, target: T, description?: string) {
+    return symbol(Arguments.Dictionary({
+        0: Argument(src),
+        1: Argument(src)
+    }), target, description);
+}
+
+const type = {
+    '@header': 'Types',
+    bool: symbol(Arguments.Dictionary({ 0: Argument(Type.AnyValue) }), Type.Bool, 'Convert a value to boolean.'),
+    num: symbol(Arguments.Dictionary({ 0: Argument(Type.AnyValue) }), Type.Num, 'Convert a value to number.'),
+    str: symbol(Arguments.Dictionary({ 0: Argument(Type.AnyValue) }), Type.Str, 'Convert a value to string.'),
+    regex: symbol(
+        Arguments.Dictionary({
+            0: Argument(Type.Str, { description: 'Expression' }),
+            1: Argument(Type.Str, { isOptional: true, description: `Flags, e.g. 'i' for ignore case` })
+        }), Types.Regex, 'Creates a regular expression from a string using the ECMAscript syntax.'),
+
+    list: symbol(Arguments.List(Types.AnyVar), Types.List()),
+    set: symbol(Arguments.List(Types.AnyValueVar), Types.Set()),
+    bitflags: symbol(Arguments.Dictionary({ 0: Argument(Type.Num) }), Types.BitFlags, 'Interpret a number as bitflags.'),
+    compositeKey: symbol(Arguments.List(Type.AnyValue), Type.AnyValue),
+};
+
+const logic = {
+    '@header': 'Logic',
+    not: unaryOp(Type.Bool),
+    and: binOp(Type.Bool),
+    or: binOp(Type.Bool),
+};
+
+const ctrl = {
+    '@header': 'Control',
+    eval: symbol(Arguments.Dictionary({ 0: Argument(Types.Fn(Types.AnyVar)) }), Types.AnyVar, 'Evaluate a function.'),
+    fn: symbol(Arguments.Dictionary({ 0: Argument(Types.AnyVar) }), Types.Fn(Types.AnyVar), 'Wrap an expression to a "lazy" function.'),
+    if: symbol(Arguments.Dictionary({
+        0: Argument(Type.Bool, { description: 'Condition' }),
+        1: Argument(Type.Variable('a', Type.Any), { description: 'If true' }),
+        2: Argument(Type.Variable('b', Type.Any), { description: 'If false' })
+    }), Type.Union([Type.Variable('a', Type.Any), Type.Variable('b', Type.Any)])),
+    assoc: symbol(Arguments.Dictionary({
+        0: Argument(Type.Str, { description: 'Name' }),
+        1: Argument(Type.Variable('a', Type.Any), {description: 'Value to assign' })
+    }), Type.Variable('a', Type.Any))
+};
+
+const rel = {
+    '@header': 'Relational',
+    eq: binRel(Type.Variable('a', Type.AnyValue, true), Type.Bool),
+    neq: binRel(Type.Variable('a', Type.AnyValue, true), Type.Bool),
+    lt: binRel(Type.Num, Type.Bool),
+    lte: binRel(Type.Num, Type.Bool),
+    gr: binRel(Type.Num, Type.Bool),
+    gre: binRel(Type.Num, Type.Bool),
+    inRange: symbol(Arguments.Dictionary({
+        0: Argument(Type.Num, { description: 'Value to test' }),
+        1: Argument(Type.Num, { description: 'Minimum value' }),
+        2: Argument(Type.Num, { description: 'Maximum value' })
+    }), Type.Bool, 'Check if the value of the 1st argument is >= 2nd and <= 3rd.'),
+};
+
+const math = {
+    '@header': 'Math',
+    add: binOp(Type.Num),
+    sub: binOp(Type.Num),
+    mult: binOp(Type.Num),
+    div: binRel(Type.Num, Type.Num),
+    pow: binRel(Type.Num, Type.Num),
+    mod: binRel(Type.Num, Type.Num),
+
+    min: binOp(Type.Num),
+    max: binOp(Type.Num),
+
+    floor: unaryOp(Type.Num),
+    ceil: unaryOp(Type.Num),
+    roundInt: unaryOp(Type.Num),
+    abs: unaryOp(Type.Num),
+    sqrt: unaryOp(Type.Num),
+    sin: unaryOp(Type.Num),
+    cos: unaryOp(Type.Num),
+    tan: unaryOp(Type.Num),
+    asin: unaryOp(Type.Num),
+    acos: unaryOp(Type.Num),
+    atan: unaryOp(Type.Num),
+    sinh: unaryOp(Type.Num),
+    cosh: unaryOp(Type.Num),
+    tanh: unaryOp(Type.Num),
+    exp: unaryOp(Type.Num),
+    log: unaryOp(Type.Num),
+    log10: unaryOp(Type.Num),
+    atan2: binRel(Type.Num, Type.Num)
+};
+
+const str = {
+    '@header': 'Strings',
+    concat: binOp(Type.Str),
+    match: symbol(Arguments.Dictionary({ 0: Argument(Types.Regex), 1: Argument(Type.Str) }), Type.Bool)
+};
+
+const list = {
+    '@header': 'Lists',
+    getAt: symbol(Arguments.Dictionary({ 0: Argument(Types.List()), 1: Argument(Type.Num) }), Types.AnyVar)
+};
+
+const set = {
+    '@header': 'Sets',
+    has: symbol(Arguments.Dictionary({ 0: Argument(Types.Set(Types.ConstrainedVar)), 1: Argument(Types.ConstrainedVar) }), Type.Bool, 'Check if the the 1st argument includes the value of the 2nd.'),
+    isSubset: symbol(Arguments.Dictionary({ 0: Argument(Types.Set(Types.ConstrainedVar)), 1: Argument(Types.Set(Types.ConstrainedVar)) }), Type.Bool, 'Check if the the 1st argument is a subset of the 2nd.')
+};
+
+const flags = {
+    '@header': 'Flags',
+    hasAny: symbol(Arguments.Dictionary({
+        0: Argument(Types.Flags(Types.ConstrainedVar)),
+        1: Argument(Types.Flags(Types.ConstrainedVar))
+    }), Type.Bool, 'Check if the the 1st argument has at least one of the 2nd one\'s flags.'),
+    hasAll: symbol(Arguments.Dictionary({
+        0: Argument(Types.Flags(Types.ConstrainedVar)),
+        1: Argument(Types.Flags(Types.ConstrainedVar))
+    }), Type.Bool, 'Check if the the 1st argument has all 2nd one\'s flags.'),
+}
+
+const table = {
+    '@header': 'Language Primitives',
+    type,
+    logic,
+    ctrl,
+    rel,
+    math,
+    str,
+    list,
+    set,
+    flags
+}
+
+normalizeTable(table);
+
+export const SymbolList = symbolList(table);
+
+export const SymbolMap = (function() {
+    const map: { [id: string]: Symbol | undefined } = Object.create(null);
+    for (const s of SymbolList) map[s.id] = s;
+    return map;
+})();
+
+export default table;
+
+

+ 127 - 0
src/mol-script/expression-formatter.ts

@@ -0,0 +1,127 @@
+/*
+ * 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'
+
+const { isLiteral, 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;
+    private prefixLength = 0;
+    private _prefix: string = '';
+    private localPrefix: string = '';
+
+    private setLocal() {
+        this.localPrefix = '  ';
+    }
+
+    newline() {
+        this.value.push(`\n${this._prefix}${this.localPrefix}`);
+        this.currentLineLength = 0;
+    }
+
+    push() {
+        this.value.push('(');
+        this.currentLineLength = 0;
+        this.localPrefix = '';
+        this.prefixLength += 2;
+        this._prefix = new Array(this.prefixLength + 1).join(' ');
+    }
+
+    pop() {
+        this.value.push(')');
+        this.prefixLength -= 2;
+        this._prefix = new Array(this.prefixLength + 1).join(' ');
+    }
+
+    append(str: string) {
+        if (!this.currentLineLength) {
+            this.value.push(str);
+            this.currentLineLength = str.length;
+        } else if (this.currentLineLength + this.prefixLength + this.localPrefix.length + str.length < 80) {
+            this.value.push(str);
+            this.currentLineLength += str.length;
+        } else {
+            this.setLocal();
+            this.newline();
+            this.value.push(str);
+            this.currentLineLength = str.length;
+        }
+    }
+
+    whitespace() {
+        if (this.currentLineLength + this.prefixLength + this.localPrefix.length + 1 < 80) {
+            this.value.push(' ');
+        }
+    }
+
+    getStr() {
+        return this.value.join('');
+    }
+}
+
+function _format(e: Expression, writer: Writer) {
+    if (isLiteral(e)) {
+        if (typeof e === 'string' && (/\s/.test(e) || !e.length)) writer.append(`\`${e}\``);
+        else writer.append(`${e}`);
+        return;
+    }
+
+    writer.push();
+    _format(e.head, writer);
+
+    if (!e.args) {
+        writer.pop();
+        return;
+    }
+
+    if (isArgumentsArray(e.args)) {
+        let prevLiteral = true;
+        for (const a of e.args) {
+            if (isLiteral(a)) {
+                if (prevLiteral) writer.whitespace();
+                else writer.newline();
+                prevLiteral = true;
+            } else {
+                prevLiteral = false;
+                writer.newline();
+            }
+            _format(a, writer);
+        }
+        writer.pop();
+        return;
+    }
+
+    const keys = Object.keys(e.args);
+    if (!keys.length) {
+        writer.pop();
+        return;
+    }
+
+    if (keys.length === 1 && isLiteral(e.args[keys[0]])) {
+        writer.whitespace()
+        writer.append(`:${keys[0]}`);
+        writer.whitespace();
+        _format(e.args[keys[0]], writer);
+        writer.pop();
+        return;
+    }
+
+    for (const a of keys) {
+        writer.newline();
+        writer.append(`:${a}`);
+        writer.whitespace();
+        _format(e.args[a], writer);
+    }
+    writer.pop();
+}

+ 24 - 0
src/mol-script/expression.ts

@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+type Expression =
+    | Expression.Literal
+    | Expression.Apply
+
+namespace Expression {
+    export type Literal = string | number | boolean
+    export type Arguments = Expression[] | { [name: string]: Expression }
+    export interface Apply { readonly head: Expression, readonly args?: Arguments }
+
+    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 isApply(e: Expression): e is Expression.Apply { return !! e && !!(e as Expression.Apply).head && typeof e === 'object'; }
+}
+
+export default Expression

+ 54 - 0
src/mol-script/helpers.ts

@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import Type from './type'
+import Symbol, { 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);
+}
+
+export function normalizeTable(table: any) {
+    _normalizeTable('', '', table);
+}
+
+export function symbolList(table: any): Symbol[] {
+    const list: Symbol[] = [];
+    _symbolList(table, list);
+    return list;
+}
+
+function formatKey(key: string) {
+    const regex = /([a-z])([A-Z])([a-z]|$)/g;
+    // do this twice because 'xXxX'
+    return key.replace(regex, (s, a, b, c) => `${a}-${b.toLocaleLowerCase()}${c}`).replace(regex, (s, a, b, c) => `${a}-${b.toLocaleLowerCase()}${c}`);
+}
+
+function _normalizeTable(namespace: string, key: string, obj: any) {
+    if (isSymbol(obj)) {
+        obj.info.namespace = namespace;
+        obj.info.name = obj.info.name || formatKey(key);
+        obj.id = `${obj.info.namespace}.${obj.info.name}`;
+        return;
+    }
+    const currentNs = `${obj['@namespace'] || formatKey(key)}`;
+    const newNs = namespace ? `${namespace}.${currentNs}` : currentNs;
+    for (const childKey of Object.keys(obj)) {
+        if (typeof obj[childKey] !== 'object' && !isSymbol(obj[childKey])) continue;
+        _normalizeTable(newNs, childKey, obj[childKey]);
+    }
+}
+
+function _symbolList(obj: any, list: Symbol[]) {
+    if (isSymbol(obj)) {
+        list.push(obj);
+        return;
+    }
+    for (const childKey of Object.keys(obj)) {
+        if (typeof obj[childKey] !== 'object' && !isSymbol(obj[childKey])) continue;
+        _symbolList(obj[childKey], list);
+    }
+}

+ 14 - 0
src/mol-script/runtime/environment.ts

@@ -0,0 +1,14 @@
+/*
+ * 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'
+
+interface Environment<T = any> {
+    readonly symbolTable: SymbolRuntimeTable,
+    readonly context: T
+}
+
+export default Environment

+ 25 - 0
src/mol-script/runtime/expression.ts

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

+ 32 - 0
src/mol-script/runtime/symbol.ts

@@ -0,0 +1,32 @@
+/*
+ * 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'
+
+export type RuntimeArguments = ArrayLike<RuntimeExpression> | { [name: string]: RuntimeExpression | undefined }
+
+type SymbolRuntime = (env: Environment, args: RuntimeArguments) => any
+
+namespace SymbolRuntime {
+    export interface Info {
+        readonly runtime: SymbolRuntime,
+        readonly attributes: Attributes
+    }
+
+    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 SymbolRuntimeTable = { readonly [id: string]: SymbolRuntime.Info }
+
+export default SymbolRuntime

+ 84 - 0
src/mol-script/symbol.ts

@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import Type from './type'
+import Expression from './expression'
+
+export type Argument<T extends Type = Type>  = {
+    type: T,
+    isOptional: boolean,
+    isRest: boolean,
+    defaultValue: T['@type'] | undefined,
+    description: string | undefined
+}
+export function Argument<T extends Type>(type: T, params?: { description?: string, defaultValue?: T['@type'], isOptional?: boolean, isRest?: boolean }): Argument<T> {
+    const { description = void 0, isOptional = false, isRest = false, defaultValue = void 0 } = params || {}
+    return { type, isOptional, isRest, defaultValue, description };
+}
+
+export type Arguments<T extends { [key: string]: any } = {}> =
+    | Arguments.List<T>
+    | Arguments.Dictionary<T>
+
+export namespace Arguments {
+    export const None: Arguments = Dictionary({});
+
+    export interface Dictionary<T extends { [key: string]: any } = {}, Traits = {}> {
+        kind: 'dictionary',
+        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'] }> {
+        return { kind: 'dictionary', map, '@type': 0 as any };
+    }
+
+    export interface List<T extends { [key: string]: any } = {}, Traits = {}> {
+        kind: 'list',
+        type: Type,
+        nonEmpty: boolean,
+        '@type': T
+    }
+
+    export function List<T extends Type>(type: T, params?: { nonEmpty?: boolean }): Arguments<{ [key: string]: T['@type'] }> {
+        const { nonEmpty = false } = params || { }
+        return { kind: 'list', type, nonEmpty, '@type': 0 as any };
+    }
+}
+
+export type ExpressionArguments<T> = { [P in keyof T]?: Expression } | { [index: number]: Expression }
+
+interface Symbol<A extends Arguments = Arguments, T extends Type = Type> {
+    (args?: ExpressionArguments<A['@type']>): Expression,
+    info: {
+        namespace: string,
+        name: string,
+        description?: string
+    },
+    args: A
+    type: T,
+    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']>) {
+        return Expression.Apply(symbol.id, args as any);
+    } as any;
+    symbol.info = { namespace: '', name, description };
+    symbol.id = '';
+    symbol.args = args;
+    symbol.type = 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 type SymbolMap = { [id: string]: Symbol | undefined }
+
+export default Symbol
+

+ 41 - 0
src/mol-script/type.ts

@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+type Type<T = any> =
+    | Type.Any | Type.AnyValue | Type.Variable<T> | Type.Value<T>
+    | Type.Container<T> | Type.Union<T> | Type.OneOf<T>
+
+namespace Type {
+    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 }
+    export interface Identifier { kind: 'identifier', '@type': string }
+    export interface Container<T> { kind: 'container', namespace: string, name: string, alias?: string, child: Type, '@type': T }
+    export interface Union<T> { kind: 'union', types: Type[], '@type': T }
+    export interface OneOf<T> { kind: 'oneof', type: Value<T>, namespace: string, name: string, values: { [v: string]: boolean | undefined }, '@type': T }
+
+    export function Variable<T = any>(name: string, type: Type, isConstraint?: boolean): Variable<T> { return { kind: 'variable', name, type: type, isConstraint } as any; }
+    export function Value<T>(namespace: string, name: string, parent?: Value<any>): Value<T> { return { kind: 'value', namespace, name, parent } as any; }
+    export function Container<T = any>(namespace: string, name: string, child: Type, alias?: string): Container<T> { return { kind: 'container', namespace, name, child, alias } as any; }
+    export function Union<T = any>(types: Type[]): Union<T> { return { kind: 'union', types } as any; }
+    export function OneOf<T = any>(namespace: string, name: string, type: Value<T>, values: any[]): OneOf<T> {
+        const vals = Object.create(null);
+        for (const v of values) vals[v] = true;
+        return { kind: 'oneof', namespace, name, type, values: vals } as any;
+    }
+
+    export const Any: Any = { kind: 'any' } as any;
+    export const AnyValue: AnyValue = { kind: 'any-value' } as any;
+
+    export const Num = Value<number>('', 'Number');
+    export const Str = Value<string>('', 'String');
+    export const Bool = OneOf<boolean>('', 'Bool', Str as any, ['true', 'false']);
+
+    export function oneOfValues({ values }: OneOf<any>) { return Object.keys(values).sort(); }
+}
+
+export default Type