Skip to content
Snippets Groups Projects
parser.ts 8.91 KiB
/**                                                                                                                                          
 * Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.                                           
 * @author Alexander Rose <alexander.rose@weirdbyte.de>                                                                                     \
 * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com>                                                                                \
 *                                                                                                                                           
 * @author Koya Sakuma                                                                                                                      \
 * This module was taken from MolQL and modified in similar manner as pymol and vmd tranpilers.                                              


import * as P from '../../../mol-util/monadic-parser';
import * as h from '../helper';
import { MolScriptBuilder } from '../../../mol-script/language/builder';
const B = MolScriptBuilder;
import { properties, structureMap } from './properties';
import { operators } from './operators';
import { keywords } from './keywords';
import { AtomGroupArgs } from '../types';
import { Transpiler } from '../transpiler';
import { OperatorList } from '../types';

//const propertiesDict = h.getPropertyRules(properties);

//const slash = P.MonadicParser.string('/');


// <, <=, =, >=, >, !=, and LIKE
const valueOperators: OperatorList = [
  {
    '@desc': 'value comparisons',
    '@examples': [],
    name: '=',
    abbr: ['=='],
    type: h.binaryLeft,
    rule: P.MonadicParser.regexp(/\s*(LIKE|>=|<=|=|!=|>|<)\s*/i, 1),
    map: (op, e1, e2) => {
      // console.log(op, e1, e2)
      let expr
      if (e1 === 'structure') {
        expr = B.core.flags.hasAny([B.ammp('secondaryStructureFlags'), structureMap(e2)])
      } else if (e2 === 'structure') {
        expr = B.core.flags.hasAny([B.ammp('secondaryStructureFlags'), structureMap(e1)])
      } else if (e1.head === 'core.type.regex') {
        expr = B.core.str.match([ e1, B.core.type.str([e2]) ])
      } else if (e2.head === 'core.type.regex') {
        expr = B.core.str.match([ e2, B.core.type.str([e1]) ])
      } else if (op.toUpperCase() === 'LIKE') {
        if (e1.head) {
          expr = B.core.str.match([
            B.core.type.regex([`^${e2}$`, 'i']),
            B.core.type.str([e1])
          ])
        } else {
          expr = B.core.str.match([
            B.core.type.regex([`^${e1}$`, 'i']),
            B.core.type.str([e2])
          ])
        }
      }
      if (!expr) {
        if (e1.head) e2 = h.wrapValue(e1, e2)
        if (e2.head) e1 = h.wrapValue(e2, e1)
        switch (op) {
          case '=':
            expr = B.core.rel.eq([e1, e2])
            break
          case '!=':
            expr = B.core.rel.neq([e1, e2])
            break
          case '>':
            expr = B.core.rel.gr([e1, e2])
            break
          case '<':
            expr = B.core.rel.lt([e1, e2])
            break
          case '>=':
            expr = B.core.rel.gre([e1, e2])
            break
          case '<=':
            expr = B.core.rel.lte([e1, e2])
            break
          default: throw new Error(`value operator '${op}' not supported`);
        }
      }
      return B.struct.generator.atomGroups({ 'atom-test': expr })
    }
  }
]

function atomExpressionQuery (x: any[]) {
  const [resno, inscode, chainname, atomname, altloc, ] = x[1]
  const tests: AtomGroupArgs = {}

  if (chainname) {
    // should be configurable, there is an option in Jmol to use auth or label
    tests['chain-test'] = B.core.rel.eq([ B.ammp('auth_asym_id'), chainname ])
  }

  const resProps = []
  if (resno) resProps.push(B.core.rel.eq([ B.ammp('auth_seq_id'), resno ]))
  if (inscode) resProps.push(B.core.rel.eq([ B.ammp('pdbx_PDB_ins_code'), inscode ]))
  if (resProps.length) tests['residue-test'] = h.andExpr(resProps)

  const atomProps = []
  if (atomname) atomProps.push(B.core.rel.eq([ B.ammp('auth_atom_id'), atomname ]))
  if (altloc) atomProps.push(B.core.rel.eq([ B.ammp('label_alt_id'), altloc ]))
  if (atomProps.length) tests['atom-test'] = h.andExpr(atomProps)

  return B.struct.generator.atomGroups(tests)
}

