Skip to content

Commit 62689e8

Browse files
arch1t3chtsgt0
andauthored
Use locale-independent number parsing functions (#14)
Replace the setlocale workaround (which may break when other libraries also touch the locale) with a proper fix that replaces the stoi/stoll/stof functions with std::from_chars. The behavior may not be 100% equivalent (most notably leading + characters are no longer accepted) but it should not break any existing usage. This code could be cleaned up a lot by using std::string_view instead, but for now this just does the minimal changes necessary. An alternative solution would have been to replace stoi/stol/stof with vendored locale-independent implementations. Bump minimum macOS version to 26.0 to get support for `std::from_chars()` for floating-point types. --------- Co-authored-by: sgt0 <140186177+sgt0@users.noreply.github.com>
1 parent b4908cd commit 62689e8

4 files changed

Lines changed: 40 additions & 35 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
strategy:
1515
fail-fast: false
1616
matrix:
17-
os: [ubuntu-24.04, macos-15]
17+
os: [ubuntu-24.04, macos-26]
1818
llvm: [19, 20, 21]
1919
name: Build os=${{ matrix.os }} llvm=${{ matrix.llvm }}
2020
runs-on: ${{ matrix.os }}

expr2/exprfilter.cpp

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#define USE_EXPR_CACHE
2121

2222
#include <algorithm>
23+
#include <charconv>
2324
#include <cmath>
2425
#include <cctype>
2526
#include <clocale>
@@ -286,11 +287,12 @@ ExprOp decodeToken(const std::string &token, bool extended = false)
286287
if (name.size() == 1)
287288
return name[0] >= 'x' ? name[0] - 'x' : name[0] - 'a' + 3;
288289
int idx = -1;
289-
try {
290-
idx = std::stoi(name.substr(clipNamePrefix.size()));
291-
} catch (...) {
290+
291+
auto result = std::from_chars(name.c_str() + clipNamePrefix.size(), name.c_str() + name.length(), idx);
292+
if (result.ec != std::errc()) {
292293
throw std::runtime_error("invalid clip name: " + name);
293294
}
295+
294296
return idx;
295297
};
296298

@@ -305,16 +307,15 @@ ExprOp decodeToken(const std::string &token, bool extended = false)
305307
} else if ((token.substr(0, 3) == "dup" || token.substr(0, 4) == "swap" ||
306308
token.substr(0, 4) == "drop" || token.substr(0, 4) == "sort")) {
307309
size_t prefix = token[1] == 'u' ? 3 : 4;
308-
size_t count = 0;
309310
int idx = -1;
310311

311-
try {
312-
idx = std::stoi(token.substr(prefix), &count);
313-
} catch (...) {
312+
auto result = std::from_chars(token.c_str() + prefix, token.c_str() + token.length(), idx);
313+
if (result.ec != std::errc()) {
314314
// ...
315315
}
316+
size_t count = std::distance(token.c_str(), result.ptr);
316317

317-
if (idx < 0 || prefix + count != token.size())
318+
if (idx < 0 || count != token.size())
318319
throw std::runtime_error("illegal token: " + token);
319320
if (token[1] == 'u')
320321
return{ ExprOpType::DUP, idx };
@@ -327,16 +328,15 @@ ExprOp decodeToken(const std::string &token, bool extended = false)
327328
} else if (extended && (token.substr(0, 6) == "argmin" || token.substr(0, 6) == "argmax" ||
328329
token.substr(0, 7) == "argsort")) {
329330
size_t prefix = token[3] == 's' ? 7 : 6;
330-
size_t count = 0;
331331
int idx = -1;
332332

333-
try {
334-
idx = std::stoi(token.substr(prefix), &count);
335-
} catch (...) {
333+
auto result = std::from_chars(token.c_str() + prefix, token.c_str() + token.length(), idx);
334+
if (result.ec != std::errc()) {
336335
// ...
337336
}
337+
size_t count = std::distance(token.c_str(), result.ptr);
338338

339-
if (idx < 0 || prefix + count != token.size())
339+
if (idx < 0 || count != token.size())
340340
throw std::runtime_error("illegal token: " + token);
341341
if (token[3] == 's')
342342
return{ ExprOpType::ARGSORT, idx };
@@ -365,21 +365,23 @@ ExprOp decodeToken(const std::string &token, bool extended = false)
365365
long long l = 0;
366366
float f = 0;
367367
const size_t len = token.size();
368-
try {
369-
l = std::stoll(token, &pos, 0);
370-
} catch (...) {
371-
pos = 0;
368+
369+
auto resultl = std::from_chars(token.c_str(), token.c_str() + len, l);
370+
if (resultl.ec == std::errc()) {
371+
pos = std::distance(token.c_str(), resultl.ptr);
372372
}
373+
373374
if (pos == len) {
374375
if ((int32_t)l == l) return { ExprOpType::CONSTANTI, (int32_t)l };
375376
else if ((uint32_t)l == l) return { ExprOpType::CONSTANTI, (uint32_t)l };
376377
return { ExprOpType::CONSTANTF, (float)l };
377378
}
378-
try {
379-
f = std::stof(token, &pos);
380-
} catch (...) {
381-
pos = 0;
379+
380+
auto resultf = std::from_chars(token.c_str(), token.c_str() + len, f);
381+
if (resultf.ec == std::errc()) {
382+
pos = std::distance(token.c_str(), resultf.ptr);
382383
}
384+
383385
if (pos == len)
384386
return { ExprOpType::CONSTANTF, f };
385387
else if (pos > 0)
@@ -1597,9 +1599,6 @@ static void VS_CC exprCreate(const VSMap *in, VSMap *out, void *userData, VSCore
15971599
}
15981600

15991601
static void initExpr() {
1600-
#ifndef _WIN32
1601-
std::setlocale(LC_NUMERIC, "C");
1602-
#endif
16031602
auto cfg = rr::Config::Edit()
16041603
.set(rr::Optimization::Level::Aggressive)
16051604
.set(rr::Optimization::FMF::FastMath)

flake.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.nix

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
lib,
33
stdenv,
4+
darwinMinVersionHook,
45
meson,
56
ninja,
67
pkg-config,
@@ -37,11 +38,16 @@ stdenv.mkDerivation (finalAttrs: {
3738
pkg-config
3839
];
3940

40-
buildInputs = [
41-
libllvm
42-
libxml2
43-
vapoursynth
44-
];
41+
buildInputs =
42+
[
43+
libllvm
44+
libxml2
45+
vapoursynth
46+
]
47+
# `std::to_chars()` for floating-point types was introduced in macOS 13.3.
48+
# But then `darwinMinVersionHook "13.0"` yields "error: 'from_chars' is
49+
# unavailable: introduced in macOS 26.0".
50+
++ lib.optional stdenv.hostPlatform.isDarwin (darwinMinVersionHook "26.0");
4551

4652
postPatch = ''
4753
substituteInPlace meson.build \

0 commit comments

Comments
 (0)