From 822be406882d8a5d29fba1d65875b7433aff21f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Sat, 8 Aug 2026 15:16:25 -0700 Subject: [PATCH] Scanner: Class::Identifier must not start with a digit. (#414) --- include/bx/string.h | 8 ++++++++ src/scanner.cpp | 2 +- src/string.cpp | 11 +++++++++++ tests/scanner_test.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ tests/string_test.cpp | 20 ++++++++++++++++++++ 5 files changed, 80 insertions(+), 1 deletion(-) diff --git a/include/bx/string.h b/include/bx/string.h index 99fd2ff..4575906 100644 --- a/include/bx/string.h +++ b/include/bx/string.h @@ -453,6 +453,14 @@ namespace bx /// Returns StringView of word or empty. StringView strWord(const StringView& _str); + /// Returns StringView of identifier or empty. + /// + /// @remarks Unlike `strWord` the first character must be alphabetic or '_'; the rest + /// can additionally be numeric. `strWord` accepts a leading digit, so "123abc" is a + /// word but not an identifier. + /// + StringView strIdentifier(const StringView& _str); + /// Returns substring in string. StringView strSubstr(const StringView& _str, int32_t _start, int32_t _len = INT32_MAX); diff --git a/src/scanner.cpp b/src/scanner.cpp index 8cb923b..e1f3069 100644 --- a/src/scanner.cpp +++ b/src/scanner.cpp @@ -92,7 +92,7 @@ namespace bx case Class::Identifier: { - const StringView word = strWord(m_tail); + const StringView word = strIdentifier(m_tail); return word.isEmpty() ? m_tail : StringView(word.getTerm(), m_tail.getTerm() ) diff --git a/src/string.cpp b/src/string.cpp index b27d845..4380735 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -538,6 +538,17 @@ namespace bx return StringView(ptr, term); } + StringView strIdentifier(const StringView& _str) + { + if (_str.isEmpty() + || !(isAlpha(*_str.getPtr() ) || '_' == *_str.getPtr() ) ) + { + return StringView(_str.getPtr(), _str.getPtr() ); + } + + return strWord(_str); + } + StringView strFindBlock(const StringView& _str, char _open, char _close) { const char* curr = _str.getPtr(); diff --git a/tests/scanner_test.cpp b/tests/scanner_test.cpp index 2ba5b69..ddafdc7 100644 --- a/tests/scanner_test.cpp +++ b/tests/scanner_test.cpp @@ -144,6 +144,46 @@ TEST_CASE("Scanner.class accept", "[scanner]") } } +TEST_CASE("Scanner.class Identifier start", "[scanner]") +{ + { + bx::Scanner sc(bx::StringView("123abc") ); + REQUIRE(sc.accept(bx::Scanner::Class::Identifier).isEmpty() ); + REQUIRE(sc.peek(bx::Scanner::Class::Identifier).isEmpty() ); + REQUIRE("123abc" == sc.acceptAll() ); + } + + { + bx::Scanner sc(bx::StringView("5_0") ); + REQUIRE(sc.accept(bx::Scanner::Class::Identifier).isEmpty() ); + } + + { + bx::Scanner sc(bx::StringView("abc123 def") ); + REQUIRE("abc123" == sc.accept(bx::Scanner::Class::Identifier) ); + } + + { + bx::Scanner sc(bx::StringView("_abc9") ); + REQUIRE("_abc9" == sc.accept(bx::Scanner::Class::Identifier) ); + } + + { + bx::Scanner sc(bx::StringView("ps_5_0") ); + REQUIRE("ps_5_0" == sc.accept(bx::Scanner::Class::Identifier) ); + } + + { + bx::Scanner sc(bx::StringView("-abc") ); + REQUIRE(sc.accept(bx::Scanner::Class::Identifier).isEmpty() ); + } + + { + bx::Scanner sc(bx::StringView("") ); + REQUIRE(sc.accept(bx::Scanner::Class::Identifier).isEmpty() ); + } +} + TEST_CASE("Scanner.peek", "[scanner]") { bx::Scanner sc(bx::StringView("abc def") ); diff --git a/tests/string_test.cpp b/tests/string_test.cpp index 3881c6d..c5356a5 100644 --- a/tests/string_test.cpp +++ b/tests/string_test.cpp @@ -689,6 +689,26 @@ TEST_CASE("strWord", "[string]") { REQUIRE(bx::strWord(" abvgd-1389.0").isEmpty() ); REQUIRE(0 == bx::strCmp(bx::strWord("abvgd-1389.0"), "abvgd") ); + + REQUIRE(0 == bx::strCmp(bx::strWord("1389.0"), "1389") ); +} + +TEST_CASE("strIdentifier", "[string]") +{ + REQUIRE(bx::strIdentifier("").isEmpty() ); + REQUIRE(bx::strIdentifier(" abvgd").isEmpty() ); + REQUIRE(bx::strIdentifier("-abc").isEmpty() ); + + REQUIRE(bx::strIdentifier("123abc").isEmpty() ); + REQUIRE(bx::strIdentifier("5_0").isEmpty() ); + + REQUIRE(0 == bx::strCmp(bx::strIdentifier("abvgd-1389.0"), "abvgd") ); + REQUIRE(0 == bx::strCmp(bx::strIdentifier("abc123 def"), "abc123") ); + REQUIRE(0 == bx::strCmp(bx::strIdentifier("_abc9"), "_abc9") ); + REQUIRE(0 == bx::strCmp(bx::strIdentifier("ps_5_0"), "ps_5_0") ); + + const bx::StringView test("123abc"); + REQUIRE(test.getPtr() == bx::strIdentifier(test).getPtr() ); } TEST_CASE("strFindEol strFindNl", "[string]")