molstar
Version:
A comprehensive macromolecular library.
169 lines (168 loc) • 6.8 kB
JavaScript
/**
* Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseMolScript = parseMolScript;
const monadic_parser_1 = require("../../mol-util/monadic-parser");
const expression_1 = require("./expression");
const builder_1 = require("./builder");
const type_helpers_1 = require("../../mol-util/type-helpers");
function parseMolScript(input) {
return Language.parse(input);
}
var Language;
(function (Language) {
let ASTNode;
(function (ASTNode) {
function str(value) { return { kind: 'string', value }; }
ASTNode.str = str;
function symb(value) { return { kind: 'symbol', value }; }
ASTNode.symb = symb;
function list(bracket, nodes) { return { kind: 'list', bracket, nodes }; }
ASTNode.list = list;
function comment(value) { return { kind: 'comment', value }; }
ASTNode.comment = comment;
})(ASTNode || (ASTNode = {}));
const ws = monadic_parser_1.MonadicParser.regexp(/[\n\r\s]*/);
const Expr = monadic_parser_1.MonadicParser.lazy(() => (monadic_parser_1.MonadicParser.alt(Str, List, Symb, Comment).trim(ws)));
const Str = monadic_parser_1.MonadicParser.takeWhile(c => c !== '`').trim('`').map(ASTNode.str);
const Symb = monadic_parser_1.MonadicParser.regexp(/[^()\[\]{};`,\n\r\s]+/).map(ASTNode.symb);
const Comment = monadic_parser_1.MonadicParser.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 = monadic_parser_1.MonadicParser.alt(List1, List2, List3);
const Expressions = Expr.many();
function getAST(input) { return Expressions.tryParse(input); }
function visitExpr(expr) {
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 builder_1.MolScriptBuilder.atomName(value.substr(1));
case '_': return builder_1.MolScriptBuilder.struct.type.elementSymbol([value.substr(1)]);
}
}
if (value === 'true')
return true;
if (value === 'false')
return false;
if (isNumber(value))
return +value;
return expression_1.Expression.Symbol(value);
}
case 'list': {
switch (expr.bracket) {
case '[': return builder_1.MolScriptBuilder.core.type.list(withoutComments(expr.nodes).map(visitExpr));
case '{': return builder_1.MolScriptBuilder.core.type.set(withoutComments(expr.nodes).map(visitExpr));
case '(': {
if (expr.nodes[0].kind === 'comment')
throw new Error('Invalid expression');
const head = visitExpr(expr.nodes[0]);
return expression_1.Expression.Apply(head, getArgs(expr.nodes));
}
default: (0, type_helpers_1.assertUnreachable)(expr.bracket);
}
}
default: (0, type_helpers_1.assertUnreachable)(expr);
}
}
function getArgs(nodes) {
if (nodes.length <= 1)
return void 0;
if (!hasNamedArgs(nodes)) {
const args = [];
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 = {};
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}'.`);
if (nodes[i].kind === 'comment')
throw new Error('Invalid expression');
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 = [];
for (let i = 0, _i = keys.length; i < _i; i++) {
arrayArgs[i] = args[i];
}
return arrayArgs;
}
}
return args;
}
function hasNamedArgs(nodes) {
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) {
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) {
return /-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/.test(value) && !isNaN(+value);
}
function parse(input) {
const ast = getAST(input);
const ret = [];
for (const expr of ast) {
if (expr.kind === 'comment')
continue;
ret[ret.length] = visitExpr(expr);
}
return ret;
}
Language.parse = parse;
})(Language || (Language = {}));
;