schema_editor/comdel/parser/comdellexer.cpp

440 lines
10 KiB
C++
Raw Permalink Normal View History

2022-03-29 19:31:45 +00:00
#include "comdellexer.h"
#include "token.h"
#include "tokenstype.h"
#include <fstream>
#include <cctype>
/*************************************************************************
*
* Auxiliary functions - recognizing category of characters
*
*************************************************************************/
inline bool identifierStart(char ch) {
return isalpha(ch) || ch == '_';
}
inline bool identifierContinue(char ch) {
return isalnum(ch) || ch == '_';
}
inline bool numberStart(char ch) {
return isdigit(ch) || ch == '-';
2022-03-29 19:31:45 +00:00
}
inline bool isWhitespace(char ch) {
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
}
/*************************************************************************
*
* ComdelLexer class public methods
*
*************************************************************************/
ComdelLexer::ComdelLexer(std::string fileName, std::string source,
ParseContext* parseContext)
: source(std::move(source)),
parseContext(parseContext),
2022-04-08 18:17:58 +00:00
// TODO Update this
2022-03-29 19:31:45 +00:00
fileId(this->parseContext->addFile(fileName, this->source)),
position(this->fileId, 1, 1, 0),
ch(this->source[0])
{}
LexerResult ComdelLexer::tokenize() {
while (!eof()) {
tokenBegin = position;
auto tokenType = nextTokenType();
if(!tokenType) {
errors.push_back(tokenType.error());
continue;
}
if (tokenType == TokenType::WHITESPACE || tokenType == TokenType::COMMENT) {
continue;
}
std::string text = source.substr(tokenBegin.offset,
position.offset - tokenBegin.offset);
2022-03-29 21:08:55 +00:00
tokenType = from_token(text, tokenType.value());
tokens.emplace_back(*tokenType, Span(tokenBegin, position), text);
2022-03-29 19:31:45 +00:00
}
tokens.emplace_back( TokenType::END_OF_FILE, Span(position), "");
2022-03-29 19:31:45 +00:00
return LexerResult { tokens, errors };
}
/*************************************************************************
*
* ComdelLexer class private methods
*
*************************************************************************/
// Numbers are sequences of digits and underscores
// with AT LEAST ONE digit.
unsigned ComdelLexer::takeNumberInRadix(Radix radix) {
unsigned digitsTaken = 0;
while(true) {
if (digitIsValid(ch, radix)) { // skip and count real digits
bump();
digitsTaken++;
} else if(ch == '_') { // skip underscores
bump();
} else { // some other character => end of number
return digitsTaken;
}
}
}
bool ComdelLexer::digitIsValid(char ch, Radix radix) {
2022-04-08 18:17:58 +00:00
// Check valid digits
// digit is 0..1
2022-03-29 19:31:45 +00:00
if(ch == '1' || ch == '0') {
// OK for all radixes
return true;
}
//digit is 2..9
if(isdigit(ch)) {
// OK for decimal and hex radixes
return(radix != Radix::BIN_NUMBER);
}
// digit is a..f A..F
else if(isxdigit(ch)) {
// OK only for hex radix
return(radix ==Radix::HEX_NUMBER);
}
// NOT 0..9 a..f A..F
return false;
}
ComdelLexer::Radix ComdelLexer::takeRadix(){
if (ch == '0') {
char nextChar = peek();
if (nextChar == 'x' || nextChar == 'X') {
bump(2);
return Radix::HEX_NUMBER;
} else if (nextChar == 'b' || nextChar == 'B') {
bump(2);
return Radix::BIN_NUMBER;
}
}
return Radix::DEC_NUMBER;
}
2022-04-08 18:17:58 +00:00
// color is sequence of hex-digits (preceded with # which is already consumed)
2022-03-29 19:31:45 +00:00
unsigned ComdelLexer::takeHexColor() {
unsigned digitsTaken = 0;
while(true) {
if (digitIsValid(ch, Radix::HEX_NUMBER)) { // skip and count hex digits
bump();
digitsTaken++;
} else { // some other character => end of number
return digitsTaken;
}
}
}
PResult<TokenType> ComdelLexer::takeString() {
Position lo = position;
if (ch != '"')
return PError({Span(lo), "expected string here"});
bump(); // skip starting "
2022-04-08 18:17:58 +00:00
// Check escape-sequences \t \n \\ \" but leave them in string.
2022-03-29 19:31:45 +00:00
// They will be replaced in the constructor of StringLiteral AST-node.
while (ch != '"' && ch != '\n' && !eof()) {
if(ch == '"') {
// possible start of escape-sequence
char nextCh = peek();
if(nextCh == '\\' || nextCh == '\"' || nextCh == 't' || nextCh == 'n')
bump(); // legal escape-sequence: skip backslash
else
return PError({Span(lo, position), "illegal escape-sequence (allowed: \\n \\\" \\\\ \\t"});
}
bump(); // skip normal char OR skip second char in escape-sequence
}
if (eof() || ch == '\n') {
return PError({Span(lo, position), "unterminated string"});
}
bump(); // skip closing "
return TokenType::STRING;
}
PResult<TokenType> ComdelLexer::takeRawString()
{
Position lo = position;
if (ch != '`')
return PError({Span(lo), "expected string here"});
bump(); // skip starting '
2022-04-08 18:17:58 +00:00
// Ignore escape-sequences - take all characters until closing '
2022-03-29 19:31:45 +00:00
while (ch != '`' && !eof()) {
bump(); // skip characters
}
if (eof() || ch == '\n') {
return PError({Span(lo, position), "unterminated string"});
}
bump(); // skip closing '
return TokenType::STRING;
}
void ComdelLexer::skipWhitespace() {
while (!eof()) {
if (isWhitespace(ch)) {
bump();
} else {
break;
}
}
}
void ComdelLexer::skipComment() {
while (!eof() && ch != '\n') {
bump();
}
}
// returns false if the comment is unterminated
bool ComdelLexer::skipMultilineComment() {
while (!eof()) {
if (ch == '*') {
bump();
if (ch == '/') {
bump();
return true;
}
}
bump();
}
return false;
}
PResult<TokenType> ComdelLexer::nextTokenType() {
if (isWhitespace(ch))
{
skipWhitespace();
return TokenType::WHITESPACE;
}
else if (identifierStart(ch))
{
bump();
while (identifierContinue(ch))
bump();
return TokenType::IDENTIFIER;
}
else if (numberStart(ch))
{
if(ch == '-') {
bump();
}
2022-03-29 19:31:45 +00:00
unsigned takenDigits;
Radix radix = takeRadix();
takenDigits = takeNumberInRadix(radix);
if (takenDigits == 0) {
return PError({Span(tokenBegin, position),
"no digits found for number, or misspelled number"});
}
if( isalnum(ch) ) {
return PError({Span(tokenBegin, position),
"illegal digit or letter found at the end of number"});
}
return TokenType::NUMBER;
}
else if (ch == '!')
{
bump();
return TokenType::NOT;
}
else if (ch == '<')
{
bump();
return TokenType::LT;
}
else if (ch == '>')
{
bump();
return TokenType::GT;
}
else if (ch == '#')
{
bump();
if (digitIsValid(ch, Radix::HEX_NUMBER)) {
unsigned takenDigits = takeHexColor();
if (takenDigits != 6 && takenDigits != 8) {
return PError({Span(tokenBegin, position),
"hex-color must have 6 or 8 hex-digits"});
}
if( isalnum(ch) ) {
return PError({Span(tokenBegin, position),
"illegal letter found at the end of hex-color"});
}
return TokenType::COLOR;
} else {
return PError({Span(tokenBegin, position),
2022-04-08 18:17:58 +00:00
"unexpected #"});
2022-03-29 19:31:45 +00:00
}
}
else if (ch == '@')
{
bump();
while (identifierContinue(ch))
bump();
return TokenType::KEYWORD;
}
else if (ch == '"')
{
return takeString();
}
else if (ch == '`')
{
return takeRawString();
}
else if (ch == '(')
{
bump();
return TokenType::LPAREN;
}
else if (ch == ')')
{
bump();
return TokenType::RPAREN;
}
else if (ch == '[')
{
bump();
return TokenType::LBRACKET;
}
else if (ch == ']')
{
bump();
return TokenType::RBRACKET;
}
else if (ch == '{')
{
bump();
return TokenType::LBRACE;
}
else if (ch == '}')
{
bump();
return TokenType::RBRACE;
}
else if (ch == '/')
{
bump();
if (ch == '/')
{
bump();
skipComment();
return TokenType::COMMENT;
} else if (ch == '*')
{
bump();
if (!skipMultilineComment())
{
return PError({Span(tokenBegin, position),
"unterminated multiline comment"});
}
return TokenType::COMMENT;
}
return PError({Span(tokenBegin, position),
2022-04-08 18:17:58 +00:00
"unexpected /"});
2022-03-29 19:31:45 +00:00
}
else if (ch == '.')
{
bump();
return TokenType::DOT;
}
else if (ch == ':')
{
bump();
return TokenType::COLON;
}
else if (ch == ';')
{
bump();
return TokenType::SEMICOLON;
}
else if (ch == ',')
{
bump();
return TokenType::COMMA;
}
else if (ch == '=')
{
bump();
return TokenType::EQUALS;
}
else
{
std::stringstream message;
message << "unexpected character `" << ch << "`";
bump();
return PError({Span(tokenBegin, position), message.str()});
}
}
// Move position to the new character in input file.
// Fetch the new character in 'ch'
void ComdelLexer::bump(unsigned count) {
for (unsigned i=0; i < count && !eof(); i++) {
if (ch == '\n') {
position.line += 1;
position.col = 1;
parseContext->getFile(fileId).addLineOffset(position.offset+1);
} else {
position.col += 1;
}
position.offset += 1;
ch = source[position.offset];
}
}
// Fetch and return next character without moving position.
// Fetch does not cross line boundary.
2022-04-08 18:17:58 +00:00
// Returns \n when next char does not exist (end of line or end of file)
2022-03-29 19:31:45 +00:00
char ComdelLexer::peek() {
if(position.offset+1 == source.size()) // eof
return '\n';
return source[position.offset+1];
}
bool ComdelLexer::eof() {
return position.offset == source.size();
}