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

@@ -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