[wpiutil, ntcore] Add structured data support (#5391)

This adds support for two serialization formats for complex data types:

- Protobuf for complex objects with variable length internals that need forward and backward wire compatibility (lower speed, more flexible)
- Raw struct (ByteBuffer-style) for fixed-length objects (higher speed, less flexible)

Deserialization can be done either by creating a new object (for immutable objects) or overwriting the contents of an existing object (for mutable objects).

Implementing classes should provide inner classes that implement the Protobuf or Struct interface (in Java) or specialize the wpi::Protobuf or wpi::Struct struct (in C++). It is possible for classes to implement both. If the class itself does not implement serialization, it's possible for third parties/users to provide an implementation instead.

Uses the Google protobuf implementation for C++ and the QuickBuffers alternative protobuf implementation for Java.
This commit is contained in:
Peter Johnson
2023-10-19 21:41:47 -07:00
committed by GitHub
parent ecb7cfa9ef
commit cf54d9ccb7
133 changed files with 13506 additions and 90 deletions

View File

@@ -0,0 +1,186 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <stdint.h>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace wpi::structparser {
/**
* A lexed raw struct schema token.
*/
struct Token {
enum Kind {
kUnknown,
kInteger,
kIdentifier,
kLeftBracket,
kRightBracket,
kLeftBrace,
kRightBrace,
kColon,
kSemicolon,
kComma,
kEquals,
kEndOfInput,
};
Token() = default;
Token(Kind kind, std::string_view text) : kind{kind}, text{text} {}
bool Is(Kind k) const { return kind == k; }
Kind kind = kUnknown;
std::string_view text;
};
std::string_view ToString(Token::Kind kind);
/**
* Raw struct schema lexer.
*/
class Lexer {
public:
/**
* Construct a raw struct schema lexer.
*
* @param in schema
*/
explicit Lexer(std::string_view in) : m_in{in} {}
/**
* Gets the next token.
*
* @return Token
*/
[[nodiscard]]
Token Scan();
/**
* Gets the starting position of the last lexed token.
*
* @return position (0 = first character)
*/
size_t GetPosition() const { return m_tokenStart; }
private:
Token ScanInteger();
Token ScanIdentifier();
void Get() {
if (m_pos < m_in.size()) {
[[likely]] m_current = m_in[m_pos];
} else {
m_current = -1;
}
++m_pos;
}
void Unget() {
if (m_pos > 0) {
[[likely]] m_pos--;
if (m_pos < m_in.size()) {
[[likely]] m_current = m_in[m_pos];
} else {
m_current = -1;
}
} else {
m_current = -1;
}
}
Token MakeToken(Token::Kind kind) {
return {kind, m_in.substr(m_tokenStart, m_pos - m_tokenStart)};
}
std::string_view m_in;
int m_current = -1;
size_t m_tokenStart = 0;
size_t m_pos = 0;
};
/**
* Raw struct set of enumerated values.
*/
using EnumValues = std::vector<std::pair<std::string, int64_t>>;
/**
* Raw struct schema declaration.
*/
struct ParsedDeclaration {
std::string typeString;
std::string name;
EnumValues enumValues;
size_t arraySize = 1;
unsigned int bitWidth = 0;
};
/**
* Raw struct schema.
*/
struct ParsedSchema {
std::vector<ParsedDeclaration> declarations;
};
/**
* Raw struct schema parser.
*/
class Parser {
public:
/**
* Construct a raw struct schema parser.
*
* @param in schema
*/
explicit Parser(std::string_view in) : m_lexer{in} {}
/**
* Parses the schema.
*
* @param[out] out parsed schema object
* @return true on success, false on failure (use GetError() to get error)
*/
[[nodiscard]]
bool Parse(ParsedSchema* out);
/**
* Gets the parser error if one occurred.
*
* @return parser error; blank if no error occurred
*/
const std::string& GetError() const { return m_error; }
private:
[[nodiscard]]
bool ParseDeclaration(ParsedDeclaration* out);
[[nodiscard]]
bool ParseEnum(EnumValues* out);
Token::Kind GetNextToken() {
m_token = m_lexer.Scan();
return m_token.kind;
}
[[nodiscard]]
bool Expect(Token::Kind kind) {
if (m_token.Is(kind)) {
[[likely]] return true;
}
FailExpect(kind);
return false;
}
void FailExpect(Token::Kind desired);
void Fail(std::string_view msg);
Lexer m_lexer;
Token m_token;
std::string m_error;
};
} // namespace wpi::structparser