#include <iostream> #include <sstream> #include <iomanip> #include <stdexcept> #include "string_util.hpp" #include "tokenizer.hpp" namespace xtokenizer { const char *createMessage(const char *msg, const ParseInfo &info) { std::ostringstream ostr; ostr << std::endl; ostr << info.line << std::endl; for (int i = 0; i < info.column; i++) { ostr << ' '; } ostr << "^" << std::endl; for (int i = 0; i < info.column; i++) { ostr << ' '; } ostr << msg << std::endl; return ostr.str().c_str(); } const char *createMessage(const char *msg, const Token &token) { std::ostringstream ostr; ostr << std::endl; ostr << token.line << std::endl; for (int i = 0; i < token.column; i++) { ostr << ' '; } ostr << "^" << std::endl; for (int i = 0; i < token.column; i++) { ostr << ' '; } ostr << msg << std::endl; return ostr.str().c_str(); } ParseException::ParseException(const char *msg, const ParseInfo &info) : std::logic_error(createMessage(msg, info)) {} ParseException::ParseException(const char *msg, const Token &token) : std::logic_error(createMessage(msg, token)) {} // ParseFunc typedef void (*ParseFunc)(ParseInfo &info); /** * 指定された文字が、シンボルとして利用可能な文字か否か返します。 * * @param c 文字 * @return シンボルとして利用可能な文字の場合 true */ constexpr bool isSymbolChars(char c) { return ((('A' <= c) && (c <= 'Z')) || (('a' <= c) && (c <= 'z')) || (('0' <= c) && (c <= '9')) || (c == '_')); } /** * 指定された文字が、2進数として利用可能な文字か否か返します。 * * @param c 文字 * @return 2進数として利用可能な文字の場合 true */ constexpr bool isBinChars(char c) { return ((c == '0') || (c == '1') || (c == '_')); } /** * 指定された文字が、8進数として利用可能な文字か否か返します。 * * @param c 文字 * @return 8進数として利用可能な文字の場合 true */ constexpr bool isOctChars(char c) { return ((('0' <= c) && (c <= '7')) || (c == '_')); } /** * 指定された文字が、10進数として利用可能な文字か否か返します。 * * @param c 文字 * @return 10進数として利用可能な文字の場合 true */ constexpr bool isDecChars(char c) { return ((('0' <= c) && (c <= '9')) || (c == '_')); } /** * 指定された文字が、16進数として利用可能な文字か否か返します。 * * @param c 文字 * @return 16進数として利用可能な文字の場合 true */ constexpr bool isHexChars(char c) { return ((('0' <= c) && (c <= '9')) || (('A' <= c) && (c <= 'F')) || (('a' <= c) && (c <= 'f')) || (c == '_')); } /** * 指定された文字を数値に変換します。 * * @param c 文字 * @return 数値 */ constexpr int toNumber(char c) { return (('0' <= c) && (c <= '9')) ? (c - '0') : (('A' <= c) && (c <= 'F')) ? (c - 'A' + 10) : (('a' <= c) && (c <= 'f')) ? (c - 'a' + 10) : throw std::invalid_argument("Invalid hexdecimal character"); } /** * (共通) 記号解析 */ void parseMark(ParseInfo &info, const TokenTypeInfo &tt) { // 先頭文字は合致している前提 const TokenTypeInfo *ttInfo = &tt; while (ttInfo != nullptr) { if (startsWith(info.line, ttInfo->token, info.column)) { // 合致 Token token(info.lineNumber, info.column, ttInfo->type, ttInfo->token); info.tokens.emplace_back(token); info.column += ttInfo->len; return; } // 合致していない ttInfo = ttInfo->next; } throw ParseException("Invalid TokenTypeInfo Chain", info); } /** * (共通) 記号解析 */ void parseMark(ParseInfo &info) { const TokenTypeInfo ttInfo = getTokenTypeInfo(info.line, info.column); if (ttInfo.type == TokenType::T_UNKNOWN) { throw ParseException("[Compiler Bug] Invlid TokenTypeInfo Chain", info); } Token token(info.lineNumber, info.column, ttInfo.type, ttInfo.token); info.tokens.emplace_back(token); info.column += ttInfo.len; } /** * (共通) 複数行に跨るブロック解析。 * * @param info 解析情報 * @param type タイプ * @param end ブロック終了文字列 * @param endLen ブロック終了文字列長 * @param isEscape エスケープ有効か否か */ void parseBlock(ParseInfo &info, TokenType type, const char *end, int endLen) { int endPos = unescapedIndexOf(info.line, end, info.column); int startLineNumber = info.lineNumber; std::string str = ""; while (endPos < 0) { str += escapedString(info.line, info.column, (info.line.length() - info.column)); str += '\n'; if (info.readNext()) { endPos = unescapedIndexOf(info.line, end, info.column); } else { throw ParseException("not found block end", info); } } // end が見つかった int len = endPos - info.column; std::string escaped = str + escapedString(info.line, info.column, len); Token token(startLineNumber, info.column, type, escaped); info.tokens.emplace_back(token); // カラム位置を次の位置に設定する info.column += len + endLen; } /** * (共通) 複数行に跨るブロック解析(エスケープ文字無効)。 * * @param info 解析情報 * @param type タイプ * @param end ブロック終了文字列 * @param endLen ブロック終了文字列長 * @param isEscape エスケープ有効か否か */ void parseBlockNoEscape(ParseInfo &info, TokenType type, const char *end, int endLen) { int endPos = indexOf(info.line, end, info.column); int startLineNumber = info.lineNumber; std::string str = ""; while (endPos < 0) { str += info.line.substr(info.column, (info.line.length() - info.column)); str += '\n'; if (info.readNext()) { endPos = indexOf(info.line, end, info.column); } else { throw ParseException("not found block end", info); } } // end が見つかった int len = endPos - info.column; std::string escaped = str + info.line.substr(info.column, len); Token token(startLineNumber, info.column, type, escaped); info.tokens.emplace_back(token); // カラム位置を次の位置に設定する info.column += len + endLen; } /** * 指定された token の値 (value.i) を設定します。 * * @param info 解析情報(エラー発生時にのみ参照されます) * @param token 対象トークン * @param base 基数 * @param offset 文字列解析位置オフセット */ void parseNumberInteger(const ParseInfo &info, Token &token, int base, int offset) { uint64_t tmp = 0; uint64_t val = 0; for (std::size_t pos = offset; pos < token.str.length(); pos++) { if (token.str[pos] != '_') { tmp = val * base; if (tmp < val) { throw ParseException("Overflow", info); } val = tmp; val += toNumber(token.str[pos]); } } token.value.i = val; } /** * 指定された token の値 (value.f) を設定します。 * * @param info 解析情報(未使用) * @param token 対象トークン */ void parseNumberFloat(const ParseInfo &, Token &token) { std::size_t pos = 0; // 整数部分解析 double val = 0; for (; pos < token.str.length(); pos++) { if (token.str[pos] == '.') { pos++; break; } if (token.str[pos] == 'e') { break; } if (token.str[pos] != '_') { val *= 10; val += toNumber(token.str[pos]); } } // 小数部分解析 bool isDiv = false; double power = 1; for (; pos < token.str.length(); pos++) { if (token.str[pos] == 'e') { // e あり pos++; if (pos < token.str.length()) { isDiv = (token.str[pos] == '-'); pos++; } break; } if (token.str[pos] != '_') { val *= 10; power *= 10; val += toNumber(token.str[pos]); } } // 乗数部分解析 int mul = 0; for (; pos < token.str.length(); pos++) { if (token.str[pos] != '_') { mul *= 10; mul += toNumber(token.str[pos]); } } val = val / power; if (isDiv) { for (int i = 0; i < mul; i++) { val /= 10; } } else { for (int i = 0; i < mul; i++) { val *= 10; } } token.value.f = val; } /** * (共通) 数値解析 * * @param info 解析情報 * @param isTargetChars 対象文字列判定用関数 * @param offset オフセット(解析開始位置) * @param base 基数 */ void parseNumberCore(ParseInfo &info, bool (*isTargetChars)(char), int offset, int base) { bool (*isChars)(char) = isTargetChars; int column = info.column + offset; TokenType type = TokenType::T_INTEGER; while (column < static_cast<int>(info.line.length())) { if (!isChars(info.line[column])) { // 対象外文字列に到達 if (isChars != isDecChars) { // 10進数でなければ、無条件で終了 break; } // 10進数の場合、小数であれば対応する。 if ((info.line[column] == '.') && (type == TokenType::T_INTEGER)) { // ドット or e が存在するため小数 type = TokenType::T_FLOAT; } else if (info.line[column] == 'e') { // 乗数部分処理: 乗数部分は10進数 type = TokenType::T_FLOAT; if ((column + 2) < static_cast<int>(info.line.length())) { isChars = isDecChars; column += 2; } } else { break; } } column++; } int len = column - info.column; Token token(info.lineNumber, info.column, type, info.line.substr(info.column, len)); if (type == TokenType::T_FLOAT) { try { parseNumberFloat(info, token); } catch (const std::invalid_argument &e) { std::ostringstream ostr; ostr << "Invalid Float (" << e.what() << ")"; throw ParseException(ostr.str().c_str(), info); } } else { try { parseNumberInteger(info, token, base, offset); } catch (const std::invalid_argument &e) { std::ostringstream ostr; ostr << "Invalid Integer (" << e.what() << ")"; ostr << "[token=" << token.str << "]"; throw ParseException(ostr.str().c_str(), info); } } info.tokens.emplace_back(token); info.column += len; } /** * 不正文字。 * * @param info 解析情報 */ void parseInvalid(ParseInfo &info) { throw ParseException("Invlid Charactor", info); } /** * 空白解析。 * * @param info 解析情報 */ void parseSpace(ParseInfo &info) { // 空白が続く間をトークンとする。 int startPos = info.column; while (std::isspace(info.line[info.column])) { info.column++; } Token token(info.lineNumber, startPos, TokenType::T_SPACE, info.line.substr(startPos, (info.column - startPos))); info.tokens.emplace_back(token); } /** * 改行解析。 * * @param info 解析情報 */ void parseEndOfLine(ParseInfo &info) { // getline で読み込んだ場合、ここに来るのは 0x0D (CR) の時のみで // 改行処理は別途するため、ここでは何もしない。 info.column++; } /** * ! 解析。 * * @param info 解析情報 */ inline void parseNot(ParseInfo &info) { parseMark(info, not8); } /** * 文字列リテラル解析。 * * @param info 解析情報 */ void parseStringDq(ParseInfo &info) { if (startsWith(info.line, "\"\"\"", info.column)) { // 複数行に対応した文字列リテラル info.column += 3; parseBlock(info, TokenType::T_STRING_T_DQ, "\"\"\"", 3); } else { // 文字列 info.column += 1; parseBlock(info, TokenType::T_STRING_DQ, "\"", 1); } } /** * 行コメント (#) * * @param info 解析情報 */ void parseHash(ParseInfo &info) { Token token(info.lineNumber, info.column, TokenType::T_COMMENT, info.line.substr(info.column + 1)); info.tokens.emplace_back(token); info.column = info.line.length(); } /** * $ 解析。 * * @param info 解析情報 */ inline void parseDoller(ParseInfo &info) { parseMark(info, dollar); } /** * % 解析。 * * @param info 解析情報 */ inline void parseMod(ParseInfo &info) { parseMark(info, mod1); } /** * & 解析。 * * @param info 解析情報 */ inline void parseAnd(ParseInfo &info) { parseMark(info, and3); } /** * 文字リテラル解析。 * * @param info 解析情報 */ void parseStringSq(ParseInfo &info) { int endPos = unescapedIndexOf(info.line, "'", info.column + 1); if (endPos >= 0) { std::string escaped = escapedString(info.line, info.column + 1, endPos - info.column - 1); Token token(info.lineNumber, info.column, TokenType::T_STRING_SQ, escaped); info.tokens.emplace_back(token); } else { throw ParseException("not found '\''", info); } } /** * ( 解析。 * * @param info 解析情報 */ inline void parseLParen(ParseInfo &info) { parseMark(info, lparen); } /** * ) 解析。 * * @param info 解析情報 */ inline void parseRParen(ParseInfo &info) { parseMark(info, rparen); } /** * * 解析。 * * @param info 解析情報 */ inline void parseMul(ParseInfo &info) { parseMark(info, mul3); } // * /** * + 解析。 * * @param info 解析情報 */ inline void parseAdd(ParseInfo &info) { parseMark(info, add2); } /** * , 解析。 * * @param info 解析情報 */ inline void parseComma(ParseInfo &info) { parseMark(info, comma); } /** * - 解析。 * * @param info 解析情報 */ inline void parseSub(ParseInfo &info) { parseMark(info, sub4); } /** * . 解析。 * * @param info 解析情報 */ inline void parseDot(ParseInfo &info) { parseMark(info, dot2); } /** * / 解析。 * * @param info 解析情報 */ void parseDiv(ParseInfo &info) { switch (info.line[info.column + 1]) { case '/': // 行コメント開始 { Token token(info.lineNumber, info.column, TokenType::T_COMMENT, info.line.substr(info.column + 2)); info.tokens.emplace_back(token); info.column = info.line.length(); break; } case '*': // ブロックコメント開始 info.column += 2; parseBlock(info, TokenType::T_COMMENT, "*/", 2); break; case '+': // ネスト可能なブロックコメント開始 // TODO break; default: // 上記以外 parseMark(info, div1); break; } } /** * 0 解析。 * * @param info 解析情報 */ void parseNumberZero(ParseInfo &info) { // 0, 2進数(0b[0-1_]) 8進数(0[0-9]), 16進数(0x[A-Fa-f0-9_]) 判定 int column = info.column + 1; if (column < static_cast<int>(info.line.length())) { switch (info.line[column]) { case 'b': // 2進数 [0,1,_] で構成 parseNumberCore(info, isBinChars, 2, 2); break; case 'x': // 16進数 [0-9,a-f,A-F,_] で構成 parseNumberCore(info, isHexChars, 2, 16); break; default: { if (((column + 1) < static_cast<int>(info.line.length())) && ((info.line[column] == '.') || (info.line[column] == 'e'))) { // 10進数(小数) parseNumberCore(info, isDecChars, 0, 10); } else { // 8 進数 parseNumberCore(info, isOctChars, 1, 8); } break; } } } else { // 0 Token token(info.lineNumber, info.column, TokenType::T_INTEGER, info.line.substr(info.column, 1)); token.value.i = 0; info.tokens.emplace_back(token); info.column += 1; } } /** * 1-9 解析。 * * @param info 解析情報 */ void parseNumber(ParseInfo &info) { parseNumberCore(info, isDecChars, 0, 10); } /** * : 解析。 * * @param info 解析情報 */ inline void parseColon(ParseInfo &info) { parseMark(info, colon); } /** * ; 解析。 * * @param info 解析情報 */ inline void parseSemicolon(ParseInfo &info) { parseMark(info, semicolon); } /** * < 解析。 * * @param info 解析情報 */ inline void parseLt(ParseInfo &info) { parseMark(info, lt8); } /** * = 解析。 * * @param info 解析情報 */ inline void parseEq(ParseInfo &info) { parseMark(info, eq2); } /** * > 解析。 * * @param info 解析情報 */ inline void parseGt(ParseInfo &info) { parseMark(info, gt5); } /** * ? 解析。 * * @param info 解析情報 */ inline void parseQuestion(ParseInfo &info) { parseMark(info, question3); } /** * @ 解析。 * * @param info 解析情報 */ inline void parseAt(ParseInfo &info) { parseMark(info, at); } /** * A-Z, a-z, _ 解析。 * * @param info 解析情報 */ void parseSymbol(ParseInfo &info) { int column = info.column + 1; while (column < static_cast<int>(info.line.length())) { if (!isSymbolChars(info.line[column])) { break; } column++; } int len = column - info.column; Token token(info.lineNumber, info.column, TokenType::T_IDENTIFIER, info.line.substr(info.column, len)); // キーワード解析 const KeywordInfo keywordInfo = getKeywordInfo(token.str); if (keywordInfo.keyword != Keyword::K_UNKNOWN) { token.type = TokenType::T_KEYWORD; token.keyword = keywordInfo.keyword; } info.tokens.emplace_back(token); info.column += len; } /** * [ 解析。 * * @param info 解析情報 */ inline void parseLBracket(ParseInfo &info) { parseMark(info, lbracket); } /** * '\' 解析。 * * @param info 解析情報 */ inline void parseEscape(ParseInfo &info) { parseMark(info, escape); } /** * ] 解析。 * * @param info 解析情報 */ inline void parseRBracket(ParseInfo &info) { parseMark(info, rbracket); } /** * ^ 解析。 * * @param info 解析情報 */ inline void parseHat(ParseInfo &info) { parseMark(info, hat1); } /** * ` 解析。 * * @param info 解析情報 */ inline void parseStringBq(ParseInfo &info) { if (startsWith(info.line, "```", info.column)) { // 複数行に対応した文字列リテラル info.column += 3; parseBlockNoEscape(info, TokenType::T_STRING_T_BQ, "```", 3); } else { // 文字列 info.column += 1; parseBlockNoEscape(info, TokenType::T_STRING_BQ, "`", 1); } } /** * { 解析。 * * @param info 解析情報 */ inline void parseLCurly(ParseInfo &info) { parseMark(info, lcurly); } /** * | 解析。 * * @param info 解析情報 */ inline void parseOr(ParseInfo &info) { parseMark(info, or3); } /** * } 解析。 * * @param info 解析情報 */ inline void parseRCurly(ParseInfo &info) { parseMark(info, rcurly); } /** * ~ 解析。 * * @param info 解析情報 */ inline void parseTild(ParseInfo &info) { parseMark(info, tild1); } constexpr ParseFunc parseTable[] = { parseInvalid, // 0x00: NUL parseInvalid, // 0x01: SOH (ヘッダ開始) parseInvalid, // 0x02: STX (テキスト開始) parseInvalid, // 0x03: ETX (テキスト終了) parseInvalid, // 0x04: EOT (転送終了) parseInvalid, // 0x05: ENQ (照会) parseInvalid, // 0x06: ACK (受信確認) parseInvalid, // 0x07: BEL (警告) parseInvalid, // 0x08: BS (後退) parseSpace, // 0x09: HT (水平タブ) parseEndOfLine, // 0x0A: LF (改行) parseSpace, // 0x0B: VT (垂直タブ) parseSpace, // 0x0C: FF (改ページ) parseEndOfLine, // 0x0D: CR (復帰) parseInvalid, // 0x0E: SO (シフトアウト) parseInvalid, // 0x0F: SI (シフトイン) parseInvalid, // 0x10: DLE (データリンクエスケープ) parseInvalid, // 0x11: DC1 (装置制御1) parseInvalid, // 0x12: DC2 (装置制御2) parseInvalid, // 0x13: DC3 (装置制御3) parseInvalid, // 0x14: DC4 (装置制御4) parseInvalid, // 0x15: NAK (受信失敗) parseInvalid, // 0x16: SYN (同期) parseInvalid, // 0x17: ETB (転送ブロック終了) parseInvalid, // 0x18: CAN (キャンセル) parseInvalid, // 0x19: EM (メディア終了) parseInvalid, // 0x1A: SUB (置換) parseInvalid, // 0x1B: ESC (エスケープ) parseInvalid, // 0x1C: FS (フォーム区切り) parseInvalid, // 0x1D: GS (グループ区切り) parseInvalid, // 0x1E: RS (レコード区切り) parseInvalid, // 0x1F: US (ユニット区切り) parseSpace, // 0x20: SPA (空白文字) parseNot, // 0x21: ! parseStringDq, // 0x22: " parseHash, // 0x23: # parseDoller, // 0x24: $ parseMod, // 0x25: % parseAnd, // 0x26: & parseStringSq, // 0x27: ' parseLParen, // 0x28: ( parseRParen, // 0x29: ) parseMul, // 0x2A: * parseAdd, // 0x2B: + parseComma, // 0x2C: , parseSub, // 0x2D: - parseDot, // 0x2E: . parseDiv, // 0x2F: / parseNumberZero, // 0x30: 0 parseNumber, // 0x31: 1 parseNumber, // 0x32: 2 parseNumber, // 0x33: 3 parseNumber, // 0x34: 4 parseNumber, // 0x35: 5 parseNumber, // 0x36: 6 parseNumber, // 0x37: 7 parseNumber, // 0x38: 8 parseNumber, // 0x39: 9 parseColon, // 0x3A: : parseSemicolon, // 0x3B: ; parseLt, // 0x3C: < parseEq, // 0x3D: = parseGt, // 0x3E: > parseQuestion, // 0x3F: ? parseAt, // 0x40: @ parseSymbol, // 0x41: A parseSymbol, // 0x42: B parseSymbol, // 0x43: C parseSymbol, // 0x44: D parseSymbol, // 0x45: E parseSymbol, // 0x46: F parseSymbol, // 0x47: G parseSymbol, // 0x48: H parseSymbol, // 0x49: I parseSymbol, // 0x4A: J parseSymbol, // 0x4B: K parseSymbol, // 0x4C: L parseSymbol, // 0x4D: M parseSymbol, // 0x4E: N parseSymbol, // 0x4F: O parseSymbol, // 0x50: P parseSymbol, // 0x51: Q parseSymbol, // 0x52: R parseSymbol, // 0x53: S parseSymbol, // 0x54: T parseSymbol, // 0x55: U parseSymbol, // 0x56: V parseSymbol, // 0x57: W parseSymbol, // 0x58: X parseSymbol, // 0x59: Y parseSymbol, // 0x5A: Z parseLBracket, // 0x5B: [ parseEscape, // 0x5C: '\' parseRBracket, // 0x5D: ] parseHat, // 0x5E: ^ parseSymbol, // 0x5F: _ parseStringBq, // 0x60: ` parseSymbol, // 0x61: a parseSymbol, // 0x62: b parseSymbol, // 0x63: c parseSymbol, // 0x64: d parseSymbol, // 0x65: e parseSymbol, // 0x66: f parseSymbol, // 0x67: g parseSymbol, // 0x68: h parseSymbol, // 0x69: i parseSymbol, // 0x6A: j parseSymbol, // 0x6B: k parseSymbol, // 0x6C: l parseSymbol, // 0x6D: m parseSymbol, // 0x6E: n parseSymbol, // 0x6F: o parseSymbol, // 0x70: p parseSymbol, // 0x71: q parseSymbol, // 0x72: r parseSymbol, // 0x73: s parseSymbol, // 0x74: t parseSymbol, // 0x75: u parseSymbol, // 0x76: v parseSymbol, // 0x77: w parseSymbol, // 0x78: x parseSymbol, // 0x79: y parseSymbol, // 0x7A: z parseLCurly, // 0x7B: { parseOr, // 0x7C: | parseRCurly, // 0x7D: } parseTild, // 0x7E: } parseInvalid, // 0x7F: DEL(削除) }; //////////////////////////////////////////////////////////////////////////// // // ParseInfo // ParseInfo::ParseInfo(const char *file) : tokens(), fileName(file), line(), lineNumber(0), column(0), inStream(std::ifstream(file)) { // NOP } ParseInfo::~ParseInfo() { inStream.close(); } /** * 次の行を読み込みます。 * * @return 読込で来たら true */ bool ParseInfo::readNext() { if (std::getline(inStream, line)) { lineNumber++; column = 0; std::cout << line << std::endl; return true; } return false; } //////////////////////////////////////////////////////////////////////////// // // Tokenizer // Tokenizer::Tokenizer(const char *file) : parseInfo(file) { // NOP } Tokenizer::~Tokenizer() { // NOP } std::vector<Token> &Tokenizer::parse() { if (!parseInfo.inStream.is_open()) { // error return parseInfo.tokens; } parseInfo.lineNumber = 0; while (parseInfo.readNext()) { while (static_cast<size_t>(parseInfo.column) < parseInfo.line.length()) { unsigned char val = static_cast<unsigned char>(parseInfo.line[parseInfo.column]); if ((val & 0x80) == 0) { parseTable[val](parseInfo); } else { std::cout << "Invalid value" << std::endl; } } } return parseInfo.tokens; } } // namespace xtokenizer