FreeCAD
1/***************************************************************************
2* Copyright (c) 2015 Eivind Kvedalen <eivind@kvedalen.name> *
3* *
4* This file is part of the FreeCAD CAx development system. *
5* *
6* This library is free software; you can redistribute it and/or *
7* modify it under the terms of the GNU Library General Public *
8* License as published by the Free Software Foundation; either *
9* version 2 of the License, or (at your option) any later version. *
10* *
11* This library is distributed in the hope that it will be useful, *
12* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14* GNU Library General Public License for more details. *
15* *
16* You should have received a copy of the GNU Library General Public *
17* License along with this library; see the file COPYING.LIB. If not, *
18* write to the Free Software Foundation, Inc., 59 Temple Place, *
19* Suite 330, Boston, MA 02111-1307, USA *
20* *
21***************************************************************************/
22
23#include "PreCompiled.h"24#ifndef _PreComp_25#include <string>26#include <tuple>27#endif28
29#include "ExpressionParser.h"30#include "ExpressionTokenizer.h"31
32using namespace App;33
34
35// Code below inspired by blog entry:
36// https://john.nachtimwald.com/2009/07/04/qcompleter-and-comma-separated-tags/
37
38QString ExpressionTokenizer::perform(const QString& prefix, int pos)39{
40// ExpressionParser::tokenize() only supports std::string but we need a tuple QString41// because due to UTF-8 encoding a std::string may be longer than a QString42// See https://forum.freecad.org/viewtopic.php?f=3&t=6993143auto tokenizeExpression = [](const QString& expr) {44std::vector<std::tuple<int, int, std::string>> result =45ExpressionParser::tokenize(expr.toStdString());46std::vector<std::tuple<int, int, QString>> tokens;47std::transform(result.cbegin(),48result.cend(),49std::back_inserter(tokens),50[&](const std::tuple<int, int, std::string>& item) {51return std::make_tuple(52std::get<0>(item),53QString::fromStdString(expr.toStdString().substr(0, std::get<1>(item))).size(),54QString::fromStdString(std::get<2>(item))55);56});57return tokens;58};59
60QString completionPrefix;61
62// Compute start; if prefix starts with =, start parsing from offset 1.63int start = (prefix.size() > 0 && prefix.at(0) == QChar::fromLatin1('=')) ? 1 : 0;64
65// Tokenize prefix66std::vector<std::tuple<int, int, QString> > tokens = tokenizeExpression(prefix.mid(start));67
68// No tokens69if (tokens.empty()) {70return {};71}72
73prefixEnd = prefix.size();74
75// Pop those trailing tokens depending on the given position, which may be76// in the middle of a token, and we shall include that token.77for (auto it = tokens.begin(); it != tokens.end(); ++it) {78if (std::get<1>(*it) >= pos) {79// Include the immediately followed '.' or '#', because we'll be80// inserting these separators too, in ExpressionCompleteModel::pathFromIndex()81if (it != tokens.begin() && std::get<0>(*it) != '.' && std::get<0>(*it) != '#')82it = it - 1;83tokens.resize(it - tokens.begin() + 1);84prefixEnd = start + std::get<1>(*it) + (int)std::get<2>(*it).size();85break;86}87}88
89int trim = 0;90if (prefixEnd > pos)91trim = prefixEnd - pos;92
93// Extract last tokens that can be rebuilt to a variable94long i = static_cast<long>(tokens.size()) - 1;95
96// First, check if we have unclosing string starting from the end97bool stringing = false;98for (; i >= 0; --i) {99int token = std::get<0>(tokens[i]);100if (token == ExpressionParser::STRING) {101stringing = false;102break;103}104
105if (token == ExpressionParser::LT && i > 0106&& std::get<0>(tokens[i - 1]) == ExpressionParser::LT) {107--i;108stringing = true;109break;110}111}112
113// Not an unclosed string and the last character is a space114if (!stringing && !prefix.isEmpty() &&115prefixEnd > 0 && prefixEnd <= prefix.size() &&116prefix[prefixEnd-1] == QChar(32)) {117return {};118}119
120if (!stringing) {121i = static_cast<long>(tokens.size()) - 1;122for (; i >= 0; --i) {123int token = std::get<0>(tokens[i]);124if (token != '.' &&125token != '#' &&126token != ExpressionParser::IDENTIFIER &&127token != ExpressionParser::INTEGER &&128token != ExpressionParser::STRING &&129token != ExpressionParser::UNIT &&130token != ExpressionParser::ONE)131break;132}133++i;134}135
136// Set prefix start for use when replacing later137if (i == static_cast<long>(tokens.size()))138prefixStart = prefixEnd;139else140prefixStart = start + std::get<1>(tokens[i]);141
142// Build prefix from tokens143while (i < static_cast<long>(tokens.size())) {144completionPrefix += std::get<2>(tokens[i]);145++i;146}147
148if (trim && trim < int(completionPrefix.size()))149completionPrefix.resize(completionPrefix.size() - trim);150
151return completionPrefix;152}
153