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