diff --git a/src/mol-script/compiler.ts b/src/mol-script/compiler.ts new file mode 100644 index 0000000000000000000000000000000000000000..758322a6341b800bf710e47d6df3bf3011084a57 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/src/mol-script/container.ts b/src/mol-script/container.ts new file mode 100644 index 0000000000000000000000000000000000000000..f03655aa965ff19c85da5e3c4ffb3641b8e26e7b --- /dev/null +++ b/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 \ No newline at end of file diff --git a/src/mol-script/core-symbols.ts b/src/mol-script/core-symbols.ts new file mode 100644 index 0000000000000000000000000000000000000000..67dd55ca4c4c077738192a299eefefa5aab20b79 --- /dev/null +++ b/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; + + diff --git a/src/mol-script/expression-formatter.ts b/src/mol-script/expression-formatter.ts new file mode 100644 index 0000000000000000000000000000000000000000..71e5ca6bfebdc638ff0210f0ab8d33ddf275826e --- /dev/null +++ b/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(); +} \ No newline at end of file diff --git a/src/mol-script/expression.ts b/src/mol-script/expression.ts new file mode 100644 index 0000000000000000000000000000000000000000..c9ffdcb5b79b770be5b142be2a5a1adac3aeaaa2 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/src/mol-script/helpers.ts b/src/mol-script/helpers.ts new file mode 100644 index 0000000000000000000000000000000000000000..09ced0a60a313962856c2f911a47ab3d645f4486 --- /dev/null +++ b/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); + } +} \ No newline at end of file diff --git a/src/mol-script/runtime/environment.ts b/src/mol-script/runtime/environment.ts new file mode 100644 index 0000000000000000000000000000000000000000..24e7e8b3407dda4a879fccdbbc5f7d61d83a5d17 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/src/mol-script/runtime/expression.ts b/src/mol-script/runtime/expression.ts new file mode 100644 index 0000000000000000000000000000000000000000..c2e5a71c63e25a50955a8b5cd747452f67465d00 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/src/mol-script/runtime/symbol.ts b/src/mol-script/runtime/symbol.ts new file mode 100644 index 0000000000000000000000000000000000000000..15b4493ad51ee1a7b98e66922ad740e411f02b56 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/src/mol-script/symbol.ts b/src/mol-script/symbol.ts new file mode 100644 index 0000000000000000000000000000000000000000..34630be3fa2e936aef935d3ac68e19c024628c0b --- /dev/null +++ b/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 + diff --git a/src/mol-script/type.ts b/src/mol-script/type.ts new file mode 100644 index 0000000000000000000000000000000000000000..e51a1237cffe2847eb05e0cd8eaaa4a280f07dc1 --- /dev/null +++ b/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 \ No newline at end of file