Writing a JSON parser for C++ - c++

So far I have managed to put together a lexer and a stack in the hopes of achieving a LL1 parser. I am doing this purely to understand how parsing works, and maybe to use these ideas in future projects. I understand there are much better frameworks out there like json-cpp and rapid-json but I would like to understand this for myself.
The header file is give below.
#pragma once
#include <string>
#include <vector>
#include <map>
#include <variant>
#include <fstream>
#include <stack>
#include "Helper.h"
// Debugging
#include <iostream>
// Types to store JSON ouput
struct jlist;
struct jobject;
using json_value = std::variant<int, float, bool, std::string, jlist, jobject>;
enum tag { int_value, float_value, string_value, list, object };
struct jlist {
tag type;
std::vector<json_value *> vector_value;
};
struct jobject {
tag type;
std::map<std::string, json_value *> map_value;
};
class JSONParser
{
public:
JSONParser();
~JSONParser();
void parseFile(std::string);
private:
std::stack<std::string> s;
bool checkDeliminator(char);
std::vector<std::string> lexer(std::ifstream &);
void parser(std::vector<std::string> &);
void transitionTable(std::string cursor);
};
The implementation is as follows.
#include "genetic-optimization/JSONParser.h"
JSONParser::JSONParser() {
}
JSONParser::~JSONParser() = default;
void JSONParser::parseFile(std::string FILE) {
std::ifstream configfile(FILE);
std::vector<std::string> scan = lexer(configfile);
parser(scan);
}
bool JSONParser::checkDeliminator(char piece) {
switch (piece) {
case '[':
return true;
case ']':
return true;
case '{':
return true;
case '}':
return true;
case ':':
return true;
case ',':
return true;
case '"':
return true;
default:
return false;
}
}
std::vector<std::string> JSONParser::lexer(std::ifstream & configfile) {
char piece;
std::string capture = "";
std::string conversion;
std::vector<std::string> capture_list;
while(configfile >> piece) {
if (checkDeliminator(piece)) {
conversion = piece;
if (capture != "") {
capture_list.push_back(capture);
capture_list.push_back(conversion);
capture = "";
} else {
capture_list.push_back(conversion);
}
} else {
capture += piece;
}
}
return capture_list;
}
void JSONParser::parser(std::vector<std::string> & scan) {
for (auto it = scan.begin(); it != scan.end(); ++it) {
std::cout << *it << "\n"; // Make sure the lexer works
transitionTable(*it);
}
}
void JSONParser::transitionTable(std::string cursor) {
if(s.empty()) {
s.push(cursor);
} else {
if (s.top() == "[") {
s.push(cursor);
} else if (s.top() == "]") {
s.pop();
} else if (s.top() == "{") {
s.push(cursor);
} else if (s.top() == "}") {
s.pop();
}
}
}
I am unsure of how to proceed from here but have been using the json grammar as a starting point and the following tutorial for guidance.
json -> element
value -> object|array|string|number|bool|
object -> {}|{members}
members -> member|member,members
member -> string:element
array -> []|[elements]
elements -> element|element,elements
element -> value
I have three main problems.
The JSON grammar seems to have left indirect recursion. Since the grammar is not as simple as that shown in the tutorial I do not know how to eliminate it.
I do not know how to generate the parse table (finite state machine), specifically for something like First(object), what would this be? Is there any resource that has produced a parse table for JSON and might point me in the right direction?
The tutorial seems more to verify that the expression being parsed is produced by the grammar but I would like to store the structure in a variable. Where would this be done and do you have any advice for how this might look in pseudo (or even better C++) code.
For completeness, I am using the following JSON as a test.
[
{
"libraries":[
"terminal",
"binary"
] ,
"functions":[
"terminal-basic",
"binary-basic"
]
}
,
{
"name":"addition",
"type":"binary-basic",
"function":"add_float",
"input":{
"float" : 2
},
"output":"float",
"max-number":2
}
,
{
"name":"exponent",
"type":"binary-basic",
"function":"exponent_float",
"input":{
"float":2
},
"output":"float",
"max-number":2
}
,
{
"name":"exponent",
"type":"binary-basic",
"function":"exponent_float",
"input":{
"float":2,
"int":1
},
"output":"float",
"max-number":1
}
,
{
"name":"constant_1",
"type":"terminal-basic",
"function":"non_random_constant",
"value":0.5,
"input":{ },
"output":"float",
"max-number":3
}
,
{
"name":"constant_2",
"type":"terminal-basic",
"function":"non_random_constant",
"value":2.0,
"input":{ },
"output":"float",
"max-number":3
}
,
{
"name":"constant_3",
"type":"terminal-basic",
"function":"non_random_constant",
"value":true,
"input":{
"bool":1
},
"output":"bool",
"max-number":1
}
]

