patterncppMinor
Simple ini file parser
Viewed 0 times
parserfilesimpleini
Problem
The below is a C++ parser for a reasonably simple INI grammar (although my understanding is that there isn't an official spec as such).
My grammar is roughly:
ini_parser.h
ini_parser.cpp
```
#include
#include
#include
#include
#include
#include
#include "ini_parser.h"
namespace {
bool iskey(char c){
return isalnum(c) || ispunct(c);
}
bool isval(char c){
return isalnum(c) || ispunct(c) || isblank(c);
}
}
IniParser::IniParser(const std::string &path) {
enum class State {
init,
section,
key,
value,
skipline
} state = State::init;
std::ifstream f{path, std::ios::in};
if(!f.is_open()){
throw std::runtime_error("file doesn't exist");
}
std::vector buf;
std::string section, key;
char c;
auto err_helper = &{
std::ostringstream ss;
ss (c)
std::string IniParser::get(const std::string &key, const std::string §ion){
return inimap.at(section).at(key);
}
template<>
int IniParser::get(const std::string &key, const std::string §ion){
return std::stoi(inimap.at(section).at(key));
}
template<>
std::uint16_t IniParser::get(const std::string &key, const std::string §ion){
auto val = std::stoul(inimap.at(section).at(key));
if(val > std::numeric_limits::max()){
throw std::overflow_error("value too large for type");
}
return static_cast(v
My grammar is roughly:
comments: (optional whitespace){;#}
section: (optional whitespace)[{printable ASCII}]
keyval: (optional whitespace){nows printable ASCII}={printable ASCII}ini_parser.h
#pragma once
#include
#include
class IniParser {
public:
IniParser(const std::string &path);
template
T get(const std::string &key, const std::string §ion);
//get values in 'default' section
template
T get(const std::string &key) { return get(key, ""); }
private:
using SectionMap = std::map;
using IniMap = std::map;
IniMap inimap;
};ini_parser.cpp
```
#include
#include
#include
#include
#include
#include
#include "ini_parser.h"
namespace {
bool iskey(char c){
return isalnum(c) || ispunct(c);
}
bool isval(char c){
return isalnum(c) || ispunct(c) || isblank(c);
}
}
IniParser::IniParser(const std::string &path) {
enum class State {
init,
section,
key,
value,
skipline
} state = State::init;
std::ifstream f{path, std::ios::in};
if(!f.is_open()){
throw std::runtime_error("file doesn't exist");
}
std::vector buf;
std::string section, key;
char c;
auto err_helper = &{
std::ostringstream ss;
ss (c)
std::string IniParser::get(const std::string &key, const std::string §ion){
return inimap.at(section).at(key);
}
template<>
int IniParser::get(const std::string &key, const std::string §ion){
return std::stoi(inimap.at(section).at(key));
}
template<>
std::uint16_t IniParser::get(const std::string &key, const std::string §ion){
auto val = std::stoul(inimap.at(section).at(key));
if(val > std::numeric_limits::max()){
throw std::overflow_error("value too large for type");
}
return static_cast(v
Solution
I see some things that may help you improve your code.
Consider altering the grammar
Right now the grammar will accept a line like this:
But explicitly reject lines like this one:
The reason is that the space between the key and the
Consider reordering slightly
Instead of this series of statements:
One might instead consider this:
A smart compiler might be able to generate the same code, but the latter version means that the compiler might better be able to do a move of the string rather than a create/copy/delete.
Reconsider the interface
While I can see the appeal of a generic
Consider finer grained error handling
The
This single line has three different ways to
Use
For an approach which uses
Consider altering the grammar
Right now the grammar will accept a line like this:
[user]Not a comment, but acts like oneBut explicitly reject lines like this one:
key = valueThe reason is that the space between the key and the
= causes the parser to throw a key error. That's a bit inconvenient and could be remedied by adding an additional state between the key and = that would exlicitly allow whitespace there.Consider reordering slightly
Instead of this series of statements:
const std::string token = std::string(buf.begin(), buf.end());
buf.clear();
inimap[section][key] = token;One might instead consider this:
inimap[section][key] = std::string(buf.begin(), buf.end());
buf.clear();A smart compiler might be able to generate the same code, but the latter version means that the compiler might better be able to do a move of the string rather than a create/copy/delete.
Reconsider the interface
While I can see the appeal of a generic
get interface, everything is stored internally as a std::string, so it might make sense to simplify the interface of this class to only return a std::string and let the recieving code do any conversions. This has the advantage that the conversion routines are no longer part of the class and may be customized by usage or perhaps by key. For example, a custom date class might benefit from having, essentially, a custom converter from std::string which may well be useful outside the IniParser class (as with retrieving the data from a user and then converting).Consider finer grained error handling
The
int version of get has this single line as its body:return std::stoi(inimap.at(section).at(key));This single line has three different ways to
throw a std::out_of_range error. The section lookup could fail, or the key lookup could fail or stoi could fail. It will not be easy for the calling code to determine which of these failures occurred if one does. It's possible that the calling code only needs to know that an error occured and not which one, but a finer-grained error reporting mechanism (e.g. with custom error classes) might help if the calling code would benefi from knowing the difference between a key lookup error and a section lookup error. Use
std::regexFor an approach which uses
std::regex, see ini file parser in C++Code Snippets
[user]Not a comment, but acts like onekey = valueconst std::string token = std::string(buf.begin(), buf.end());
buf.clear();
inimap[section][key] = token;inimap[section][key] = std::string(buf.begin(), buf.end());
buf.clear();return std::stoi(inimap.at(section).at(key));Context
StackExchange Code Review Q#135887, answer score: 2
Revisions (0)
No revisions yet.