我为C++编写了一个JSON解析器。它不是特别快,有效率或优雅。我想要改变这一点,首先我希望代码更优雅。我如何改进它,并消除所有的代码气味?
旁白:我一生中从未上过计算机科学课程,我不知道这与我试图实现的.i.e ( LL1解析器)的理论模型有多大的偏差。也许这就是为什么它感觉如此无趣。
头文件如下所示。
#pragma once
#include <string>
#include <vector>
#include <map>
#include <variant>
#include <algorithm>
#include <fstream>
#include <stack>
// Debugging
#include <iostream>
// Types to store JSON ouput
struct jlist;
struct jobject;
using json_value = std::variant<int, float, bool, std::string, jlist, jobject>;
struct jlist {
std::vector<json_value> vector_value;
json_value & operator [](int index) {
return vector_value[index];
}
void push_back(json_value & value) {
vector_value.push_back(value);
}
};
struct jobject {
std::map<std::string, json_value> map_value;
json_value & operator [](std::string key) {
return map_value[key];
}
void insert(json_value & key, json_value & value) {
map_value.insert( { std::get<std::string>(key), value } );
}
};
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);
};
执行情况如下
#include "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);
}
}
这样做的目的是让lexer
在每个分隔符处中断,并将生成的所有令牌放入一个向量中。然后,这个名为scan
的载体可以被循环通过。在这个循环的每一次迭代中,都会运行parser
。通常,这会读取堆栈s
的顶部,并确定方括号/大括号是打开还是关闭,还是达到了终端值。如果一个括号/大括号正在打开,则生成一个新的jobject
或jlist
,并将其放置到新的堆栈s_value
中;如果到达终端值,则运行fsm
(有限状态机),并确定值的类型并将其放在s_value
之上,如果到达逗号或结束括号,则从堆栈中移出适当的值,并将来自s_value
的元素插入到相应的容器中。
这个意大利面中最大的肉丸是如何调用JSON树中的元素。
std::cout << std::get<bool>(std::get<jobject>(std::get<jobject>(std::get<jlist>(root)[6])["input"])["bool"]);
嵌套的std::get
调用似乎完全错误,我不确定它们是否可以合并到operator []
中。
为了完整起见,这是解析的JSON文件,因此上面的调用将输出1。
[
{
"libraries":[
"terminal",
"binary"
]
,
"functions":[
"terminal-basic",
"binary-basic"
]
}
,
{
"name":"addition",
"type":"binary-basic",
"function":"add_float",
"input":{
"float" : 2.0f
},
"output":"float",
"max-number":2
}
,
{
"name":"exponent",
"type":"binary-basic",
"function":"exponent_float",
"input":{
"float":2.0f
},
"output":"float",
"max-number":2
}
,
{
"name":"exponent",
"type":"binary-basic",
"function":"exponent_float",
"input":{
"float":2.0f,
"int":1
},
"output":"float",
"max-number":1
}
,
{
"name":"constant_1",
"type":"terminal-basic",
"function":"non_random_constant",
"value":0.5f,
"input":{ },
"output":"float",
"max-number":3
}
,
{
"name":"constant_2",
"type":"terminal-basic",
"function":"non_random_constant",
"value":2.0f,
"input":{ },
"output":"float",
"max-number":3
}
,
{
"name":"constant_3",
"type":"terminal-basic",
"function":"non_random_constant",
"value":true,
"input":{
"bool":true
},
"output":"bool",
"max-number":1
}
]
我怎样才能改进我所拥有的?
发布于 2019-11-25 15:02:12
我的主要问题是首先将整个流转换为令牌。然后解析令牌。这个可能会很贵。通常,您将解析(并推送)足够多的标记来理解要解释的下一部分。然后弹出你可以转换的下一部分。
我不喜欢有两种不同类型的令牌状态的方式:
stack_conversion = { { "[", list_open }, { "]", list_close }, { "{", object_open }, { "}", object_close }, { ":", colon }, { ",", comma }, { "buffer", buffer } };
....
enum state { int_value, float_value, bool_value, string_value, default_value, bad_state};
我将有一个所有标记的列表(JSON中没有那么多标记)。
{, }, [, ], :, ',', null, true, false, number(int), number(float), string
使用lex和yacc (或者实际上有更多的现代对等物)、flex和bison可能是更好的编写方法。为了实现这一点,已经对这些工具进行了大量的研究,您可以在大约20行代码中指定一个json解析器。
这不像你想的那样。
template<typename ... T>
bool JSONParser::multiComparision(const char scope, T ... args) {
return (scope == (args || ...));
}
这就扩大了
JSONParser::multiComparision('a', '1', '2', '3');
=>
return ('a' == ('1' || '2' || '3'));
我不认为这是你想要的。
发布于 2019-11-25 19:15:28
出于测试目的,我创建了一个main()
。我使用VisualStudio2019进行测试,并使用C++17。注意,发布的代码没有在C++11中编译。
#include <iostream>
#include <string>
#include <cstdlib>
#include "JSONParser.h"
int main(int argc, char* argv[])
{
std::string jsonFile;
if (argc > 1)
{
jsonFile = argv[1];
}
else
{
std::cout << "Please enter a JSON file name." << std::endl;
std::cin >> jsonFile;
}
JSONParser jsonParser;
try
{
jsonParser.parseFile(jsonFile);
}
catch (std::runtime_error ex)
{
std::cerr << ex.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
我在测试过程中发现了几个可能的问题,有些问题在这里没有列出,因为我没有足够的时间进行调试。
没有测试是否找到包含JSON的输入文件,这会导致程序异常终止。由于用户必须指示要在某个地方打开哪个文件,因此需要进行测试,以确定是否可以打开该文件,以及是否可以从中读取该文件。当没有输入文件时,函数std::vector<std::string> JSONParser::lexer(std::string fileName)
返回一个空字符串列表,这将导致程序在处理空列表时终止,而不是在输入本身中终止。
lexor
的一个可能的替代实现是:
std::vector<std::string> JSONParser::lexer(std::string fileName) {
char piece;
std::string capture = "";
std::string conversion;
std::vector<std::string> capture_list;
std::ifstream configfile(fileName);
piece = configfile.get();
if (configfile.bad() || configfile.fail())
{
std::string emsg("Can't read json from file: ");
emsg += fileName;
throw std::runtime_error(emsg);
}
while (configfile.good()) {
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;
}
piece = configfile.get();
}
configfile.close();
return capture_list;
}
注意lexor
__输入的变化: ifstream configfile仅在函数lexor
中使用,所以最好在lexor
__的主体中实例化configfile变量。这还允许通过传递文件名来提供更好的错误消息。
void JSONParser::fsm(std::string value)
中有几个if语句可能无法返回正确的结果,而不是使用logical
操作符&&
和||
,而是使用了bit操作符|
和&
。这可能导致使用解析器的程序在应该通过时失败,或者在应该失败时传递。还应该注意的是,需要维护代码的任何人都不清楚函数名fsm
。
快速词法分析器(如lex或flex生成的词法分析器)通常使用表作为状态机实现。解析器通常一次接受词法分析器中的单个令牌。解析器生成器(如YACC或野牛 )生成下推自动机,它作为状态机使用与堆栈耦合的表。为了提高性能,最好以这种方式实现lexer和解析器。
https://codereview.stackexchange.com/questions/232917
复制相似问题