I wouldn't like to leave this question unanswered for anyone coming here in the future, however, I am personally not a big fan of the code that accompanies this answer. It feels inefficient, not particularly elegant and I am unsure if it represents the theoretical model I was trying to implement in the first place. I took my lead from #MSalters comment, which to me meant build something that works and worry if the model is theoretically sound later. Below is my attempt.
The header adds a few more functions. Many of them purely to assist fsm and parser.
class JSONParser
{
public:
JSONParser();
~JSONParser();
void parseFile(std::string);
private:
json_value root;
std::stack<std::string> s;
std::stack<json_value> s_value;
// Lexer
bool checkDeliminator(char);
std::vector<std::string> lexer(std::ifstream &);
// FSM varaibles
enum state { int_value, float_value, bool_value, string_value, default_value, bad_state};
state current;
// FSM
void fsm(std::string);
// Parser variables
enum stack_map { list_open, list_close, object_open, object_close, colon, comma, buffer, follow};
std::map<std::string, stack_map> stack_conversion;
// Parser helper functions
template<typename T> void addElement();
template<typename T> void insert(std::string &, T (*)(const std::string &));
template<typename T> void insert();
void insert(std::string &);
void pushBuffer();
template<typename ... T> bool multiComparision(const char scope, T ... args);
bool isDigit(const char);
static int st2i(const std::string & value);
static float st2f(const std::string & value);
static bool st2b(const std::string & value);
// Parser
void parser(const std::string & cursor);
};
The implementation file follows.
#include "genetic-optimization/JSONParser.h"
JSONParser::JSONParser() {
state current = default_value;
stack_conversion = { { "[", list_open }, { "]", list_close }, { "{", object_open }, { "}", object_close }, { ":", colon }, { ",", comma }, { "buffer", buffer } };
}
JSONParser::~JSONParser() = default;
void JSONParser::parseFile(std::string FILE) {
std::ifstream configfile(FILE);
std::vector<std::string> scan = lexer(configfile);
scan.push_back("terminate");
for (auto it = scan.begin(); it != scan.end(); ++it) {
parser(*it);
}
root = s_value.top();
s_value.pop();
}
// Lexer
bool JSONParser::checkDeliminator(char piece) {
switch (piece) {
case '[':
return true;
case ']':
return true;
case '{':
return true;
case '}':
return true;
case ':':
return true;
case ',':
return true;
default:
return false;
}
}
std::vector<std::string> JSONParser::lexer(std::ifstream & configfile) {
char piece;
std::string capture = "";
std::string conversion;
std::vector<std::string> capture_list;
while(configfile >> piece) {
if (checkDeliminator(piece)) {
conversion = piece;
if (capture != "") {
capture_list.push_back(capture);
capture_list.push_back(conversion);
capture = "";
} else {
capture_list.push_back(conversion);
}
} else {
capture += piece;
}
}
return capture_list;
}
// FSM
void JSONParser::fsm(std::string value) {
current = default_value;
char point;
auto it = value.begin();
while (it != value.end()) {
point = *it;
if (point == '"' & current == default_value) {
current = string_value;
return;
} else if (isdigit(point)) {
if (current == default_value | current == int_value) {
current = int_value;
++it;
} else if (current == float_value) {
++it;
} else {
current = bad_state;
return;
}
} else if (point == '.' & current == int_value) {
current = float_value;
++it;
} else if (point == 'f' & current == float_value) {
++it;
} else if (current == default_value) {
if (value == "true" | value == "false") {
current = bool_value;
return;
} else {
current = bad_state;
return;
}
} else {
current = bad_state;
return;
}
}
}
// Parser Helper functions
template<>
void JSONParser::addElement<jobject>() {
json_value value_read;
json_value key_read;
value_read = s_value.top();
s_value.pop();
key_read = s_value.top();
s_value.pop();
std::get<jobject>(s_value.top()).insert(key_read, value_read);
}
template<>
void JSONParser::addElement<jlist>() {
json_value value_read;
value_read = s_value.top();
s_value.pop();
std::get<jlist>(s_value.top()).push_back(value_read);
}
template<typename T>
void JSONParser::insert(std::string & value, T (*fptr)(const std::string &)) {
T T_value(fptr(value));
s_value.push(T_value);
}
template<typename T>
void JSONParser::insert() {
T T_value;
s_value.push(T_value);
}
void JSONParser::insert(std::string & value) {
value.erase(std::remove(value.begin(), value.end(), '"'), value.end());
s_value.push(value);
}
void JSONParser::pushBuffer() {
s.pop();
s.push("buffer");
}
template<typename ... T>
bool JSONParser::multiComparision(const char scope, T ... args) {
return (scope == (args || ...));
}
bool JSONParser::isDigit(const char c) {
return multiComparision<char>(c, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0');
}
int JSONParser::st2i(const std::string & value) {
return stoi(value);
}
float JSONParser::st2f(const std::string & value) {
return stof(value);
}
bool JSONParser::st2b(const std::string & value) {
if (value == "true") {
return true;
} else {
return false;
}
}
// Parser
void JSONParser::parser(const std::string & cursor) {
if(s.empty()) {
s.push(cursor);
} else {
stack_map stack_value;
std::string value = s.top();
if (stack_conversion.find(value) != stack_conversion.end()) {
stack_value = stack_conversion[s.top()];
} else {
stack_value = follow;
}
switch (stack_value) {
case buffer:
s.pop();
break;
case list_open:
insert<jlist>();
if (cursor == "]") {
pushBuffer();
return;
}
break;
case list_close:
addElement<jlist>();
s.pop();
s.pop();
break;
case object_open:
insert<jobject>();
if (cursor == "}") {
pushBuffer();
return;
}
break;
case object_close:
addElement<jobject>();
s.pop();
s.pop();
break;
case colon:
s.pop();
break;
case comma:
s.pop();
if (s.top() == "{") {
addElement<jobject>();
} else {
addElement<jlist>();
}
break;
default:
s.pop();
fsm(value);
switch (current) {
case string_value:
insert(value);
break;
case int_value:
insert<int>(value, st2i);
break;
case float_value:
insert<float>(value, st2f);
break;
case bool_value:
insert<bool>(value, st2b);
break;
default:
std::cout << "Bad state\n";
}
}
s.push(cursor);
}
}
The idea was to have the lexer break at each deliminator and place all the generated tokens into a vector. This vector called scan could then be looped through. At each iteration of this loop parser would be run. In general this reads the top of the stack s and determines whether a bracket/brace is opening or closing or a terminal value has been reached. If a bracket/brace is opening, a new jobject or jlist is generated and placed onto a new stack s_value, if a terminal value is reached fsm (finite state machine) runs and determines the type of value and places it on top of s_value, should a comma or closing bracket be reached the appropriate values are moved off the stack and the elements in s_value are inserted into their appropriate containers.
The biggest meatball in this spaghetti is how elements in the JSON tree are called.
std::cout << std::get<bool>(std::get<jobject>(std::get<jobject>(std::get<jlist>(root)[6])["input"])["bool"]); // Should return 1
While this does indeed return 1. The nested std::get calls seem just plain wrong and I'm not sure if they can be incorporated into the operator [] or through (sigh) a third stack that tracks the type of object being stored.
This was my basic attempt, it's not pretty but it does work. Hopefully I can refine it further and improve on what I have.

I am not an expert at parsing so my answer would be very heuristic...
JSON grammar is simple. I believe we do not need to try to understand of follow over-specified (E)BNF form to actually parse JSON string. Try to write your own simple form. After you do that, you may feel a need for a better form. Then you can re-try to fully understand why there are such grammars.
Isn't FSM simply "you have to do that in this state?" States are preferably managed by a stack (not like you have to have an instance whose members indicate states like an abstract figure in text book in many cases of the real world) and you will do what you have to do in loops based on a top state of the stack. I believe you do not need an instance of 'parse table.' Can it be abstract or pervasively exists somewhere in code?
I also started to practice parsing with JSON. Please check my single header file.
I used 7 stack statuses:
enum status {
READING_OBJECT_KEY,
READ_OBJECT_KEY,
READING_OBJECT_VALUE, READING_ARRAY_VALUE,
READ_OBJECT_VALUE, READ_ARRAY_VALUE, READ_OTHER_VALUE
};
Heuristically, I started actual parsing after skipping preceding whitespace and check the first non-whitespace character:
} else if (p.c == '{') {
p.ps.push(json::parsing::READING_OBJECT_KEY);
j = json::object();
p.js.push(j.v);
break;
} else if (p.c == '[') {
p.ps.push(json::parsing::READING_ARRAY_VALUE);
j = json::array();
p.js.push(j.v);
break;
}
Then I actually started to parse with 8 functions:
while (p.iss.get(p.c)) {
p.i++;
if (p.c == ' ' ) {}
else if (p.c == '{' ) json::parse__left_brace(p);
else if (p.c == '}' ) json::parse__right_brace(p);
else if (p.c == '[' ) json::parse__left_bracket(p);
else if (p.c == ']' ) json::parse__right_bracket(p);
else if (p.c == ':' ) json::parse__colon(p);
else if (p.c == ',' ) json::parse__comma(p);
else if (p.c == '\"') json::parse__quote(p);
else json::parse__else(p);
}