const lang = P.MonadicParser.createLanguage({
  Integer: () => P.MonadicParser.regexp(/-?[0-9]+/).map(Number).desc('integer'),

    Parens: function (r:any) {
    return P.MonadicParser.alt(
      r.Parens,
      r.Operator,
      r.Expression
    ).wrap(P.MonadicParser.string('('), P.MonadicParser.string(')'))
  },

    Expression: function(r:any) {
    return P.MonadicParser.alt(
      r.Keywords,

	r.Resno.lookahead(P.MonadicParser.regexp(/\s*(?!(LIKE|>=|<=|!=|[:^%/.=><]))/i)).map((x:any) => B.struct.generator.atomGroups({
          'residue-test': B.core.rel.eq([ B.ammp('auth_seq_id'), x ])
      })),
      r.AtomExpression.map(atomExpressionQuery),

      r.ValueQuery,

      r.Element.map((x: string) => B.struct.generator.atomGroups({
        'atom-test': B.core.rel.eq([ B.acp('elementSymbol'), B.struct.type.elementSymbol(x) ])
      })),
      r.Resname.map((x: string) => B.struct.generator.atomGroups({
        'residue-test': B.core.rel.eq([ B.ammp('label_comp_id'), x ])
      })),
    )
  },

Operator: function(r:any) {
    return h.combineOperators(operators, P.MonadicParser.alt(r.Parens, r.Expression))
  },

AtomExpression: function(r:any) {
    return P.MonadicParser.seq(
      P.MonadicParser.lookahead(r.AtomPrefix),
      P.MonadicParser.seq(
        r.Resno.or(P.MonadicParser.of(null)),
        r.Inscode.or(P.MonadicParser.of(null)),
        r.Chainname.or(P.MonadicParser.of(null)),
        r.Atomname.or(P.MonadicParser.of(null)),
        r.Altloc.or(P.MonadicParser.of(null)),
        r.Model.or(P.MonadicParser.of(null))
      )
    )
  },

  AtomPrefix: () => P.MonadicParser.regexp(/[0-9:^%/.]/).desc('atom-prefix'),

  Chainname: () => P.MonadicParser.regexp(/:([A-Za-z]{1,3})/, 1).desc('chainname'),
  Model: () => P.MonadicParser.regexp(/\/([0-9]+)/, 1).map(Number).desc('model'),
  Element: () => P.MonadicParser.regexp(/_([A-Za-z]{1,3})/, 1).desc('element'),
  Atomname: () => P.MonadicParser.regexp(/\.([a-zA-Z0-9]{1,4})/, 1).map(B.atomName).desc('atomname'),
  Resname: () => P.MonadicParser.regexp(/[a-zA-Z0-9]{1,4}/).desc('resname'),
Resno: (r:any) => r.Integer.desc('resno'),
  Altloc: () => P.MonadicParser.regexp(/%([a-zA-Z0-9])/, 1).desc('altloc'),
  Inscode: () => P.MonadicParser.regexp(/\^([a-zA-Z0-9])/, 1).desc('inscode'),

  // BracketedResname: function (r) {
  //   return P.MonadicParser.regexp(/\.([a-zA-Z0-9]{1,4})/, 1)
  //     .desc('bracketed-resname')
  //   // [0SD]
  // },

  // ResnoRange: function (r) {
  //   return P.MonadicParser.regexp(/\.([\s]){1,3}/, 1)
  //     .desc('resno-range')
  //   // 123-200
  //   // -12--3
  // },

  Keywords: () => P.MonadicParser.alt(...h.getKeywordRules(keywords)),

Query: function(r:any) {
    return P.MonadicParser.alt(
      r.Operator,
      r.Parens,
      r.Expression
    ).trim(P.MonadicParser.optWhitespace)
  },

  Number: function () {
    return P.MonadicParser.regexp(/-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/)
      .map(Number)
      .desc('number')
  },

  String: function () {
    const w = h.getReservedWords(properties, keywords, operators)
      .sort(h.strLenSortFn).map(h.escapeRegExp).join('|')
    return P.MonadicParser.alt(
      P.MonadicParser.regexp(new RegExp(`(?!(${w}))[A-Z0-9_]+`, 'i')),
      P.MonadicParser.regexp(/'((?:[^"\\]|\\.)*)'/, 1),
      P.MonadicParser.regexp(/"((?:[^"\\]|\\.)*)"/, 1).map(x => B.core.type.regex([`^${x}$`, 'i']))
    )
  },

Value: function (r:any) {
    return P.MonadicParser.alt(r.Number, r.String)
  },

ValueParens: function (r:any) {
    return P.MonadicParser.alt(
      r.ValueParens,
      r.ValueOperator,
      r.ValueExpressions
    ).wrap(P.MonadicParser.string('('), P.MonadicParser.string(')'))
  },

  ValuePropertyNames: function() {
    return P.MonadicParser.alt(...h.getPropertyNameRules(properties, /LIKE|>=|<=|=|!=|>|<|\)|\s/i))
  },

ValueOperator: function(r:any) {
    return h.combineOperators(valueOperators, P.MonadicParser.alt(r.ValueParens, r.ValueExpressions))
  },

ValueExpressions: function(r:any) {
    return P.MonadicParser.alt(
      r.Value,
      r.ValuePropertyNames
    )
  },

ValueQuery: function(r:any) {
    return P.MonadicParser.alt(
	r.ValueOperator.map((x:any) => {
        if (x.head) {
          if (x.head.startsWith('structure.generator')) return x
        } else {
          if (typeof x === 'string' && x.length <= 4) {
            return B.struct.generator.atomGroups({
              'residue-test': B.core.rel.eq([ B.ammp('label_comp_id'), x ])
            })
          }
        }
        throw new Error(`values must be part of an comparison, value '${x}'`)
      })
    )
  }
})

const transpiler: Transpiler = str => lang.Query.tryParse(str)
export default transpiler