some parsing
This commit is contained in:
parent
e18cf2f52e
commit
391f04e4cb
2
.clangd
2
.clangd
@ -16,6 +16,8 @@ Diagnostics:
|
||||
- bugprone-easily-swappable-parameters
|
||||
- readability-convert-member-functions-to-static
|
||||
- bugprone-exception-escape
|
||||
- modernize-use-nodiscard
|
||||
- readability-else-after-return
|
||||
|
||||
CheckOptions:
|
||||
UnusedIncludes: Strict
|
||||
|
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@ -8,9 +8,9 @@
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug Browser",
|
||||
"program": "${workspaceFolder}/builddir/web-browser",
|
||||
"program": "${workspaceFolder}/builddir/browser",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
"cwd": "${workspaceFolder}/builddir"
|
||||
}
|
||||
]
|
||||
}
|
114
bong_grammar.txt
Normal file
114
bong_grammar.txt
Normal file
@ -0,0 +1,114 @@
|
||||
|
||||
top_level ->
|
||||
| element
|
||||
| value
|
||||
|
||||
element -> Name single_line_fields element_body:?
|
||||
|
||||
element_body -> "{" element_fields "}"
|
||||
|
||||
element_fields -> (_ element_field (__linebreak__ element_field):*):? __linebreak__
|
||||
|
||||
element_field ->
|
||||
| element
|
||||
| Id
|
||||
| Class
|
||||
| element_property
|
||||
| value
|
||||
|
||||
single_line_fields ->
|
||||
(first_single_line_field
|
||||
(__singleline__ single_line_field):*):? __singleline__
|
||||
|
||||
first_single_line_field ->
|
||||
| Id
|
||||
| Class
|
||||
| __singleline__ element_property
|
||||
| __singleline__ single_line_value
|
||||
|
||||
single_line_field ->
|
||||
| Id
|
||||
| Class
|
||||
| element_property
|
||||
| single_line_value
|
||||
|
||||
element_property -> Name _ ("=" | ":") _ value
|
||||
|
||||
single_line_value ->
|
||||
| array
|
||||
| Int
|
||||
| Float
|
||||
| String
|
||||
| bool
|
||||
| Null
|
||||
|
||||
value ->
|
||||
| object
|
||||
| array
|
||||
| Int
|
||||
| Float
|
||||
| String
|
||||
| bool
|
||||
| Null
|
||||
|
||||
object -> "{" object_properties "}"
|
||||
|
||||
object_properties -> (_ object_property (_ "," _ object_property):* _ ",":?):? _
|
||||
|
||||
object_property -> (Name | String) _ ("=" | ":") _ value
|
||||
|
||||
array -> "[" array_values "]"
|
||||
|
||||
array_values -> (_ value (_ "," _ value):* _ ",":?):? _
|
||||
|
||||
bool -> True | False
|
||||
|
||||
__singleline__ -> mandatory_same_line_whitespace
|
||||
_singleline_ -> optional_same_line_whitespace
|
||||
__linebreak__ -> mandatory_linebreak
|
||||
__ -> mandatory_whitespace
|
||||
_ -> optional_whitespace
|
||||
|
||||
mandatory_same_line_whitespace -> single_line_whitespace:+
|
||||
optional_same_line_whitespace -> single_line_whitespace:*
|
||||
|
||||
mandatory_linebreak ->
|
||||
single_line_whitespace:*
|
||||
line_breaker
|
||||
whitespace_and_line_break:*
|
||||
|
||||
single_line_whitespace -> SingleLineComment | SingleLineWhitespace
|
||||
|
||||
line_breaker -> MultiLineComment | MultiLineWhitespace | ";"
|
||||
|
||||
whitespace_and_line_break -> singular_whitespace | ";"
|
||||
|
||||
optional_whitespace -> singular_whitespace:*
|
||||
|
||||
mandatory_whitespace -> singular_whitespace:+
|
||||
|
||||
singular_whitespace ->
|
||||
| SingleLineComment
|
||||
| MultiLineWhitespace
|
||||
| SingleLineComment
|
||||
| MultiLineComment
|
||||
|
||||
=== tokens ===
|
||||
|
||||
SingleLineWhitespace -> [ \t\r\f\v]+
|
||||
MultiLineWhitespace -> [ \t\n\r\f\v]+
|
||||
SingleLineComment -> \/\/[^\n]+
|
||||
MultiLineComment -> \/\*.*?\*\/
|
||||
|
||||
Name -> [a-zA-Z_][a-zA-Z0-9_]*
|
||||
Id -> #[a-zA-Z0-9_]+
|
||||
Class \.[a-zA-Z0-9_]+
|
||||
|
||||
Int -> [0-9]+
|
||||
Float -> [0-9]+\.[0-9]*
|
||||
String -> "<sequence of optionally escaped characters \
|
||||
and escaped special character placeholders \
|
||||
terminated by an unescaped '"'>
|
||||
Null -> "null"
|
||||
False -> "false"
|
||||
True -> "true"
|
18
examples/helloworld/main.bong
Normal file
18
examples/helloworld/main.bong
Normal file
@ -0,0 +1,18 @@
|
||||
page {
|
||||
text.title {
|
||||
"hello world"
|
||||
}
|
||||
input#answer {
|
||||
placeholder="answer"
|
||||
}
|
||||
// text { "hello world" }
|
||||
/*
|
||||
input#non_answer {
|
||||
placeholder="non answer"
|
||||
}
|
||||
*/
|
||||
button#submit {
|
||||
"submit"
|
||||
}
|
||||
text { "hello world" }; text { "hello world" }
|
||||
}
|
364
src/bong.cpp
364
src/bong.cpp
@ -1,48 +1,376 @@
|
||||
#include "bong.hpp"
|
||||
#include "utils.hpp"
|
||||
#include <cctype>
|
||||
#include <fmt/core.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
namespace bong {
|
||||
|
||||
auto token_type_to_string(Tokens type) noexcept -> std::string_view
|
||||
{
|
||||
switch (type) {
|
||||
case Tokens::Eof:
|
||||
return "Eof";
|
||||
case Tokens::SingleLineWhitespace:
|
||||
return "SingleLineWhitespace";
|
||||
case Tokens::MultiLineWhitespace:
|
||||
return "MultiLineWhitespace";
|
||||
case Tokens::SingleLineComment:
|
||||
return "SingleLineComment";
|
||||
case Tokens::MultiLineComment:
|
||||
return "MultiLineComment";
|
||||
case Tokens::Name:
|
||||
return "Name";
|
||||
case Tokens::Id:
|
||||
return "Id";
|
||||
case Tokens::Class:
|
||||
return "Class";
|
||||
case Tokens::Int:
|
||||
return "Int";
|
||||
case Tokens::Float:
|
||||
return "Float";
|
||||
case Tokens::String:
|
||||
return "String";
|
||||
case Tokens::Null:
|
||||
return "Null";
|
||||
case Tokens::False:
|
||||
return "False";
|
||||
case Tokens::True:
|
||||
return "True";
|
||||
case Tokens::LBrace:
|
||||
return "LBrace";
|
||||
case Tokens::RBrace:
|
||||
return "RBrace";
|
||||
case Tokens::LBracket:
|
||||
return "LBracket";
|
||||
case Tokens::RBracket:
|
||||
return "RBracket";
|
||||
case Tokens::Equal:
|
||||
return "Equal";
|
||||
case Tokens::Colon:
|
||||
return "Colon";
|
||||
case Tokens::SemiColon:
|
||||
return "SemiColon";
|
||||
case Tokens::Comma:
|
||||
return "Comma";
|
||||
}
|
||||
}
|
||||
|
||||
auto Token::value() const noexcept -> std::string_view
|
||||
{
|
||||
return text.substr(location.index, length);
|
||||
}
|
||||
|
||||
auto char_to_escaped_string(char c) noexcept -> std::string
|
||||
{
|
||||
switch (c) {
|
||||
case '\n':
|
||||
return "\\n";
|
||||
case '\t':
|
||||
return "\\t";
|
||||
case '\r':
|
||||
return "\\r";
|
||||
case '\f':
|
||||
return "\\f";
|
||||
case '\v':
|
||||
return "\\v";
|
||||
default:
|
||||
return { c };
|
||||
}
|
||||
}
|
||||
|
||||
auto escape_string(std::string_view value) noexcept -> std::string
|
||||
{
|
||||
auto result = std::string {};
|
||||
for (auto c : value)
|
||||
result += char_to_escaped_string(c);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto Token::to_string() const noexcept -> std::string
|
||||
{
|
||||
return fmt::format(
|
||||
"Token {{ [{}:{}], {}:{}, \t{}, \033[01;32m\"{}\"\033[00m }}",
|
||||
location.index, length, location.line, location.col,
|
||||
token_type_to_string(type), escape_string(value()));
|
||||
}
|
||||
|
||||
auto Lexer::collect() noexcept -> Result<std::vector<Token>, Error>
|
||||
{
|
||||
auto tokens = std::vector<Token> {};
|
||||
while (true) {
|
||||
auto token = peek();
|
||||
if (!token)
|
||||
return token.transform<std::vector<Token>>();
|
||||
else if (token->type == Tokens::Eof)
|
||||
break;
|
||||
else
|
||||
tokens.push_back(*token);
|
||||
next();
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
auto Lexer::make_token() noexcept -> Result<Token, Error>
|
||||
{
|
||||
if (done())
|
||||
return Token { Tokens::Eof, location(), 0, text };
|
||||
auto c = current();
|
||||
if (std::isspace(c))
|
||||
if (std::isspace(c) != 0)
|
||||
return make_whitespace();
|
||||
else if (std::isdigit(c))
|
||||
else if (std::isdigit(c) != 0)
|
||||
return make_number();
|
||||
else if (std::isalpha(c))
|
||||
else if (std::isalpha(c) != 0)
|
||||
return make_name();
|
||||
else
|
||||
return make_static();
|
||||
}
|
||||
|
||||
auto Lexer::make_name() noexcept -> Result<Token, Error>
|
||||
auto Lexer::make_whitespace() noexcept -> Result<Token, Error>
|
||||
{
|
||||
auto begin_index = index;
|
||||
auto begin = location;
|
||||
while (!done()
|
||||
and (std ::isalpha(current()) or std::isdigit(current())
|
||||
or current() == '_' or current() == '-')) {
|
||||
auto begin = location();
|
||||
while (!done() and std::isspace(current()) != 0 and current() != '\n') {
|
||||
step();
|
||||
}
|
||||
return Token { Tokens::Name, begin_index, index - begin_index, begin };
|
||||
if (!done() and current() == '\n') {
|
||||
while (!done() and std::isspace(current()) != 0) {
|
||||
step();
|
||||
}
|
||||
return Token {
|
||||
Tokens::MultiLineWhitespace,
|
||||
begin,
|
||||
length_from(begin),
|
||||
text,
|
||||
};
|
||||
} else {
|
||||
return Token {
|
||||
Tokens::SingleLineWhitespace,
|
||||
begin,
|
||||
length_from(begin),
|
||||
text,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
auto Lexer::make_number() noexcept -> Result<Token, Error>;
|
||||
auto substring_matches(std::string_view text, size_t index,
|
||||
std::string_view literal) noexcept -> bool
|
||||
{
|
||||
return literal.size() == 4
|
||||
and text.substr(index, literal.size()).compare(literal) == 0;
|
||||
}
|
||||
|
||||
auto Lexer::make_static() noexcept -> Result<Token, Error>;
|
||||
auto Lexer::make_name() noexcept -> Result<Token, Error>
|
||||
{
|
||||
auto begin = location();
|
||||
while (!done()
|
||||
and ((std::isalpha(current()) != 0) or (std::isdigit(current()) != 0)
|
||||
or current() == '_')) {
|
||||
step();
|
||||
}
|
||||
auto type = [&] {
|
||||
if (substring_matches(text, begin.index, "null"))
|
||||
return Tokens::Null;
|
||||
else if (substring_matches(text, begin.index, "false"))
|
||||
return Tokens::False;
|
||||
else if (substring_matches(text, begin.index, "true"))
|
||||
return Tokens::True;
|
||||
else
|
||||
return Tokens::Name;
|
||||
}();
|
||||
return Token { type, begin, length_from(begin), text };
|
||||
}
|
||||
|
||||
auto Lexer::make_whitespace() noexcept -> Result<Token, Error>;
|
||||
auto Lexer::make_number() noexcept -> Result<Token, Error>
|
||||
{
|
||||
auto begin = location();
|
||||
while (!done() and (std::isdigit(current()) != 0)) {
|
||||
step();
|
||||
}
|
||||
if (!done() and current() == '.') {
|
||||
step();
|
||||
if (done() or std::isdigit(current()) == 0) {
|
||||
return Error { "expected digits after '.'", location() };
|
||||
}
|
||||
while (!done() and (std::isdigit(current()) != 0)) {
|
||||
step();
|
||||
}
|
||||
return Token { Tokens::Float, begin, length_from(begin), text };
|
||||
} else {
|
||||
return Token { Tokens::Int, begin, length_from(begin), text };
|
||||
}
|
||||
}
|
||||
|
||||
auto Lexer::make_singleline_comment() noexcept -> Result<Token, Error>;
|
||||
auto Lexer::make_static() noexcept -> Result<Token, Error>
|
||||
{
|
||||
switch (current()) {
|
||||
case '/':
|
||||
return make_comment();
|
||||
case '"':
|
||||
return make_string();
|
||||
case '#':
|
||||
return make_id();
|
||||
case '.':
|
||||
return make_class();
|
||||
case '{':
|
||||
return make_single_char_token(Tokens::LBrace);
|
||||
case '}':
|
||||
return make_single_char_token(Tokens::RBrace);
|
||||
case '[':
|
||||
return make_single_char_token(Tokens::LBracket);
|
||||
case ']':
|
||||
return make_single_char_token(Tokens::RBracket);
|
||||
case '=':
|
||||
return make_single_char_token(Tokens::Equal);
|
||||
case ':':
|
||||
return make_single_char_token(Tokens::Colon);
|
||||
case ';':
|
||||
return make_single_char_token(Tokens::SemiColon);
|
||||
case ',':
|
||||
return make_single_char_token(Tokens::Comma);
|
||||
default:
|
||||
return Error { fmt::format("unexpected character '{}'", current()),
|
||||
location() };
|
||||
}
|
||||
}
|
||||
|
||||
auto Lexer::make_multiline_comment() noexcept -> Result<Token, Error>;
|
||||
auto Lexer::make_comment() noexcept -> Result<Token, Error>
|
||||
{
|
||||
auto begin = location();
|
||||
step();
|
||||
if (current() == '/')
|
||||
return make_single_line_comment(begin);
|
||||
else if (current() == '*')
|
||||
return make_multi_line_comment(begin);
|
||||
else
|
||||
return Error {
|
||||
fmt::format("expected '/' or '*', got '{}'", current()),
|
||||
location(),
|
||||
};
|
||||
}
|
||||
|
||||
auto Lexer::make_string() noexcept -> Result<Token, Error>;
|
||||
auto Lexer::make_multi_line_comment(Location begin) noexcept
|
||||
-> Result<Token, Error>
|
||||
{
|
||||
step();
|
||||
step();
|
||||
while (!done() and text.at(index - 1) != '*' and current() != '/') {
|
||||
step();
|
||||
}
|
||||
if (done()) {
|
||||
return Error { "expected \"*/\", got EOF", location() };
|
||||
}
|
||||
step();
|
||||
return Token { Tokens::MultiLineComment, begin, length_from(begin), text };
|
||||
}
|
||||
|
||||
auto Lexer::make_id() noexcept -> Result<Token, Error>;
|
||||
auto Lexer::make_single_line_comment(Location begin) noexcept
|
||||
-> Result<Token, Error>
|
||||
{
|
||||
step();
|
||||
while (!done() and current() != '\n') {
|
||||
step();
|
||||
}
|
||||
return Token { Tokens::SingleLineComment, begin, length_from(begin), text };
|
||||
}
|
||||
|
||||
auto Lexer::make_class() noexcept -> Result<Token, Error>;
|
||||
auto Lexer::make_string() noexcept -> Result<Token, Error>
|
||||
{
|
||||
auto begin = location();
|
||||
step();
|
||||
auto escaped = false;
|
||||
while (!done() and (escaped || current() != '"')) {
|
||||
escaped = !escaped and current() == '\\';
|
||||
step();
|
||||
}
|
||||
if (done()) {
|
||||
return Error {
|
||||
fmt::format("expected '\"', got Eof"),
|
||||
location(),
|
||||
};
|
||||
} else if (current() != '\"') {
|
||||
return Error {
|
||||
fmt::format("expected '\"', got '{}'", current()),
|
||||
location(),
|
||||
};
|
||||
}
|
||||
step();
|
||||
return Token { Tokens::String, begin, length_from(begin), text };
|
||||
}
|
||||
|
||||
auto Lexer::make_id() noexcept -> Result<Token, Error>
|
||||
{
|
||||
auto begin = location();
|
||||
step();
|
||||
while (!done()
|
||||
and ((std::isalpha(current()) != 0) or (std::isdigit(current()) != 0)
|
||||
or current() == '_')) {
|
||||
step();
|
||||
}
|
||||
return Token { Tokens::Id, begin, length_from(begin), text };
|
||||
}
|
||||
|
||||
auto Lexer::make_class() noexcept -> Result<Token, Error>
|
||||
{
|
||||
auto begin = location();
|
||||
step();
|
||||
while (!done()
|
||||
and ((std::isalpha(current()) != 0) or (std::isdigit(current()) != 0)
|
||||
or current() == '_')) {
|
||||
step();
|
||||
}
|
||||
return Token { Tokens::Class, begin, length_from(begin), text };
|
||||
}
|
||||
auto Lexer::make_single_char_token(Tokens type) noexcept -> Result<Token, Error>
|
||||
{
|
||||
auto begin = location();
|
||||
step();
|
||||
return Token { type, begin, length_from(begin), text };
|
||||
}
|
||||
|
||||
auto Lexer::step() noexcept -> void
|
||||
{
|
||||
index++;
|
||||
col++;
|
||||
if (!done() and current() == '\n') {
|
||||
line++;
|
||||
col = 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto Parser::parse_top_level() noexcept -> Result<std::unique_ptr<Node>, Error>
|
||||
{
|
||||
if (!lexer.peek())
|
||||
return { lexer.peek().unwrap_error() };
|
||||
else if (lexer.peek()->type == Tokens::Name)
|
||||
return parse_element();
|
||||
else
|
||||
return parse_value();
|
||||
}
|
||||
|
||||
auto Parser::parse_element() noexcept -> Result<std::unique_ptr<Node>, Error>
|
||||
{
|
||||
auto name = *lexer.peek();
|
||||
|
||||
auto ids = Element::Ids {};
|
||||
auto classes = Element::Classes {};
|
||||
auto properties = Element::Properties {};
|
||||
auto values = Element::Values {};
|
||||
|
||||
if (!lexer.next())
|
||||
return { lexer.peek().unwrap_error() };
|
||||
return Result<std::unique_ptr<Node>, Error>::create_ok(
|
||||
std::make_unique<Element>(
|
||||
Element { std::string { name.value() }, {}, {}, {}, {} }));
|
||||
}
|
||||
|
||||
auto Parser::parse_single_line_fields(
|
||||
Element::Initializer& initializer) noexcept -> Result<void, Error>
|
||||
{
|
||||
if (auto result = parse_first_single_line_field(initializer); !result)
|
||||
return result;
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
203
src/bong.hpp
203
src/bong.hpp
@ -1,14 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "src/result.hpp"
|
||||
#include "utils.hpp"
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace bong {
|
||||
|
||||
enum class Tokens {
|
||||
Eof,
|
||||
Whitespace,
|
||||
SingleLineWhitespace,
|
||||
MultiLineWhitespace,
|
||||
SingleLineComment,
|
||||
MultiLineComment,
|
||||
|
||||
@ -19,7 +25,9 @@ enum class Tokens {
|
||||
Int,
|
||||
Float,
|
||||
String,
|
||||
Bool,
|
||||
Null,
|
||||
False,
|
||||
True,
|
||||
|
||||
LBrace,
|
||||
RBrace,
|
||||
@ -32,61 +40,218 @@ enum class Tokens {
|
||||
Comma,
|
||||
};
|
||||
|
||||
auto token_type_to_string(Tokens type) noexcept -> std::string_view;
|
||||
|
||||
struct Location {
|
||||
size_t index;
|
||||
int line, col;
|
||||
};
|
||||
|
||||
struct Token {
|
||||
Tokens type;
|
||||
size_t index, length;
|
||||
Location location;
|
||||
size_t length;
|
||||
std::string_view text;
|
||||
|
||||
auto value() const noexcept -> std::string_view;
|
||||
auto to_string() const noexcept -> std::string;
|
||||
};
|
||||
|
||||
class Lexer {
|
||||
public:
|
||||
struct Error {
|
||||
std::string message;
|
||||
size_t index;
|
||||
Location location;
|
||||
};
|
||||
|
||||
Lexer(std::string_view text)
|
||||
: text { text }
|
||||
inline Lexer(std::string_view text)
|
||||
: current_token { make_token() }
|
||||
, text { text }
|
||||
{
|
||||
(void)next();
|
||||
next();
|
||||
}
|
||||
|
||||
auto next() noexcept -> Result<Token, Error>
|
||||
inline auto next() noexcept -> Result<Token, Error>
|
||||
{
|
||||
return current_token = make_token();
|
||||
}
|
||||
[[nodiscard]] auto peek() const noexcept -> Result<Token, Error>
|
||||
[[nodiscard]] inline auto peek() const noexcept -> Result<Token, Error>
|
||||
{
|
||||
return current_token;
|
||||
}
|
||||
[[nodiscard]] auto collect() noexcept -> Result<std::vector<Token>, Error>;
|
||||
|
||||
private:
|
||||
auto make_token() noexcept -> Result<Token, Error>;
|
||||
auto make_whitespace() noexcept -> Result<Token, Error>;
|
||||
auto make_name() noexcept -> Result<Token, Error>;
|
||||
auto make_number() noexcept -> Result<Token, Error>;
|
||||
auto make_static() noexcept -> Result<Token, Error>;
|
||||
auto make_whitespace() noexcept -> Result<Token, Error>;
|
||||
auto make_singleline_comment() noexcept -> Result<Token, Error>;
|
||||
auto make_multiline_comment() noexcept -> Result<Token, Error>;
|
||||
auto make_comment() noexcept -> Result<Token, Error>;
|
||||
auto make_single_line_comment(Location begin) noexcept
|
||||
-> Result<Token, Error>;
|
||||
auto make_multi_line_comment(Location begin) noexcept
|
||||
-> Result<Token, Error>;
|
||||
auto make_string() noexcept -> Result<Token, Error>;
|
||||
auto make_id() noexcept -> Result<Token, Error>;
|
||||
auto make_class() noexcept -> Result<Token, Error>;
|
||||
auto make_single_char_token(Tokens type) noexcept -> Result<Token, Error>;
|
||||
|
||||
auto current() const noexcept -> char { return text.at(index); }
|
||||
auto done() const noexcept -> bool { return index >= text.size(); }
|
||||
auto step() noexcept -> void { index++; }
|
||||
inline auto current() const noexcept -> char { return text.at(index); }
|
||||
inline auto done() const noexcept -> bool { return index >= text.size(); }
|
||||
auto step() noexcept -> void;
|
||||
|
||||
Result<Token, Error> current_token {
|
||||
Error { "next() not called first", index, location },
|
||||
};
|
||||
auto location() const noexcept -> Location { return { index, line, col }; }
|
||||
inline auto length_from(Location begin) const noexcept -> size_t
|
||||
{
|
||||
return index - begin.index;
|
||||
}
|
||||
inline auto length_from(size_t begin_index) const noexcept -> size_t
|
||||
{
|
||||
return index - begin_index;
|
||||
}
|
||||
|
||||
Result<Token, Error> current_token;
|
||||
std::string_view text;
|
||||
size_t index { 0 };
|
||||
Location location { 1, 1 };
|
||||
int line { 1 }, col { 1 };
|
||||
};
|
||||
|
||||
enum class Nodes {
|
||||
Element,
|
||||
Object,
|
||||
Array,
|
||||
Int,
|
||||
Float,
|
||||
Bool,
|
||||
String,
|
||||
};
|
||||
|
||||
struct Node {
|
||||
Node() = default;
|
||||
virtual ~Node() = default;
|
||||
virtual auto type() const noexcept -> Nodes;
|
||||
};
|
||||
|
||||
struct Element final : public Node {
|
||||
using Ids = std::vector<std::string>;
|
||||
using Classes = std::vector<std::string>;
|
||||
using Properties = std::map<std::string, std::unique_ptr<Node>>;
|
||||
using Values = std::vector<std::unique_ptr<Node>>;
|
||||
struct Initializer {
|
||||
Ids ids;
|
||||
Classes classes;
|
||||
Properties properties;
|
||||
Values values;
|
||||
};
|
||||
|
||||
Element(std::string name, std::vector<std::string> ids,
|
||||
std::vector<std::string> classes,
|
||||
std::map<std::string, std::unique_ptr<Node>> properties,
|
||||
std::vector<std::unique_ptr<Node>> values)
|
||||
: name { std::move(name) }
|
||||
, ids { std::move(ids) }
|
||||
, classes { std::move(classes) }
|
||||
, properties { std::move(properties) }
|
||||
, values { std::move(values) }
|
||||
{ }
|
||||
|
||||
std::string name;
|
||||
Ids ids;
|
||||
Classes classes;
|
||||
Properties properties;
|
||||
Values values;
|
||||
|
||||
auto type() const noexcept -> Nodes override { return Nodes::Element; }
|
||||
};
|
||||
|
||||
struct Object final : public Node {
|
||||
std::map<std::string, std::unique_ptr<Node>> properties;
|
||||
|
||||
auto type() const noexcept -> Nodes override { return Nodes::Object; }
|
||||
};
|
||||
|
||||
struct Array final : public Node {
|
||||
std::vector<std::unique_ptr<Node>> values;
|
||||
|
||||
auto type() const noexcept -> Nodes override { return Nodes::Array; }
|
||||
};
|
||||
|
||||
struct Int final : public Node {
|
||||
int64_t value;
|
||||
|
||||
auto type() const noexcept -> Nodes override { return Nodes::Int; }
|
||||
};
|
||||
|
||||
struct Float final : public Node {
|
||||
double value;
|
||||
|
||||
auto type() const noexcept -> Nodes override { return Nodes::Float; }
|
||||
};
|
||||
|
||||
struct String final : public Node {
|
||||
std::string value;
|
||||
|
||||
auto type() const noexcept -> Nodes override { return Nodes::String; }
|
||||
};
|
||||
|
||||
struct Bool final : public Node {
|
||||
bool value;
|
||||
|
||||
auto type() const noexcept -> Nodes override { return Nodes::Bool; }
|
||||
};
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
struct Error {
|
||||
Error(Lexer::Error&& error)
|
||||
: message { std::move(error.message) }
|
||||
, location { error.location }
|
||||
{ }
|
||||
|
||||
std::string message;
|
||||
Location location;
|
||||
};
|
||||
|
||||
Parser(Lexer lexer)
|
||||
: lexer { std::move(lexer) }
|
||||
{ }
|
||||
|
||||
auto parse_top_level() noexcept -> Result<std::unique_ptr<Node>, Error>;
|
||||
auto parse_element() noexcept -> Result<std::unique_ptr<Node>, Error>;
|
||||
auto parse_element_body(Element::Initializer& initializer) noexcept
|
||||
-> Result<void, Error>;
|
||||
auto parse_element_fields(Element::Initializer& initializer) noexcept
|
||||
-> Result<void, Error>;
|
||||
auto parse_element_field(Element::Initializer& initializer) noexcept
|
||||
-> Result<void, Error>;
|
||||
auto parse_single_line_fields(Element::Initializer& initializer) noexcept
|
||||
-> Result<void, Error>;
|
||||
auto parse_first_single_line_field(
|
||||
Element::Initializer& initializer) noexcept -> Result<void, Error>;
|
||||
auto parse_single_line_field(Element::Initializer& initializer) noexcept
|
||||
-> Result<void, Error>;
|
||||
auto parse_element_property() noexcept -> Result<void, Error>;
|
||||
auto parse_single_line_value() noexcept -> Result<void, Error>;
|
||||
auto parse_value() noexcept -> Result<std::unique_ptr<Node>, Error>;
|
||||
auto parse_object() noexcept -> Result<void, Error>;
|
||||
auto parse_object_properties() noexcept -> Result<void, Error>;
|
||||
auto parse_object_property() noexcept -> Result<void, Error>;
|
||||
auto parse_array() noexcept -> Result<void, Error>;
|
||||
auto parse_array_values() noexcept -> Result<void, Error>;
|
||||
auto parse_bool() noexcept -> Result<void, Error>;
|
||||
auto parse_mandatory_same_line_whitespace() noexcept -> Result<void, Error>;
|
||||
auto parse_optional_same_line_whitespace() noexcept -> Result<void, Error>;
|
||||
auto parse_mandatory_linebreak() noexcept -> Result<void, Error>;
|
||||
auto parse_single_line_whitespace() noexcept -> Result<void, Error>;
|
||||
auto parse_line_breaker() noexcept -> Result<void, Error>;
|
||||
auto parse_whitespace_and_line_break() noexcept -> Result<void, Error>;
|
||||
auto parse_optional_whitespace() noexcept -> Result<void, Error>;
|
||||
auto parse_mandatory_whitespace() noexcept -> Result<void, Error>;
|
||||
auto parse_singular_whitespace() noexcept -> Result<void, Error>;
|
||||
|
||||
private:
|
||||
Lexer lexer;
|
||||
};
|
||||
|
||||
}
|
||||
|
54
src/main.cpp
54
src/main.cpp
@ -3,10 +3,15 @@
|
||||
#include "SDL_rect.h"
|
||||
#include "SDL_render.h"
|
||||
#include "SDL_video.h"
|
||||
#include "src/bong.hpp"
|
||||
#include "utils.hpp"
|
||||
#include <SDL.h>
|
||||
#include <fmt/core.h>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
class GUI {
|
||||
public:
|
||||
@ -66,19 +71,46 @@ private:
|
||||
SDL_Renderer* renderer;
|
||||
};
|
||||
|
||||
auto read_file_into_string(const std::string& filename)
|
||||
{
|
||||
auto file = std::ifstream { filename };
|
||||
auto contents = std::string(
|
||||
std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
|
||||
return contents;
|
||||
}
|
||||
struct Data {
|
||||
int a, b;
|
||||
};
|
||||
|
||||
auto func() -> std::unique_ptr<Data>
|
||||
{
|
||||
return std::make_unique<Data>(Data { 5, 3 });
|
||||
}
|
||||
auto main() -> int
|
||||
{
|
||||
|
||||
// test
|
||||
fmt::print("browser: hello world!\n");
|
||||
auto gui = GUI::create().unwrap();
|
||||
while (true) {
|
||||
bool should_exit = gui->should_exit();
|
||||
if (should_exit)
|
||||
break;
|
||||
gui->set_background_color(100, 180, 220);
|
||||
SDL_Rect rect = { .x = 0, .y = 0, .w = 50, .h = 50 };
|
||||
gui->create_rect(rect, 255, 0, 0);
|
||||
gui->update_gui();
|
||||
auto text = read_file_into_string("../examples/helloworld/main.bong");
|
||||
auto tokens = bong::Lexer { text }.collect();
|
||||
if (tokens) {
|
||||
fmt::print("tokens:\n");
|
||||
for (const auto& token : *tokens)
|
||||
fmt::print(" {}\n", token.to_string());
|
||||
} else {
|
||||
fmt::print("lexer error: {}\n at {}:{}\n",
|
||||
tokens.unwrap_error().message, tokens.unwrap_error().location.line,
|
||||
tokens.unwrap_error().location.col);
|
||||
}
|
||||
|
||||
// test
|
||||
// fmt::print("browser: hello world!\n");
|
||||
// auto gui = GUI::create().unwrap();
|
||||
// while (true) {
|
||||
// bool should_exit = gui->should_exit();
|
||||
// if (should_exit)
|
||||
// break;
|
||||
// gui->set_background_color(100, 180, 220);
|
||||
// SDL_Rect rect = { .x = 0, .y = 0, .w = 50, .h = 50 };
|
||||
// gui->create_rect(rect, 255, 0, 0);
|
||||
// gui->update_gui();
|
||||
// }
|
||||
}
|
@ -32,7 +32,7 @@ template <typename InnerValue> struct Extracter {
|
||||
using Error = void;
|
||||
};
|
||||
|
||||
template <typename Value, typename Error> class [[nodiscard]] Result {
|
||||
template <typename Value, typename Error> class Result {
|
||||
static_assert(std::is_object_v<Value> && std::is_destructible_v<Value>,
|
||||
"incompatible Value in Result<Value, Error>");
|
||||
static_assert(std::is_object_v<Error> && std::is_destructible_v<Error>,
|
||||
@ -209,28 +209,28 @@ public:
|
||||
return is_ok() ? if_ok(unwrap()) : if_error(unwrap_error());
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto flatten() noexcept requires
|
||||
std::same_as<Error, typename Extracter<Value>::Error>
|
||||
[[nodiscard]] constexpr auto flatten() noexcept
|
||||
requires std::same_as<Error, typename Extracter<Value>::Error>
|
||||
{
|
||||
using InnerValue = typename Extracter<Value>::Value;
|
||||
return is_ok() ? unwrap() : Result<InnerValue, Error>(unwrap_error());
|
||||
}
|
||||
[[nodiscard]] constexpr auto flatten() const noexcept requires
|
||||
std::same_as<Error, typename Extracter<Value>::Error>
|
||||
[[nodiscard]] constexpr auto flatten() const noexcept
|
||||
requires std::same_as<Error, typename Extracter<Value>::Error>
|
||||
{
|
||||
using InnerValue = typename Extracter<Value>::Value;
|
||||
return is_ok() ? unwrap() : Result<InnerValue, Error>(unwrap_error());
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto flatten_error() noexcept requires
|
||||
std::same_as<Value, typename Extracter<Error>::Value>
|
||||
[[nodiscard]] constexpr auto flatten_error() noexcept
|
||||
requires std::same_as<Value, typename Extracter<Error>::Value>
|
||||
{
|
||||
using InnerError = typename Extracter<Error>::Error;
|
||||
return is_error() ? unwrap_error()
|
||||
: Result<Value, InnerError>(unwrap());
|
||||
}
|
||||
[[nodiscard]] constexpr auto flatten_error() const noexcept requires
|
||||
std::same_as<Value, typename Extracter<Error>::Value>
|
||||
[[nodiscard]] constexpr auto flatten_error() const noexcept
|
||||
requires std::same_as<Value, typename Extracter<Error>::Value>
|
||||
{
|
||||
using InnerError = typename Extracter<Error>::Error;
|
||||
return is_error() ? unwrap_error()
|
||||
@ -267,7 +267,7 @@ struct Extracter<Result<ValueInferer, ErrorInferer>> {
|
||||
using Error = ErrorInferer;
|
||||
};
|
||||
|
||||
template <typename Value> class [[nodiscard]] Result<Value, void> {
|
||||
template <typename Value> class Result<Value, void> {
|
||||
static_assert(std::is_object_v<Value> && std::is_destructible_v<Value>,
|
||||
"incompatible Error in Result<Value, Error>");
|
||||
|
||||
@ -385,14 +385,14 @@ public:
|
||||
return is_ok() ? if_ok(unwrap()) : if_error();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto flatten() noexcept requires
|
||||
std::same_as<void, typename Extracter<Value>::Error>
|
||||
[[nodiscard]] constexpr auto flatten() noexcept
|
||||
requires std::same_as<void, typename Extracter<Value>::Error>
|
||||
{
|
||||
using InnerValue = typename Extracter<Value>::Value;
|
||||
return is_ok() ? unwrap() : Result<InnerValue, void>();
|
||||
}
|
||||
[[nodiscard]] constexpr auto flatten() const noexcept requires
|
||||
std::same_as<void, typename Extracter<Value>::Error>
|
||||
[[nodiscard]] constexpr auto flatten() const noexcept
|
||||
requires std::same_as<void, typename Extracter<Value>::Error>
|
||||
{
|
||||
using InnerValue = typename Extracter<Value>::Value;
|
||||
return is_ok() ? unwrap() : Result<InnerValue, void>();
|
||||
@ -422,7 +422,7 @@ private:
|
||||
std::optional<Value> maybe_value;
|
||||
};
|
||||
|
||||
template <typename Error> class [[nodiscard]] Result<void, Error> {
|
||||
template <typename Error> class Result<void, Error> {
|
||||
static_assert(std::is_object_v<Error> && std::is_destructible_v<Error>,
|
||||
"incompatible Error in Result<Value, Error>");
|
||||
|
||||
@ -528,14 +528,14 @@ public:
|
||||
return is_ok() ? if_ok() : if_error(unwrap_error());
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto flatten_error() noexcept requires
|
||||
std::same_as<void, typename Extracter<Error>::Value>
|
||||
[[nodiscard]] constexpr auto flatten_error() noexcept
|
||||
requires std::same_as<void, typename Extracter<Error>::Value>
|
||||
{
|
||||
using InnerError = typename Extracter<Error>::Error;
|
||||
return is_error() ? unwrap_error() : Result<void, InnerError>();
|
||||
}
|
||||
[[nodiscard]] constexpr auto flatten_error() const noexcept requires
|
||||
std::same_as<void, typename Extracter<Error>::Value>
|
||||
[[nodiscard]] constexpr auto flatten_error() const noexcept
|
||||
requires std::same_as<void, typename Extracter<Error>::Value>
|
||||
{
|
||||
using InnerError = typename Extracter<Error>::Error;
|
||||
return is_error() ? unwrap_error() : Result<void, InnerError>();
|
||||
@ -565,7 +565,7 @@ private:
|
||||
std::optional<Error> maybe_error;
|
||||
};
|
||||
|
||||
template <> class [[nodiscard]] Result<void, void> {
|
||||
template <> class Result<void, void> {
|
||||
public:
|
||||
enum class States { Ok, Error };
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "result.hpp"
|
||||
|
||||
namespace utils {
|
||||
|
Loading…
Reference in New Issue
Block a user