diff --git a/src/mol-script/expression.ts b/src/mol-script/expression.ts index 5a5d41e4b1376103b415a1e6332f54cfa1183514..d243dff0cdc3f922ca71461ee4748d11a9a62201 100644 --- a/src/mol-script/expression.ts +++ b/src/mol-script/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 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); } 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 \ No newline at end of file diff --git a/src/mol-script/runtime/environment.ts b/src/mol-script/runtime/environment.ts index 24e7e8b3407dda4a879fccdbbc5f7d61d83a5d17..d13d5738b8c7e528a361052eea01d5572aca65e1 100644 --- a/src/mol-script/runtime/environment.ts +++ b/src/mol-script/runtime/environment.ts @@ -1,14 +1,16 @@ -/* +/** * 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 { Macro } from './macro'; interface Environment<T = any> { readonly symbolTable: SymbolRuntimeTable, - readonly context: T + readonly context: T, + readonly macros: Macro.Table } export default Environment \ No newline at end of file diff --git a/src/mol-script/runtime/expression.ts b/src/mol-script/runtime/expression.ts index c2e5a71c63e25a50955a8b5cd747452f67465d00..2d8f7d8adc81a5d4d3a3d9ade5571479800de39f 100644 --- a/src/mol-script/runtime/expression.ts +++ b/src/mol-script/runtime/expression.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> diff --git a/src/mol-script/runtime/macro.ts b/src/mol-script/runtime/macro.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb7524054452bbdefc756a6a6a1371687f9ea0e4 --- /dev/null +++ b/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 '../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 } \ No newline at end of file diff --git a/src/mol-script/runtime/symbol.ts b/src/mol-script/runtime/symbol.ts index 15b4493ad51ee1a7b98e66922ad740e411f02b56..81a3590e1a1adafb25036afc2924ec4329e8e73a 100644 --- a/src/mol-script/runtime/symbol.ts +++ b/src/mol-script/runtime/symbol.ts @@ -1,4 +1,4 @@ -/* +/** * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> @@ -20,7 +20,7 @@ namespace SymbolRuntime { export interface Attributes { isStatic: boolean } } -function SymbolRuntime(symbol: Symbol, attributes: Partial<SymbolRuntime.Attributes> = {}) { +function SymbolRuntime(attributes: Partial<SymbolRuntime.Attributes> = {}) { const { isStatic = false } = attributes; return (runtime: SymbolRuntime): SymbolRuntime.Info => { return ({ runtime, attributes: { isStatic } });