Related

Pointer to member function of instance instead of class

I have the following class when I get a pointer to a member function according to some condition and then call the function.
class Test
{
public:
bool isChar(char ch) { return (ch >= 'a' && ch <= 'z'); }
bool isNumeric(char ch) { return (ch >= '0' && ch <= '0'); }
enum class TestType
{
Undefined,
Char,
Numeric,
AnotherOne,
};
bool TestFor(TestType type, char ch)
{
typedef bool (Test::*fptr)(char);
fptr f = nullptr;
switch(type)
{
case TestType::Char:
f = &Test::isChar;
break;
case TestType::Numeric:
f = &Test::isNumeric;
break;
default: break;
}
if(f != nullptr)
{
return (this->*f)(ch);
}
return false;
}
};
But actually I don't like the syntax. Is there a way to replace
(this->*f)(ch)
with
f(ch)
?
In my real code the function a big enough and it's not so clear what (this->*f) is. I'm looking for some c++11 solution. I know about std::function and I will use it if if no solution will be found.
Update
The solution that I decided to use, if suddenly someone needs it: (thanks for #StoryTeller - Unslander Monica)
bool TestFor(TestType type, char ch)
{
bool(Test::* fptr)(char) = nullptr;
switch(type)
{
case TestType::Char:
fptr = &Test::isChar;
break;
case TestType::Numeric:
fptr = &Test::isNumeric;
break;
default: break;
}
if(fptr != nullptr)
{
auto caller = std::mem_fn(fptr);
return caller(this, ch);
}
return false;
}
If the syntax bothers you so much, you can always use std::mem_fn to generate a cheap one-time wrapper around a member function.
auto caller = std::mem_fn(f);
caller(this, ch);

c++ macro using variable from an other macro

I need to make foo compile by implementing the macros for it:
int foo(std::string tag)
{
SWITCH_STRING(tag)
{
STRING_CASE(a)
{
return 1;
}
STRING_CASE(b)
{
return 2;
}
STRING_CASE(abc)
{
return 3;
}
STRING_ELSE
{
return -1;
}
}
}
I would like to use the tag parameter in SWITCH_STRING(tag) and compare it to the letter parameter in STRING_CASE(letter), to implement this switch like syntax, I'm stuck for a while and new to macros in c++ could you offer a solution to how to implement the macros please?
#include <iostream>
#include <string>
// Write macros here |
#define SWITCH_STRING(tag)
#define STRING_CASE(letter) letter == tag ? true : false
#define STRING_ELSE
I have to admit: Macros can be fun. We all should know that they should be avoided. Though, as this is an exercise about macros, we can put the discussion whether to use a macro or not aside.
The point of the exercise is that you cannot (directly) switch on a std::string. This answer shows how this limitation can be worked-around. Being required to write exremely verbose repetetive code, the macro is kind of justified. For the sake of completeness I want to add how it can be solved using your original approach, using a series of if instead of the switch.
First, I write the function that does what is asked for without any macro involved:
int foo(std::string tag)
{
std::string& temp = tag;
{
if (temp == "a")
{
return 1;
}
if (temp == "b")
{
return 2;
}
if (temp == "abc")
{
return 3;
}
{
return -1;
}
}
}
It isnt that nice that it uses ifs not else if that should be prefered for mutually exclusive cases. However, as each case returns, the result wont differ (if that isnt the case, you'll have to add some goto vodoo as outlined in the other answer). Having that, it is straightforward to see what macros are needed:
#define SWITCH_STRING(tag) std::string& temp = tag;
#define STRING_CASE(X) if (temp == #X)
#define STRING_ELSE
This kind of answers your question about how to use the parameter of one macro in a second one: You don't. Instead you can use a reference whose name does not depend on the actual name of tag anymore.
Full example
What you might do to switch on string:
constexpr std::size_t myhash(std::string_view) { /* .. */ }
int foo(const std::string& tag)
{
switch (tag)
{
case myhash("a"): { return 1; }
case myhash("b"): { return 2; }
case myhash("abc"): { return 3; }
default: { return -1; }
}
}
That doesn't need MACRO.
If you have collisions with your cases, compilation would fail (same value in switch)
and you will need another hash function.
If you want to prevent collisions (from input string), you might do:
constexpr std::size_t myhash(std::string_view) { /* .. */ }
int foo(const std::string& tag)
{
switch (tag)
{
case myhash("a"): { if (tag != "a") { goto def; } return 1; }
case myhash("b"): { if (tag != "b") { goto def; } return 2; }
case myhash("abc"): { if (tag != "abc") { goto def; } return 3; }
default: { def: return -1; }
}
}
which might indeed be less verbose with MACRO
#define CaseHash(str, c) case myhash(c): if (str != c) { goto def; }
#define DefaultHash default: def
to result to
constexpr std::size_t myhash(std::string_view) { /* .. */ }
int foo(const std::string& tag)
{
switch (tag)
{
CaseHash(tag, "a") { return 1; }
CaseHash(tag, "b") { return 2; }
CaseHash(tag, "abc") { return 3; }
DefaultHash: { return -1; }
}
}

Cannot dereference end list iterator

I'm currently working on a project about browser histories. I'm using the STL's list implementation to keep track of the different websites. I have most of it figured out but I can't seem to resolve the error:
Screenshot; cannot dereference end list iterator.
The website data is contained within my Site objects.
class BrowserHistory {
private:
list<Site> history;
list<Site>::iterator current = history.begin();
public:
void visitSite(string, size_t)
void backButton();
void forwardButton();
void readFile(string);
};
void BrowserHistory::visitSite(string x, size_t y)
{
while (current != history.end()) {
history.pop_back();
}
history.push_back({ x, y });
current++;
}
void BrowserHistory::backButton()
{
if (current != history.begin())
current--;
}
void BrowserHistory::forwardButton()
{
if (current != history.end())
current++;
}
void BrowserHistory::readFile(string filename)
{
string action, url;
size_t pageSize;
ifstream dataIn;
dataIn.open(filename);
while (!dataIn.eof()) {
dataIn >> action;
if (action == "visit") {
dataIn >> url >> pageSize;
history.push_back({ url, pageSize });
current++;
}
if (action == "back") {
current--;
}
if (action == "forward") {
current++;
}
}
dataIn.close();
}
Can anyone explain to me what's wrong? Thanks in advance for any help.
When you initialise current, the list is empty.
Adding elements to the list does not suddenly make it a valid iterator, even though it will be different from end.
It looks like you're thinking of iterators as very much like pointers, but they're not.
Iterators are for iterating and should be considered transient, and not stored for later use.
Use a vector, and use an index for current instead of an iterator.
Further, I would rename the "button" functions (what does the history care about buttons?) to "goBack" and "goForward" and use your actual interface when reading:
void BrowserHistory::readFile(string filename)
{
ifstream dataIn(filename);
string action;
while (dataIn >> action) {
if (action == "visit") {
string url;
size_t pageSize;
if (dataIn >> url >> pageSize) {
visitSite(url, pageSize);
}
else {
// Handle error
}
}
else if (action == "back") {
goBack();
}
else if (action == "forward") {
goForward();
}
else {
// Handle error
}
}
}

Evaluate expression in a string recursively

Lets say we would like to evaluate expressions in a string. Expressions represented by (###) for simplicity in the example. We only count the hashtags in the example for simplicity. Expressions can be nested.
#include <iostream>
#include <string>
std::string expression{ "(###(##)#(###)##)" };
int countHash(std::string::iterator stringIterator, std::string::iterator stringEnd)
{
int result = 0;
while (stringIterator != stringEnd)
{
if (*stringIterator == '#')
{
result += 1;
}
else if (*stringIterator == '(')
{
result += countHash(++stringIterator, stringEnd);
}
else if (*stringIterator == ')')
{
return result += countHash(++stringIterator, stringEnd);
}
++stringIterator;
}
return result;
}
int main()
{
std::cout << countHash(expression.begin(), expression.end()) << std::endl;
return 0;
}
Output: 51
Expexted output: 11
So my problem is when I return from the recursive call the iterator is not updated. It is behind. The processing goes through parts of the string multiple times. How should I handle this?
My main goal by the way is to be able to evaluate expressions like this:
std::string expr = "(+1 (+22 3 25) 5 (+44 (*3 2)))";
EXPECT(106== evalExpression(expr.begin(), expr.end()));
Thanks.
EDIT:
I updated my question based on the suggestions in the comments.
#include <string>
#include <iostream>
std::string expression{ "#####-###-##" };
int countHash(std::string::iterator & stringIterator, std::string::iterator stringEnd)
{
int result = 0;
while (stringIterator != stringEnd)
{
switch (*stringIterator++)
{
case '#':
result += 1;
break;
case '-':
result += countHash(stringIterator, stringEnd);
break;
default:
// indicate error ?
break;
}
}
return result;
}
int main()
{
std::string::iterator b = expression.begin();
std::cout << countHash(b, expression.end()) << std::endl;
return 0;
}
OK so as I edited my original question, here is a solution for that:
#include <iostream>
#include <string>
std::string expression{ "(###((##)#)(#(#)#)#(#))" };
int countHash(std::string::iterator& stringIterator, std::string::iterator stringEnd)
{
int result = 0;
while (stringIterator != stringEnd)
{
if (*stringIterator == '#')
{
result += 1;
}
else if (*stringIterator == '(')
{
result += countHash(++stringIterator, stringEnd);
continue;
}
else if (*stringIterator == ')')
{
++stringIterator;
return result;
}
++stringIterator;
}
return result;
}
int countHash(std::string expression)
{
auto it = expression.begin();
return countHash(it, expression.end());
}
int main()
{
std::cout << countHash(expression) << std::endl;
return 0;
}
Output: 11
So one important thing was that you need to pass the string by reference to avoid processing the same segments of the string multiple times after you return from your recursive calls.
What I also had difficulty with is that you need to do a continue after the recursive call in my while loop. This is because you don't want to increment stringIterator after your return from the recursive call.
You could also do this with the post increment operator and with a switch-case as #bruno did it in his answer. That was the insight for me. If you are not only checking for characters switch-case is not possible though. You could use a do-while loop but I don't like that.
On more important thing was that you need to increment your iterator before returning from the ) branch. That is because that's the end of an expression and if it was a recursive call you want to go on with the expression on the caller side.
One other problem was that you cant pass expression.begin() if your function takes a reference to iterator.
For the
std::string expr = "(+1 (+22 3 25) 5 (+44 (*3 2)))";
expression my solution is available at https://github.com/bencemeszaroshu/expeval/blob/master/expeval/expeval.cpp. I don't like it as it is now but I will try to improve it later. (Happy to hear suggestions.) It is working however. Thanks everyone for your help, I'm marking #bruno answer as accepted because it helped me the most.

How to read in a .cfg.txt file

I've been trying to read this file for some time now and tried about everything I could think of. I placed the file in my Products folder and In my resource folder and included (ResourcePath + "File.cfg.txt") and neither worked. I'd appreciate it if someone could tell me what I'm missing and where to put this file to read it in. Again I'm using Xcode and the SFML library with it.
Keys.cfg.txt
Window_close 0:0
Fullscreen_toggle 5:89
Move 9:0 24:38
/////////////////////////////////////////
.CPP
#include "EventManager.h"
using namespace std;
EventManager::EventManager(): m_hasFocus(true){ LoadBindings(); }
EventManager::~EventManager(){
for (auto &itr : m_bindings){
delete itr.second;
itr.second = nullptr;
}
}
bool EventManager::AddBinding(Binding *l_binding){
if (m_bindings.find(l_binding->m_name) != m_bindings.end())
return false;
return m_bindings.emplace(l_binding->m_name, l_binding).second;
}
bool EventManager::RemoveBinding(std::string l_name){
auto itr = m_bindings.find(l_name);
if (itr == m_bindings.end()){ return false; }
delete itr->second;
m_bindings.erase(itr);
return true;
}
void EventManager::SetFocus(const bool& l_focus){ m_hasFocus = l_focus; }
void EventManager::HandleEvent(sf::Event& l_event){
// Handling SFML events.
for (auto &b_itr : m_bindings){
Binding* bind = b_itr.second;
for (auto &e_itr : bind->m_events){
EventType sfmlEvent = (EventType)l_event.type;
if (e_itr.first != sfmlEvent){ continue; }
if (sfmlEvent == EventType::KeyDown || sfmlEvent == EventType::KeyUp){
if (e_itr.second.m_code == l_event.key.code){
// Matching event/keystroke.
// Increase count.
if (bind->m_details.m_keyCode != -1){
bind->m_details.m_keyCode = e_itr.second.m_code;
}
++(bind->c);
break;
}
} else if (sfmlEvent == EventType::MButtonDown || sfmlEvent == EventType::MButtonUp){
if (e_itr.second.m_code == l_event.mouseButton.button){
// Matching event/keystroke.
// Increase count.
bind->m_details.m_mouse.x = l_event.mouseButton.x;
bind->m_details.m_mouse.y = l_event.mouseButton.y;
if (bind->m_details.m_keyCode != -1){
bind->m_details.m_keyCode = e_itr.second.m_code;
}
++(bind->c);
break;
}
} else {
// No need for additional checking.
if (sfmlEvent == EventType::MouseWheel){
bind->m_details.m_mouseWheelDelta = l_event.mouseWheel.delta;
} else if (sfmlEvent == EventType::WindowResized){
bind->m_details.m_size.x = l_event.size.width;
bind->m_details.m_size.y = l_event.size.height;
} else if (sfmlEvent == EventType::TextEntered){
bind->m_details.m_textEntered = l_event.text.unicode;
}
++(bind->c);
}
}
}
}
void EventManager::Update(){
if (!m_hasFocus){ return; }
for (auto &b_itr : m_bindings){
Binding* bind = b_itr.second;
for (auto &e_itr : bind->m_events){
switch (e_itr.first){
case(EventType::Keyboard) :
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key(e_itr.second.m_code))){
if (bind->m_details.m_keyCode != -1){
bind->m_details.m_keyCode = e_itr.second.m_code;
}
++(bind->c);
}
break;
case(EventType::Mouse) :
if (sf::Mouse::isButtonPressed(sf::Mouse::Button(e_itr.second.m_code))){
if (bind->m_details.m_keyCode != -1){
bind->m_details.m_keyCode = e_itr.second.m_code;
}
++(bind->c);
}
break;
case(EventType::Joystick) :
// Up for expansion.
break;
default:
break;
}
}
if (bind->m_events.size() == bind->c){
auto callItr = m_callbacks.find(bind->m_name);
if(callItr != m_callbacks.end()){
callItr->second(&bind->m_details);
}
}
bind->c = 0;
bind->m_details.Clear();
}
}
void EventManager::LoadBindings(){
std::string delimiter = ":";
std::ifstream bindings;
bindings.open("keys.cfg");
if (!bindings.is_open()){ std::cout << "! Failed loading keys.cfg." << std::endl; return; }
std::string line;
while (std::getline(bindings, line)){
std::stringstream keystream(line);
std::string callbackName;
keystream >> callbackName;
Binding* bind = new Binding(callbackName);
while (!keystream.eof()){
std::string keyval;
keystream >> keyval;
int start = 0;
int end = keyval.find(delimiter);
if (end == std::string::npos){ delete bind; bind = nullptr; break; }
EventType type = EventType(stoi(keyval.substr(start, end - start)));
int code = stoi(keyval.substr(end + delimiter.length(),
keyval.find(delimiter, end + delimiter.length())));
EventInfo eventInfo;
eventInfo.m_code = code;
bind->BindEvent(type, eventInfo);
}
if (!AddBinding(bind)){ delete bind; }
bind = nullptr;
}
bindings.close();
}
The problem is that you have to copy the respective files in the bundle, and that only objective-c can provide you the full name of the file on the destination device.
To overcome this, make a .mm-file and place a c++ trampoline function in there, which gives you the full path (see code below).
One pitfall can be that you have to make sure that the config- and text files like "keys.cfg" are actually copied into the bundle. Select the respective file in the project and open the property inspector; make sure that - the respective target in "Target Membership" is checked.
// File: myFileNameProvider.mm
#import <Foundation/Foundation.h>
#include <iostream>
std::string GetTextureFilename(const char *name)
{
NSString *nameAsNSString = [NSString stringWithUTF8String:name];
NSString *fullName = [[NSBundle mainBundle]
pathForResource:nameAsNSString ofType: nil];
if (fullName)
return std::string([fullName UTF8String]);
else
return "";
}
Then, in your CPP-code, declare the signature of std::string GetTextureFilename(const char *name), and before opening the file get the full path by calling it:
// MyCPPFile.cpp
#include <iostream>
#include <fstream>
// declaration:
std::string GetTextureFilename(const char *name);
void myC_Func {
std::string fullPath = GetTextureFilename("keys.cfg");
std::ifstream bindings;
bindings.open(fullPath.c_str());
if (!bindings.is_open()) {
std::cout << "! Failed loading keys.cfg." << std::endl;
}
...
}