Initial commit
This commit is contained in:
commit
174313fe12
|
@ -0,0 +1 @@
|
||||||
|
lml_testbed
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Linux",
|
||||||
|
"includePath": [
|
||||||
|
"${workspaceFolder}/**"
|
||||||
|
],
|
||||||
|
"defines": [],
|
||||||
|
"compilerPath": "/usr/sbin/clang",
|
||||||
|
"cStandard": "c17",
|
||||||
|
"cppStandard": "c++20",
|
||||||
|
"intelliSenseMode": "linux-clang-x64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 4
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "LML",
|
||||||
|
"type": "gdb",
|
||||||
|
"request": "launch",
|
||||||
|
"target": "${workspaceRoot}/lml_testbed",
|
||||||
|
"cwd": "${workspaceRoot}/",
|
||||||
|
"valuesFormatting": "parseText"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
{
|
||||||
|
"files.associations": {
|
||||||
|
"unordered_map": "cpp",
|
||||||
|
"variant": "cpp",
|
||||||
|
"array": "cpp",
|
||||||
|
"atomic": "cpp",
|
||||||
|
"bit": "cpp",
|
||||||
|
"*.tcc": "cpp",
|
||||||
|
"cctype": "cpp",
|
||||||
|
"clocale": "cpp",
|
||||||
|
"cmath": "cpp",
|
||||||
|
"compare": "cpp",
|
||||||
|
"concepts": "cpp",
|
||||||
|
"cstdarg": "cpp",
|
||||||
|
"cstddef": "cpp",
|
||||||
|
"cstdint": "cpp",
|
||||||
|
"cstdio": "cpp",
|
||||||
|
"cstdlib": "cpp",
|
||||||
|
"cwchar": "cpp",
|
||||||
|
"cwctype": "cpp",
|
||||||
|
"deque": "cpp",
|
||||||
|
"map": "cpp",
|
||||||
|
"string": "cpp",
|
||||||
|
"vector": "cpp",
|
||||||
|
"exception": "cpp",
|
||||||
|
"algorithm": "cpp",
|
||||||
|
"functional": "cpp",
|
||||||
|
"iterator": "cpp",
|
||||||
|
"memory": "cpp",
|
||||||
|
"memory_resource": "cpp",
|
||||||
|
"numeric": "cpp",
|
||||||
|
"optional": "cpp",
|
||||||
|
"random": "cpp",
|
||||||
|
"string_view": "cpp",
|
||||||
|
"system_error": "cpp",
|
||||||
|
"tuple": "cpp",
|
||||||
|
"type_traits": "cpp",
|
||||||
|
"utility": "cpp",
|
||||||
|
"initializer_list": "cpp",
|
||||||
|
"iosfwd": "cpp",
|
||||||
|
"limits": "cpp",
|
||||||
|
"new": "cpp",
|
||||||
|
"numbers": "cpp",
|
||||||
|
"ostream": "cpp",
|
||||||
|
"stdexcept": "cpp",
|
||||||
|
"streambuf": "cpp",
|
||||||
|
"cinttypes": "cpp",
|
||||||
|
"typeinfo": "cpp",
|
||||||
|
"span": "cpp",
|
||||||
|
"iostream": "cpp",
|
||||||
|
"istream": "cpp",
|
||||||
|
"fstream": "cpp"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
clang++ lml.cpp main.cpp -g --std=c++20 -o lml_testbed
|
|
@ -0,0 +1,386 @@
|
||||||
|
#include "lml.hpp"
|
||||||
|
|
||||||
|
using namespace Lml;
|
||||||
|
|
||||||
|
struct Token final
|
||||||
|
{
|
||||||
|
enum class Kind
|
||||||
|
{
|
||||||
|
Unexpected,
|
||||||
|
Newline,
|
||||||
|
Identifier,
|
||||||
|
Colon,
|
||||||
|
Comma,
|
||||||
|
BraceLeft,
|
||||||
|
BraceRight,
|
||||||
|
};
|
||||||
|
|
||||||
|
Kind kind {Kind::Unexpected};
|
||||||
|
|
||||||
|
std::string_view view {""};
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool isLmlSymbol(char c)
|
||||||
|
{
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case ':':
|
||||||
|
case '[':
|
||||||
|
case ']':
|
||||||
|
case '{':
|
||||||
|
case '}':
|
||||||
|
case ' ':
|
||||||
|
case ';':
|
||||||
|
case ',':
|
||||||
|
case '`':
|
||||||
|
case '\'':
|
||||||
|
case '\t':
|
||||||
|
case '\n':
|
||||||
|
case '"': return false;
|
||||||
|
|
||||||
|
default: return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Tokenizer final
|
||||||
|
{
|
||||||
|
std::string_view data {""};
|
||||||
|
|
||||||
|
std::optional<Token> Next()
|
||||||
|
{
|
||||||
|
while (!this->data.empty())
|
||||||
|
{
|
||||||
|
switch (this->data[0])
|
||||||
|
{
|
||||||
|
case ' ':
|
||||||
|
case '\t':
|
||||||
|
{
|
||||||
|
this->data = this->data.substr(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\n':
|
||||||
|
{
|
||||||
|
this->data = this->data.substr(1);
|
||||||
|
|
||||||
|
return Token
|
||||||
|
{
|
||||||
|
.kind {Token::Kind::Newline},
|
||||||
|
.view {"\n"},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '{':
|
||||||
|
{
|
||||||
|
this->data = this->data.substr(1);
|
||||||
|
|
||||||
|
return Token
|
||||||
|
{
|
||||||
|
.kind {Token::Kind::BraceLeft},
|
||||||
|
.view {"{"},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '}':
|
||||||
|
{
|
||||||
|
this->data = this->data.substr(1);
|
||||||
|
|
||||||
|
return Token
|
||||||
|
{
|
||||||
|
.kind {Token::Kind::BraceRight},
|
||||||
|
.view {"}"},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ':':
|
||||||
|
{
|
||||||
|
this->data = this->data.substr(1);
|
||||||
|
|
||||||
|
return Token
|
||||||
|
{
|
||||||
|
.kind {Token::Kind::Colon},
|
||||||
|
.view {":"},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ',':
|
||||||
|
{
|
||||||
|
this->data = this->data.substr(1);
|
||||||
|
|
||||||
|
return Token
|
||||||
|
{
|
||||||
|
.kind {Token::Kind::Comma},
|
||||||
|
.view {","},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
{
|
||||||
|
size_t const identifierEndIndex {this->data.find_first_of('"', 1)};
|
||||||
|
|
||||||
|
if (identifierEndIndex == std::string_view::npos)
|
||||||
|
{
|
||||||
|
this->data = this->data.substr(this->data.length() - 1, 0);
|
||||||
|
|
||||||
|
return Token
|
||||||
|
{
|
||||||
|
.kind {Token::Kind::Unexpected},
|
||||||
|
.view {""},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view const identifier = this->data.substr(1, identifierEndIndex - 1);
|
||||||
|
|
||||||
|
this->data = this->data.substr(identifierEndIndex + 1);
|
||||||
|
|
||||||
|
return Token
|
||||||
|
{
|
||||||
|
.kind {Token::Kind::Identifier},
|
||||||
|
.view {identifier},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
size_t identifierLength {0};
|
||||||
|
|
||||||
|
{
|
||||||
|
size_t dataLength {this->data.length()};
|
||||||
|
|
||||||
|
while (identifierLength < this->data.length() && isLmlSymbol(this->data[identifierLength]))
|
||||||
|
{
|
||||||
|
identifierLength += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (identifierLength == 0)
|
||||||
|
{
|
||||||
|
return Token
|
||||||
|
{
|
||||||
|
.kind {Token::Kind::Unexpected},
|
||||||
|
.view {""},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view const identifier {this->data.substr(0, identifierLength)};
|
||||||
|
|
||||||
|
this->data = this->data.substr(identifierLength);
|
||||||
|
|
||||||
|
return Token
|
||||||
|
{
|
||||||
|
.kind {Token::Kind::Identifier},
|
||||||
|
.view {identifier},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::optional<Scalar> ParseScalar(Tokenizer & tokenizer)
|
||||||
|
{
|
||||||
|
Scalar scalar {};
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
std::optional const elementToken {tokenizer.Next()};
|
||||||
|
|
||||||
|
if (elementToken == std::nullopt)
|
||||||
|
{
|
||||||
|
return scalar;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (elementToken->kind)
|
||||||
|
{
|
||||||
|
case Token::Kind::Newline: return scalar;
|
||||||
|
|
||||||
|
case Token::Kind::Identifier:
|
||||||
|
{
|
||||||
|
scalar.push_back(std::string{elementToken->view});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional const delimiterToken {tokenizer.Next()};
|
||||||
|
|
||||||
|
if (delimiterToken == std::nullopt)
|
||||||
|
{
|
||||||
|
return scalar;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (delimiterToken->kind)
|
||||||
|
{
|
||||||
|
case Token::Kind::Comma: continue;
|
||||||
|
case Token::Kind::Newline: return scalar;
|
||||||
|
default: return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ParseDocument(Tokenizer & tokenizer, Document & document, bool isRoot)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
std::optional const initialToken {tokenizer.Next()};
|
||||||
|
|
||||||
|
if (initialToken == std::nullopt)
|
||||||
|
{
|
||||||
|
return isRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (initialToken->kind)
|
||||||
|
{
|
||||||
|
case Token::Kind::Newline: continue;
|
||||||
|
case Token::Kind::Identifier: break;
|
||||||
|
case Token::Kind::BraceRight: return !isRoot;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional kindToken {tokenizer.Next()};
|
||||||
|
|
||||||
|
if (kindToken == std::nullopt)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kindToken->kind == Token::Kind::Colon)
|
||||||
|
{
|
||||||
|
std::optional const scalar {ParseScalar(tokenizer)};
|
||||||
|
|
||||||
|
if (scalar == std::nullopt)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.properties[std::string{initialToken->view}] = *scalar;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (kindToken->kind != Token::Kind::BraceLeft)
|
||||||
|
{
|
||||||
|
kindToken = tokenizer.Next();
|
||||||
|
|
||||||
|
if (kindToken == std::nullopt)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (kindToken->kind)
|
||||||
|
{
|
||||||
|
case Token::Kind::Newline: break;
|
||||||
|
case Token::Kind::BraceLeft: break;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional const beginSpaceToken {tokenizer.Next()};
|
||||||
|
|
||||||
|
if (beginSpaceToken == std::nullopt)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (beginSpaceToken->kind)
|
||||||
|
{
|
||||||
|
case Token::Kind::Newline:
|
||||||
|
{
|
||||||
|
Document elementDocument {};
|
||||||
|
|
||||||
|
if (!ParseDocument(tokenizer, elementDocument, false))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.elements.emplace_back(std::string{initialToken->view}, elementDocument);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Token::Kind::BraceRight:
|
||||||
|
{
|
||||||
|
document.elements.emplace_back(std::string{initialToken->view}, Document{});
|
||||||
|
|
||||||
|
std::optional const endSpaceToken {tokenizer.Next()};
|
||||||
|
|
||||||
|
if (endSpaceToken == std::nullopt)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (endSpaceToken->kind)
|
||||||
|
{
|
||||||
|
case Token::Kind::Newline: return true;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Lml::Document::Parse(std::string_view const & data)
|
||||||
|
{
|
||||||
|
Tokenizer tokenizer {data};
|
||||||
|
|
||||||
|
return ParseDocument(tokenizer, *this, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> Lml::Document::Property(std::string_view const & property)
|
||||||
|
{
|
||||||
|
if (PropertyMap::iterator found {this->properties.find(property)}; found != this->properties.end())
|
||||||
|
{
|
||||||
|
if (!found->second.empty())
|
||||||
|
{
|
||||||
|
return found->second[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lml::Document::SerializeTo(std::string & string, size_t indentation) const
|
||||||
|
{
|
||||||
|
for (auto const & [property, scalar] : this->properties)
|
||||||
|
{
|
||||||
|
string.append(indentation, '\t');
|
||||||
|
string.append(property);
|
||||||
|
string.append(": ");
|
||||||
|
|
||||||
|
if (!scalar.empty())
|
||||||
|
{
|
||||||
|
string.append(scalar[0]);
|
||||||
|
|
||||||
|
for (std::string const & scalarString : std::span<std::string const>{scalar.begin() + 1, scalar.end()})
|
||||||
|
{
|
||||||
|
string.append(", ");
|
||||||
|
string.append(scalarString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string.push_back('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto const & [element, document] : this->elements)
|
||||||
|
{
|
||||||
|
string.append(indentation, '\t');
|
||||||
|
string.append(element);
|
||||||
|
string.append(" {\n");
|
||||||
|
document.SerializeTo(string, indentation + 1);
|
||||||
|
string.append(indentation, '\t');
|
||||||
|
string.append("}\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
string.push_back('\n');
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
#include <optional>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <tuple>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Lml
|
||||||
|
{
|
||||||
|
///
|
||||||
|
/// Tree leaf composed of zero or more values.
|
||||||
|
///
|
||||||
|
using Scalar = std::vector<std::string>;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Content body capable of holding Scalars as properties and other child Documents as elements.
|
||||||
|
///
|
||||||
|
struct Document final
|
||||||
|
{
|
||||||
|
///
|
||||||
|
/// Custom hashing mechanism used by properties to support lookups with string types other than std::string.
|
||||||
|
///
|
||||||
|
struct PropertyHash
|
||||||
|
{
|
||||||
|
using HashType = std::hash<std::string_view>;
|
||||||
|
|
||||||
|
using is_transparent = void;
|
||||||
|
|
||||||
|
size_t operator()(const char* str) const
|
||||||
|
{
|
||||||
|
return HashType{}(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t operator()(std::string_view str) const
|
||||||
|
{
|
||||||
|
return HashType{}(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t operator()(std::string const& str) const
|
||||||
|
{
|
||||||
|
return HashType{}(str);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Map of property names and their associated Scalar.
|
||||||
|
///
|
||||||
|
using PropertyMap = std::unordered_map<std::string, Scalar, PropertyHash, std::equal_to<>>;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// List of associated element names and Documents.
|
||||||
|
///
|
||||||
|
using ElementList = std::vector<std::tuple<std::string, Document>>;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// All live property Scalars belonging to the Document.
|
||||||
|
///
|
||||||
|
PropertyMap properties {};
|
||||||
|
|
||||||
|
///
|
||||||
|
/// All live element Documents belonging to the Document.
|
||||||
|
///
|
||||||
|
ElementList elements {};
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Attempts to parse the provided data into the live Document object model, returning true if it was
|
||||||
|
/// successful, otherwise false.
|
||||||
|
///
|
||||||
|
bool Parse(std::string_view const & data);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Attempts to retrieve a single string value from a Scalar defined under the provided property name,
|
||||||
|
/// returning it or null if no such property exists.
|
||||||
|
///
|
||||||
|
std::optional<std::string> Property(std::string_view const & property);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Serializes the live Document object model to the provided string with the specified indentation level.
|
||||||
|
///
|
||||||
|
void SerializeTo(std::string & string, size_t indentation) const;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "lml.hpp"
|
||||||
|
|
||||||
|
int main(int argc, char const * const * argv)
|
||||||
|
{
|
||||||
|
Lml::Document document {};
|
||||||
|
|
||||||
|
{
|
||||||
|
std::ifstream testFile {"test.lml", std::ios::binary};
|
||||||
|
std::string testData {};
|
||||||
|
|
||||||
|
testFile.seekg(0, std::ios::end);
|
||||||
|
testData.resize(testFile.tellg(), 0);
|
||||||
|
testFile.seekg(0, std::ios::beg);
|
||||||
|
testFile.read(testData.data(), testData.size());
|
||||||
|
|
||||||
|
if (!document.Parse(testData))
|
||||||
|
{
|
||||||
|
std::cout << "Parse error";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
testData.clear();
|
||||||
|
document.SerializeTo(testData, 0);
|
||||||
|
|
||||||
|
std::cout << testData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
Styles {
|
||||||
|
Readout {
|
||||||
|
fontFamily: "Comic Sans.ttf"
|
||||||
|
labelSize: 2em
|
||||||
|
color: #ffffffff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Items {
|
||||||
|
Canvas {
|
||||||
|
rect: 0.85, 0.9, 0.95, 0.95
|
||||||
|
|
||||||
|
Sprite {
|
||||||
|
path: "hudtexture.dds"
|
||||||
|
position: 0, 0
|
||||||
|
stretch: 1, 1
|
||||||
|
}
|
||||||
|
|
||||||
|
SpeedLabel {
|
||||||
|
text: "Current Speed: $Speed"
|
||||||
|
position: 0.5, 0.5
|
||||||
|
alignment: mid, mid
|
||||||
|
styles: Readout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue