Еще один синтаксический анализатор C ++ JSON

Это основа для парсера JSON, который я собрал вчера вечером.

Любые комментарии оценены.

  • JsonLexer.l: прерывает ввод в лексемы
  • JsonParser.y: понимает синтаксис языка
  • JsonParser.h: файл заголовка для приведения его в порядок
  • main.cpp: Проверить жгут проводов, чтобы он мог быть протестирован.
  • Makefile: скрипт Sinmple build.

Тестирование:

echo '{ "Plop": "Test" }' | ./test

JsonLexer.l

%option c++
%option noyywrap

%{
#define  IN_LEXER
#include "JsonParser.tab.hpp"
%}

DIGIT           [0-9]
DIGIT1          [1-9]
INTNUM          {DIGIT1}{DIGIT}*
FRACT           "."{DIGIT}+
FLOAT           ({INTNUM}|0){FRACT}?
EXP             [eE][+-]?{DIGIT}+
NUMBER          -?{FLOAT}{EXP}?

UNICODE         \\u[A-Fa-f0-9]{4}
ESCAPECHAR      \\["\\/bfnrt]
CHAR            [^"\\]|{ESCAPECHAR}|{UNICODE}
STRING          \"{CHAR}*\"

WHITESPACE      [ \t\n]


%%

\{              {LOG("LEX({)");     return '{';}
\}              {LOG("LEX(})");     return '}';}
\[              {LOG("LEX([)");     return '[';}
\]              {LOG("LEX(])");     return ']';}
,               {LOG("LEX(,)");     return ',';}
:               {LOG("LEX(:)");     return ':';}
true            {LOG("LEX(true)");  return yy::JsonParser::token::JSON_TRUE;}
false           {LOG("LEX(false)"); return yy::JsonParser::token::JSON_FALSE;}
null            {LOG("LEX(null)");  return yy::JsonParser::token::JSON_NULL;}
{STRING}        {LOG("LEX(String)");return yy::JsonParser::token::JSON_STRING;}
{NUMBER}        {LOG("LEX(Number)");return yy::JsonParser::token::JSON_NUMBER;}

{WHITESPACE}    {/*IGNORE*/}

%%

JsonParser.y

%skeleton "lalr1.cc"
%require "2.1a"
%defines
%define "parser_class_name" "JsonParser"

%{

#include "JsonParser.h"
#include <stdexcept>

%}

%parse-param {FlexLexer& lexer}
%lex-param   {FlexLexer& lexer}

%token  JSON_STRING
%token  JSON_NUMBER
%token  JSON_TRUE
%token  JSON_FALSE
%token  JSON_NULL


%%

JsonObject              :   JsonMap                                 {LOG("JsonObject: JsonMap");}
                        |   JsonArray                               {LOG("JsonObject: JsonArray");}

JsonMap                 :   '{' JsonMapValueListOpt '}'             {LOG("JsonMap: { JsonMapValueListOpt }");}
JsonMapValueListOpt     :                                           {LOG("JsonMapValueListOpt: EMPTY");}
                        |   JsonMapValueList                        {LOG("JsonMapValueListOpt: JsonMapValueList");}
JsonMapValueList        :   JsonMapValue                            {LOG("JsonMapValueList: JsonMapValue");}
                        |   JsonMapValueList ',' JsonMapValue       {LOG("JsonMapValueList: JsonMapValueList , JsonMapValue");}
JsonMapValue            :   JSON_STRING ':' JsonValue               {LOG("JsonMapValue: JSON_STRING : JsonValue");}    

JsonArray               :   '[' JsonArrayValueListOpt ']'           {LOG("JsonArray: [ JsonArrayValueListOpt ]");}
JsonArrayValueListOpt   :                                           {LOG("JsonArrayValueListOpt: EMPTY");}
                        |   JsonArrayValueList                      {LOG("JsonArrayValueListOpt: JsonArrayValueList");}
JsonArrayValueList      :   JsonValue                               {LOG("JsonArrayValueList: JsonValue");}
                        |   JsonArrayValueList ',' JsonValue        {LOG("JsonArrayValueList: JsonArrayValueList , JsonValue");}

JsonValue               :   JsonMap                                 {LOG("JsonValue: JsonMap");}
                        |   JsonArray                               {LOG("JsonValue: JsonArray");}
                        |   JSON_STRING                             {LOG("JsonValue: JSON_STRING");}
                        |   JSON_NUMBER                             {LOG("JsonValue: JSON_NUMBER");}
                        |   JSON_TRUE                               {LOG("JsonValue: JSON_TRUE");}
                        |   JSON_FALSE                              {LOG("JsonValue: JSON_FALSE");}
                        |   JSON_NULL                               {LOG("JsonValue: JSON_NULL");}


%%

int yylex(int*, FlexLexer& lexer)
{
    return lexer.yylex();
}

void yy::JsonParser::error(yy::location const&, std::string const& msg)
{
    throw std::runtime_error(msg);
}

main.cpp

#include "JsonParser.tab.hpp"
#include <iostream>

int main()
{
    try
    {
        yyFlexLexer         lexer(&std::cin, &std::cout);
        yy::JsonParser      parser(lexer);

        std::cout << (parser.parse() == 0 ? "OK" : "FAIL") << "\n";
    }
    catch(std::exception const& e)
    {
        std::cout << "Exception: " << e.what() << "\n";
    }
}

JsonParser.h

#ifndef THORSANVIL_JSON_PARSER_H
#define THORSANVIL_JSON_PARSER_H

#ifndef IN_LEXER
#include <FlexLexer.h>
#endif

int yylex(int*, FlexLexer& lexer);

#ifdef DEBUG_LOG
#include <iostream>
#define LOG(x)      std::cout << x << "\n"
#else
#define LOG(x)      0 /*Empty Statement that will be optimized out*/
#endif

#endif

Makefile

YACC            = bison
LEX             = flex
CXX             = g++

CXXFLAGS        = -DDEBUG_LOG

TARGET          = json
LEX_SRC         = $(wildcard *.l)
YACC_SRC        = $(wildcard *.y)
CPP_SRC         = $(filter-out %.lex.cpp %.tab.cpp,$(wildcard *.cpp))
SRC             = $(patsubst %.y,%.tab.cpp,$(YACC_SRC)) $(patsubst %.l,%.lex.cpp,$(LEX_SRC)) $(CPP_SRC)
OBJ             = $(patsubst %.cpp,%.o,$(SRC))


all: $(OBJ)
    $(CXX) -o test $(OBJ) -lFl

clean:
    rm $(OBJ) $(patsubst %.y,%.tab.cpp,$(YACC_SRC)) $(patsubst %.l,%.lex.cpp,$(LEX_SRC))


$(TARGET):  $(OBJ)
    $(CXX)  -o $* $(OBJ)

.PRECIOUS: %.tab.cpp

%.tab.cpp: %.y
    $(YACC) -o [email protected] -d $<

.PRECIOUS: %.lex.cpp

%.lex.cpp: %.l
    $(LEX) -t $< > [email protected]
41 голос | спросил Martin York 6 Jpm1000000pmFri, 06 Jan 2012 21:26:31 +040012 2012, 21:26:31

1 ответ


16

Я некоторое время колебался, но решил, что есть несколько замечательных замечаний.

Первым и наиболее очевидным является то, что (по дизайну) это просто не делает много. Он может регистрировать типы операторов, встречающихся в JSON, которые вы ему предоставляете, но это все. Чтобы быть очень реальным, вы обычно хотели бы создать что-то, по крайней мере, смутно AST-like, сохраняя входные данные JSON в виде набора узлов с атрибутами, описывающими данные в узлах.

В качестве альтернативы вы можете создать какую-то структуру обратного стиля обратного вызова, в которой парсер вызывал определенную функцию при обнаружении каждого типа ввода, и клиентский код мог бы решить, как хранить данные (и данные для хранения).

Однако, поскольку он стоит прямо сейчас, большинство из них может привести к описанию общей структуры входного файла. Если это действительно все, что вам нужно, я думаю, стоит подумать, пытаетесь ли вы производить вывод для чтения людьми или машиной для дальнейшей обработки.

Если вы хотите получать данные, доступные для человека, я думаю, что я предпочел бы, чтобы обработка данных была сделана еще до того, как она была распечатана. Например, в настоящее время такой массив, как [1, 2, 3, 4], создает строку вывода для массива JSON, другую для списка значений и другую для каждого элемента в массиве.

Если я собираюсь прочитать файл, я бы предпочел, чтобы эти записи журнала сливались во что-то вроде Array (4 Numbers). Учитывая, что для этого необходимо проанализировать JSON, вероятно, достаточно указать «JSON» в одном месте на выходе, а не префикс каждой отдельной строки вывода с помощью «JSON». Короче говоря, поскольку он стоит прямо сейчас, это приводит к тому, что вывод является чрезвычайно многословным, что приводит к очень низкой плотности информации, поэтому читателю необходимо прочитать и переварить большую часть информации, чтобы понять даже относительно небольшой объем ввода - на самом деле, Я уверен, что обычно проще читать входной файл напрямую.

Если ваше основное намерение состоит в том, чтобы произвести вывод для компьютера для чтения для дальнейшей обработки, менее необходимо перейти к дополнительной работе для объединения информации, но все же полезно сохранить информацию компактной. Учитывая небольшое количество возможностей в файле JSON, я бы, вероятно, назначил одну букву каждой из строк, которые теперь может произвести парсер, и просто напишет их. В качестве альтернативы, здесь также можно коалесцировать, так что повторения одного и того же шаблона сигнализируются шаблоном (в скобках, если это более одной буквы), за которым следует число в скобках. Например, карта из четырех пар строк /чисел, за которыми следует массив из 6 чисел, может выглядеть примерно так: M(SN)[4]AN[6]. Это все еще достаточно читаемо для человека (при необходимости), и намного быстрее для анализатора на принимающей стороне, чтобы разобраться (не говоря уже о том, чтобы быть намного меньше данных для хранения, передачи и т. Д.) 1

Что касается стиля самого кода, у меня действительно есть только несколько комментариев, и довольно незначительные.

  1. Учитывая список альтернатив, таких как:

    production : 
               | Alternative1
               | Alternative2
    

    Я предпочитаю добавлять точку с запятой, чтобы сигнализировать конец списка:

    production : 
               | Alternative1
               | Alternative2
               ;
    
  2. Это в большом масштабе и может даже оказаться вне вашего контроля, но я нашел этот код:

    try
    {
        yyFlexLexer         lexer(&std::cin, &std::cout);
        yy::JsonParser      parser(lexer);
    
        std::cout << (parser.parse() == 0 ? "OK" : "FAIL") << "\n";
    }
    catch (std::exception const& e)
    {
        std::cout << "Exception: " << e.what() << "\n";
    }
    

    ... несколько уродливый. Объединение возвращаемых значений и исключений, подобных этому, и необходимость реагировать на них делает его скорее ... несоразмерным. Я бы предпочел увидеть один стиль, принятый повсюду, поэтому вы можете зависеть от отказа, который всегда сигнализируется путем выброса исключения, или иначе, что он всегда сигнализируется возвращаемым значением. Поскольку это прямо сейчас, мы не только закончили тем, что этот код был довольно уродливым, но и в итоге получилось довольно неравномерное сообщение об ошибках - ошибки, сообщаемые через исключение, могут иметь довольно подробные сообщения об ошибках, но те, которые сообщаются с помощью возвращаемого значения, идентичный (и, вероятно, бесполезный) «FAIL».


1. Я не пытался проработать все детали обеспечения того, чтобы вложенные структуры оставались недвусмысленными, но, по крайней мере, из-за этого это не кажется ужасно трудным.

ответил Jerry Coffin 29 J0000006Europe/Moscow 2014, 19:04:54

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132