Util: Add JSON lexical analyzer
This commit is contained in:
@@ -0,0 +1,254 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "util/json/lexer.h"
|
||||||
|
|
||||||
|
namespace Json {
|
||||||
|
|
||||||
|
Lexer::Lexer(const std::string& input)
|
||||||
|
: m_input(input)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Lexer::~Lexer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
void Lexer::analyze()
|
||||||
|
{
|
||||||
|
printf("---------\n");
|
||||||
|
printf("Input JSON:\n%s\n", m_input.c_str());
|
||||||
|
printf("---------\n");
|
||||||
|
printf("Lexing:\n");
|
||||||
|
|
||||||
|
while (m_index < m_input.length()) {
|
||||||
|
switch (peek()) {
|
||||||
|
case '{':
|
||||||
|
printf("Pushing -> BraceOpen: \"{\"\t%zu[%zu]\n", m_line, m_column);
|
||||||
|
m_tokens.push_back({ Token::Type::BraceOpen, m_line, m_column, "" });
|
||||||
|
break;
|
||||||
|
case '}':
|
||||||
|
printf("Pushing -> BraceClose: \"}\"\t%zu[%zu]\n", m_line, m_column);
|
||||||
|
m_tokens.push_back({ Token::Type::BraceClose, m_line, m_column, "" });
|
||||||
|
break;
|
||||||
|
case '[':
|
||||||
|
printf("Pushing -> BracketOpen: \"[\"\t%zu[%zu]\n", m_line, m_column);
|
||||||
|
m_tokens.push_back({ Token::Type::BracketOpen, m_line, m_column, "" });
|
||||||
|
break;
|
||||||
|
case ']':
|
||||||
|
printf("Pushing -> BracketClose: \"]\"\t%zu[%zu]\n", m_line, m_column);
|
||||||
|
m_tokens.push_back({ Token::Type::BracketClose, m_line, m_column, "" });
|
||||||
|
break;
|
||||||
|
case ':':
|
||||||
|
printf("Pushing -> Colon: \":\"\t%zu[%zu]\n", m_line, m_column);
|
||||||
|
m_tokens.push_back({ Token::Type::Colon, m_line, m_column, "" });
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
printf("Pushing -> Comma: \",\"\t%zu[%zu]\n", m_line, m_column);
|
||||||
|
m_tokens.push_back({ Token::Type::Comma, m_line, m_column, "" });
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
if (!getString()) {
|
||||||
|
// Error!
|
||||||
|
printf("Invalid JSON!\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '-':
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
if (!getNumber()) {
|
||||||
|
// Error!
|
||||||
|
printf("Invalid JSON!\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
case 'n':
|
||||||
|
case 't':
|
||||||
|
if (!getLiteral()) {
|
||||||
|
// Error!
|
||||||
|
printf("Invalid JSON!\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
case '\t':
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
if (peekNext() == '\n') { // CRLF \r\n
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_column = 0;
|
||||||
|
m_line++;
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
m_column = 0;
|
||||||
|
m_line++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Error!
|
||||||
|
printf("Invalid JSON!\n");
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_index++;
|
||||||
|
m_column++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
char Lexer::peek()
|
||||||
|
{
|
||||||
|
return m_input[m_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
char Lexer::peekNext()
|
||||||
|
{
|
||||||
|
return m_input[m_index + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
char Lexer::consume()
|
||||||
|
{
|
||||||
|
char character = peek();
|
||||||
|
m_index++;
|
||||||
|
m_column++;
|
||||||
|
return character;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Lexer::consumeSpecific(char character)
|
||||||
|
{
|
||||||
|
if (peek() != character) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_index++;
|
||||||
|
m_column++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Lexer::getString()
|
||||||
|
{
|
||||||
|
size_t column = m_column;
|
||||||
|
std::string symbol = "";
|
||||||
|
|
||||||
|
char character = consume();
|
||||||
|
for (;;) {
|
||||||
|
character = peek();
|
||||||
|
|
||||||
|
// TODO: Escape logic goes here
|
||||||
|
// ", \, /, b(ackspace), f(orm feed), l(ine feed), c(arriage return), t(ab), u(nicode) \u0021
|
||||||
|
|
||||||
|
if (character == '"') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_index++;
|
||||||
|
m_column++;
|
||||||
|
symbol += character;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Pushing -> String: \"%s\"\t%zu[%zu]\n", symbol.c_str(), m_line, column);
|
||||||
|
m_tokens.push_back({ Token::Type::String, m_line, column, symbol });
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Lexer::getNumber()
|
||||||
|
{
|
||||||
|
size_t index = m_index;
|
||||||
|
size_t column = m_column;
|
||||||
|
std::string symbol = "";
|
||||||
|
|
||||||
|
bool seenDot = false;
|
||||||
|
char character;
|
||||||
|
for (;;) {
|
||||||
|
character = peek();
|
||||||
|
|
||||||
|
// FIXME: Break on separator }], rather than valid number symbols to
|
||||||
|
// get the entire thing, resulting in better error handling
|
||||||
|
// FIXME: e/E and exponent are also valid characters (?)
|
||||||
|
if (character != 45 // -
|
||||||
|
&& character != 46 // .
|
||||||
|
&& (character < 48 || character > 57)) { // 0-9
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail if '.' is used more than once
|
||||||
|
if (seenDot == true && character == 46) { // .
|
||||||
|
m_index = index;
|
||||||
|
m_column = column;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (character == 46) { // .
|
||||||
|
seenDot = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_index++;
|
||||||
|
m_column++;
|
||||||
|
symbol += character;
|
||||||
|
}
|
||||||
|
m_index--;
|
||||||
|
m_column--;
|
||||||
|
|
||||||
|
printf("Pushing -> Number: \"%s\"\t%zu[%zu]\n", symbol.c_str(), m_line, column);
|
||||||
|
m_tokens.push_back({ Token::Type::Number, m_line, column, symbol });
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Lexer::getLiteral()
|
||||||
|
{
|
||||||
|
size_t index = m_index;
|
||||||
|
size_t column = m_column;
|
||||||
|
|
||||||
|
std::string symbol = "";
|
||||||
|
|
||||||
|
char character;
|
||||||
|
for (;;) {
|
||||||
|
character = peek();
|
||||||
|
|
||||||
|
// Literals can only contain lower-case letters
|
||||||
|
if (character < 97 || character > 122) { // a-z
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_index++;
|
||||||
|
m_column++;
|
||||||
|
symbol += character;
|
||||||
|
}
|
||||||
|
m_index--;
|
||||||
|
m_column--;
|
||||||
|
|
||||||
|
// Literal name validation
|
||||||
|
if (symbol != "false" && symbol != "null" && symbol != "true") {
|
||||||
|
m_index = index;
|
||||||
|
m_column = column;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Pushing -> Literal: \"%s\"\t%zu[%zu]\n", symbol.c_str(), m_line, column);
|
||||||
|
m_tokens.push_back({ Token::Type::Literal, m_line, column, symbol });
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Json
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Riyyi
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef JSON_LEXER_H
|
||||||
|
#define JSON_LEXER_H
|
||||||
|
|
||||||
|
// The JavaScript Object Notation (JSON) Data Interchange Format
|
||||||
|
// home/rick/code/cpp/manafiles/ https://www.rfc-editor.org/rfc/pdfrfc/rfc8259.txt.pdf
|
||||||
|
|
||||||
|
#include <cstddef> // size_t
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Json {
|
||||||
|
|
||||||
|
struct Token {
|
||||||
|
enum class Type {
|
||||||
|
None,
|
||||||
|
BraceOpen, // {
|
||||||
|
BraceClose, // }
|
||||||
|
BracketOpen, // [
|
||||||
|
BracketClose, // ]
|
||||||
|
Colon, // :
|
||||||
|
Comma, // ,
|
||||||
|
String, // "foobar"
|
||||||
|
Number, // 123.456
|
||||||
|
Literal, // false/null/true (case sensitive)
|
||||||
|
};
|
||||||
|
|
||||||
|
Type type { Type::None };
|
||||||
|
size_t line { 0 };
|
||||||
|
size_t column { 0 };
|
||||||
|
std::string symbol;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Lexical analyzer
|
||||||
|
class Lexer {
|
||||||
|
public:
|
||||||
|
Lexer(const std::string& input);
|
||||||
|
virtual ~Lexer();
|
||||||
|
|
||||||
|
void analyze();
|
||||||
|
|
||||||
|
const std::vector<Token>& tokens() const { return m_tokens; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
char peek();
|
||||||
|
char peekNext();
|
||||||
|
|
||||||
|
char consume();
|
||||||
|
bool consumeSpecific(char character);
|
||||||
|
|
||||||
|
bool getString();
|
||||||
|
bool getNumber();
|
||||||
|
bool getLiteral();
|
||||||
|
|
||||||
|
std::string m_input;
|
||||||
|
size_t m_index { 0 };
|
||||||
|
size_t m_column { 1 };
|
||||||
|
size_t m_line { 1 };
|
||||||
|
|
||||||
|
std::vector<Token> m_tokens;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Json
|
||||||
|
|
||||||
|
#endif // JSON_LEXER_H
|
||||||
Reference in New Issue
Block a user