#pragma once
#include "maphoon-lexer/includes.h"
#include "parser_tools.h"
#include "blockchart.h"
#include "blockcsh.h"

namespace block {

/* This class contains the tokenizer grammar.
 * It cannot generate the full symbol, just the token type. */
template <typename TOKEN>
lexing::classifier<char, TOKEN> build_classifier() {
    using namespace lexing;
    classifier< char, TOKEN > cl(TOKEN::TOK_UNRECOGNIZED_CHAR);

    /* Newline characters in all forms accepted */
    const auto newline = just('\n') | just('\r') | just('\r') * just('\n');
    cl.insert(newline, TOKEN::TOK_NEWLINE);
    /* # starts a comment last until end of line */
    auto noCRLF = every<char>().without("\n\r");
    cl.insert(just('#') * noCRLF.star(), TOKEN::TOK_COMMENT);
    /* // starts a comment last until end of line */
    cl.insert(just('/') * just('/') * noCRLF.star(), TOKEN::TOK_COMMENT);
    /* / * .. * / block comments*/
    cl.insert(word("/*") *
              (every<char>().without('*') |
               (just('*').plus() * every<char>().without("/*")).star()
               ).star() * just('*').plus() * just('/'), TOKEN::TOK_COMMENT);
      /* / * ... unclosed block comments */
    cl.insert(word("/*"), TOKEN::TOK_COMMENT);
    /* ignore whitespace */
    auto WS = just(' ') | just('\t');
    cl.insert(WS, TOKEN::TOK_WHITESPACE);

    cl.insert(word(INLINE_OPEN_STR), TOKEN::TOK_OINLINE);
    cl.insert(word(INLINE_CLOSE_STR), TOKEN::TOK_CINLINE);

    cl.insert(just('%'), TOKEN::TOK_PERCENT);
    cl.insert(just('@'), TOKEN::TOK_ATSYMBOL);
    cl.insert(just('*'), TOKEN::TOK_ASTERISK);
    cl.insert(just('='), TOKEN::TOK_EQUAL);
    cl.insert(just(','), TOKEN::TOK_COMMA);
    cl.insert(just(';'), TOKEN::TOK_SEMICOLON);
    cl.insert(just('['), TOKEN::TOK_OSBRACKET);
    cl.insert(just(']'), TOKEN::TOK_CSBRACKET);
    cl.insert(word("++"), TOKEN::TOK_PLUS_PLUS);
    cl.insert(just('+'), TOKEN::TOK_PLUS);
    cl.insert(just('-'), TOKEN::TOK_MINUS);
    cl.insert(just('~'), TOKEN::TOK_TILDE);
    cl.insert(just('('), TOKEN::TOK_OPARENTHESIS);
    cl.insert(just(')'), TOKEN::TOK_CPARENTHESIS);
    cl.insert(just('{'), TOKEN::TOK_OCBRACKET);
    cl.insert(just('}'), TOKEN::TOK_CCBRACKET);

    cl.insert(just('<'), TOKEN::TOK_COMP_OP);
    cl.insert(word("<="), TOKEN::TOK_COMP_OP);
    cl.insert(word("=="), TOKEN::TOK_COMP_OP);
    cl.insert(word("<>"), TOKEN::TOK_COMP_OP);
    cl.insert(word("=>"), TOKEN::TOK_COMP_OP);
    cl.insert(just('>'), TOKEN::TOK_COMP_OP);

    cl.insert(word("->"), TOKEN::TOK_ARROW_FW);
    cl.insert(word(">>"), TOKEN::TOK_ARROW_FW);
    cl.insert(word("<-"), TOKEN::TOK_ARROW_BW);
    cl.insert(word("<<"), TOKEN::TOK_ARROW_BW);
    cl.insert(word("<->"), TOKEN::TOK_ARROW_BIDIR);
    cl.insert(word("<=>"), TOKEN::TOK_ARROW_BIDIR);
    cl.insert(word("<<>>"), TOKEN::TOK_ARROW_BIDIR);
    cl.insert(word("--"), TOKEN::TOK_ARROW_NO);
    cl.insert(word(".."), TOKEN::TOK_ARROW_NO);

    /* This is a colon-quoted string, finished by a quotation mark (UTF-8 allowed)
     * : "<string>"
     * ... or a colon-quoted string, finished by a newline or file-end (UTF-8 allowed)
     * : "<string>$
     * <string> can contain escaped quotation marks, hashmarks, but no line breaks
     */
    cl.insert(just(':') * WS.star() * just('"') *
              (noCRLF.without('"') | (word("\\\""))).star() *
              just('"').optional(), TOKEN::TOK_COLON_QUOTED_STRING);
    /* This is a non quoted colon-string
     * : <string>
     * terminated by any of: [ { or ;
     * Honors escaping of the above via a backslash. (UTF-8 allowed)
     * Can contain quotation marks (escaped or unescaped), but can not start with it
     * If it contains a hashmark, unescaped [ { or ; is allowed till the end of the line
     * (representing a commented section inside a label)
     */
    auto noterm = every<char>().without(";[{#\\");
    cl.insert(just(':') * (WS | newline).star() *
              (
                  just('#') * noCRLF.star() | //starts with a comment
                  just('\\') * noCRLF |       //or an escape sequence
                  noterm.without("\" \t\n\r") //or something else, but not a quotation mark or whitespace
              ) * (
                  just('#') * noCRLF.star() | // continues with a comment
                  just('\\') * noCRLF |       //or an escape sequence
                  noterm                      // or something else
              ).star(),
              TOKEN::TOK_COLON_STRING);
    //degenerate colon string: empty or just a solo escape char
    cl.insert(just(':') * WS.star() * just('\\').optional(), TOKEN::TOK_COLON_STRING);

    /* A simple quoted string, that can have escaped quotation marks inside. (UTF-8 allowed)
     * ...or a quoted string missing the terminating quotation mark (at line-end).*/
    cl.insert(just('"') * (noCRLF.without('"') | word("\\\"")).star() * just('"').optional(),
              TOKEN::TOK_QSTRING);

    /* Numbers */
    auto digit = range('0', '9');
    auto dot = just('.');
    auto sign = oneof("+-");
    auto num = digit.plus() * (dot * digit.star()).optional(); //no sign
    cl.insert(sign.optional() * num, TOKEN::TOK_NUMBER);

    /* Strings (UTF-8 allowed)
     * Starts with letter or underscore, may also include numbers or dots
     * (but not 2 consecutive dots and dots may not be followed by digits).
     * May end in zero, one or two dots.*/
    auto utf8 = range('A', 'Z') | range('a', 'z') | just('_') | range('\x80', '\xff');
    auto str = utf8 * (utf8 | digit | dot * utf8).star() * dot.optional(); //may end in dots
    cl.insert(str * dot.optional(), TOKEN::TOK_STRING);

   /* Parameter names: $ followed by a string not ending with a dot.
    * A single '$" also matches.*/
    cl.insert(just('$') * utf8 * (utf8 | digit | dot * utf8).star(), TOKEN::TOK_PARAM_NAME);
    cl.insert(just('$'), TOKEN::TOK_PARAM_NAME);
    /* Parameter names: $$ representing a value unique to the actual invocation.*/
    cl.insert(just('$') * just('$'), TOKEN::TOK_PARAM_NAME);

    //Have concrete keywords (also matching strings) late as for
    //equal length matches, the later will be selected.

    /* These shape definition keywords are case sensitive */
    cl.insert(oneof("MLCESTHP"), TOKEN::TOK_SHAPE_COMMAND);
    /* These keywords are case insensitive */
    cl.insert(iword("bye"), TOKEN::TOK_BYE);
    cl.insert(iword("defshape"), TOKEN::TOK_COMMAND_DEFSHAPE);
    cl.insert(iword("defstyle"), TOKEN::TOK_COMMAND_DEFSTYLE);
    cl.insert(iword("defcolor"), TOKEN::TOK_COMMAND_DEFCOLOR);
    cl.insert(iword("defdesign"), TOKEN::TOK_COMMAND_DEFDESIGN);
    cl.insert(iword("usedesign"), TOKEN::TOK_COMMAND_USEDESIGN);
    cl.insert(iword("use"), TOKEN::TOK_COMMAND_USESTYLE);
    cl.insert( word("arrows"), TOKEN::TOK_COMMAND_ARROWS);  //Originally these are case
    cl.insert( word("blocks"), TOKEN::TOK_COMMAND_BLOCKS);  //senistive. Is that OK?
    cl.insert(iword("defproc"), TOKEN::TOK_COMMAND_DEFPROC);
    cl.insert(iword("replay"), TOKEN::TOK_COMMAND_REPLAY);
    cl.insert(iword("set"), TOKEN::TOK_COMMAND_SET);
    cl.insert(iword("include"), TOKEN::TOK_COMMAND_INCLUDE);
    cl.insert(iword("if"), TOKEN::TOK_IF);
    cl.insert(iword("then"), TOKEN::TOK_THEN);
    cl.insert(iword("else"), TOKEN::TOK_ELSE);

    cl.insert(iword("box"), TOKEN::TOK_BOX);
    cl.insert(iword("boxcol"), TOKEN::TOK_BOXCOL);
    cl.insert(iword("col"), TOKEN::TOK_COLUMN);
    cl.insert(iword("row"), TOKEN::TOK_ROW);
    cl.insert(iword("cell"), TOKEN::TOK_CELL);
    cl.insert(iword("text"), TOKEN::TOK_TEXT);
    cl.insert(iword("shape"), TOKEN::TOK_SHAPE);
    cl.insert(iword("chart"), TOKEN::TOK_CHART);
    cl.insert(iword("space"), TOKEN::TOK_SPACE_COMMAND);
    cl.insert(iword("break"), TOKEN::TOK_BREAK_COMMAND);
    cl.insert(iword("multi"), TOKEN::TOK_MULTI_COMMAND);
    cl.insert(iword("around"), TOKEN::TOK_AROUND_COMMAND);
    cl.insert(iword("join"), TOKEN::TOK_JOIN_COMMAND);
    cl.insert(iword("copy"), TOKEN::TOK_COPY_COMMAND);
    cl.insert(iword("as"), TOKEN::TOK_AS);
    cl.insert(iword("mark"), TOKEN::TOK_MARK_COMMAND);
    cl.insert(iword("extend"), TOKEN::TOK_EXTEND_COMMAND);
    cl.insert(iword("template"), TOKEN::TOK_TEMPLATE_COMMAND);

    cl.insert(iword("below"), TOKEN::TOK_ALIGN_MODIFIER);
    cl.insert(iword("above"), TOKEN::TOK_ALIGN_MODIFIER);
    cl.insert(iword("leftof"), TOKEN::TOK_ALIGN_MODIFIER);
    cl.insert(iword("rightof"), TOKEN::TOK_ALIGN_MODIFIER);
    cl.insert(iword("bottom"), TOKEN::TOK_ALIGN_MODIFIER);
    cl.insert(iword("top"), TOKEN::TOK_ALIGN_MODIFIER);
    cl.insert(iword("left"), TOKEN::TOK_ALIGN_MODIFIER);
    cl.insert(iword("right"), TOKEN::TOK_ALIGN_MODIFIER);

    cl.insert( word("add"), TOKEN::TOK_CLONE_MODIFIER_ADD);
    cl.insert( word("move"), TOKEN::TOK_CLONE_MODIFIER_MOVE);
    cl.insert( word("drop"), TOKEN::TOK_CLONE_MODIFIER_DROP);
    cl.insert( word("update"), TOKEN::TOK_CLONE_MODIFIER_UPDATE);
    cl.insert( word("replace"), TOKEN::TOK_CLONE_MODIFIER_REPLACE);
    cl.insert( word("before"), TOKEN::TOK_CLONE_MODIFIER_BEFORE);
    cl.insert( word("recursive"), TOKEN::TOK_CLONE_MODIFIER_RECURSIVE);

    cl = make_deterministic(cl);
    cl = minimize(cl);
    return cl;
}

//When we do Color Syntax Highlight parsing the chart object is Csh instead.
template <bool CSH>
using ChartOrCsh = std::conditional<CSH, BlockCsh, BlockChart>::type;

/* This class tokenizes the input and generates a full symbol. */

template <typename TOKEN, bool CSH, typename READER_FN, typename CSH_STYPE, typename CSH_LTYPE> requires std::is_enum_v<TOKEN>
TOKEN get_token(sv_reader<CSH>& inp, ChartOrCsh<CSH>& C, READER_FN readandclassify, CSH_STYPE& val, CSH_LTYPE& loc) {
    while (true) {
        inp.try_pop();
        if (!inp.has(1)) {
            loc = inp.template commit<false, false>(0);
            return TOKEN::TOK_EOF;
        }

        auto [int_token, len] = readandclassify(0, inp);
    re_scanned:
        TOKEN token = TOKEN(int_token);
        if (len == 0) {
            loc = inp.template commit<false, false>(1);
            return TOKEN::TOK_UNRECOGNIZED_CHAR;
        }
        switch (token) {
        case TOKEN::TOK_WHITESPACE:
            inp.template commit<false, false>(len);
            continue;
        case TOKEN::TOK_NEWLINE:
            inp.template commit<true, false>(len);
            continue;
        case TOKEN::TOK_COMMENT: {
            //if equals to "/*" then this is a comment, that is not closed(file contains no "*/")
            const bool open_only = inp.view(len) == "/*";
            auto loc = inp.template commit<true, false>(len);
            if constexpr (CSH)
                C.AddCSH(loc, COLOR_COMMENT);
            if (open_only) {
                if constexpr (CSH)
                    C.AddCSH_Error(loc, "Unpaired beginning of block comment '/" "*'.");
                else
                    C.Error.Error(loc.start(), "Unpaired beginning of block comment '/" "*'.");
            }
            continue;
        }
        case TOKEN::TOK_PERCENT:
        case TOKEN::TOK_ATSYMBOL:
        case TOKEN::TOK_ASTERISK:
        case TOKEN::TOK_EQUAL:
        case TOKEN::TOK_COMMA:
        case TOKEN::TOK_SEMICOLON:
        case TOKEN::TOK_PLUS_PLUS:
        case TOKEN::TOK_PLUS:
        case TOKEN::TOK_MINUS:
        case TOKEN::TOK_OSBRACKET:
        case TOKEN::TOK_CSBRACKET:
        case TOKEN::TOK_TILDE:
        case TOKEN::TOK_OPARENTHESIS:
        case TOKEN::TOK_CPARENTHESIS:
        case TOKEN::TOK_CINLINE:
        case TOKEN::TOK_UNRECOGNIZED_CHAR:
        case TOKEN::TOK_EOF:
            loc = inp.template commit<false, false>(len);
            return token;

        case TOKEN::TOK_OCBRACKET:
        case TOKEN::TOK_CCBRACKET:
            val.input_text_ptr = inp.view(1).data();
            loc = inp.template commit<false, false>(len);
            return token;

        case TOKEN::TOK_COMP_OP:
            val.compare_op = [text = inp.view(len)] {
                if (text.size() == 1) {
                    if (text[0] == '<') return ECompareOperator::SMALLER;
                    _ASSERT(text[0] == '>'); return ECompareOperator::GREATER;
                }
                _ASSERT(text.size() == 2);
                if (text[0] == '<') {
                    if (text[1] == '=') return ECompareOperator::SMALLER_OR_EQUAL;
                    _ASSERT(text[1] == '>'); return ECompareOperator::NOT_EQUAL;
                }
                _ASSERT(text[0] == '=');
                if (text[1] == '=') return ECompareOperator::EQUAL;
                _ASSERT(text[1] == '>'); return ECompareOperator::GREATER_OR_EQUAL;
            } ();
            loc = inp.template commit<false, false>(len);
            return TOKEN::TOK_COMP_OP;

        case TOKEN::TOK_ARROW_FW:
            _ASSERT(len == 2);
            val.arrowstyle = inp.peek(0) == '-' ? EArrowStyle::SOLID : EArrowStyle::DASHED;
            loc = inp.template commit<false, false>(len);
            return TOKEN::TOK_ARROW_FW;

        case TOKEN::TOK_ARROW_BW:
            _ASSERT(len == 2);
            val.arrowstyle = inp.peek(1) == '-' ? EArrowStyle::SOLID : EArrowStyle::DASHED;
            loc = inp.template commit<false, false>(len);
            return TOKEN::TOK_ARROW_BW;

        case TOKEN::TOK_ARROW_BIDIR:
            _ASSERT(len == 3 || len == 4);
            val.arrowstyle = inp.peek(1) == '-' ? EArrowStyle::SOLID :
                             inp.peek(1) == '=' ? EArrowStyle::DOUBLE
                                                : EArrowStyle::DASHED;
            loc = inp.template commit<false, false>(len);
            return TOKEN::TOK_ARROW_BIDIR;

        case TOKEN::TOK_ARROW_NO:
            _ASSERT(len == 2);
            val.arrowstyle = inp.peek(1) == '-' ? EArrowStyle::SOLID : EArrowStyle::DOTTED;
            loc = inp.template commit<false, false>(len);
            return TOKEN::TOK_ARROW_NO;

        case TOKEN::TOK_SHAPE_COMMAND: {
            _ASSERT(len == 1);
            const char c = inp.peek(0);
            loc = inp.template commit<false, false>(len);
            switch (c) {
            case 'M': val.shapecommand = ShapeElement::MOVE_TO; return TOKEN::TOK_SHAPE_COMMAND;
            case 'L': val.shapecommand = ShapeElement::LINE_TO; return TOKEN::TOK_SHAPE_COMMAND;
            case 'C': val.shapecommand = ShapeElement::CURVE_TO; return TOKEN::TOK_SHAPE_COMMAND;
            case 'E': val.shapecommand = ShapeElement::CLOSE_PATH; return TOKEN::TOK_SHAPE_COMMAND;
            case 'S': val.shapecommand = ShapeElement::SECTION_BG; return TOKEN::TOK_SHAPE_COMMAND;
            case 'T': val.shapecommand = ShapeElement::TEXT_AREA; return TOKEN::TOK_SHAPE_COMMAND;
            case 'H': val.shapecommand = ShapeElement::HINT_AREA; return TOKEN::TOK_SHAPE_COMMAND;
            case 'P': val.shapecommand = ShapeElement::PORT; return TOKEN::TOK_SHAPE_COMMAND;
            default: _ASSERT(0); return TOKEN::TOK_UNRECOGNIZED_CHAR;
            }
        }
        case TOKEN::TOK_ALIGN_MODIFIER:
            // Or-ing 0x20 will make A->a
            switch (inp.peek(0) | 0x20) {
            case 'a': val.alignmodifier = {true,  EDirection::Above}; break;    //'above'
            case 't': val.alignmodifier = {false,  EDirection::Above}; break;   //'top'
            case 'b': val.alignmodifier = {(inp.peek(1) | 0x20) =='e',  EDirection::Below}; break;  //'below'=true, 'bottom'=false
            case 'l': val.alignmodifier = {len==6,  EDirection::Left}; break;   //'leftof' = true, 'left'==false
            case 'r': val.alignmodifier = {len==7,  EDirection::Right}; break;  //'rightof' = true, 'right' = false
            default: _ASSERT(0); break;
            }
            loc = inp.template commit<false, false>(len);
            return TOKEN::TOK_ALIGN_MODIFIER;

        //built-in keywords
        case TOKEN::TOK_BYE:
        case TOKEN::TOK_COMMAND_DEFSHAPE:
        case TOKEN::TOK_COMMAND_DEFCOLOR:
        case TOKEN::TOK_COMMAND_DEFSTYLE:
        case TOKEN::TOK_COMMAND_DEFDESIGN:
        case TOKEN::TOK_COMMAND_USEDESIGN:
        case TOKEN::TOK_COMMAND_USESTYLE:
        case TOKEN::TOK_COMMAND_ARROWS:
        case TOKEN::TOK_COMMAND_BLOCKS:
        case TOKEN::TOK_COMMAND_DEFPROC:
        case TOKEN::TOK_COMMAND_REPLAY:
        case TOKEN::TOK_COMMAND_SET:
        case TOKEN::TOK_IF:
        case TOKEN::TOK_THEN:
        case TOKEN::TOK_ELSE:
        case TOKEN::TOK_COMMAND_INCLUDE:
        case TOKEN::TOK_BOX:
        case TOKEN::TOK_BOXCOL:
        case TOKEN::TOK_COLUMN:
        case TOKEN::TOK_ROW:
        case TOKEN::TOK_CELL:
        case TOKEN::TOK_TEXT:
        case TOKEN::TOK_SHAPE:
        case TOKEN::TOK_SPACE_COMMAND:
        case TOKEN::TOK_BREAK_COMMAND:
        case TOKEN::TOK_MULTI_COMMAND:
        case TOKEN::TOK_AROUND_COMMAND:
        case TOKEN::TOK_JOIN_COMMAND:
        case TOKEN::TOK_COPY_COMMAND:
        case TOKEN::TOK_AS:
        case TOKEN::TOK_MARK_COMMAND:
        case TOKEN::TOK_EXTEND_COMMAND:
        case TOKEN::TOK_TEMPLATE_COMMAND:
        case TOKEN::TOK_CLONE_MODIFIER_ADD:
        case TOKEN::TOK_CLONE_MODIFIER_MOVE:
        case TOKEN::TOK_CLONE_MODIFIER_DROP:
        case TOKEN::TOK_CLONE_MODIFIER_UPDATE:
        case TOKEN::TOK_CLONE_MODIFIER_REPLACE:
        case TOKEN::TOK_CLONE_MODIFIER_BEFORE:
        case TOKEN::TOK_CLONE_MODIFIER_RECURSIVE:
        case TOKEN::TOK_CHART:

        //special tokens, whose value is their text
        case TOKEN::TOK_NUMBER:
        case TOKEN::TOK_PARAM_NAME:
            val.str = inp.view(len);
            loc = inp.template commit<false, false>(len);
            return token;

        case TOKEN::TOK_COLON_STRING: {
            const std::string_view text = inp.view(len);
            _ASSERT(text.starts_with(':'));
            loc = inp.template commit<true, false>(len);
            val.multi_str.init();
            if constexpr (CSH) {
                C.AddCSH_ColonString_CheckAndAddEscapeHint(loc, text, true);
                C.AddColonLabel(loc, text);
            } else
                val.multi_str.set_owning(process_colon_string(text, loc.start()));
            return TOKEN::TOK_COLON_STRING;
        }
        case TOKEN::TOK_COLON_QUOTED_STRING: { //This is transmuted to a TOK_COLON_STRING
            //if last char is not '"' the quotation is not closed
            std::string_view text = inp.view(len);
            _ASSERT(text.starts_with(':'));
            loc = inp.template commit<false, false>(len);
            if constexpr (CSH) {
                if (!text.ends_with('"'))
                    C.AddCSH_ErrorAfter(loc, "Missing closing quotation mark.");
                C.AddCSH_ColonString_CheckAndAddEscapeHint(loc, text, false);
                C.AddColonLabel(loc, text);
                val.multi_str.init();
            } else {
                const char* const colon_pos = text.data();
                const bool closed = text.ends_with('"');
                if (closed)
                    text.remove_suffix(1);
                text.remove_prefix(1); //the colon
                remove_head_tail_whitespace(text);
                FileLineCol pos = loc.start();
                pos.AdvanceCol(text.data() - colon_pos);
                if (!closed)
                    C.Error.Error(loc.start(),
                                  "This opening quotation mark misses its closing pair. "
                                  "Assuming string termination at line-end.",
                                  "Quoted strings cannot have line breaks. Use \'\\n\' to insert a line break.");
                text.remove_prefix(1); //leading quotation mark
                pos.AdvanceCol(1);
                val.multi_str.CombineThemToMe(pos.Print(), text);
            }
            return TOKEN::TOK_COLON_STRING;
        }
        case TOKEN::TOK_STRING: {
            std::string_view text = inp.view(len);
            //TOK_STRING: may end in two dots. In that case we should
            //re-scan without the two dots. (E.g., in "xxx..", we want to
            //scan "xxx" as TOK_XXX and not as TOK_STRING
            if (text.ends_with("..")) {
                text.remove_suffix(2);
                sv_reader<CSH> input(text, {});
                std::tie(int_token, len) = readandclassify(0, input);
                goto re_scanned;
            }
            loc = inp.template commit<false, false>(len);
            val.str.set(text);
            return TOKEN::TOK_STRING;
        }
        case TOKEN::TOK_QSTRING: {
            std::string_view text = inp.view(len);
            loc = inp.template commit<false, false>(len);
            text.remove_prefix(1); //opening quotation mark
            //TOK_QSTRING: if last char is not '"' the quotation is not closed
            if (text.ends_with('"'))
                text.remove_suffix(1); //closing quotation mark
            else if constexpr (CSH)
                C.AddCSH_ErrorAfter(loc, "Missing closing quotation mark.");
            else
                C.Error.Error(loc.start(),
                              "This opening quotation mark misses its closing pair. "
                              "Assuming string termination at line-end.",
                              "Quoted strings cannot have line breaks. Use \'\\n\' to insert a line break.");
            val.str.set(text);
            return TOKEN::TOK_QSTRING;
        }
        case TOKEN::TOK_OINLINE: {
            const auto loc_oinline = inp.template commit<false, false>(len);
            loc = inp.template commit<false, false>(0);
            const char* start_text_ptr = inp.view(0).data(), * end_text_ptr = start_text_ptr;
            unsigned nesting_level = 0;
            while (inp.has(1)) {
                auto [int_token, len] = readandclassify(0, inp);
                switch (TOKEN(int_token)) {
                case TOKEN::TOK_OINLINE:
                    nesting_level++;
                    FALLTHROUGH;
                default:
                    if (!len) len = 1;
                    end_text_ptr = inp.view(len).data() + len;
                    loc.merge(inp.template commit<true, false>(len));
                    break;
                case TOKEN::TOK_CINLINE:
                    if (nesting_level) {
                        nesting_level--;
                        end_text_ptr = inp.view(len).data() + len;
                        loc.merge(inp.template commit<true, false>(len));
                        break;
                    }
                    inp.template commit<true, false>(len);
                    val.str = std::string_view(start_text_ptr, end_text_ptr - start_text_ptr);
                    return TOKEN::TOK_INLINE;
                }
            }
            //Unpaired OINLINE
            if constexpr (CSH) {
                C.AddCSH_Error(loc_oinline, "Unpaired beginning of inlined chart '" INLINE_OPEN_STR "'.");
                C.AddCSH(loc, COLOR_COMMENT); //we have consumed the whole file, color as comment
            } else
                C.Error.Error(loc_oinline.start(), "Unpaired beginning of inlined chart '" INLINE_OPEN_STR "'.");
            loc = loc_oinline;
            return TOKEN::TOK_UNMATCHED_INLINE;
        }
        default:
            _ASSERT(0);
            loc = inp.template commit<false, false>(len);
            return token;
        } //switch(token)
    }
}
}
