Added scanner. (#410)

This commit is contained in:
Branimir Karadžić
2026-07-25 12:23:58 -07:00
committed by GitHub
parent cd6720ce9a
commit 9916e720fc
21 changed files with 2157 additions and 1899 deletions

View File

@@ -18,6 +18,7 @@
#include "mutex.cpp"
#include "os.cpp"
#include "process.cpp"
#include "scanner.cpp"
#include "semaphore.cpp"
#include "settings.cpp"
#include "sort.cpp"

View File

@@ -8,6 +8,7 @@
#include <bx/readerwriter.h> // WriterI
#include <bx/os.h> // exit
#include <bx/process.h> // ProcessReader
#include <bx/scanner.h> // Scanner
#include <inttypes.h> // PRIx*
@@ -947,22 +948,6 @@ namespace bx
#elif BX_CONFIG_CALLSTACK_USE_EXECINFO
StringView strConsumeTo(StringView& _input, const StringView& _find)
{
const StringView to = strFind(_input, _find);
if (!to.isEmpty() )
{
const StringView result(_input.getPtr(), to.getPtr() );
_input.set(to.getTerm(), _input.getTerm() );
return result;
}
return StringView();
}
int32_t writeCallstack(WriterI* _writer, const uintptr_t* _stack, uint32_t _num, Error* _err)
{
int32_t total = write(_writer, _err, "Callstack (%d):\n", _num);
@@ -1126,16 +1111,18 @@ namespace bx
{
for (LineReader lr({atosBuffer, bytes}); !lr.isDone();)
{
StringView input = lr.next();
Scanner scanner(lr.next() );
atosFunctionName = strConsumeTo(input, " (");
atosFunctionName = scanner.acceptUntil(" (");
if (atosFunctionName.isEmpty() )
{
break;
}
filePath = strConsumeTo(input, ":");
scanner.accept(" (");
filePath = scanner.acceptUntil(":");
if (filePath.isEmpty() )
{
@@ -1143,7 +1130,9 @@ namespace bx
break;
}
const StringView lineStr = strConsumeTo(input, ")");
scanner.accept(':');
const StringView lineStr = scanner.acceptUntil(")");
if (!lineStr.isEmpty() )
{

View File

@@ -6,6 +6,7 @@
#include <bx/file.h>
#include <bx/os.h>
#include <bx/readerwriter.h>
#include <bx/scanner.h>
#if BX_CRT_MSVC
# include <direct.h> // _getcwd
@@ -24,12 +25,9 @@ extern "C" int _NSGetExecutablePath(char* _buf, uint32_t* _bufSize);
namespace bx
{
static bool isPathSeparator(char _ch)
static bool isNotPathSeparator(char _ch)
{
return false
|| '/' == _ch
|| '\\' == _ch
;
return !isPathSeparator(_ch);
}
static int32_t normalizeFilePath(char* _dst, int32_t _dstSize, const char* _src, int32_t _num)
@@ -38,9 +36,9 @@ namespace bx
// - Lexical File Names in Plan 9 or Getting Dot-Dot Right
// https://web.archive.org/web/20180629044444/https://9p.io/sys/doc/lexnames.html
const int32_t num = strLen(_src, _num);
const StringView src(_src, strLen(_src, _num) );
if (0 == num)
if (src.isEmpty() )
{
return strCopy(_dst, _dstSize, ".");
}
@@ -50,91 +48,80 @@ namespace bx
StaticMemoryBlockWriter writer(_dst, _dstSize);
Error err;
int32_t idx = 0;
int32_t dotdot = 0;
Scanner scanner(src);
if (2 <= num
&& ':' == _src[1])
// Everything below `dotdot` is a prefix `..` can't back out of.
int32_t dotdot = 0;
if (2 <= src.getLength()
&& ':' == src.getPtr()[1])
{
size += write(&writer, toUpper(_src[idx]), &err);
size += write(&writer, toUpper(src.getPtr()[0]), &err);
size += write(&writer, ':', &err);
idx += 2;
scanner.seek(2);
dotdot = size;
}
const int32_t slashIdx = idx;
const bool rooted = !scanner.accept(isPathSeparator).isEmpty();
bool rooted = isPathSeparator(_src[idx]);
if (rooted)
{
size += write(&writer, '/', &err);
++idx;
dotdot = size;
}
const int32_t rootSize = size;
bool trailingSlash = false;
while (idx < num && err.isOk() )
while (!scanner.isDone()
&& err.isOk() )
{
switch (_src[idx])
if (!scanner.acceptWhile(isPathSeparator).isEmpty() )
{
case '/':
case '\\':
++idx;
trailingSlash = idx == num;
break;
case '.':
if (idx+1 == num
|| isPathSeparator(_src[idx+1]) )
{
++idx;
break;
}
if ('.' == _src[idx+1]
&& (idx+2 == num || isPathSeparator(_src[idx+2]) ) )
{
idx += 2;
if (dotdot < size)
{
for (--size
; dotdot < size && !isPathSeparator(_dst[size])
; --size)
{
}
seek(&writer, size, Whence::Begin);
}
else if (!rooted)
{
if (0 < size)
{
size += write(&writer, '/', &err);
}
size += write(&writer, "..", &err);
dotdot = size;
}
break;
}
[[fallthrough]];
default:
if ( ( rooted && slashIdx+1 != size)
|| (!rooted && 0 != size) )
{
size += write(&writer, '/', &err);
}
for (; idx < num && !isPathSeparator(_src[idx]); ++idx)
{
size += write(&writer, _src[idx], &err);
}
break;
trailingSlash = scanner.isDone();
continue;
}
const StringView component = scanner.acceptWhile(isNotPathSeparator);
if (component == ".")
{
continue;
}
if (component == "..")
{
if (dotdot < size)
{
for (--size
; dotdot < size && !isPathSeparator(_dst[size])
; --size)
{
}
seek(&writer, size, Whence::Begin);
}
else if (!rooted)
{
if (0 < size)
{
size += write(&writer, '/', &err);
}
size += write(&writer, "..", &err);
dotdot = size;
}
continue;
}
if (rooted ? rootSize != size : 0 != size)
{
size += write(&writer, '/', &err);
}
size += write(&writer, component, &err);
}
if (0 == size)

167
src/scanner.cpp Normal file
View File

@@ -0,0 +1,167 @@
/*
* Copyright 2010-2026 Branimir Karadzic. All rights reserved.
* License: https://github.com/bkaradzic/bx/blob/master/LICENSE
*/
#include <bx/scanner.h>
namespace bx
{
bool Scanner::seek(const StringView& _to)
{
if (contain(m_input, _to) )
{
moveTo(_to);
return true;
}
BX_TRACE("StringView '%S' is not a view into input string!", &_to);
return false;
}
uint32_t Scanner::getColumn() const
{
return uint32_t(between(getCursor(Cursor::LineStart) ).getLength() ) + 1;
}
StringView Scanner::getCursor(Cursor _which) const
{
const char* ptr = m_tail.getPtr();
if (Cursor::LineStart == _which)
{
while (ptr > m_input.getPtr()
&& '\n' != ptr[-1])
{
--ptr;
}
}
else if (Cursor::LineEnd == _which)
{
const char* term = m_input.getTerm();
while (ptr < term
&& '\n' != *ptr)
{
++ptr;
}
}
return StringView(ptr, ptr);
}
StringView Scanner::moveTo(const StringView& _to)
{
if (BX_LIKELY(!_to.isEmpty() ) )
{
if (BX_LIKELY(overlap(m_tail, _to) ) )
{
const StringView result(m_tail.getPtr(), _to.getTerm() );
m_tail.set(_to.getTerm(), m_tail.getTerm() );
m_line += countLines(result);
return result;
}
const StringView result(_to.getPtr(), m_tail.getPtr() );
m_tail.set(_to.getPtr(), m_tail.getTerm() );
m_line -= countLines(result);
return result;
}
return StringView();
}
StringView Scanner::strFunc(Class _class) const
{
switch (_class)
{
case Class::Space:
return strLTrimSpace(m_tail);
case Class::NonSpace:
return strLTrimNonSpace(m_tail);
case Class::Identifier:
{
const StringView word = strWord(m_tail);
return word.isEmpty()
? m_tail
: StringView(word.getTerm(), m_tail.getTerm() )
;
}
case Class::EndOfLine:
return strFindEol(m_tail);
case Class::NewLine:
return strFindNl(m_tail);
default:
BX_ASSERT(false, "Bug, _class can't be %d!", _class);
BX_UNREACHABLE;
}
}
StringView Scanner::moveBy(int32_t _bytes)
{
const int32_t len = m_tail.getLength();
_bytes = clamp(_bytes, len - m_input.getLength(), len);
if (BX_LIKELY(0 < _bytes) )
{
const StringView result(m_tail.getPtr(), m_tail.getPtr() + _bytes);
m_tail.set(result.getTerm(), m_tail.getTerm() );
m_line += countLines(result);
return result;
}
const StringView result(m_tail.getPtr() + _bytes, m_tail.getPtr() );
m_tail.set(result.getPtr(), m_tail.getTerm() );
m_line -= countLines(result);
return result;
}
uint32_t Scanner::countLines(const StringView& _str)
{
uint32_t line = 0;
StringView str = strFindEol(_str);
while (!str.isEmpty() )
{
++line;
str = strFindEol(strFindNl(str) );
}
return line;
}
StringView LineReader::next()
{
if (!m_scanner.isDone() )
{
++m_line;
const StringView cursor = m_scanner.getCursor();
m_scanner.acceptUntil(Scanner::Class::EndOfLine);
const StringView line = m_scanner.between(cursor);
m_scanner.accept(Scanner::Class::NewLine);
// A line terminator is `\n` or `\r\n`, and `Class::EndOfLine` already
// excludes it. Only malformed input (`\r` at the end of input, or
// `\r\r\n`) can leave a trailing `\r` behind.
return strRTrim(line, "\r");
}
return m_scanner.getCursor();
}
} // namespace bx

View File

@@ -4,209 +4,496 @@
*/
#include <bx/settings.h>
namespace
{
#define INI_MALLOC(_ctx, _size) (bx::alloc(reinterpret_cast<bx::AllocatorI*>(_ctx), _size) )
#define INI_FREE(_ctx, _ptr) (bx::free(reinterpret_cast<bx::AllocatorI*>(_ctx), _ptr) )
#define INI_MEMCPY(_dst, _src, _count) (bx::memCopy(_dst, _src, _count) )
#define INI_STRLEN(_str) (bx::strLen(_str) )
#define INI_STRNICMP(_s1, _s2, _len) (bx::strCmpI(_s1, _s2, _len) )
#define INI_IMPLEMENTATION
BX_PRAGMA_DIAGNOSTIC_IGNORED_CLANG_GCC("-Wunused-function");
BX_PRAGMA_DIAGNOSTIC_PUSH();
BX_PRAGMA_DIAGNOSTIC_IGNORED_CLANG_GCC("-Wsign-compare");
#include <ini/ini.h>
BX_PRAGMA_DIAGNOSTIC_POP();
}
#include <bx/filepath.h>
#include <bx/scanner.h>
namespace bx
{
Settings::Settings(AllocatorI* _allocator, const void* _data, uint32_t _len)
: m_allocator(_allocator)
, m_ini(NULL)
{
load(_data, _len);
}
#define INI_T(_ptr) reinterpret_cast<ini_t*>(_ptr)
Settings::~Settings()
{
ini_destroy(INI_T(m_ini) );
}
void Settings::clear()
{
load(NULL, 0);
}
void Settings::load(const void* _data, uint32_t _len)
{
if (NULL != m_ini)
static StringView settingsAlloc(AllocatorI* _allocator, const StringView& _str)
{
ini_destroy(INI_T(m_ini) );
}
const int32_t len = _str.getLength();
if (NULL == _data)
{
m_ini = ini_create(m_allocator);
}
else
{
m_ini = ini_load( (const char*)_data, _len, m_allocator);
}
}
StringView Settings::get(const StringView& _name) const
{
ini_t* ini = INI_T(m_ini);
FilePath uri(_name);
const StringView path(strTrim(uri.getPath(), "/") );
const StringView& fileName(uri.getFileName() );
int32_t section = INI_GLOBAL_SECTION;
if (!path.isEmpty() )
{
section = ini_find_section(ini, path.getPtr(), path.getLength() );
if (INI_NOT_FOUND == section)
if (0 == len)
{
section = INI_GLOBAL_SECTION;
return StringView();
}
char* ptr = (char*)alloc(_allocator, len);
memCopy(ptr, _str.getPtr(), len);
return StringView(ptr, len);
}
static void settingsFree(AllocatorI* _allocator, StringView& _str)
{
if (!_str.isEmpty() )
{
free(_allocator, const_cast<char*>(_str.getPtr() ) );
}
_str.clear();
}
static void settingsAssign(AllocatorI* _allocator, StringView& _dst, const StringView& _src)
{
const StringView next = settingsAlloc(_allocator, _src);
settingsFree(_allocator, _dst);
_dst = next;
}
struct Settings::Ini
{
static constexpr int32_t kGlobal = 0;
static constexpr int32_t kInvalid = -1;
struct Property
{
StringView name;
StringView value;
};
struct Section
{
StringView name;
Property* props;
uint32_t count;
uint32_t capacity;
};
void init(AllocatorI* _allocator)
{
m_allocator = _allocator;
m_sections = NULL;
m_count = 0;
m_capacity = 0;
clear();
}
void shutdown()
{
reset();
if (NULL != m_sections)
{
free(m_allocator, m_sections);
m_sections = NULL;
}
m_capacity = 0;
}
void clear()
{
reset();
addSection(StringView() );
}
void load(const StringView& _data)
{
clear();
Scanner scanner(_data);
int32_t section = kGlobal;
while (!scanner.isDone() )
{
scanner.accept(Scanner::Class::Space);
if (scanner.isDone() )
{
break;
}
Scanner line(scanner.acceptUntil(Scanner::Class::EndOfLine) );
// Line comment.
if (!line.accept(';').isEmpty() )
{
continue;
}
// [section] header.
if (!line.accept('[').isEmpty() )
{
line.accept(Scanner::Class::Space);
const StringView name = strRTrimSpace(line.acceptUntil("]") );
if (!line.accept(']').isEmpty()
&& !name.isEmpty() )
{
const int32_t existing = findSection(name);
section = kInvalid == existing ? addSection(name) : existing;
}
continue;
}
// name = value property.
const StringView name = strRTrimSpace(line.acceptUntil("=") );
if (line.accept('=').isEmpty()
|| name.isEmpty() )
{
continue;
}
line.accept(Scanner::Class::Space);
setProperty(section, name, strRTrimSpace(line.acceptAll() ) );
}
}
int32_t find(const StringView& _name, StringView& _propertyName) const
{
const FilePath uri(_name);
const StringView path(strTrim(uri.getPath(), "/") );
_propertyName = uri.getFileName();
if (path.isEmpty() )
{
return kGlobal;
}
const int32_t section = findSection(path);
return kInvalid == section ? kGlobal : section;
}
int32_t findSection(const StringView& _name) const
{
for (uint32_t ss = 0; ss < m_count; ++ss)
{
const StringView& name = m_sections[ss].name;
if (isEqual(name, _name, false) )
{
return int32_t(ss);
}
}
return kInvalid;
}
int32_t addSection(const StringView& _name)
{
if (m_count == m_capacity)
{
m_capacity = 0 == m_capacity ? 8 : m_capacity * 2;
m_sections = (Section*)realloc(m_allocator, m_sections, m_capacity * sizeof(Section) );
}
Section& section = m_sections[m_count];
section.name = settingsAlloc(m_allocator, _name);
section.props = NULL;
section.count = 0;
section.capacity = 0;
return int32_t(m_count++);
}
void removeSection(int32_t _section)
{
if (kGlobal >= _section
|| uint32_t(_section) >= m_count)
{
return;
}
freeSection(m_sections[_section]);
for (uint32_t ss = uint32_t(_section); ss+1 < m_count; ++ss)
{
m_sections[ss] = m_sections[ss+1];
}
--m_count;
}
int32_t findProperty(int32_t _section, const StringView& _name) const
{
if (0 > _section
|| uint32_t(_section) >= m_count)
{
return kInvalid;
}
const Section& section = m_sections[_section];
for (uint32_t pp = 0; pp < section.count; ++pp)
{
const StringView& name = section.props[pp].name;
if (isEqual(name, _name, false) )
{
return int32_t(pp);
}
}
return kInvalid;
}
void setProperty(int32_t _section, const StringView& _name, const StringView& _value)
{
if (0 > _section
|| uint32_t(_section) >= m_count)
{
return;
}
const int32_t property = findProperty(_section, _name);
Section& section = m_sections[_section];
if (kInvalid != property)
{
settingsAssign(m_allocator, section.props[property].value, _value);
return;
}
if (section.count == section.capacity)
{
section.capacity = 0 == section.capacity ? 8 : section.capacity * 2;
section.props = (Property*)realloc(m_allocator, section.props, section.capacity * sizeof(Property) );
}
Property& prop = section.props[section.count];
prop.name = settingsAlloc(m_allocator, _name);
prop.value = settingsAlloc(m_allocator, _value);
++section.count;
}
void removeProperty(int32_t _section, int32_t _property)
{
if (0 > _section
|| uint32_t(_section) >= m_count)
{
return;
}
Section& section = m_sections[_section];
if (0 > _property
|| uint32_t(_property) >= section.count)
{
return;
}
settingsFree(m_allocator, section.props[_property].name);
settingsFree(m_allocator, section.props[_property].value);
for (uint32_t pp = uint32_t(_property); pp+1 < section.count; ++pp)
{
section.props[pp] = section.props[pp+1];
}
--section.count;
}
StringView getPropertyValue(int32_t _section, int32_t _property) const
{
if (0 > _section
|| uint32_t(_section) >= m_count)
{
return StringView();
}
const Section& section = m_sections[_section];
return (0 <= _property && uint32_t(_property) < section.count)
? section.props[_property].value
: StringView()
;
}
uint32_t getPropertyCount(int32_t _section) const
{
return (0 <= _section && uint32_t(_section) < m_count)
? m_sections[_section].count
: 0
;
}
int32_t save(WriterI* _writer, Error* _err) const
{
int32_t total = 0;
for (uint32_t ss = 0; ss < m_count; ++ss)
{
const Section& section = m_sections[ss];
if (!section.name.isEmpty() )
{
total += bx::write(_writer, "[", _err);
total += bx::write(_writer, section.name, _err);
total += bx::write(_writer, "]\n", _err);
}
for (uint32_t pp = 0; pp < section.count; ++pp)
{
total += bx::write(_writer, section.props[pp].name, _err);
total += bx::write(_writer, "=", _err);
total += bx::write(_writer, section.props[pp].value, _err);
total += bx::write(_writer, "\n", _err);
}
if (0 != total)
{
total += bx::write(_writer, "\n", _err);
}
}
return total;
}
void freeSection(Section& _section)
{
for (uint32_t pp = 0; pp < _section.count; ++pp)
{
settingsFree(m_allocator, _section.props[pp].name);
settingsFree(m_allocator, _section.props[pp].value);
}
if (NULL != _section.props)
{
free(m_allocator, _section.props);
}
settingsFree(m_allocator, _section.name);
_section.props = NULL;
_section.count = 0;
_section.capacity = 0;
}
void reset()
{
for (uint32_t ss = 0; ss < m_count; ++ss)
{
freeSection(m_sections[ss]);
}
m_count = 0;
}
AllocatorI* m_allocator;
Section* m_sections;
uint32_t m_count;
uint32_t m_capacity;
};
Settings::Settings(AllocatorI* _allocator, const void* _data, uint32_t _len)
: m_allocator(_allocator)
, m_ini(BX_NEW(_allocator, Ini) )
{
m_ini->init(_allocator);
load(_data, _len);
}
Settings::~Settings()
{
m_ini->shutdown();
deleteObject(m_allocator, m_ini);
}
void Settings::clear()
{
m_ini->clear();
}
void Settings::load(const void* _data, uint32_t _len)
{
if (NULL == _data
|| 0 == _len)
{
m_ini->clear();
return;
}
m_ini->load(StringView( (const char*)_data, int32_t(_len) ) );
}
StringView Settings::get(const StringView& _name) const
{
StringView propertyName;
const int32_t section = m_ini->find(_name, propertyName);
return m_ini->getPropertyValue(section, m_ini->findProperty(section, propertyName) );
}
void Settings::set(const StringView& _name, const StringView& _value)
{
const FilePath uri(_name);
const StringView path(strTrim(uri.getPath(), "/") );
const StringView& fileName(uri.getFileName() );
int32_t section = Ini::kGlobal;
if (!path.isEmpty() )
{
section = m_ini->findSection(path);
if (Ini::kInvalid == section)
{
section = m_ini->addSection(path);
}
}
m_ini->setProperty(section, fileName, _value);
}
void Settings::remove(const StringView& _name) const
{
StringView propertyName;
const int32_t section = m_ini->find(_name, propertyName);
const int32_t property = m_ini->findProperty(section, propertyName);
if (Ini::kInvalid == property)
{
return;
}
m_ini->removeProperty(section, property);
if (Ini::kGlobal != section
&& 0 == m_ini->getPropertyCount(section) )
{
m_ini->removeSection(section);
}
}
int32_t property = ini_find_property(ini, section, fileName.getPtr(), fileName.getLength() );
if (INI_NOT_FOUND == property)
int32_t Settings::read(ReaderSeekerI* _reader, Error* _err)
{
return StringView();
int32_t size = int32_t(getRemain(_reader) );
void* data = bx::alloc(m_allocator, size);
int32_t total = bx::read(_reader, data, size, _err);
load(data, size);
bx::free(m_allocator, data);
return total;
}
return ini_property_value(ini, section, property);
}
void Settings::set(const StringView& _name, const StringView& _value)
{
ini_t* ini = INI_T(m_ini);
FilePath uri(_name);
const StringView path(strTrim(uri.getPath(), "/") );
const StringView& fileName(uri.getFileName() );
int32_t section = INI_GLOBAL_SECTION;
if (!path.isEmpty() )
int32_t Settings::write(WriterI* _writer, Error* _err) const
{
section = ini_find_section(ini, path.getPtr(), path.getLength() );
if (INI_NOT_FOUND == section)
{
section = ini_section_add(ini, path.getPtr(), path.getLength() );
}
return m_ini->save(_writer, _err);
}
int32_t property = ini_find_property(ini, section, fileName.getPtr(), fileName.getLength() );
if (INI_NOT_FOUND == property)
int32_t read(ReaderSeekerI* _reader, Settings& _settings, Error* _err)
{
ini_property_add(
ini
, section
, fileName.getPtr()
, fileName.getLength()
, _value.getPtr()
, _value.getLength()
);
}
else
{
ini_property_value_set(
ini
, section
, property
, _value.getPtr()
, _value.getLength()
);
}
}
void Settings::remove(const StringView& _name) const
{
ini_t* ini = INI_T(m_ini);
FilePath uri(_name);
const StringView path = strTrim(uri.getPath(), "/");
const StringView& fileName = uri.getFileName();
int32_t section = INI_GLOBAL_SECTION;
if (!path.isEmpty() )
{
section = ini_find_section(ini, path.getPtr(), path.getLength() );
if (INI_NOT_FOUND == section)
{
section = INI_GLOBAL_SECTION;
}
BX_ERROR_SCOPE(_err);
return _settings.read(_reader, _err);
}
int32_t property = ini_find_property(ini, section, fileName.getPtr(), fileName.getLength() );
if (INI_NOT_FOUND == property)
int32_t write(WriterI* _writer, const Settings& _settings, Error* _err)
{
return;
BX_ERROR_SCOPE(_err);
return _settings.write(_writer, _err);
}
ini_property_remove(ini, section, property);
if (INI_GLOBAL_SECTION != section
&& 0 == ini_property_count(ini, section) )
{
ini_section_remove(ini, section);
}
}
int32_t Settings::read(ReaderSeekerI* _reader, Error* _err)
{
int32_t size = int32_t(getRemain(_reader) );
void* data = bx::alloc(m_allocator, size);
int32_t total = bx::read(_reader, data, size, _err);
load(data, size);
bx::free(m_allocator, data);
return total;
}
int32_t Settings::write(WriterI* _writer, Error* _err) const
{
ini_t* ini = INI_T(m_ini);
int32_t size = ini_save(ini, NULL, 0);
void* data = bx::alloc(m_allocator, size);
ini_save(ini, (char*)data, size);
int32_t total = bx::write(_writer, data, size-1, _err);
bx::free(m_allocator, data);
return total;
}
#undef INI_T
int32_t read(ReaderSeekerI* _reader, Settings& _settings, Error* _err)
{
BX_ERROR_SCOPE(_err);
return _settings.read(_reader, _err);
}
int32_t write(WriterI* _writer, const Settings& _settings, Error* _err)
{
BX_ERROR_SCOPE(_err);
return _settings.write(_writer, _err);
}
} // namespace bx

View File

@@ -10,65 +10,6 @@
namespace bx
{
inline bool isInRange(char _ch, char _from, char _to)
{
return unsigned(_ch - _from) <= unsigned(_to-_from);
}
bool isSpace(char _ch)
{
return ' ' == _ch // Space.
|| '\t' == _ch // Horizontal tab.
|| '\n' == _ch // Line feed / new line.
|| '\r' == _ch // Carriage return.
|| '\v' == _ch // Vertical tab.
|| '\f' == _ch // Form feed / new page.
;
}
bool isUpper(char _ch)
{
return isInRange(_ch, 'A', 'Z');
}
bool isLower(char _ch)
{
return isInRange(_ch, 'a', 'z');
}
bool isAlpha(char _ch)
{
return isLower(_ch) || isUpper(_ch);
}
bool isNumeric(char _ch)
{
return isInRange(_ch, '0', '9');
}
bool isAlphaNum(char _ch)
{
return false
|| isAlpha(_ch)
|| isNumeric(_ch)
;
}
bool isHexNum(char _ch)
{
return false
|| isInRange(toLower(_ch), 'a', 'f')
|| isNumeric(_ch)
;
}
bool isPrint(char _ch)
{
return isInRange(_ch, ' ', '~');
}
typedef bool (*CharTestFn)(char _ch);
template<CharTestFn fn>
inline bool isCharTest(const StringView& _str)
{
@@ -125,11 +66,6 @@ namespace bx
return isCharTest<isPrint>(_str);
}
char toLower(char _ch)
{
return _ch + (isUpper(_ch) ? 0x20 : 0);
}
void toLowerUnsafe(char* _inOutStr, int32_t _len)
{
for (int32_t ii = 0; ii < _len; ++ii)
@@ -144,11 +80,6 @@ namespace bx
toLowerUnsafe(_inOutStr, len);
}
char toUpper(char _ch)
{
return _ch - (isLower(_ch) ? 0x20 : 0);
}
void toUpperUnsafe(char* _inOutStr, int32_t _len)
{
for (int32_t ii = 0; ii < _len; ++ii)
@@ -165,7 +96,7 @@ namespace bx
typedef char (*CharFn)(char _ch);
inline constexpr char toNoop(char _ch)
inline BX_CONSTEXPR_FUNC char toNoop(char _ch)
{
return _ch;
}
@@ -560,8 +491,6 @@ namespace bx
);
}
constexpr uint32_t kFindStep = 1024;
StringView strFindNl(const StringView& _str)
{
StringView str(_str);
@@ -578,23 +507,19 @@ namespace bx
StringView strFindEol(const StringView& _str)
{
StringView str(_str);
const StringView eol = strFind(_str, '\n');
for (; str.getPtr() != _str.getTerm()
; str = StringView(min(str.getPtr() + kFindStep, _str.getTerm() ), min(str.getPtr() + kFindStep*2, _str.getTerm() ) )
)
if (!eol.isEmpty() )
{
StringView eol = strFind(str, "\r\n");
if (!eol.isEmpty() )
const char* ptr = eol.getPtr();
if (ptr != _str.getPtr()
&& '\r' == ptr[-1])
{
return StringView(eol.getPtr(), _str.getTerm() );
--ptr;
}
eol = strFind(str, '\n');
if (!eol.isEmpty() )
{
return StringView(eol.getPtr(), _str.getTerm() );
}
return StringView(ptr, _str.getTerm() );
}
return StringView(_str.getTerm(), _str.getTerm() );

View File

@@ -3,10 +3,36 @@
* License: https://github.com/bkaradzic/bnet#license-bsd-2-clause
*/
#include <bx/scanner.h>
#include <bx/url.h>
namespace bx
{
static bool isNotSlash(char _ch)
{
return '/' != _ch;
}
static bool isNotColon(char _ch)
{
return ':' != _ch;
}
static bool isNotQuery(char _ch)
{
return '?' != _ch;
}
static bool isNotFragment(char _ch)
{
return '#' != _ch;
}
static bool isNotQueryOrFragment(char _ch)
{
return isNotQuery(_ch) && isNotFragment(_ch);
}
UrlView::UrlView()
{
}
@@ -23,22 +49,16 @@ namespace bx
{
clear();
const char* term = _url.getTerm();
StringView schemeEnd = strFind(_url, "://");
const char* hostStart = !schemeEnd.isEmpty() ? schemeEnd.getTerm() : _url.getPtr();
StringView path = strFind(StringView(hostStart, term), '/');
Scanner scanner(_url);
if (schemeEnd.isEmpty()
&& path.isEmpty() )
const StringView schemeBegin = scanner.getCursor();
scanner.acceptUntil("://");
const StringView scheme = scanner.between(schemeBegin);
const bool hasScheme = !scanner.accept("://").isEmpty();
if (hasScheme)
{
return false;
}
if (!schemeEnd.isEmpty()
&& (path.isEmpty() || path.getPtr() > schemeEnd.getPtr() ) )
{
const StringView scheme(_url.getPtr(), schemeEnd.getPtr() );
if (!isAlpha(scheme) )
{
return false;
@@ -47,65 +67,72 @@ namespace bx
m_tokens[Scheme].set(scheme);
}
if (!path.isEmpty() )
{
path.set(path.getPtr(), term);
const StringView query = strFind(path, '?');
const StringView fragment = strFind(path, '#');
const StringView authorityBegin = scanner.getCursor();
scanner.acceptWhile(isNotSlash);
const StringView authority = scanner.between(authorityBegin);
if (!fragment.isEmpty()
&& fragment.getPtr() < query.getPtr() )
const bool hasPath = !scanner.peek('/').isEmpty();
if (!hasScheme
&& !hasPath)
{
return false;
}
if (hasPath)
{
const StringView pathBegin = scanner.getCursor();
scanner.acceptWhile(isNotQueryOrFragment);
m_tokens[Path].set(scanner.between(pathBegin) );
if (!scanner.accept('?').isEmpty() )
{
const StringView queryBegin = scanner.getCursor();
scanner.acceptWhile(isNotFragment);
m_tokens[Query].set(scanner.between(queryBegin) );
}
if (!scanner.accept('#').isEmpty() )
{
const StringView fragmentBegin = scanner.getCursor();
scanner.acceptWhile(isNotQuery);
m_tokens[Fragment].set(scanner.between(fragmentBegin) );
}
// Anything left over is a query following a fragment.
if (!scanner.isDone() )
{
return false;
}
m_tokens[Path].set(path.getPtr()
, !query.isEmpty() ? query.getPtr()
: !fragment.isEmpty() ? fragment.getPtr()
: term
);
if (!query.isEmpty() )
{
m_tokens[Query].set(query.getPtr()+1
, !fragment.isEmpty() ? fragment.getPtr()
: term
);
}
if (!fragment.isEmpty() )
{
m_tokens[Fragment].set(fragment.getPtr()+1, term);
}
term = path.getPtr();
}
const StringView userPassEnd = strFind(StringView(hostStart, term), '@');
const char* userPassStart = !userPassEnd.isEmpty() ? hostStart : NULL;
hostStart = !userPassEnd.isEmpty() ? userPassEnd.getPtr()+1 : hostStart;
const StringView portStart = strFind(StringView(hostStart, term), ':');
Scanner authorityScanner(authority);
m_tokens[Host].set(hostStart, !portStart.isEmpty() ? portStart.getPtr() : term);
const StringView userInfoBegin = authorityScanner.getCursor();
authorityScanner.acceptUntil("@");
const StringView userInfo = authorityScanner.between(userInfoBegin);
if (!portStart.isEmpty())
if (!authorityScanner.accept('@').isEmpty() )
{
m_tokens[Port].set(portStart.getPtr()+1, term);
Scanner userInfoScanner(userInfo);
const StringView userNameBegin = userInfoScanner.getCursor();
userInfoScanner.acceptWhile(isNotColon);
m_tokens[UserName].set(userInfoScanner.between(userNameBegin) );
if (!userInfoScanner.accept(':').isEmpty() )
{
m_tokens[Password].set(userInfoScanner.acceptAll() );
}
}
if (NULL != userPassStart)
const StringView hostBegin = authorityScanner.getCursor();
authorityScanner.acceptWhile(isNotColon);
m_tokens[Host].set(authorityScanner.between(hostBegin) );
if (!authorityScanner.accept(':').isEmpty() )
{
StringView passStart = strFind(StringView(userPassStart, userPassEnd.getPtr() ), ':');
m_tokens[UserName].set(userPassStart
, !passStart.isEmpty() ? passStart.getPtr()
: userPassEnd.getPtr()
);
if (!passStart.isEmpty() )
{
m_tokens[Password].set(passStart.getPtr()+1, userPassEnd.getPtr() );
}
m_tokens[Port].set(authorityScanner.acceptAll() );
}
return true;