123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- /**
- * 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';
- import { assertUnreachable } from '../../mol-util/type-helpers';
- 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 type ExpressionWithoutComment = Str | Symb | List
- 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.ExpressionWithoutComment): 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 '(': {
- if (expr.nodes[0].kind === 'comment') throw new Error('Invalid expression');
- const head = visitExpr(expr.nodes[0]);
- return Expression.Apply(head, getArgs(expr.nodes));
- }
- default: assertUnreachable(expr.bracket);
- }
- }
- default: assertUnreachable(expr);
- }
- }
- 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}'.`);
- 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: 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 as ASTNode.ExpressionWithoutComment[];
- return nodes.filter(n => n.kind !== 'comment') as ASTNode.ExpressionWithoutComment[];
- }
- function isNumber(value: string) {
- return /-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/.test(value) && !isNaN(+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;
- }
- }
|