91 lines
3.6 KiB
C++
91 lines
3.6 KiB
C++
// mars::parse -- reader for the engine's brace-block key/value data format.
|
|
//
|
|
// This one grammar carries almost every catalog the game ships: *.weapon,
|
|
// *.shipsection, *.tech, *.combat, *.def, *.script and the block-form *.txt
|
|
// files (scenarios, tutorial, credits, system names, sky defs, ...).
|
|
//
|
|
// body := (block | pair | item)*
|
|
// block := NAME '{' body '}'
|
|
// pair := NAME value
|
|
// item := QUOTED | NAME (a NAME directly before '}' or EOF)
|
|
// value := QUOTED | BAREWORD
|
|
// comment := '//' ... end of line
|
|
//
|
|
// The tokenizer is whitespace-driven, not line-based: a block may open on the
|
|
// same line as a preceding pair, a name may sit on the same line as its brace,
|
|
// and a quoted string may span lines. There is no escape syntax -- backslashes
|
|
// and '//' inside quotes are literal. Bytes pass through untouched (cp1252).
|
|
//
|
|
// Leniency (the shipped data needs both; the engine accepts them):
|
|
// * end of file closes every open block -> Diagnostic in Document::warnings
|
|
// * a stray '}' at top level is ignored -> Diagnostic in Document::warnings
|
|
// With Options::strict both become errors. Three things are always errors:
|
|
// an unterminated quoted string, a '{' with no name before it, and nothing else.
|
|
#pragma once
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
#include "result.h"
|
|
|
|
namespace mars::parse {
|
|
|
|
struct Node;
|
|
|
|
// One thing inside a block, in file order.
|
|
struct Entry {
|
|
enum class Kind { Pair, Item, Block };
|
|
|
|
Kind kind = Kind::Pair;
|
|
std::string key; // Pair/Block: the name as written (original case). Item: empty.
|
|
std::string value; // Pair: the value text; Item: the item text. Block: empty.
|
|
bool quoted = false; // value/item was written inside double quotes
|
|
std::unique_ptr<Node> block; // Block only
|
|
int line = 0; // 1-based line of the key (or of the item)
|
|
|
|
Entry();
|
|
Entry(Entry&&) noexcept;
|
|
Entry& operator=(Entry&&) noexcept;
|
|
~Entry();
|
|
Entry(const Entry&) = delete;
|
|
Entry& operator=(const Entry&) = delete;
|
|
|
|
bool is_pair() const { return kind == Kind::Pair; }
|
|
bool is_item() const { return kind == Kind::Item; }
|
|
bool is_block() const { return kind == Kind::Block; }
|
|
};
|
|
|
|
// A named block (or the document root, whose name is empty).
|
|
struct Node {
|
|
std::string name;
|
|
std::vector<Entry> entries;
|
|
int line = 0; // line of the opening brace (0 for the root)
|
|
|
|
// Lookups are case-insensitive on the key and see pairs *and* blocks
|
|
// (the data uses `option X` and `option { ... }` under one key).
|
|
const Entry* first(std::string_view key) const;
|
|
std::vector<const Entry*> all(std::string_view key) const;
|
|
|
|
// Convenience filters. `first_value` returns the first *pair* under the key.
|
|
const std::string* first_value(std::string_view key) const;
|
|
std::vector<std::string_view> values(std::string_view key) const;
|
|
const Node* first_block(std::string_view key) const;
|
|
std::vector<const Node*> blocks(std::string_view key) const;
|
|
std::vector<std::string_view> items() const; // bare quoted / trailing barewords
|
|
bool has(std::string_view key) const { return first(key) != nullptr; }
|
|
};
|
|
|
|
struct Document {
|
|
Node root; // the file's top level is itself a body
|
|
std::vector<Diagnostic> warnings; // lenient recoveries taken
|
|
};
|
|
|
|
struct Options {
|
|
bool strict = false; // reject files the engine would repair
|
|
};
|
|
|
|
Result<Document> parse_blocks(std::string_view text, Options options = {});
|
|
|
|
} // namespace mars::parse
|