parser.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. /**
  2. * Copyright (c) 2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { MonadicParser as P } from '../../mol-util/monadic-parser';
  7. import { Expression } from './expression';
  8. import { MolScriptBuilder as B } from './builder';
  9. import { assertUnreachable } from '../../mol-util/type-helpers';
  10. export function parseMolScript(input: string) {
  11. return Language.parse(input);
  12. }
  13. namespace Language {
  14. type AST = ASTNode.Expression[]
  15. namespace ASTNode {
  16. export type Expression = Str | Symb | List | Comment
  17. export type ExpressionWithoutComment = Str | Symb | List
  18. export interface Str {
  19. kind: 'string',
  20. value: string
  21. }
  22. export interface Symb {
  23. kind: 'symbol',
  24. value: string
  25. }
  26. export interface List {
  27. kind: 'list',
  28. bracket: '(' | '[' | '{',
  29. nodes: Expression[]
  30. }
  31. export interface Comment {
  32. kind: 'comment',
  33. value: string
  34. }
  35. export function str(value: string): Str { return { kind: 'string', value }; }
  36. export function symb(value: string): Symb { return { kind: 'symbol', value }; }
  37. export function list(bracket: '(' | '[' | '{', nodes: Expression[]): List { return { kind: 'list', bracket, nodes }; }
  38. export function comment(value: string): Comment { return { kind: 'comment', value }; }
  39. }
  40. const ws = P.regexp(/[\n\r\s]*/);
  41. const Expr: P<ASTNode.Expression> = P.lazy(() => (P.alt(Str, List, Symb, Comment).trim(ws)));
  42. const Str = P.takeWhile(c => c !== '`').trim('`').map(ASTNode.str);
  43. const Symb = P.regexp(/[^()\[\]{};`,\n\r\s]+/).map(ASTNode.symb);
  44. const Comment = P.regexp(/\s*;+([^\n\r]*)\n/, 1).map(ASTNode.comment);
  45. const Args = Expr.many();
  46. const List1 = Args.wrap('(', ')').map(args => ASTNode.list('(', args));
  47. const List2 = Args.wrap('[', ']').map(args => ASTNode.list('[', args));
  48. const List3 = Args.wrap('{', '}').map(args => ASTNode.list('{', args));
  49. const List = P.alt(List1, List2, List3);
  50. const Expressions: P<AST> = Expr.many();
  51. function getAST(input: string) { return Expressions.tryParse(input); }
  52. function visitExpr(expr: ASTNode.ExpressionWithoutComment): Expression {
  53. switch (expr.kind) {
  54. case 'string': return expr.value;
  55. case 'symbol': {
  56. const value = expr.value;
  57. if (value.length > 1) {
  58. const fst = value.charAt(0);
  59. switch (fst) {
  60. case '.': return B.atomName(value.substr(1));
  61. case '_': return B.struct.type.elementSymbol([value.substr(1)]);
  62. }
  63. }
  64. if (value === 'true') return true;
  65. if (value === 'false') return false;
  66. if (isNumber(value)) return +value;
  67. return Expression.Symbol(value);
  68. }
  69. case 'list': {
  70. switch (expr.bracket) {
  71. case '[': return B.core.type.list(withoutComments(expr.nodes).map(visitExpr));
  72. case '{': return B.core.type.set(withoutComments(expr.nodes).map(visitExpr));
  73. case '(': {
  74. if (expr.nodes[0].kind === 'comment') throw new Error('Invalid expression');
  75. const head = visitExpr(expr.nodes[0]);
  76. return Expression.Apply(head, getArgs(expr.nodes));
  77. }
  78. default: assertUnreachable(expr.bracket);
  79. }
  80. }
  81. default: assertUnreachable(expr);
  82. }
  83. }
  84. function getArgs(nodes: ASTNode.Expression[]): Expression.Arguments | undefined {
  85. if (nodes.length <= 1) return void 0;
  86. if (!hasNamedArgs(nodes)) {
  87. const args: Expression[] = [];
  88. for (let i = 1, _i = nodes.length; i < _i; i++) {
  89. const n = nodes[i];
  90. if (n.kind === 'comment') continue;
  91. args[args.length] = visitExpr(n);
  92. }
  93. return args;
  94. }
  95. const args: { [name: string]: Expression } = {};
  96. let allNumeric = true;
  97. let pos = 0;
  98. for (let i = 1, _i = nodes.length; i < _i; i++) {
  99. const n = nodes[i];
  100. if (n.kind === 'comment') continue;
  101. if (n.kind === 'symbol' && n.value.length > 1 && n.value.charAt(0) === ':') {
  102. const name = n.value.substr(1);
  103. ++i;
  104. while (i < _i && nodes[i].kind === 'comment') { i++; }
  105. if (i >= _i) throw new Error(`There must be a value foolowed a named arg ':${name}'.`);
  106. if (nodes[i].kind === 'comment') throw new Error('Invalid expression');
  107. args[name] = visitExpr(nodes[i]);
  108. if (isNaN(+name)) allNumeric = false;
  109. } else {
  110. args[pos++] = visitExpr(n);
  111. }
  112. }
  113. if (allNumeric) {
  114. const keys = Object.keys(args).map(a => +a).sort((a, b) => a - b);
  115. let isArray = true;
  116. for (let i = 0, _i = keys.length; i < _i; i++) {
  117. if (keys[i] !== i) {
  118. isArray = false;
  119. break;
  120. }
  121. }
  122. if (isArray) {
  123. const arrayArgs: Expression[] = [];
  124. for (let i = 0, _i = keys.length; i < _i; i++) {
  125. arrayArgs[i] = args[i];
  126. }
  127. return arrayArgs;
  128. }
  129. }
  130. return args;
  131. }
  132. function hasNamedArgs(nodes: ASTNode.Expression[]) {
  133. for (let i = 1, _i = nodes.length; i < _i; i++) {
  134. const n = nodes[i];
  135. if (n.kind === 'symbol' && n.value.length > 1 && n.value.charAt(0) === ':') return true;
  136. }
  137. return false;
  138. }
  139. function withoutComments(nodes: ASTNode.Expression[]) {
  140. let hasComment = false;
  141. for (let i = 0, _i = nodes.length; i < _i; i++) {
  142. if (nodes[i].kind === 'comment') {
  143. hasComment = true;
  144. break;
  145. }
  146. }
  147. if (!hasComment) return nodes as ASTNode.ExpressionWithoutComment[];
  148. return nodes.filter(n => n.kind !== 'comment') as ASTNode.ExpressionWithoutComment[];
  149. }
  150. function isNumber(value: string) {
  151. return /-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/.test(value) && !isNaN(+value);
  152. }
  153. export function parse(input: string): Expression[] {
  154. const ast = getAST(input);
  155. const ret: Expression[] = [];
  156. for (const expr of ast) {
  157. if (expr.kind === 'comment') continue;
  158. ret[ret.length] = visitExpr(expr);
  159. }
  160. return ret;
  161. }
  162. }