// 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 #include #include #include #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 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 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 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 values(std::string_view key) const; const Node* first_block(std::string_view key) const; std::vector blocks(std::string_view key) const; std::vector 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 warnings; // lenient recoveries taken }; struct Options { bool strict = false; // reject files the engine would repair }; Result parse_blocks(std::string_view text, Options options = {}); } // namespace mars::parse