100 lines
5.0 KiB
C++
100 lines
5.0 KiB
C++
#ifndef YERBACON_PARSER_HPP
|
|
#define YERBACON_PARSER_HPP
|
|
|
|
#include <string>
|
|
#include "ParseComponents.hpp"
|
|
#include "../Yerbacon.hpp"
|
|
#include <concepts>
|
|
#include <span>
|
|
|
|
namespace Parser {
|
|
typedef Yerbacon::Exception ParsingException;
|
|
void error(const tok& token, const string& text, unsigned long line, const bool& quoteTokenText = false) {
|
|
throw ParsingException(quoteTokenText ? "\"" + token.toktext + "\"" + text : text, line);
|
|
}
|
|
inline void parsingError(
|
|
const tok& token, const string& text,
|
|
const bool& quoteTokenText = false
|
|
) { error(token, text, token.line, quoteTokenText); }
|
|
IS(ParseTree)
|
|
T parse(const span<const tok>&& lexed) {
|
|
T parseTree;
|
|
using namespace StandardComponents;
|
|
using enum tok::type;
|
|
unsigned int i = 0;
|
|
const auto nextAre = [&i, &lexed]<convertible_to<tok::type> Y>(const initializer_list<Y>& nextValues) -> bool {
|
|
unsigned int j = 1;
|
|
for (const Y& nextValue: nextValues) {
|
|
if (!cmp_greater(lexed.size() - i, nextValues.size()) || lexed[i + j].toktype != nextValue) {
|
|
return false;
|
|
}
|
|
++j;
|
|
}
|
|
return true;
|
|
};
|
|
for (;i < lexed.size(); ++i) {
|
|
const bool hasNext = (i + 1) < lexed.size();
|
|
const tok& current = lexed[i], next = hasNext ? lexed[i + 1] : tok(UNEXPECTED, current.line);
|
|
|
|
switch (current.toktype) {
|
|
case STRING: parseTree << types::String(current.toktext.data()); break;
|
|
case IDENTIFIER: {
|
|
if (current.toktext == "class" || current.toktext == "structure") {
|
|
if (next.toktype == IDENTIFIER) {
|
|
parseTree << Class(next.toktext); ++i;
|
|
} else {
|
|
parsingError(next, hasNext ? " is not a valid class identifier" : "A class identifier is required", hasNext);
|
|
}
|
|
} else {
|
|
bool isFinalDefine = nextAre({TAG, DEFINE});
|
|
if (isFinalDefine || next.toktype == DEFINE) {
|
|
const optional previousDefinition = parseTree.template findReferenceByName<Define>(current.toktext);
|
|
if (previousDefinition.has_value()) {
|
|
if (previousDefinition.value().get().final || isFinalDefine) {
|
|
parsingError(current, previousDefinition->get().final ? " cannot be redefined as it is final" : " cannot be made final after it has been declared", true);
|
|
}
|
|
}
|
|
parseTree << Define(isFinalDefine, current.toktext);
|
|
i += 1 + isFinalDefine;
|
|
} else {
|
|
parseTree << Reference(current.toktext);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case LPAR: case LBRACE: case LBRACKET: {
|
|
const auto inverseCharacter = tok::inverseLCharacter(current.toktype);
|
|
const auto closingCharacter = find_if(lexed.begin() + i, lexed.end(), [&inverseCharacter](const tok& it){
|
|
return it.toktype == inverseCharacter;
|
|
});
|
|
if (closingCharacter != lexed.end()) {
|
|
vector<tok> subTokens(lexed.begin() + i + 1, closingCharacter);
|
|
if (current.toktype == LPAR || current.toktype == LBRACKET) {
|
|
if (subTokens.size() >= 2 && subTokens[1].toktype != RPAR) {
|
|
for (auto iterator = subTokens.cbegin(); iterator < (subTokens.cend() - 1); ++iterator) {
|
|
const auto nextIterator = iterator + 1;
|
|
if (nextIterator->toktype == COMMA) {
|
|
subTokens.erase(nextIterator);
|
|
} else throw ParsingException("Missing comma after \"" + iterator->toktext + '"');
|
|
}
|
|
}
|
|
}
|
|
switch (current.toktype) {
|
|
case LPAR: parseTree << parse<Call>(subTokens); break;
|
|
case LBRACE: // TODO Add structures for class/function bodies
|
|
case LBRACKET:
|
|
default: parseTree << parse(subTokens); break;
|
|
}
|
|
i = distance(lexed.begin(), closingCharacter);
|
|
} else parsingError(current, string(" is missing a closing \"").append(1, inverseCharacter) + '"', true);
|
|
break;
|
|
}
|
|
case RPAR: case RBRACE: case RBRACKET: parsingError(current, " \u27F5 Unexpected character", true);
|
|
default: break;
|
|
}
|
|
}
|
|
return parseTree;
|
|
}
|
|
}
|
|
|
|
#endif //YERBACON_PARSER_HPP
|