Initial commit

This commit is contained in:
kayomn 2023-07-06 00:51:15 +01:00
commit 174313fe12
9 changed files with 614 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
lml_testbed

16
.vscode/c_cpp_properties.json vendored Normal file
View File

@ -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
}

13
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "LML",
"type": "gdb",
"request": "launch",
"target": "${workspaceRoot}/lml_testbed",
"cwd": "${workspaceRoot}/",
"valuesFormatting": "parseText"
}
]
}

54
.vscode/settings.json vendored Normal file
View File

@ -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"
}
}

3
build.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
clang++ lml.cpp main.cpp -g --std=c++20 -o lml_testbed

386
lml.cpp Normal file
View File

@ -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');
}

84
lml.hpp Normal file
View File

@ -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;
};
}

31
main.cpp Normal file
View File

@ -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;
}
}
}

26
test.lml Normal file
View File

@ -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
}
}
}