OpenMD 3.2
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
SelectionCompiler.cpp
1/*
2 * Copyright (c) 2004-present, The University of Notre Dame. All rights
3 * reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * 3. Neither the name of the copyright holder nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 *
31 * SUPPORT OPEN SCIENCE! If you use OpenMD or its source code in your
32 * research, please cite the following paper when you publish your work:
33 *
34 * [1] Drisko et al., J. Open Source Softw. 9, 7004 (2024).
35 *
36 * Good starting points for code and simulation methodology are:
37 *
38 * [2] Meineke, et al., J. Comp. Chem. 26, 252-271 (2005).
39 * [3] Fennell & Gezelter, J. Chem. Phys. 124, 234104 (2006).
40 * [4] Sun, Lin & Gezelter, J. Chem. Phys. 128, 234107 (2008).
41 * [5] Vardeman, Stocker & Gezelter, J. Chem. Theory Comput. 7, 834 (2011).
42 * [6] Kuang & Gezelter, Mol. Phys., 110, 691-701 (2012).
43 * [7] Lamichhane, Gezelter & Newman, J. Chem. Phys. 141, 134109 (2014).
44 * [8] Bhattarai, Newman & Gezelter, Phys. Rev. B 99, 094106 (2019).
45 * [9] Drisko & Gezelter, J. Chem. Theory Comput. 20, 4986-4997 (2024).
46 */
47
48#include "selection/SelectionCompiler.hpp"
49
50#include <any>
51#include <string>
52
53#include "utils/StringUtils.hpp"
54
55namespace OpenMD {
56
57 bool SelectionCompiler::compile(const std::string& filename,
58 const std::string& script) {
59 this->filename = filename;
60 this->script = script;
61 lineNumbers.clear();
62 lineIndices.clear();
63 aatokenCompiled.clear();
64
65 if (internalCompile()) { return true; }
66
67 std::size_t icharEnd;
68 if ((icharEnd = script.find('\r', ichCurrentCommand)) ==
69 std::string::npos &&
70 (icharEnd = script.find('\n', ichCurrentCommand)) ==
71 std::string::npos) {
72 icharEnd = script.size();
73 }
74 errorLine = script.substr(ichCurrentCommand, icharEnd);
75 return false;
76 }
77
78 bool SelectionCompiler::internalCompile() {
79 cchScript = script.size();
80 ichToken = 0;
81 lineCurrent = 1;
82
83 error = false;
84
85 // std::vector<Token> lltoken;
86 aatokenCompiled.clear();
87 std::vector<Token> ltoken;
88
89 Token tokenCommand;
90 int tokCommand = Token::nada;
91
92 for (; true; ichToken += cchToken) {
93 if (lookingAtLeadingWhitespace()) continue;
94 // if (lookingAtComment())
95 // continue;
96 bool endOfLine = lookingAtEndOfLine();
97 if (endOfLine || lookingAtEndOfStatement()) {
98 if (tokCommand != Token::nada) {
99 if (!compileCommand(ltoken)) { return false; }
100 aatokenCompiled.push_back(atokenCommand);
101 lineNumbers.push_back(lineCurrent);
102 lineIndices.push_back(ichCurrentCommand);
103 ltoken.clear();
104 tokCommand = Token::nada;
105 }
106
107 if (ichToken < cchScript) {
108 if (endOfLine) ++lineCurrent;
109 continue;
110 }
111 break;
112 }
113
114 if (tokCommand != Token::nada) {
115 if (lookingAtString()) {
116 std::string str = getUnescapedStringLiteral();
117 ltoken.push_back(Token(Token::string, str));
118 continue;
119 }
120 // if ((tokCommand & Token::specialstring) != 0 &&
121 // lookingAtSpecialString()) {
122 // std::string str = script.substr(ichToken, ichToken + cchToken);
123 // ltoken.push_back(Token(Token::string, str));
124 // continue;
125 //}
126 // if (lookingAtDecimal((tokCommand & Token::negnums) != 0)) {
127 if (lookingAtDecimal((tokCommand) != 0)) {
128 float value = lexi_cast<float>(script.substr(ichToken, cchToken));
129 ltoken.push_back(Token(Token::decimal, std::any(value)));
130 continue;
131 }
132 // if (lookingAtInteger((tokCommand & Token::negnums) != 0)) {
133 if (lookingAtInteger((tokCommand) != 0)) {
134 int val = lexi_cast<int>(script.substr(ichToken, cchToken));
135 ltoken.push_back(Token(Token::integer, std::any(val)));
136 continue;
137 }
138 }
139
140 if (lookingAtLookupToken()) {
141 std::string ident = script.substr(ichToken, cchToken);
142 Token token;
143 Token* pToken = TokenMap::getInstance().getToken(ident);
144 if (pToken != NULL) {
145 token = *pToken;
146 } else {
147 token = Token(Token::identifier, ident);
148 }
149
150 int tok = token.tok;
151
152 switch (tokCommand) {
153 case Token::nada:
154 ichCurrentCommand = ichToken;
155 // tokenCommand = token;
156 tokCommand = tok;
157 if ((tokCommand & Token::command) == 0) return commandExpected();
158 break;
159
160 case Token::define:
161 if (ltoken.size() == 1) {
162 // we are looking at the variable name
163 if (tok != Token::identifier &&
164 (tok & Token::predefinedset) != Token::predefinedset)
165 return invalidExpressionToken(ident);
166 } else {
167 // we are looking at the expression
168 if (tok != Token::identifier &&
169 (tok & (Token::expression | Token::predefinedset)) == 0)
170 return invalidExpressionToken(ident);
171 }
172
173 break;
174
175 case Token::select:
176 if (tok != Token::identifier && (tok & Token::expression) == 0)
177 return invalidExpressionToken(ident);
178 break;
179 }
180 ltoken.push_back(token);
181 continue;
182 }
183
184 if (ltoken.empty()) { return commandExpected(); }
185
186 return unrecognizedToken();
187 }
188
189 return true;
190 }
191
192 bool SelectionCompiler::lookingAtLeadingWhitespace() {
193 int ichT = ichToken;
194 while (ichT < cchScript && std::isspace(script[ichT])) {
195 ++ichT;
196 }
197 cchToken = ichT - ichToken;
198 return cchToken > 0;
199 }
200
201 bool SelectionCompiler::lookingAtEndOfLine() {
202 if (ichToken == cchScript) return true;
203 int ichT = ichToken;
204 char ch = script[ichT];
205 if (ch == '\r') {
206 ++ichT;
207 if (ichT < cchScript && script[ichT] == '\n') ++ichT;
208 } else if (ch == '\n') {
209 ++ichT;
210 } else {
211 return false;
212 }
213 cchToken = ichT - ichToken;
214 return true;
215 }
216
217 bool SelectionCompiler::lookingAtEndOfStatement() {
218 if (ichToken == cchScript || script[ichToken] != ';') return false;
219 cchToken = 1;
220 return true;
221 }
222
223 bool SelectionCompiler::lookingAtString() {
224 if (ichToken == cchScript) return false;
225 if (script[ichToken] != '"') return false;
226 // remove support for single quote
227 // in order to use it in atom expressions
228 // char chFirst = script.charAt(ichToken);
229 // if (chFirst != '"' && chFirst != '\'')
230 // return false;
231 int ichT = ichToken + 1;
232 // while (ichT < cchScript && script.charAt(ichT++) != chFirst)
233 char ch;
234 bool previousCharBackslash = false;
235 while (ichT < cchScript) {
236 ch = script[ichT++];
237 if (ch == '"' && !previousCharBackslash) break;
238 previousCharBackslash = ch == '\\' ? !previousCharBackslash : false;
239 }
240 cchToken = ichT - ichToken;
241
242 return true;
243 }
244
245 std::string SelectionCompiler::getUnescapedStringLiteral() {
246 /** @todo */
247 std::string sb(cchToken - 2, ' ');
248
249 int ichMax = ichToken + cchToken - 1;
250 int ich = ichToken + 1;
251
252 while (ich < ichMax) {
253 char ch = script[ich++];
254 if (ch == '\\' && ich < ichMax) {
255 ch = script[ich++];
256 switch (ch) {
257 case 'b':
258 ch = '\b';
259 break;
260 case 'n':
261 ch = '\n';
262 break;
263 case 't':
264 ch = '\t';
265 break;
266 case 'r':
267 ch = '\r';
268 // fall into
269 case '"':
270 case '\\':
271 case '\'':
272 break;
273 case 'x':
274 case 'u':
275 int digitCount = ch == 'x' ? 2 : 4;
276 if (ich < ichMax) {
277 int unicode = 0;
278 for (int k = digitCount; --k >= 0 && ich < ichMax;) {
279 char chT = script[ich];
280 int hexit = getHexitValue(chT);
281 if (hexit < 0) break;
282 unicode <<= 4;
283 unicode += hexit;
284 ++ich;
285 }
286 ch = (char)unicode;
287 }
288 }
289 }
290 sb.append(1, ch);
291 }
292
293 return sb;
294 }
295
296 int SelectionCompiler::getHexitValue(char ch) {
297 if (ch >= '0' && ch <= '9')
298 return ch - '0';
299 else if (ch >= 'a' && ch <= 'f')
300 return 10 + ch - 'a';
301 else if (ch >= 'A' && ch <= 'F')
302 return 10 + ch - 'A';
303 else
304 return -1;
305 }
306
307 bool SelectionCompiler::lookingAtSpecialString() {
308 int ichT = ichToken;
309 char ch = script[ichT];
310 while (ichT < cchScript && ch != ';' && ch != '\r' && ch != '\n') {
311 ++ichT;
312 }
313 cchToken = ichT - ichToken;
314 return cchToken > 0;
315 }
316
317 bool SelectionCompiler::lookingAtDecimal(bool) {
318 if (ichToken == cchScript) { return false; }
319
320 int ichT = ichToken;
321 if (script[ichT] == '-') { ++ichT; }
322 bool digitSeen = false;
323 char ch = 'X';
324 while (ichT < cchScript && std::isdigit(ch = script[ichT])) {
325 ++ichT;
326 digitSeen = true;
327 }
328
329 if (ichT == cchScript || ch != '.') { return false; }
330
331 // to support DMPC.1, let's check the character before the dot
332 if (ch == '.' && (ichT > 0) && std::isalpha(script[ichT - 1])) {
333 return false;
334 }
335
336 ++ichT;
337 while (ichT < cchScript && std::isdigit(script[ichT])) {
338 ++ichT;
339 digitSeen = true;
340 }
341 cchToken = ichT - ichToken;
342 return digitSeen;
343 }
344
345 bool SelectionCompiler::lookingAtInteger(bool allowNegative) {
346 if (ichToken == cchScript) { return false; }
347 int ichT = ichToken;
348 if (allowNegative && script[ichToken] == '-') { ++ichT; }
349 int ichBeginDigits = ichT;
350 while (ichT < cchScript && std::isdigit(script[ichT])) {
351 ++ichT;
352 }
353 if (ichBeginDigits == ichT) { return false; }
354 cchToken = ichT - ichToken;
355 return isInteger(script.substr(ichToken, cchToken).c_str());
356 }
357
358 bool SelectionCompiler::lookingAtLookupToken() {
359 if (ichToken == cchScript) { return false; }
360
361 int ichT = ichToken;
362 char ch;
363 switch (ch = script[ichT++]) {
364 case '(':
365 case ')':
366 case ',':
367 case '[':
368 case ']':
369 break;
370 case '&':
371 case '|':
372 if (ichT < cchScript && script[ichT] == ch) { ++ichT; }
373 break;
374 case '<':
375 case '=':
376 case '>':
377 if (ichT < cchScript &&
378 ((ch = script[ichT]) == '<' || ch == '=' || ch == '>')) {
379 ++ichT;
380 }
381 break;
382 case '/':
383 case '!':
384 if (ichT < cchScript && script[ichT] == '=') { ++ichT; }
385 break;
386 default:
387 if ((ch < 'a' || ch > 'z') && (ch < 'A' && ch > 'Z') && ch != '_') {
388 return false;
389 }
390 [[fallthrough]];
391 case '*':
392 case '?': // include question marks in identifier for atom expressions
393 while (ichT < cchScript && !std::isspace(ch = script[ichT]) &&
394 (std::isalpha(ch) || std::isdigit(ch) || ch == '_' || ch == '.' ||
395 ch == '*' || ch == '?' || ch == '+' || ch == '-' || ch == '[' ||
396 ch == ']')) {
397 ++ichT;
398 }
399 break;
400 }
401
402 cchToken = ichT - ichToken;
403
404 return true;
405 }
406
407 bool SelectionCompiler::compileCommand(const std::vector<Token>& ltoken) {
408 const Token& tokenCommand = ltoken[0];
409 int tokCommand = tokenCommand.tok;
410
411 atokenCommand = ltoken;
412 if ((tokCommand & Token::expressionCommand) != 0 && !compileExpression()) {
413 return false;
414 }
415
416 return true;
417 }
418
419 bool SelectionCompiler::compileExpression() {
420 /** todo */
421 unsigned int i = 1;
422 int tokCommand = atokenCommand[0].tok;
423 if (tokCommand == Token::define) {
424 i = 2;
425 } else if ((tokCommand & Token::embeddedExpression) != 0) {
426 // look for the open parenthesis
427 while (i < atokenCommand.size() &&
428 atokenCommand[i].tok != Token::leftparen)
429 ++i;
430 }
431
432 if (i >= atokenCommand.size()) { return true; }
433 return compileExpression(i);
434 }
435
436 bool SelectionCompiler::addTokenToPostfix(const Token& token) {
437 ltokenPostfix.push_back(token);
438 return true;
439 }
440
441 bool SelectionCompiler::compileExpression(int itoken) {
442 ltokenPostfix.clear();
443 for (int i = 0; i < itoken; ++i) {
444 addTokenToPostfix(atokenCommand[i]);
445 }
446
447 atokenInfix = atokenCommand;
448 itokenInfix = itoken;
449
450 addTokenToPostfix(Token::tokenExpressionBegin);
451 if (!clauseOr()) { return false; }
452
453 addTokenToPostfix(Token::tokenExpressionEnd);
454 if (itokenInfix != atokenInfix.size()) { return endOfExpressionExpected(); }
455
456 atokenCommand = ltokenPostfix;
457 return true;
458 }
459
460 Token SelectionCompiler::tokenNext() {
461 if (itokenInfix == atokenInfix.size()) { return Token(); }
462 return atokenInfix[itokenInfix++];
463 }
464
465 std::any SelectionCompiler::valuePeek() {
466 if (itokenInfix == atokenInfix.size()) {
467 return std::any();
468 } else {
469 return atokenInfix[itokenInfix].value;
470 }
471 }
472
473 int SelectionCompiler::tokPeek() {
474 if (itokenInfix == atokenInfix.size()) {
475 return 0;
476 } else {
477 return atokenInfix[itokenInfix].tok;
478 }
479 }
480
481 bool SelectionCompiler::clauseOr() {
482 if (!clauseAnd()) { return false; }
483
484 while (tokPeek() == Token::opOr) {
485 Token tokenOr = tokenNext();
486 if (!clauseAnd()) { return false; }
487 addTokenToPostfix(tokenOr);
488 }
489 return true;
490 }
491
492 bool SelectionCompiler::clauseAnd() {
493 if (!clauseNot()) { return false; }
494
495 while (tokPeek() == Token::opAnd) {
496 Token tokenAnd = tokenNext();
497 if (!clauseNot()) { return false; }
498 addTokenToPostfix(tokenAnd);
499 }
500 return true;
501 }
502
503 bool SelectionCompiler::clauseNot() {
504 if (tokPeek() == Token::opNot) {
505 Token tokenNot = tokenNext();
506 if (!clauseNot()) { return false; }
507 return addTokenToPostfix(tokenNot);
508 }
509 return clausePrimitive();
510 }
511
512 bool SelectionCompiler::clausePrimitive() {
513 int tok = tokPeek();
514 switch (tok) {
515 case Token::within:
516 return clauseWithin();
517
518 case Token::alphahull:
519 return clauseAlphaHull();
520
521 case Token::asterisk:
522 case Token::identifier:
523 return clauseChemObjName();
524
525 case Token::integer:
526 return clauseIndex();
527 default:
528 if ((tok & Token::atomproperty) == Token::atomproperty) {
529 return clauseComparator();
530 }
531 if ((tok & Token::predefinedset) != Token::predefinedset) { break; }
532 // fall into the code and below and just add the token
533 [[fallthrough]];
534 case Token::all:
535 case Token::none:
536 case Token::hull:
537 return addTokenToPostfix(tokenNext());
538 case Token::leftparen:
539 tokenNext();
540 if (!clauseOr()) { return false; }
541 if (tokenNext().tok != Token::rightparen) {
542 return rightParenthesisExpected();
543 }
544 return true;
545 }
546 return unrecognizedExpressionToken();
547 }
548
549 bool SelectionCompiler::clauseComparator() {
550 Token tokenAtomProperty = tokenNext();
551 Token tokenComparator = tokenNext();
552 if ((tokenComparator.tok & Token::comparator) == 0) {
553 return comparisonOperatorExpected();
554 }
555
556 Token tokenValue = tokenNext();
557 if (tokenValue.tok != Token::integer && tokenValue.tok != Token::decimal) {
558 return numberExpected();
559 }
560
561 float val;
562 if (tokenValue.value.type() == typeid(int)) {
563 val = std::any_cast<int>(tokenValue.value);
564 } else if (tokenValue.value.type() == typeid(float)) {
565 val = std::any_cast<float>(tokenValue.value);
566 } else {
567 return false;
568 }
569
570 std::any floatVal;
571 floatVal = val;
572 return addTokenToPostfix(
573 Token(tokenComparator.tok, tokenAtomProperty.tok, floatVal));
574 }
575
576 bool SelectionCompiler::clauseWithin() {
577 tokenNext(); // WITHIN
578 if (tokenNext().tok != Token::leftparen) { // (
579 return leftParenthesisExpected();
580 }
581
582 std::any distance;
583 Token tokenDistance = tokenNext(); // distance
584 switch (tokenDistance.tok) {
585 case Token::integer:
586 case Token::decimal:
587 distance = tokenDistance.value;
588 break;
589 default:
590 return numberOrKeywordExpected();
591 }
592
593 if (tokenNext().tok != Token::opOr) { // ,
594 return commaExpected();
595 }
596
597 if (!clauseOr()) { // *expression*
598 return false;
599 }
600
601 if (tokenNext().tok != Token::rightparen) { // )T
602 return rightParenthesisExpected();
603 }
604
605 return addTokenToPostfix(Token(Token::within, distance));
606 }
607
608 bool SelectionCompiler::clauseAlphaHull() {
609 tokenNext(); // alphaHull
610 if (tokenNext().tok != Token::leftparen) { // (
611 return leftParenthesisExpected();
612 }
613
614 std::any alpha;
615 Token tokenAlpha = tokenNext(); // alpha
616 switch (tokenAlpha.tok) {
617 case Token::integer:
618 case Token::decimal:
619 alpha = tokenAlpha.value;
620 break;
621 default:
622 return numberOrKeywordExpected();
623 }
624
625 if (tokenNext().tok != Token::rightparen) { // )T
626 return rightParenthesisExpected();
627 }
628
629 return addTokenToPostfix(Token(Token::alphahull, alpha));
630 }
631
632 bool SelectionCompiler::clauseChemObjName() {
633 Token token = tokenNext();
634 if (token.tok == Token::identifier &&
635 token.value.type() == typeid(std::string)) {
636 std::string name = std::any_cast<std::string>(token.value);
637 if (isNameValid(name)) {
638 return addTokenToPostfix(Token(Token::name, name));
639 } else {
640 return compileError("invalid name: " + name);
641 }
642 }
643
644 return false;
645 }
646
647 bool SelectionCompiler::isNameValid(const std::string& name) {
648 int nbracket = 0;
649 int ndot = 0;
650 for (unsigned int i = 0; i < name.size(); ++i) {
651 switch (name[i]) {
652 case '[':
653 ++nbracket;
654 break;
655 case ']':
656 --nbracket;
657 break;
658 case '.':
659 ++ndot;
660 break;
661 }
662 }
663
664 // only allow 3 dots at most
665 return (ndot <= 3 && nbracket == 0) ? true : false;
666 }
667
668 bool SelectionCompiler::clauseIndex() {
669 Token token = tokenNext();
670 if (token.tok == Token::integer) {
671 int index = std::any_cast<int>(token.value);
672 int tok = tokPeek();
673 if (tok == Token::to) {
674 tokenNext();
675 tok = tokPeek();
676 if (tok != Token::integer) { return numberExpected(); }
677
678 std::any intVal = tokenNext().value;
679 int first = index;
680 if (intVal.type() != typeid(int)) { return false; }
681 int second = std::any_cast<int>(intVal);
682
683 return addTokenToPostfix(
684 Token(Token::index, std::any(std::make_pair(first, second))));
685
686 } else {
687 return addTokenToPostfix(Token(Token::index, std::any(index)));
688 }
689 } else {
690 return numberExpected();
691 }
692 }
693} // namespace OpenMD
This basic Periodic Table class was originally taken from the data.cpp file in OpenBabel.
Real distance(const DynamicVector< Real > &v1, const DynamicVector< Real > &v2)
Returns the distance between two DynamicVectors.