Explorar el Código

Merge pull request #14 from yakomaxa/rasmol

merge from rasmol branch
KoyaS hace 2 años
padre
commit
9e9ec57a5f

+ 1 - 1
src/mol-plugin-state/transforms/model.ts

@@ -786,7 +786,7 @@ const StructureSelectionFromScript = PluginStateTransform.BuiltIn({
     from: SO.Molecule.Structure,
     to: SO.Molecule.Structure,
     params: () => ({
-        script: PD.Script({ language: 'pymol', expression: 'all' }),
+        script: PD.Script({ language: 'rasmol', expression: 'all' }),
         label: PD.Optional(PD.Text(''))
     })
 })({

+ 21 - 1
src/mol-script/transpilers/pymol/properties.ts

@@ -19,8 +19,28 @@ function rangeMap(x: string) {
     return { min, max };
 }
 function listOrRangeMap(x: string) {
-    return x.includes('-') ? rangeMap(x) : listMap(x).map(x => parseInt(x));
+    if (x.includes('-') && x.includes('+')){
+        const pSplit = x.split('+').map(x => x.replace(/^["']|["']$/g, ''));
+        const res : number[] =[];
+        pSplit.forEach( x => {
+            if (x.includes('-')){
+                const [min, max] = x.split('-').map(x=>parseInt(x));
+                for (var i = min;  i <= max;  i++){
+                    res.push(i);
+                }
+            }else{
+                res.push(parseInt(x));
+            }
+        });
+//        console.log(res)
+        return res;
+    }else if(x.includes('-') && !x.includes('+')){
+        return rangeMap(x)
+    }else if(!x.includes('-') && x.includes('+')){
+        return listMap(x)
+    }
 }
+
 function elementListMap(x: string) {
     return x.split('+').map(B.struct.type.elementSymbol);
 }

+ 10 - 7
src/mol-script/transpilers/rasmol/keywords.ts

@@ -20,18 +20,21 @@ function nucleicExpr() {
     return B.struct.combinator.merge([
         B.struct.generator.atomGroups({
 	  'residue-test': B.core.set.has([
-                B.set(...['G', 'C', 'A', 'T', 'U', 'I', 'DG', 'DC', 'DA', 'DT', 'DU', 'DI', '+G', '+C', '+A', '+T', '+U', '+I']),
+              B.core.type.set(['G', 'C', 'A', 'T', 'U', 'I', 'DG', 'DC', 'DA', 'DT', 'DU', 'DI', '+G', '+C', '+A', '+T', '+U', '+I']),
                 B.ammp('label_comp_id')
 	  ])
         }),
         B.struct.filter.pick({
-	  0: B.struct.generator.atomGroups({
+	    0: B.struct.generator.atomGroups({
                 'group-by': B.ammp('residueKey')
-	  }),
-	  test: B.core.logic.and([
-                B.core.rel.eq([B.struct.atomSet.atomCount(), 1]),
-                B.core.rel.eq([B.ammp('label_atom_id'), B.atomName('P')]),
-	  ])
+	    }),
+	    test: B.core.logic.and([
+              B.core.set.isSubset([
+                // B.core.type.set([ 'P', 'O1P', 'O2P' ]),
+                h.atomNameSet(['P']),
+                B.ammpSet('label_atom_id')
+              ]),
+	    ])
         }),
         B.struct.filter.pick({
 	  0: B.struct.generator.atomGroups({

+ 1 - 1
src/mol-script/transpilers/rasmol/operators.ts

@@ -38,7 +38,7 @@ export const operators: OperatorList = [
         '@examples': ['ASP or GLU'],
         name: 'or',
         type: h.binaryLeft,
-        rule: h.infixOp(/OR|\|/i),
+        rule: h.infixOp(/OR|\||\|\|/i),
         map: (op, s1, s2) => B.struct.combinator.merge([s1, s2])
     }
 ];

+ 172 - 168
src/mol-script/transpilers/rasmol/parser.ts

@@ -11,7 +11,10 @@ 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 { properties, structureMap, structureDict } from './properties';
+import { special_properties } from './special_properties';
+import { special_keywords } from './special_keywords';
+import { special_operators } from './special_operators';
 import { operators } from './operators';
 import { keywords } from './keywords';
 import { AtomGroupArgs } from '../types';
@@ -22,96 +25,45 @@ import { OperatorList } from '../types';
 
 // const slash = P.MonadicParser.string('/');
 
+const propertiesDict = h.getPropertyRules(special_properties);
 
-// <, <=, =, >=, >, !=, 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 !== undefined) {
-                if (e1.head.name === 'core.type.regex') {
-                    expr = B.core.str.match([e1, B.core.type.str([e2])]);
-                }
-            } else if (e2.head !== undefined) {
-                if (e2.head.name === '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 });
-        }
-    }
-];
+const slash = P.MonadicParser.string('/');
+const dot = P.MonadicParser.string('.');
+const colon = P.MonadicParser.string(':');
+const comma = P.MonadicParser.string(',');
+const star = P.MonadicParser.string('*');
+const bra = P.MonadicParser.string('(');
+const ket = P.MonadicParser.string(')');
+
+
+/* is Parser -> MonadicParser substitution correct? */
+function orNull(rule: P.MonadicParser<any>) {
+    return rule.or(P.MonadicParser.of(null));
+}
 
-function atomExpressionQuery(x: any[]) {
-    const [resno, inscode, chainname, atomname, altloc] = x[1];
+
+function atomSelectionQuery2(x: any) {
     const tests: AtomGroupArgs = {};
+    const props: { [k: string]: any[] } = {};
 
-    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]);
+    for (const k in x) {
+        const ps = special_properties[k];
+        if (!ps) {
+            throw new Error(`property '${k}' not supported, value '${x[k]}'`);
+        }
+        if (x[k] === null) continue;
+        if (!props[ps.level]) props[ps.level] = [];
+        props[ps.level].push(x[k]);
     }
 
-    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);
+    for (const p in props) {
+        tests[p] = h.andExpr(props[p]);
+    }
 
     return B.struct.generator.atomGroups(tests);
 }
 
+
 const lang = P.MonadicParser.createLanguage({
     Integer: () => P.MonadicParser.regexp(/-?[0-9]+/).map(Number).desc('integer'),
 
@@ -125,76 +77,166 @@ const lang = P.MonadicParser.createLanguage({
 
     Expression: function (r: any) {
         return P.MonadicParser.alt(
-	    r.NamedAtomProperties,
 	    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])
-	    })),
+	    r.NamedAtomProperties,
+	    r.AtomSelectionMacro.map(atomSelectionQuery2),
+	    r.Object
         );
     },
 
-    NamedAtomProperties: function () {
-        return P.MonadicParser.alt(...h.getNamedPropertyRules(properties));
+
+    //    lys:a.ca  -> resn lys and chain A and name ca
+    //    lys*a.ca  -> resn lys and chain A and name ca
+    //
+    //    :a.ca -> chain A and name ca
+    //    *a.ca -> chain A and name ca
+    //
+    //    *.cg -> name ca
+    //    :.cg -> name ca
+    AtomSelectionMacro: function (r: any) {
+        return P.MonadicParser.alt(
+	    // :A.CA :.CA
+            colon.then(P.MonadicParser.alt(
+                P.MonadicParser.seq(                
+                    orNull(propertiesDict.chain).skip(dot),
+                    orNull(propertiesDict.name)
+                ).map(x => { return { chain: x[0], name: x[1]}; }),
+                P.MonadicParser.seq(
+                    orNull(propertiesDict.name).skip(dot)
+                ).map(x => { return {name: x[0] }; }),
+            )),
+	    // *A.CA *.CA
+	    star.then(P.MonadicParser.alt(
+                P.MonadicParser.seq(                
+                    orNull(propertiesDict.chain).skip(dot),
+                    orNull(propertiesDict.name)
+                ).map(x => { return { chain: x[0], name: x[1]}; }),
+                P.MonadicParser.seq(
+                    orNull(propertiesDict.name).skip(dot)
+                ).map(x => { return {name: x[0] }; }),
+            )),
+	    // 1-100+201
+	    bra.then(P.MonadicParser.alt(
+		P.MonadicParser.alt(
+		    P.MonadicParser.seq(
+			orNull(propertiesDict.resi).skip(ket),
+		    ).map(x => { return { resi: x[0] };})
+		))),
+	    //  lys:a.ca lys:a lys lys.ca
+	    P.MonadicParser.alt(
+		P.MonadicParser.alt(
+                    P.MonadicParser.seq(
+			orNull(propertiesDict.resn).skip(colon),
+			orNull(propertiesDict.chain).skip(dot),
+			orNull(propertiesDict.name)
+                    ).map(x => { return { resn: x[0], chain: x[1], name: x[2] }; }),
+		    P.MonadicParser.seq(
+			orNull(propertiesDict.resn).skip(star),
+			orNull(propertiesDict.chain).skip(dot),
+			orNull(propertiesDict.name)
+                    ).map(x => { return { resn: x[0], chain: x[1], name: x[2] }; }),
+                    P.MonadicParser.seq(
+			orNull(propertiesDict.resn).skip(colon),
+			orNull(propertiesDict.chain),
+                    ).map(x => { return { resn: x[0], chain: x[1] }; }),
+		    P.MonadicParser.seq(
+			orNull(propertiesDict.resn).skip(star),
+			orNull(propertiesDict.chain),
+                    ).map(x => { return { resn: x[0], chain: x[1] }; }),
+		    P.MonadicParser.seq(
+			orNull(propertiesDict.resn).skip(dot),
+			orNull(propertiesDict.name),
+                    ).map(x => { return { resn: x[0], name: x[1] }; }),
+		    P.MonadicParser.seq(
+			orNull(propertiesDict.resn),
+		    ).map(x => {  return { resn: x[0] };}),
+		)
+	    )		  
+	    
+	)
+		    
     },
 
-    Operator: function (r: any) {
-        return h.combineOperators(operators, P.MonadicParser.alt(r.Parens, r.Expression));
+    ObjectProperty: () => {
+	const w = h.getReservedWords(special_properties, special_keywords, special_operators)
+              .sort(h.strLenSortFn).map(h.escapeRegExp).join('|');
+        return P.MonadicParser.regexp(new RegExp(`(?!(${w}))[A-Z0-9_]+`, 'i'));
     },
 
-    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))),
-        );
+    ObjectProperty2: () => {
+	const w = h.getReservedWords(properties, keywords, operators)
+              .sort(h.strLenSortFn).map(h.escapeRegExp).join('|');
+        return P.MonadicParser.regexp(new RegExp(`(?!(${w}))[A-Z0-9_]+`, 'i'));
     },
+    
+    Object: (r: any) => {
+        return r.ObjectProperty2
+            .map((x: any) => { throw new Error(`property 'object' not supported, value '${x}'`); });
+    },
+
+
 
-    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'),
-    Resno2: (r: any) => r.split(',').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'),
+    NamedAtomProperties: function () {
+        return P.MonadicParser.alt(...h.getNamedPropertyRules(properties));
+    },
 
+        ValueRange: function (r: any) {
+        return P.MonadicParser.seq(
+            r.Value
+                .skip(P.MonadicParser.regexp(/-/i)),
+            r.Value
+        ).map(x => ({ range: x }));
+    },
 
-    //    function listMap(x: string) { return x.split(',').map(x => x.replace(/^["']|["']$/g, '')); }
+    RangeListProperty: function (r: any) {
+        return P.MonadicParser.seq(
+            P.MonadicParser.alt(...h.getPropertyNameRules(special_properties, /\s/))
+                .skip(P.MonadicParser.whitespace),
+            P.MonadicParser.alt(
+                r.ValueRange,
+                r.Value
+            ).sepBy1(comma)
+        ).map(x => {
+            const [property, values] = x;
+            const listValues: (string | number)[] = [];
+            const rangeValues: any[] = [];
+
+            values.forEach((v: any) => {
+                if (v.range) {
+                    rangeValues.push(
+                        B.core.rel.inRange([property, v.range[0], v.range[1]])
+                    );
+                } else {
+                    listValues.push(h.wrapValue(property, v, structureDict));
+                }
+            });
 
+            const rangeTest = h.orExpr(rangeValues);
+            const listTest = h.valuesTest(property, listValues);
 
+            let test;
+            if (rangeTest && listTest) {
+                test = B.core.logic.or([rangeTest, listTest]);
+            } else {
+                test = rangeTest ? rangeTest : listTest;
+            }
 
-    BracketedResname: function (r: any) {
-        return P.MonadicParser.regexp(/\.([a-zA-Z0-9]{1,4})/, 1)
-	    .desc('bracketed-resname');
-        // [0SD]
+	    return B.struct.generator.atomGroups({ [h.testLevel(property)]: test });
+        });
     },
 
-    ResnoRange: function (r: any) {
-        return P.MonadicParser.regexp(/\.([\s]){1,3}/, 1)
-	    .desc('resno-range');
-        // 123-200
-        // -12--3
+//    Operator: function (r: any) {
+//        return h.combineOperators(operators, P.MonadicParser.alt(r.Parens, r.Expression));
+//    },
+
+    Operator: function (r: any) {
+        return h.combineOperators(operators, P.MonadicParser.alt(r.Parens, r.Expression, r.Operator));
     },
 
+
     Keywords: () => P.MonadicParser.alt(...h.getKeywordRules(keywords)),
 
+
     Query: function (r: any) {
         return P.MonadicParser.alt(
             r.Operator,
@@ -223,45 +265,7 @@ const lang = P.MonadicParser.createLanguage({
         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.name) {
-                    if (x.head.name.startsWith('structure-query.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}'`);
-            })
-        );
-    }
 });
 
 export const transpiler: Transpiler = str => lang.Query.tryParse(str);

+ 16 - 1
src/mol-script/transpilers/rasmol/properties.ts

@@ -16,7 +16,14 @@ const rePosInt = /[0-9]+/;
 
 function str(x: string) { return x; }
 
-const structureDict: {[key: string]: string} = {
+export function sstrucMap(x: string) {
+    return B.struct.type.secondaryStructureFlags(
+        [structureDict[x.toUpperCase()] || 'none']
+    );
+}
+
+
+export const structureDict: {[key: string]: string} = {
     none: 'none',
     turn: 'turn',
     sheet: 'beta',
@@ -446,6 +453,14 @@ export const properties: PropertyDict = {
         regex: /-?[0-9]+/, map: x => parseInt(x),
         level: 'residue-test', property: B.ammp('auth_seq_id')
     },
+    hoge: {
+        '@desc': 'PDB residue number, not including insertion code (see also seqcode, below)',
+        '@examples': ['resno = 100'],
+//        isNumeric: true,
+        regex: /-?[0-9]+/, map: x => parseInt(x),
+        level: 'residue-test', property: B.ammp('auth_seq_id')
+
+    },
     selected: {
         '@desc': '1.0 if atom is selected; 0.0 if not',
         '@examples': [''],

+ 232 - 0
src/mol-script/transpilers/rasmol/special_keywords.ts

@@ -0,0 +1,232 @@
+/**
+ * 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>
+ */
+
+import { MolScriptBuilder } from '../../../mol-script/language/builder';
+const B = MolScriptBuilder;
+import * as h from '../helper';
+import { KeywordDict } from '../types';
+
+const ResDict = {
+    nucleic: ['A', 'C', 'T', 'G', 'U', 'DA', 'DC', 'DT', 'DG', 'DU'],
+    protein: ['ALA', 'ARG', 'ASN', 'ASP', 'CYS', 'CYX', 'GLN', 'GLU', 'GLY', 'HIS', 'HID', 'HIE', 'HIP', 'ILE', 'LEU', 'LYS', 'MET', 'MSE', 'PHE', 'PRO', 'SER', 'THR', 'TRP', 'TYR', 'VAL'],
+    solvent: ['HOH', 'WAT', 'H20', 'TIP', 'SOL']
+};
+
+const Backbone = {
+    nucleic: ['P', "O3'", "O5'", "C5'", "C4'", "C3'", 'OP1', 'OP2', 'O3*', 'O5*', 'C5*', 'C4*', 'C3*'],
+    protein: ['C', 'N', 'CA', 'O']
+};
+
+
+export const special_keywords: KeywordDict = {
+    all: {
+        '@desc': 'All atoms currently loaded into PyMOL',
+        abbr: ['*'],
+        map: () => B.struct.generator.all()
+    },
+    none: {
+        '@desc': 'No atoms (empty selection)',
+        map: () => B.struct.generator.empty()
+    },
+    hydrogens: {
+        '@desc': 'All hydrogen atoms currently loaded into PyMOL',
+        abbr: ['hydro', 'h.'],
+        map: () => B.struct.generator.atomGroups({
+            'atom-test': B.core.rel.eq([
+                B.acp('elementSymbol'),
+                B.es('H')
+            ])
+        })
+    },
+    hetatm: {
+        '@desc': 'All atoms loaded from Protein Data Bank HETATM records',
+        abbr: ['het'],
+        map: () => B.struct.generator.atomGroups({
+            'atom-test': B.core.rel.eq([B.ammp('isHet'), true])
+        })
+    },
+    visible: {
+        '@desc': 'All atoms in enabled objects with at least one visible representation',
+        abbr: ['v.']
+    },
+    polymer: {
+        '@desc': 'All atoms on the polymer (not het). Finds atoms with residue identifiers matching a known polymer, such a peptide and DNA.',
+        abbr: ['pol.'],
+        map: () => B.struct.generator.atomGroups({
+            'residue-test': B.core.set.has([
+                B.core.type.set(ResDict.nucleic.concat(ResDict.protein)),
+                B.ammp('label_comp_id')
+            ])
+        })
+    },
+    sidechain: {
+        '@desc': 'Polymer non-backbone atoms (new in PyMOL 1.6.1)',
+    },
+    present: {
+        '@desc': 'All atoms with defined coordinates in the current state (used in creating movies)',
+        abbr: ['pr.']
+    },
+    center: {
+        '@desc': 'Pseudo-atom at the center of the scene'
+    },
+    origin: {
+        '@desc': 'Pseudo-atom at the origin of rotation',
+    },
+    enabled: {
+        '@desc': 'All enabled objects or selections from the object list.',
+    },
+    masked: {
+        '@desc': 'All masked atoms.',
+        abbr: ['msk.']
+    },
+    protected: {
+        '@desc': 'All protected atoms.',
+        abbr: ['pr.']
+    },
+    bonded: {
+        '@desc': 'All bonded atoms',
+        map: () => B.struct.generator.atomGroups({
+            'atom-test': B.core.rel.gr([B.struct.atomProperty.core.bondCount({
+                flags: B.struct.type.bondFlags(['covalent', 'metallic', 'sulfide'])
+            }), 0])
+        })
+    },
+    donors: {
+        '@desc': 'All hydrogen bond donor atoms.',
+        abbr: ['don.']
+    },
+    acceptors: {
+        '@desc': 'All hydrogen bond acceptor atoms.',
+        abbr: ['acc.']
+    },
+    fixed: {
+        '@desc': 'All fixed atoms.',
+        abbr: ['fxd.']
+    },
+    restrained: {
+        '@desc': 'All restrained atoms.',
+        abbr: ['rst.']
+    },
+    organic: {
+        '@desc': 'All atoms in non-polymer organic compounds (e.g. ligands, buffers). Finds carbon-containing molecules that do not match known polymers.',
+        abbr: ['org.'],
+        map: () => h.asAtoms(B.struct.modifier.expandProperty({
+            '0': B.struct.modifier.union([
+                B.struct.generator.queryInSelection({
+                    '0': B.struct.generator.atomGroups({
+                        'residue-test': B.core.logic.not([
+                            B.core.set.has([
+                                B.core.type.set(ResDict.nucleic.concat(ResDict.protein)),
+                                B.ammp('label_comp_id')
+                            ])
+                        ])
+                    }),
+                    query: B.struct.generator.atomGroups({
+                        'atom-test': B.core.rel.eq([
+                            B.es('C'),
+                            B.acp('elementSymbol')
+                        ])
+                    })
+                })
+            ]),
+            property: B.ammp('residueKey')
+        }))
+    },
+    inorganic: {
+        '@desc': 'All non-polymer inorganic atoms/ions. Finds atoms in molecules that do not contain carbon and do not match any known solvent residues.',
+        abbr: ['ino.'],
+        map: () => h.asAtoms(B.struct.modifier.expandProperty({
+            '0': B.struct.modifier.union([
+                B.struct.filter.pick({
+                    '0': B.struct.generator.atomGroups({
+                        'residue-test': B.core.logic.not([
+                            B.core.set.has([
+                                B.core.type.set(ResDict.nucleic.concat(ResDict.protein).concat(ResDict.solvent)),
+                                B.ammp('label_comp_id')
+                            ])
+                        ]),
+                        'group-by': B.ammp('residueKey')
+                    }),
+                    test: B.core.logic.not([
+                        B.core.set.has([
+                            B.struct.atomSet.propertySet([B.acp('elementSymbol')]),
+                            B.es('C')
+                        ])
+                    ])
+                })
+            ]),
+            property: B.ammp('residueKey')
+        }))
+    },
+    solvent: {
+        '@desc': 'All water molecules. The hardcoded solvent residue identifiers are currently: HOH, WAT, H20, TIP, SOL.',
+        abbr: ['sol.'],
+        map: () => B.struct.generator.atomGroups({
+            'residue-test': B.core.set.has([
+                B.core.type.set(ResDict.solvent),
+                B.ammp('label_comp_id')
+            ])
+        })
+    },
+    guide: {
+        '@desc': 'All protein CA and nucleic acid C4*/C4',
+        map: () => B.struct.combinator.merge([
+            B.struct.generator.atomGroups({
+                'atom-test': B.core.rel.eq([
+                    B.atomName('CA'),
+                    B.ammp('label_atom_id')
+                ]),
+                'residue-test': B.core.set.has([
+                    B.core.type.set(ResDict.protein),
+                    B.ammp('label_comp_id')
+                ])
+            }),
+            B.struct.generator.atomGroups({
+                'atom-test': B.core.set.has([
+                    h.atomNameSet(['C4*', 'C4']),
+                    B.ammp('label_atom_id')
+                ]),
+                'residue-test': B.core.set.has([
+                    B.core.type.set(ResDict.nucleic),
+                    B.ammp('label_comp_id')
+                ])
+            })
+        ]),
+    },
+    metals: {
+        '@desc': 'All metal atoms (new in PyMOL 1.6.1)'
+    },
+    backbone: {
+        '@desc': 'the C, N, CA, and O atoms of a protein and the equivalent atoms in a nucleic acid.',
+        map: () => B.struct.generator.atomGroups({
+            'atom-test': B.core.set.has([
+                B.core.type.set(Backbone.protein.concat(ResDict.protein)),
+                B.ammp('label_atom_id')
+	    ])
+        }),
+    },
+    proteinxxxxxx: {
+        '@desc': 'protein................',
+        abbr: ['polymer.protein'],
+        map: () => B.struct.generator.atomGroups({
+            'residue-test': B.core.set.has([
+                B.core.type.set(ResDict.protein),
+                B.ammp('label_comp_id')
+            ])
+        })
+    },
+    nucleicxxxxx: {
+        '@desc': 'protein................',
+        abbr: ['polymer.nucleic'],
+        map: () => B.struct.generator.atomGroups({
+            'residue-test': B.core.set.has([
+                B.core.type.set(ResDict.nucleic),
+                B.ammp('label_comp_id')
+            ])
+        })
+    }
+};

+ 369 - 0
src/mol-script/transpilers/rasmol/special_operators.ts

@@ -0,0 +1,369 @@
+/**
+ * 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>
+ */
+
+import * as P from '../../../mol-util/monadic-parser';
+import * as h from '../helper';
+import { MolScriptBuilder } from '../../../mol-script/language/builder';
+const B = MolScriptBuilder;
+import { OperatorList } from '../types';
+import { Expression } from '../../language/expression';
+
+export const special_operators: OperatorList = [
+    {
+        '@desc': 'Selects atoms that are not included in s1.',
+        '@examples': [
+            'NOT resn ALA',
+            'not (resi 42 or chain A)',
+            '!resi 42 or chain A',
+        ],
+        name: 'not',
+        type: h.prefix,
+        rule: P.MonadicParser.alt(
+            P.MonadicParser.regexp(/NOT/i).skip(P.MonadicParser.whitespace),
+            P.MonadicParser.string('!').skip(P.MonadicParser.optWhitespace)
+        ),
+        map: (op, selection) => h.invertExpr(selection),
+    },
+    {
+        '@desc': 'Selects atoms included in both s1 and s2.',
+        '@examples': ['chain A AND name CA'],
+        name: 'and',
+        type: h.binaryLeft,
+        rule: h.infixOp(/AND|&/i),
+        map: (op, selection, by) =>
+            B.struct.modifier.intersectBy({ 0: selection, by }),
+    },
+    {
+        '@desc': 'Selects atoms included in either s1 or s2.',
+        '@examples': ['chain A OR chain B'],
+        name: 'or',
+        type: h.binaryLeft,
+        rule: h.infixOp(/OR|\|/i),
+        map: (op: string, s1: Expression, s2: Expression) => B.struct.combinator.merge([s1, s2]),
+    },
+    {
+        '@desc':
+            'Selects atoms in s1 whose identifiers name, resi, resn, chain and segi all match atoms in s2.',
+        '@examples': ['chain A IN chain B'],
+        name: 'in',
+        type: h.binaryLeft,
+        rule: h.infixOp(/IN/i),
+        map: (op: string, selection: Expression, source: Expression) => {
+            return B.struct.filter.withSameAtomProperties({
+                0: selection,
+                source,
+                property: B.core.type.compositeKey([
+                    B.ammp('label_atom_id'),
+                    B.ammp('label_seq_id'),
+                    B.ammp('label_comp_id'),
+                    B.ammp('auth_asym_id'),
+                    B.ammp('label_asym_id'),
+                ]),
+            });
+        },
+    },
+    {
+        '@desc':
+            'Selects atoms in s1 whose identifiers name and resi match atoms in s2.',
+        '@examples': ['chain A LIKE chain B'],
+        name: 'like',
+        type: h.binaryLeft,
+        rule: h.infixOp(/LIKE|l\./i),
+        map: (op: string, selection: Expression, source: Expression) => {
+            return B.struct.filter.withSameAtomProperties({
+                0: selection,
+                source,
+                property: B.core.type.compositeKey([
+                    B.ammp('label_atom_id'),
+                    B.ammp('label_seq_id'),
+                ]),
+            });
+        },
+    },
+    {
+        '@desc':
+            'Selects all atoms whose van der Waals radii are separated from the van der Waals radii of s1 by a minimum of X Angstroms.',
+        '@examples': ['solvent GAP 2'],
+        name: 'gap',
+        type: h.postfix,
+        rule: h
+            .postfixOp(/GAP\s+([-+]?[0-9]*\.?[0-9]+)/i, 1)
+            .map((x: any) => parseFloat(x)),
+        map: (distance: number, target: Expression) => {
+            return B.struct.filter.within({
+                '0': B.struct.generator.all(),
+                target,
+                'atom-radius': B.acp('vdw'),
+                'max-radius': distance,
+                invert: true,
+            });
+        },
+    },
+    {
+        '@desc':
+            'Selects atoms with centers within X Angstroms of the center of any atom in s1.',
+        '@examples': ['resname LIG AROUND 1'],
+        name: 'around',
+        abbr: ['a.'],
+        type: h.postfix,
+        rule: h
+            .postfixOp(/(AROUND|a\.)\s+([-+]?[0-9]*\.?[0-9]+)/i, 2)
+            .map((x: any) => parseFloat(x)),
+        map: (radius: number, target: Expression) => {
+            return B.struct.modifier.exceptBy({
+                '0': B.struct.filter.within({
+                    '0': B.struct.generator.all(),
+                    target,
+                    'max-radius': radius,
+                }),
+                by: target,
+            });
+        },
+    },
+    {
+        '@desc':
+            'Expands s1 by all atoms within X Angstroms of the center of any atom in s1.',
+        '@examples': ['chain A EXPAND 3'],
+        name: 'expand',
+        abbr: ['x.'],
+        type: h.postfix,
+        rule: h
+            .postfixOp(/(EXPAND|x\.)\s+([-+]?[0-9]*\.?[0-9]+)/i, 2)
+            .map((x: any) => parseFloat(x)),
+        map: (radius: number, selection: Expression) => {
+            return B.struct.modifier.includeSurroundings({ 0: selection, radius });
+        },
+    },
+    {
+        '@desc':
+            'Selects atoms in s1 that are within X Angstroms of any atom in s2.',
+        '@examples': ['chain A WITHIN 3 OF chain B'],
+        name: 'within',
+        abbr: ['w.'],
+        type: h.binaryLeft,
+        rule: h.ofOp('WITHIN', 'w.'),
+        map: (radius: number, selection: Expression, target: Expression) => {
+            return B.struct.filter.within({
+                0: selection,
+                target,
+                'max-radius': radius,
+            });
+        },
+    },
+    {
+        '@desc':
+            'Same as within, but excludes s2 from the selection (and thus is identical to s1 and s2 around X).',
+        '@examples': ['chain A NEAR_TO 3 OF chain B'],
+        name: 'near_to',
+        abbr: ['nto.'],
+        type: h.binaryLeft,
+        rule: h.ofOp('NEAR_TO', 'nto.'),
+        map: (radius: number, selection: Expression, target: Expression) => {
+            return B.struct.modifier.exceptBy({
+                '0': B.struct.filter.within({
+                    '0': selection,
+                    target,
+                    'max-radius': radius,
+                }),
+                by: target,
+            });
+        },
+    },
+    {
+        '@desc': 'Selects atoms in s1 that are at least X Anstroms away from s2.',
+        '@examples': ['solvent BEYOND 2 OF chain A'],
+        name: 'beyond',
+        abbr: ['be.'],
+        type: h.binaryLeft,
+        rule: h.ofOp('BEYOND', 'be.'),
+        map: (radius: number, selection: Expression, target: Expression) => {
+            return B.struct.modifier.exceptBy({
+                '0': B.struct.filter.within({
+                    '0': selection,
+                    target,
+                    'max-radius': radius,
+                    invert: true,
+                }),
+                by: target,
+            });
+        },
+    },
+    {
+        '@desc': 'Expands selection to complete residues.',
+        '@examples': ['BYRESIDUE name N'],
+        name: 'byresidue',
+        abbr: ['byresi', 'byres', 'br.'],
+        type: h.prefix,
+        rule: h.prefixOp(/BYRESIDUE|byresi|byres|br\./i),
+        map: (op: string, selection: Expression) => {
+            return h.asAtoms(
+                B.struct.modifier.expandProperty({
+                    '0': B.struct.modifier.union({ 0: selection }),
+                    property: B.ammp('residueKey'),
+                })
+            );
+        },
+    },
+    {
+        '@desc':
+            'Completely selects all alpha carbons in all residues covered by a selection.',
+        '@examples': ['BYCALPHA chain A'],
+        name: 'bycalpha',
+        abbr: ['bca.'],
+        type: h.prefix,
+        rule: h.prefixOp(/BYCALPHA|bca\./i),
+        map: (op: string, selection: Expression) => {
+            return B.struct.generator.queryInSelection({
+                '0': B.struct.modifier.expandProperty({
+                    '0': B.struct.modifier.union({ 0: selection }),
+                    property: B.ammp('residueKey'),
+                }),
+                query: B.struct.generator.atomGroups({
+                    'atom-test': B.core.rel.eq([
+                        B.atomName('CA'),
+                        B.ammp('label_atom_id'),
+                    ]),
+                }),
+            });
+        },
+    },
+    {
+        '@desc': 'Expands selection to complete molecules.',
+        '@examples': ['BYMOLECULE resi 20-30'],
+        name: 'bymolecule',
+        isUnsupported: true, // structure-query.atom-property.topology.connected-component-key' is not implemented
+        abbr: ['bymol', 'bm.'],
+        type: h.prefix,
+        rule: h.prefixOp(/BYMOLECULE|bymol|bm\./i),
+        map: (op: string, selection: Expression) => {
+            return h.asAtoms(
+                B.struct.modifier.expandProperty({
+                    '0': B.struct.modifier.union({ 0: selection }),
+                    property: B.atp('connectedComponentKey'),
+                })
+            );
+        },
+    },
+    {
+        '@desc': 'Expands selection to complete fragments.',
+        '@examples': ['BYFRAGMENT resi 10'],
+        name: 'byfragment',
+        abbr: ['byfrag', 'bf.'],
+        isUnsupported: true,
+        type: h.prefix,
+        rule: h.prefixOp(/BYFRAGMENT|byfrag|bf\./i),
+        map: (op: string, selection: Expression) => [op, selection],
+    },
+    {
+        '@desc': 'Expands selection to complete segments.',
+        '@examples': ['BYSEGMENT resn CYS'],
+        name: 'bysegment',
+        abbr: ['bysegi', 'byseg', 'bs.'],
+        type: h.prefix,
+        rule: h.prefixOp(/BYSEGMENT|bysegi|byseg|bs\./i),
+        map: (op: string, selection: Expression) => {
+            return h.asAtoms(
+                B.struct.modifier.expandProperty({
+                    '0': B.struct.modifier.union({ 0: selection }),
+                    property: B.ammp('chainKey'),
+                })
+            );
+        },
+    },
+    {
+        '@desc': 'Expands selection to complete objects.',
+        '@examples': ['BYOBJECT chain A'],
+        name: 'byobject',
+        abbr: ['byobj', 'bo.'],
+        isUnsupported: true,
+        type: h.prefix,
+        rule: h.prefixOp(/BYOBJECT|byobj|bo\./i),
+        map: (op: string, selection: Expression) => [op, selection],
+    },
+    {
+        '@desc': 'Expands selection to unit cell.',
+        '@examples': ['BYCELL chain A'],
+        name: 'bycell',
+        isUnsupported: true,
+        type: h.prefix,
+        rule: h.prefixOp(/BYCELL/i),
+        map: (op: string, selection: Expression) => [op, selection],
+    },
+    {
+        '@desc': 'All rings of size ≤ 7 which have at least one atom in s1.',
+        '@examples': ['BYRING resn HEM'],
+        name: 'byring',
+        // isUnsupported: true, // structure-query.atom-set.atom-count' is not implemented.
+        type: h.prefix,
+        rule: h.prefixOp(/BYRING/i),
+        map: (op: string, selection: Expression) => {
+            return h.asAtoms(
+                B.struct.modifier.intersectBy({
+                    '0': B.struct.filter.pick({
+                        '0': B.struct.generator.rings(),
+                        test: B.core.logic.and([
+                            B.core.rel.lte([B.struct.atomSet.atomCount(), 7]),
+                            B.core.rel.gr([B.struct.atomSet.countQuery([selection]), 1]),
+                        ]),
+                    }),
+                    by: selection,
+                })
+            );
+        },
+    },
+    {
+        '@desc': 'Selects atoms directly bonded to s1, excludes s1.',
+        '@examples': ['NEIGHBOR resn CYS'],
+        name: 'neighbor',
+        type: h.prefix,
+        abbr: ['nbr.'],
+        rule: h.prefixOp(/NEIGHBOR|nbr\./i),
+        map: (op: string, selection: Expression) => {
+            return B.struct.modifier.exceptBy({
+                '0': h.asAtoms(
+                    B.struct.modifier.includeConnected({
+                        '0': B.struct.modifier.union({ 0: selection }),
+                        'bond-test': true,
+                    })
+                ),
+                by: selection,
+            });
+        },
+    },
+    {
+        '@desc': 'Selects atoms directly bonded to s1, may include s1.',
+        '@examples': ['BOUND_TO name CA'],
+        name: 'bound_to',
+        abbr: ['bto.'],
+        type: h.prefix,
+        rule: h.prefixOp(/BOUND_TO|bto\./i),
+        map: (op: string, selection: Expression) => {
+            return h.asAtoms(
+                B.struct.modifier.includeConnected({
+                    '0': B.struct.modifier.union({ 0: selection }),
+                })
+            );
+        },
+    },
+    {
+        '@desc': 'Extends s1 by X bonds connected to atoms in s1.',
+        '@examples': ['resname LIG EXTEND 3'],
+        name: 'extend',
+        abbr: ['xt.'],
+        type: h.postfix,
+        rule: h.postfixOp(/(EXTEND|xt\.)\s+([0-9]+)/i, 2).map((x: any) => parseInt(x)),
+        map: (count: number, selection: Expression) => {
+            return h.asAtoms(
+                B.struct.modifier.includeConnected({
+                    '0': B.struct.modifier.union({ 0: selection }),
+                    'bond-test': true,
+                    'layer-count': count,
+                })
+            );
+        },
+    },
+];

+ 98 - 0
src/mol-script/transpilers/rasmol/special_properties.ts

@@ -0,0 +1,98 @@
+/**
+ * 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>
+ */
+
+import { MolScriptBuilder } from '../../../mol-script/language/builder';
+const B = MolScriptBuilder;
+import { PropertyDict } from '../types';
+
+//const reFloat = /[-+]?[0-9]*\.?[0-9]+/;
+// const rePosInt = /[0-9]+/;
+
+function atomNameListMap(x: string) { return x.split(',').map(B.atomName); }
+function listMap(x: string) { return x.split(',').map(x => x.replace(/^["']|["']$/g, '')); }
+function rangeMap(x: string) {
+    const [min, max] = x.split('-').map(x => parseInt(x));
+    return { min, max };
+}
+function listOrRangeMap(x: string) {
+    if (x.includes('-') && x.includes(',')){
+	const pSplit = x.split(',').map(x => x.replace(/^["']|["']$/g, ''));
+	const res : number[] =[];
+	pSplit.forEach( x => {
+	    if (x.includes('-')){
+		const [min, max] = x.split('-').map(x=>parseInt(x));
+		for (var i = min;  i <= max;  i++){
+		    res.push(i);
+		}		 
+	    }else{
+		res.push(parseInt(x));
+	    }
+	});
+//	console.log(res)
+	return res;		    	
+    }else if(x.includes('-') && !x.includes(',')){
+	return rangeMap(x)
+    }else if(!x.includes('-') && x.includes(',')){
+	return listMap(x)
+    }
+}
+function elementListMap(x: string) {
+    return x.split('+').map(B.struct.type.elementSymbol);
+}
+
+//const sstrucDict: { [k: string]: string } = {
+//    H: 'helix',
+//    S: 'beta',
+//    L: 'none'
+//};
+//function sstrucListMap(x: string) {
+//    return {
+//        flags: B.struct.type.secondaryStructureFlags(
+//            x.toUpperCase().split('+').map(ss => sstrucDict[ss] || 'none')
+//        )
+//    };
+//}
+
+export const special_properties: PropertyDict = {
+    symbol: {
+        '@desc': 'chemical-symbol-list: list of 1- or 2-letter chemical symbols from the periodic table',
+        '@examples': ['symbol O+N'],
+        abbr: ['e.'], regex: /[a-zA-Z'",]+/, map: elementListMap,
+        level: 'atom-test', property: B.acp('elementSymbol')
+    },
+    name: {
+        '@desc': 'atom-name-list: list of up to 4-letter codes for atoms in proteins or nucleic acids',
+        '@examples': ['name CA+CB+CG+CD'],
+        abbr: ['n.'], regex: /[a-zA-Z0-9'",]+/, map: atomNameListMap,
+        level: 'atom-test', property: B.ammp('label_atom_id')
+    },
+    resn: {
+        '@desc': 'residue-name-list: list of 3-letter codes for amino acids or list of up to 2-letter codes for nucleic acids',
+        '@examples': ['resn ASP+GLU+ASN+GLN', 'resn A+G'],
+        abbr: ['resname', 'r.'], regex: /[a-zA-Z0-9'",]+/, map: listMap,
+        level: 'residue-test', property: B.ammp('label_comp_id')
+    },
+    resi: {
+        '@desc': 'residue-identifier-list list of up to 4-digit residue numbers or residue-identifier-range',
+        '@examples': ['resi 1+10+100+1000', 'resi 1-10'],
+        abbr: ['resident', 'residue', 'resid', 'i.'], regex: /[0-9,-]+/, map: listOrRangeMap,
+        level: 'residue-test', property: B.ammp('auth_seq_id')
+    },
+    alt: {
+        '@desc': 'alternate-conformation-identifier-list list of single letters',
+        '@examples': ['alt A+B', 'alt ""', 'alt ""+A'],
+        abbr: [], regex: /[a-zA-Z0-9'",]+/, map: listMap,
+        level: 'atom-test', property: B.ammp('label_alt_id')
+    },
+    chain: {
+        '@desc': 'chain-identifier-list list of single letters or sometimes numbers',
+        '@examples': ['chain A'],
+        abbr: ['c.'], regex: /[a-zA-Z0-9'",]+/, map: listMap,
+        level: 'chain-test', property: B.ammp('auth_asym_id')
+    },
+
+};