KOSMIndoorMap

mapcssterm.cpp
1/*
2 SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "mapcssterm_p.h"
7
8#include "mapcssdeclaration_p.h"
9#include "mapcssexpressioncontext_p.h"
10#include "mapcssresult.h"
11#include "mapcssstate_p.h"
12#include "logging.h"
13
14#include "content/osmconditionalexpression_p.h"
15#include "content/osmconditionalexpressioncontext_p.h"
16
17#include <QIODevice>
18
19#include <cstdint>
20#include <limits>
21#include <span>
22
23using namespace KOSMIndoorMap;
24
25MapCSSTerm::MapCSSTerm(Operation op)
26 : m_op(op)
27{
28}
29
30MapCSSTerm::~MapCSSTerm() = default;
31
32void MapCSSTerm::addChildTerm(MapCSSTerm *term)
33{
34 m_children.emplace_back(term);
35}
36
37// ### alphabetically sorted
38struct {
39 const char *name;
40 MapCSSTerm::Operation op;
41} static constexpr const function_name_map[] = {
42 { "KOSM_conditional", MapCSSTerm::KOSM_Conditional },
43 { "any", MapCSSTerm::Any },
44 { "boolean", MapCSSTerm::BooleanCast },
45 { "concat", MapCSSTerm::Concatenate },
46 { "cond", MapCSSTerm::Conditional },
47 { "int", MapCSSTerm::Integer },
48 { "max", MapCSSTerm::Maximum },
49 { "metric", MapCSSTerm::Metric },
50 { "min", MapCSSTerm::Minimum },
51 { "num", MapCSSTerm::NumericalCast },
52 { "prop", MapCSSTerm::ReadProperty },
53 { "replace", MapCSSTerm::Replace },
54 { "sqrt", MapCSSTerm::Sqrt },
55 { "str", MapCSSTerm::StringCast },
56 { "tag", MapCSSTerm::ReadTag },
57 { "zmetric", MapCSSTerm::ZMetric },
58};
59
60MapCSSTerm::Operation MapCSSTerm::parseOperation(const char *str, std::size_t len)
61{
62 const auto it = std::lower_bound(std::begin(function_name_map), std::end(function_name_map), std::span(str, len), [](const auto &m, const auto &name) {
63 return std::strncmp(m.name, name.data(), name.size()) < 0;
64 });
65 if (it != std::end(function_name_map) && std::strncmp((*it).name, str, len) == 0) {
66 return (*it).op;
67 }
68 return Unknown;
69}
70
71// ### sorted by operation
72struct {
73 uint16_t minArgs;
74 uint16_t maxArgs;
75} static constexpr const argument_count_map[] = {
76 { 0, 0 },
77 { 0, 0 },
78 // numerical operations
79 { 2, 2 },
80 { 2, 2 },
81 { 2, 2 },
82 { 2, 2 },
83 // logical operations
84 { 2, 2 },
85 { 2, 2 },
86 { 1, 1 },
87 // comparisions
88 { 2, 2 },
89 { 2, 2 },
90 { 2, 2 },
91 { 2, 2 },
92 { 2, 2 },
93 { 2, 2 },
94 // casts
95 { 1, 1 },
96 { 1, 1 },
97 { 1, 1 },
98 // conditionals
99 { 3, 3 },
100 { 1, std::numeric_limits<uint16_t>::max() },
101 // string functions
102 { 2, std::numeric_limits<uint16_t>::max() },
103 { 3, 3 },
104 // numerical functions
105 { 1, 1 },
106 { 2, std::numeric_limits<uint16_t>::max() },
107 { 2, std::numeric_limits<uint16_t>::max() },
108 { 1, 1 },
109 // unit conversions
110 { 1, 1 },
111 { 1, 1 },
112 // list functions
113 // data/style state access functions
114 { 1, 1 },
115 { 1, 1 },
116 // our own extensions
117 { 1, 1 },
118};
119
120bool MapCSSTerm::validChildCount() const
121{
122 return m_children.size() >= argument_count_map[m_op].minArgs && m_children.size() <= argument_count_map[m_op].maxArgs;
123}
124
125void MapCSSTerm::compile(const OSM::DataSet &dataSet)
126{
127 for (const auto &c : m_children) {
128 c->compile(dataSet);
129 }
130
131 // TODO resolve tag key in case of m_op == ReadTag and m_children[0] being a constant expression
132 // TODO resolve property name in case of m_op == ReadProperty and m_children[0] being a constant expression
133}
134
135MapCSSValue MapCSSTerm::evaluate(const MapCSSExpressionContext &context) const
136{
137 switch (m_op) {
138 case Unknown:
139 return {};
140 case Literal:
141 return m_literal;
142
143 case Addition:
144 return m_children[0]->evaluate(context).asNumber() + m_children[1]->evaluate(context).asNumber();
145 case Subtraction:
146 return m_children[0]->evaluate(context).asNumber() - m_children[1]->evaluate(context).asNumber();
147 case Multiplication:
148 return m_children[0]->evaluate(context).asNumber() * m_children[1]->evaluate(context).asNumber();
149 case Division: {
150 const auto divisor = m_children[1]->evaluate(context);
151 if (!divisor.isNone() && divisor.asNumber() != 0.0) {
152 return m_children[0]->evaluate(context).asNumber() / divisor.asNumber();
153 }
154 return {};
155 }
156
157 case LogicalAnd:
158 return m_children[0]->evaluate(context).asBoolean() && m_children[1]->evaluate(context).asBoolean();
159 case LogicalOr:
160 return m_children[0]->evaluate(context).asBoolean() || m_children[1]->evaluate(context).asBoolean();
161 case LogicalNot:
162 return !m_children[0]->evaluate(context).asBoolean();
163
164 case CompareEqual:
165 return m_children[0]->evaluate(context).compareEqual(m_children[1]->evaluate(context));
166 case CompareNotEqual:
167 return !m_children[0]->evaluate(context).compareEqual(m_children[1]->evaluate(context));
168 case CompareLess:
169 return m_children[0]->evaluate(context).asNumber() < m_children[1]->evaluate(context).asNumber();
170 case CompareGreater:
171 return m_children[0]->evaluate(context).asNumber() > m_children[1]->evaluate(context).asNumber();
172 case CompareLessOrEqual:
173 return m_children[0]->evaluate(context).asNumber() <= m_children[1]->evaluate(context).asNumber();
174 case CompareGreaterOrEqual:
175 return m_children[0]->evaluate(context).asNumber() >= m_children[1]->evaluate(context).asNumber();
176
177 case NumericalCast:
178 return m_children[0]->evaluate(context).asNumber();
179 case StringCast:
180 return m_children[0]->evaluate(context).asString();
181 case BooleanCast:
182 return m_children[0]->evaluate(context).asBoolean();
183
184 case Conditional:
185 if (m_children[0]->evaluate(context).asBoolean()) {
186 return m_children[1]->evaluate(context);
187 } else {
188 return m_children[2]->evaluate(context);
189 }
190 break;
191 case Any: {
192 for (const auto &child : m_children) {
193 auto r = child->evaluate(context);
194 if (!r.isNone()) {
195 return r;
196 }
197 }
198 return {};
199 }
200
201 case Concatenate:
202 {
203 QByteArray s;
204 for (const auto &child : m_children) {
205 s += child->evaluate(context).asString();
206 }
207 return s;
208 }
209 case Replace:
210 return m_children[0]->evaluate(context).asString().replace(m_children[1]->evaluate(context).asString(), m_children[2]->evaluate(context).asString());
211
212 case Integer: {
213 const auto i = (int)m_children[0]->evaluate(context).asNumber();
214 return (double)i;
215 }
216 case Maximum: {
217 double r = std::numeric_limits<double>::lowest();
218 for (const auto &child : m_children) {
219 r = std::max(r, child->evaluate(context).asNumber());
220 }
221 return r;
222 }
223 case Minimum: {
224 double r = std::numeric_limits<double>::max();
225 for (const auto &child : m_children) {
226 r = std::min(r, child->evaluate(context).asNumber());
227 }
228 return r;
229 }
230 case Sqrt:
231 return std::sqrt(m_children[0]->evaluate(context).asNumber());
232
233 case Metric:
234 case ZMetric:
235 return m_children[0]->evaluate(context); // our unit handling should deal with this already
236
237 case ReadProperty: {
238 const auto propName = m_children[0]->evaluate(context).asString();
239 const auto prop = MapCSSDeclaration::propertyFromName(propName.constData(), propName.size());
240 const auto decl = context.result.declaration(prop);
241 if (!decl || !decl->isValid()) {
242 return {};
243 }
244 if (decl->hasExpression()) {
245 qCWarning(Log) << "Recursive eval() expression not supported, evaluation aborted";
246 return {};
247 }
248 return decl->stringValue().toUtf8(); // TODO support other property types
249 }
250 case ReadTag:
251 {
252 const auto v = context.result.resolvedTagValue(m_children[0]->evaluate(context).asString().constData(), context.state);
253 return v ? *v : MapCSSValue();
254 }
255 case KOSM_Conditional:
256 {
257 OSMConditionalExpression expr; // TODO cache those
258 expr.parse(m_children[0]->evaluate(context).asString());
259
260 OSMConditionalExpressionContext condContext;
261 condContext.element = context.state.element;
262 condContext.openingHoursCache = context.state.openingHours;
263 return expr.evaluate(condContext);
264 }
265 }
266
267 return {};
268}
269
270struct {
271 MapCSSTerm::Operation op;
272 const char *name;
273} static constexpr const infix_output_map[] = {
274 { MapCSSTerm::Addition, "+" },
275 { MapCSSTerm::Subtraction, "-" },
276 { MapCSSTerm::Multiplication, "*" },
277 { MapCSSTerm::Division, "/" },
278 { MapCSSTerm::LogicalAnd, "&&" },
279 { MapCSSTerm::LogicalOr, "||" },
280 { MapCSSTerm::CompareEqual, "==" },
281 { MapCSSTerm::CompareNotEqual, "!=" },
282 { MapCSSTerm::CompareLess, "<" },
283 { MapCSSTerm::CompareGreater, ">" },
284 { MapCSSTerm::CompareLessOrEqual, "<=", },
285 { MapCSSTerm::CompareGreaterOrEqual, ">=" },
286};
287
288void MapCSSTerm::write(QIODevice *out) const
289{
290 if (m_op == Unknown) {
291 return;
292 }
293 if (m_op == Literal) {
294 m_literal.write(out);
295 return;
296 }
297 if (m_op == LogicalNot) {
298 out->write("!");
299 m_children[0]->write(out);
300 }
301
302 for (const auto &m : infix_output_map) {
303 if (m.op == m_op) {
304 out->write("(");
305 m_children[0]->write(out);
306 out->write(m.name);
307 m_children[1]->write(out);
308 out->write(")");
309 return;
310 }
311 }
312
313 for (const auto &m : function_name_map) {
314 if (m.op == m_op) {
315 out->write(m.name);
316 out->write("(");
317 if (!m_children.empty()) {
318 for (auto it = m_children.begin(); it != std::prev(m_children.end()); ++it) {
319 (*it)->write(out);
320 out->write(", ");
321 }
322 m_children.back()->write(out);
323 }
324 out->write(")");
325 return;
326 }
327 }
328}
A set of nodes, ways and relations.
Definition datatypes.h:343
OSM-based multi-floor indoor maps for buildings.
qint64 write(const QByteArray &data)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:57:46 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.