some parsing
This commit is contained in:
parent
e18cf2f52e
commit
391f04e4cb
2
.clangd
2
.clangd
@ -16,6 +16,8 @@ Diagnostics:
|
|||||||
- bugprone-easily-swappable-parameters
|
- bugprone-easily-swappable-parameters
|
||||||
- readability-convert-member-functions-to-static
|
- readability-convert-member-functions-to-static
|
||||||
- bugprone-exception-escape
|
- bugprone-exception-escape
|
||||||
|
- modernize-use-nodiscard
|
||||||
|
- readability-else-after-return
|
||||||
|
|
||||||
CheckOptions:
|
CheckOptions:
|
||||||
UnusedIncludes: Strict
|
UnusedIncludes: Strict
|
||||||
|
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@ -8,9 +8,9 @@
|
|||||||
"type": "lldb",
|
"type": "lldb",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Debug Browser",
|
"name": "Debug Browser",
|
||||||
"program": "${workspaceFolder}/builddir/web-browser",
|
"program": "${workspaceFolder}/builddir/browser",
|
||||||
"args": [],
|
"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 "bong.hpp"
|
||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <fmt/core.h>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace bong {
|
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>
|
auto Lexer::make_token() noexcept -> Result<Token, Error>
|
||||||
{
|
{
|
||||||
|
if (done())
|
||||||
|
return Token { Tokens::Eof, location(), 0, text };
|
||||||
auto c = current();
|
auto c = current();
|
||||||
if (std::isspace(c))
|
if (std::isspace(c) != 0)
|
||||||
return make_whitespace();
|
return make_whitespace();
|
||||||
else if (std::isdigit(c))
|
else if (std::isdigit(c) != 0)
|
||||||
return make_number();
|
return make_number();
|
||||||
else if (std::isalpha(c))
|
else if (std::isalpha(c) != 0)
|
||||||
return make_name();
|
return make_name();
|
||||||
else
|
else
|
||||||
return make_static();
|
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();
|
||||||
auto begin = location;
|
while (!done() and std::isspace(current()) != 0 and current() != '\n') {
|
||||||
while (!done()
|
|
||||||
and (std ::isalpha(current()) or std::isdigit(current())
|
|
||||||
or current() == '_' or current() == '-')) {
|
|
||||||
step();
|
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
|
#pragma once
|
||||||
|
|
||||||
|
#include "src/result.hpp"
|
||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace bong {
|
namespace bong {
|
||||||
|
|
||||||
enum class Tokens {
|
enum class Tokens {
|
||||||
Eof,
|
Eof,
|
||||||
Whitespace,
|
SingleLineWhitespace,
|
||||||
|
MultiLineWhitespace,
|
||||||
SingleLineComment,
|
SingleLineComment,
|
||||||
MultiLineComment,
|
MultiLineComment,
|
||||||
|
|
||||||
@ -19,7 +25,9 @@ enum class Tokens {
|
|||||||
Int,
|
Int,
|
||||||
Float,
|
Float,
|
||||||
String,
|
String,
|
||||||
Bool,
|
Null,
|
||||||
|
False,
|
||||||
|
True,
|
||||||
|
|
||||||
LBrace,
|
LBrace,
|
||||||
RBrace,
|
RBrace,
|
||||||
@ -32,61 +40,218 @@ enum class Tokens {
|
|||||||
Comma,
|
Comma,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto token_type_to_string(Tokens type) noexcept -> std::string_view;
|
||||||
|
|
||||||
struct Location {
|
struct Location {
|
||||||
|
size_t index;
|
||||||
int line, col;
|
int line, col;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Token {
|
struct Token {
|
||||||
Tokens type;
|
Tokens type;
|
||||||
size_t index, length;
|
|
||||||
Location location;
|
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 {
|
class Lexer {
|
||||||
public:
|
public:
|
||||||
struct Error {
|
struct Error {
|
||||||
std::string message;
|
std::string message;
|
||||||
size_t index;
|
|
||||||
Location location;
|
Location location;
|
||||||
};
|
};
|
||||||
|
|
||||||
Lexer(std::string_view text)
|
inline Lexer(std::string_view text)
|
||||||
: text { 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();
|
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;
|
return current_token;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] auto collect() noexcept -> Result<std::vector<Token>, Error>;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
auto make_token() noexcept -> Result<Token, Error>;
|
auto make_token() noexcept -> Result<Token, Error>;
|
||||||
|
auto make_whitespace() noexcept -> Result<Token, Error>;
|
||||||
auto make_name() noexcept -> Result<Token, Error>;
|
auto make_name() noexcept -> Result<Token, Error>;
|
||||||
auto make_number() noexcept -> Result<Token, Error>;
|
auto make_number() noexcept -> Result<Token, Error>;
|
||||||
auto make_static() noexcept -> Result<Token, Error>;
|
auto make_static() noexcept -> Result<Token, Error>;
|
||||||
auto make_whitespace() noexcept -> Result<Token, Error>;
|
auto make_comment() noexcept -> Result<Token, Error>;
|
||||||
auto make_singleline_comment() noexcept -> Result<Token, Error>;
|
auto make_single_line_comment(Location begin) noexcept
|
||||||
auto make_multiline_comment() noexcept -> Result<Token, Error>;
|
-> Result<Token, Error>;
|
||||||
|
auto make_multi_line_comment(Location begin) noexcept
|
||||||
|
-> Result<Token, Error>;
|
||||||
auto make_string() noexcept -> Result<Token, Error>;
|
auto make_string() noexcept -> Result<Token, Error>;
|
||||||
auto make_id() noexcept -> Result<Token, Error>;
|
auto make_id() noexcept -> Result<Token, Error>;
|
||||||
auto make_class() 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); }
|
inline auto current() const noexcept -> char { return text.at(index); }
|
||||||
auto done() const noexcept -> bool { return index >= text.size(); }
|
inline auto done() const noexcept -> bool { return index >= text.size(); }
|
||||||
auto step() noexcept -> void { index++; }
|
auto step() noexcept -> void;
|
||||||
|
|
||||||
Result<Token, Error> current_token {
|
auto location() const noexcept -> Location { return { index, line, col }; }
|
||||||
Error { "next() not called first", index, location },
|
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;
|
std::string_view text;
|
||||||
size_t index { 0 };
|
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_rect.h"
|
||||||
#include "SDL_render.h"
|
#include "SDL_render.h"
|
||||||
#include "SDL_video.h"
|
#include "SDL_video.h"
|
||||||
|
#include "src/bong.hpp"
|
||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <fmt/core.h>
|
#include <fmt/core.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iterator>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
class GUI {
|
class GUI {
|
||||||
public:
|
public:
|
||||||
@ -66,19 +71,46 @@ private:
|
|||||||
SDL_Renderer* renderer;
|
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
|
auto main() -> int
|
||||||
{
|
{
|
||||||
|
|
||||||
|
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
|
// test
|
||||||
fmt::print("browser: hello world!\n");
|
// fmt::print("browser: hello world!\n");
|
||||||
auto gui = GUI::create().unwrap();
|
// auto gui = GUI::create().unwrap();
|
||||||
while (true) {
|
// while (true) {
|
||||||
bool should_exit = gui->should_exit();
|
// bool should_exit = gui->should_exit();
|
||||||
if (should_exit)
|
// if (should_exit)
|
||||||
break;
|
// break;
|
||||||
gui->set_background_color(100, 180, 220);
|
// gui->set_background_color(100, 180, 220);
|
||||||
SDL_Rect rect = { .x = 0, .y = 0, .w = 50, .h = 50 };
|
// SDL_Rect rect = { .x = 0, .y = 0, .w = 50, .h = 50 };
|
||||||
gui->create_rect(rect, 255, 0, 0);
|
// gui->create_rect(rect, 255, 0, 0);
|
||||||
gui->update_gui();
|
// gui->update_gui();
|
||||||
}
|
// }
|
||||||
}
|
}
|
@ -32,7 +32,7 @@ template <typename InnerValue> struct Extracter {
|
|||||||
using Error = void;
|
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>,
|
static_assert(std::is_object_v<Value> && std::is_destructible_v<Value>,
|
||||||
"incompatible Value in Result<Value, Error>");
|
"incompatible Value in Result<Value, Error>");
|
||||||
static_assert(std::is_object_v<Error> && std::is_destructible_v<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());
|
return is_ok() ? if_ok(unwrap()) : if_error(unwrap_error());
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] constexpr auto flatten() noexcept requires
|
[[nodiscard]] constexpr auto flatten() noexcept
|
||||||
std::same_as<Error, typename Extracter<Value>::Error>
|
requires std::same_as<Error, typename Extracter<Value>::Error>
|
||||||
{
|
{
|
||||||
using InnerValue = typename Extracter<Value>::Value;
|
using InnerValue = typename Extracter<Value>::Value;
|
||||||
return is_ok() ? unwrap() : Result<InnerValue, Error>(unwrap_error());
|
return is_ok() ? unwrap() : Result<InnerValue, Error>(unwrap_error());
|
||||||
}
|
}
|
||||||
[[nodiscard]] constexpr auto flatten() const noexcept requires
|
[[nodiscard]] constexpr auto flatten() const noexcept
|
||||||
std::same_as<Error, typename Extracter<Value>::Error>
|
requires std::same_as<Error, typename Extracter<Value>::Error>
|
||||||
{
|
{
|
||||||
using InnerValue = typename Extracter<Value>::Value;
|
using InnerValue = typename Extracter<Value>::Value;
|
||||||
return is_ok() ? unwrap() : Result<InnerValue, Error>(unwrap_error());
|
return is_ok() ? unwrap() : Result<InnerValue, Error>(unwrap_error());
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] constexpr auto flatten_error() noexcept requires
|
[[nodiscard]] constexpr auto flatten_error() noexcept
|
||||||
std::same_as<Value, typename Extracter<Error>::Value>
|
requires std::same_as<Value, typename Extracter<Error>::Value>
|
||||||
{
|
{
|
||||||
using InnerError = typename Extracter<Error>::Error;
|
using InnerError = typename Extracter<Error>::Error;
|
||||||
return is_error() ? unwrap_error()
|
return is_error() ? unwrap_error()
|
||||||
: Result<Value, InnerError>(unwrap());
|
: Result<Value, InnerError>(unwrap());
|
||||||
}
|
}
|
||||||
[[nodiscard]] constexpr auto flatten_error() const noexcept requires
|
[[nodiscard]] constexpr auto flatten_error() const noexcept
|
||||||
std::same_as<Value, typename Extracter<Error>::Value>
|
requires std::same_as<Value, typename Extracter<Error>::Value>
|
||||||
{
|
{
|
||||||
using InnerError = typename Extracter<Error>::Error;
|
using InnerError = typename Extracter<Error>::Error;
|
||||||
return is_error() ? unwrap_error()
|
return is_error() ? unwrap_error()
|
||||||
@ -267,7 +267,7 @@ struct Extracter<Result<ValueInferer, ErrorInferer>> {
|
|||||||
using Error = 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>,
|
static_assert(std::is_object_v<Value> && std::is_destructible_v<Value>,
|
||||||
"incompatible Error in Result<Value, Error>");
|
"incompatible Error in Result<Value, Error>");
|
||||||
|
|
||||||
@ -385,14 +385,14 @@ public:
|
|||||||
return is_ok() ? if_ok(unwrap()) : if_error();
|
return is_ok() ? if_ok(unwrap()) : if_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] constexpr auto flatten() noexcept requires
|
[[nodiscard]] constexpr auto flatten() noexcept
|
||||||
std::same_as<void, typename Extracter<Value>::Error>
|
requires std::same_as<void, typename Extracter<Value>::Error>
|
||||||
{
|
{
|
||||||
using InnerValue = typename Extracter<Value>::Value;
|
using InnerValue = typename Extracter<Value>::Value;
|
||||||
return is_ok() ? unwrap() : Result<InnerValue, void>();
|
return is_ok() ? unwrap() : Result<InnerValue, void>();
|
||||||
}
|
}
|
||||||
[[nodiscard]] constexpr auto flatten() const noexcept requires
|
[[nodiscard]] constexpr auto flatten() const noexcept
|
||||||
std::same_as<void, typename Extracter<Value>::Error>
|
requires std::same_as<void, typename Extracter<Value>::Error>
|
||||||
{
|
{
|
||||||
using InnerValue = typename Extracter<Value>::Value;
|
using InnerValue = typename Extracter<Value>::Value;
|
||||||
return is_ok() ? unwrap() : Result<InnerValue, void>();
|
return is_ok() ? unwrap() : Result<InnerValue, void>();
|
||||||
@ -422,7 +422,7 @@ private:
|
|||||||
std::optional<Value> maybe_value;
|
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>,
|
static_assert(std::is_object_v<Error> && std::is_destructible_v<Error>,
|
||||||
"incompatible Error in Result<Value, Error>");
|
"incompatible Error in Result<Value, Error>");
|
||||||
|
|
||||||
@ -528,14 +528,14 @@ public:
|
|||||||
return is_ok() ? if_ok() : if_error(unwrap_error());
|
return is_ok() ? if_ok() : if_error(unwrap_error());
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] constexpr auto flatten_error() noexcept requires
|
[[nodiscard]] constexpr auto flatten_error() noexcept
|
||||||
std::same_as<void, typename Extracter<Error>::Value>
|
requires std::same_as<void, typename Extracter<Error>::Value>
|
||||||
{
|
{
|
||||||
using InnerError = typename Extracter<Error>::Error;
|
using InnerError = typename Extracter<Error>::Error;
|
||||||
return is_error() ? unwrap_error() : Result<void, InnerError>();
|
return is_error() ? unwrap_error() : Result<void, InnerError>();
|
||||||
}
|
}
|
||||||
[[nodiscard]] constexpr auto flatten_error() const noexcept requires
|
[[nodiscard]] constexpr auto flatten_error() const noexcept
|
||||||
std::same_as<void, typename Extracter<Error>::Value>
|
requires std::same_as<void, typename Extracter<Error>::Value>
|
||||||
{
|
{
|
||||||
using InnerError = typename Extracter<Error>::Error;
|
using InnerError = typename Extracter<Error>::Error;
|
||||||
return is_error() ? unwrap_error() : Result<void, InnerError>();
|
return is_error() ? unwrap_error() : Result<void, InnerError>();
|
||||||
@ -565,7 +565,7 @@ private:
|
|||||||
std::optional<Error> maybe_error;
|
std::optional<Error> maybe_error;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <> class [[nodiscard]] Result<void, void> {
|
template <> class Result<void, void> {
|
||||||
public:
|
public:
|
||||||
enum class States { Ok, Error };
|
enum class States { Ok, Error };
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
#include "result.hpp"
|
#include "result.hpp"
|
||||||
|
|
||||||
namespace utils {
|
namespace utils {
|
||||||
|
Loading…
Reference in New Issue
Block a user