OpenSWMM Engine  6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
InputParseUtils.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2//
3// Copyright 2026 Caleb Buahin
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
31
32#ifndef OPENSWMM_ENGINE_INPUT_PARSE_UTILS_HPP
33#define OPENSWMM_ENGINE_INPUT_PARSE_UTILS_HPP
34
35#include <string_view>
36#include <string>
37#include <vector>
38#include <charconv>
40#include "../core/DateTime.hpp"
42#include "../data/NameIndex.hpp"
43
44namespace openswmm::input {
45
46// ============================================================================
47// Numeric parsing
48// ============================================================================
49
61inline double to_double(std::string_view sv, double def = 0.0) noexcept {
62 double v = def;
63 openswmm::from_chars_double(sv.data(), sv.data() + sv.size(), v);
64 return v;
65}
66
78inline bool parse_double_strict(std::string_view sv, double& out) noexcept {
79 if (sv.empty()) {
80 out = 0.0;
81 return true;
82 }
83 if (sv.front() == '+') { // strtod accepts a leading '+'
84 sv.remove_prefix(1);
85 if (sv.empty() || sv.front() == '+' || sv.front() == '-')
86 return false;
87 }
88 double v = 0.0;
89 const auto [ptr, ec] = openswmm::from_chars_double(sv.data(), sv.data() + sv.size(), v);
90 if (ec != std::errc{} || ptr != sv.data() + sv.size())
91 return false;
92 out = v;
93 return true;
94}
95
105inline int to_int(std::string_view sv, int def = 0) noexcept {
106 int v = def;
107 auto [ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), v);
108 if (ec != std::errc{})
109 return def;
110 return v;
111}
112
113// ============================================================================
114// Date/time parsing
115// ============================================================================
116
127inline double parse_date(std::string_view sv) {
128 unsigned m = 0, d = 0, y = 0;
129 const char* p = sv.data();
130 const char* end = sv.data() + sv.size();
131
132 auto read_uint = [&](unsigned& out) -> bool {
133 auto [np, ec] = std::from_chars(p, end, out);
134 if (ec != std::errc{})
135 return false;
136 p = np;
137 return true;
138 };
139 auto read_sep = [&]() -> bool {
140 if (p < end && (*p == '/' || *p == '-')) {
141 ++p;
142 return true;
143 }
144 return false;
145 };
146
147 if (!read_uint(m))
148 return 0.0;
149 if (!read_sep())
150 return 0.0;
151 if (!read_uint(d))
152 return 0.0;
153 if (!read_sep())
154 return 0.0;
155 if (!read_uint(y))
156 return 0.0;
157
158 return datetime::encodeDate(static_cast<int>(y), static_cast<int>(m), static_cast<int>(d));
159}
160
173inline double parse_time_seconds(std::string_view sv) {
174 // Try plain number first
175 double val = 0.0;
176 auto [ptr, ec] = openswmm::from_chars_double(sv.data(), sv.data() + sv.size(), val);
177 if (ec == std::errc{} && ptr == sv.data() + sv.size()) {
178 return val; // plain number = seconds
179 }
180
181 // Try HH:MM:SS or HH:MM
182 unsigned h = 0, m = 0;
183 double s = 0.0;
184 const char* p = sv.data();
185 const char* end = sv.data() + sv.size();
186
187 auto read_uint = [&](unsigned& out) -> bool {
188 auto [np, nec] = std::from_chars(p, end, out);
189 if (nec != std::errc{})
190 return false;
191 p = np;
192 return true;
193 };
194
195 if (!read_uint(h))
196 return 0.0;
197 if (p < end && *p == ':') {
198 ++p;
199 if (!read_uint(m))
200 return static_cast<double>(h);
201 if (p < end && *p == ':') {
202 ++p;
204 }
205 }
206 return h * 3600.0 + m * 60.0 + s;
207}
208
220inline double parse_time_day_fraction(std::string_view sv) {
221 const char* p = sv.data();
222 const char* end = sv.data() + sv.size();
223
224 // A time token is EITHER decimal hours (a bare number such as "0.167" or
225 // "10") OR a clock string "HH:MM[:SS]" — matching legacy datetime_strToTime
226 // ("accepts time as hr:min:sec or as decimal hours"). Try a full decimal
227 // parse first: if the WHOLE token is a number it is decimal hours (/24);
228 // otherwise fall back to integer HH:MM:SS. This helper (and parse_datetime
229 // through it) previously handled only HH:MM:SS, so a bare "0.167" on a dated
230 // [TIMESERIES] row was truncated to 0 — corrupting the series and colliding
231 // with the prior day's "24" row (dx == 0 -> spurious ERR 173).
232 double dec_hours = 0.0;
233 auto [dp, dec] = openswmm::from_chars_double(p, end, dec_hours);
234 if (dec == std::errc{} && dp == end)
235 return dec_hours / 24.0;
236
237 unsigned h = 0, m = 0, s = 0;
238 auto read_uint = [&](unsigned& out) -> bool {
239 auto [np, ec] = std::from_chars(p, end, out);
240 if (ec != std::errc{})
241 return false;
242 p = np;
243 return true;
244 };
245
246 if (!read_uint(h))
247 return 0.0;
248 if (p < end && *p == ':')
249 ++p;
250 read_uint(m);
251 if (p < end && *p == ':')
252 ++p;
253 read_uint(s);
254
255 return datetime::encodeTime(static_cast<int>(h), static_cast<int>(m), static_cast<int>(s));
256}
257
266inline double parse_datetime(std::string_view date_sv, std::string_view time_sv) {
267 return parse_date(date_sv) + parse_time_day_fraction(time_sv);
268}
269
270// ============================================================================
271// Object registration
272// ============================================================================
273
285inline int add_unique(NameIndex& reg, const std::string& name, std::vector<std::string>& errors) {
286 const int idx = reg.try_add(name);
287 if (idx < 0) {
288 const std::string* canon = reg.canonical(name);
289 if (canon != nullptr && *canon != name) {
290 errors.push_back(
292 "(matches existing ID '" + *canon + "'; names are case-insensitive)"));
293 } else {
294 errors.push_back(format_error(ERR_DUP_NAME, name));
295 }
296 }
297 return idx;
298}
299
300} /* namespace openswmm::input */
301
302#endif /* OPENSWMM_ENGINE_INPUT_PARSE_UTILS_HPP */
DateTime utility functions — numerically identical to legacy datetime.c.
Legacy-compatible error and warning codes with description lookup.
O(1) name-to-index lookup for SWMM objects.
Portable from_chars for floating-point types.
Bidirectional name↔index registry for SWMM objects.
Definition NameIndex.hpp:71
int try_add(const std::string &name)
Copy a new name, returning a duplicate sentinel instead of a duplicate exception.
Definition NameIndex.hpp:115
const std::string * canonical(std::string_view name) const noexcept
Borrow the original spelling of an equivalent registry key.
Definition NameIndex.hpp:169
DateTime encodeTime(int hour, int minute, int second)
Convert nonnegative time components to an elapsed-day fraction.
Definition DateTime.hpp:134
DateTime encodeDate(int year, int month, int day)
Encode a Gregorian calendar date as midnight in SWMM days.
Definition DateTime.hpp:110
Definition CatchmentHandler.cpp:70
double parse_datetime(std::string_view date_sv, std::string_view time_sv)
Combine the permissive date and decimal-hour/clock parsers.
Definition InputParseUtils.hpp:266
bool parse_double_strict(std::string_view sv, double &out) noexcept
Parse a complete token while preserving the output on refusal.
Definition InputParseUtils.hpp:78
double parse_time_day_fraction(std::string_view sv)
Parse decimal hours or permissive clock fields into elapsed days.
Definition InputParseUtils.hpp:220
double to_double(std::string_view sv, double def=0.0) noexcept
Parse a numeric prefix, retaining a supplied default when conversion fails.
Definition InputParseUtils.hpp:61
double parse_time_seconds(std::string_view sv)
Parse a duration as decimal seconds or permissive hour/minute/second fields.
Definition InputParseUtils.hpp:173
int to_int(std::string_view sv, int def=0) noexcept
Parse a signed integer prefix using decimal from_chars.
Definition InputParseUtils.hpp:105
std::string format_error(int code, std::string_view name="")
Format an error message with object name substitution.
Definition ErrorCodes.cpp:304
int add_unique(NameIndex &reg, const std::string &name, std::vector< std::string > &errors)
Register a one-definition object name or append a duplicate diagnostic.
Definition InputParseUtils.hpp:285
double parse_date(std::string_view sv)
Parse month/day/year or month-day-year input into SWMM decimal days.
Definition InputParseUtils.hpp:127
std::from_chars_result from_chars_double(const char *first, const char *last, double &value) noexcept
Parse a double prefix using the available numeric backend.
Definition charconv_compat.hpp:76
@ ERR_DUP_NAME
duplicate ID name s
Definition ErrorCodes.hpp:124
double * y
Definition odesolve.c:37