UNPKG

cookeylang-ts

Version:
282 lines (281 loc) 13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Lexer = void 0; const token_1 = require("./token"); class Lexer { constructor(code, file) { this.hasError = false; this.line = 1; this.col = 1; this.i = 0; this.tokens = []; this.reserved = { "var": token_1.TType.VAR, "final": token_1.TType.FINAL, "deleteVariable": token_1.TType.DELETEVARIABLE, "function": token_1.TType.FUNCTION, "ret": token_1.TType.RET, "exit": token_1.TType.EXIT, "lambda": token_1.TType.LAMBDA, "class": token_1.TType.CLASS, "this": token_1.TType.THIS, "extends": token_1.TType.EXTENDS, "superClass": token_1.TType.SUPERCLASS, "NaV": token_1.TType.NAV, "true": token_1.TType.TRUE, "false": token_1.TType.FALSE, "if": token_1.TType.IF, "el": token_1.TType.EL, "and": token_1.TType.AND, "or": token_1.TType.OR, "foreach": token_1.TType.FOREACH, "for": token_1.TType.FOR, "forrep": token_1.TType.FORREP, "in": token_1.TType.IN, "while": token_1.TType.WHILE, "break": token_1.TType.BREAK, "do": token_1.TType.DO, "switch": token_1.TType.SWITCH, "case": token_1.TType.CASE, "default": token_1.TType.DEFAULT }; this.code = code; this.file = file; } init() { while (this.isValid()) { if (this.i == 0 && this.curr() == "#" && this.peek() == "!") { this.next(); this.next(); while (this.curr() != '\n') { this.next(); } } if (this.isNumber(this.curr())) { let number = this.curr(); while (this.isValid() && (this.isNumber(this.peek()) || this.peek() == '_')) { this.next(); if (this.curr() != '_') number += this.curr(); } if (this.peek() == '.') { this.next(); number += this.curr(); while (this.isValid() && (this.isNumber(this.peek()) || this.peek() == '_')) { this.next(); if (this.curr() != '_') number += this.curr(); } } this.append(token_1.TType.NUMBER, Number(number)); } else if (this.isAlpha(this.curr())) { let text = this.curr(); while (this.isValid() && this.isAlphaNum(this.peek())) { this.next(); text += this.curr(); } if (this.reserved[text] != null) this.append(this.reserved[text], text); else this.append(token_1.TType.IDENTIFIER, text); } else { const char = this.curr(); switch (char) { case '(': this.append(token_1.TType.LEFT_PAREN); break; case ')': this.append(token_1.TType.RIGHT_PAREN); break; case '{': this.append(token_1.TType.LEFT_BRACE); break; case '}': this.append(token_1.TType.RIGHT_BRACE); break; case ',': this.append(token_1.TType.COMMA); break; case '.': this.append(token_1.TType.DOT); break; case ';': this.append(token_1.TType.SEMI); break; case '@': this.append(token_1.TType.AT); break; case '?': this.append(token_1.TType.QUE); break; case ':': this.append(token_1.TType.COL); break; case '+': this.match('=') ? this.append(token_1.TType.PLUS_EQ) : this.match('+') ? this.append(token_1.TType.PLUS_PLUS) : this.append(token_1.TType.PLUS); break; case '-': this.match('=') ? this.append(token_1.TType.MINUS_EQ) : this.match('-') ? this.append(token_1.TType.MINUS_MINUS) : this.append(token_1.TType.MINUS); break; case '*': this.match('=') ? this.append(token_1.TType.TIMES_EQ) : this.append(token_1.TType.TIMES); break; case '/': this.match('=') ? this.append(token_1.TType.DIVIDE_EQ) : this.append(token_1.TType.DIVIDE); break; case '^': this.match('=') ? this.append(token_1.TType.POWER_EQ) : this.append(token_1.TType.POWER); break; case '!': this.match('=') ? this.append(token_1.TType.BANG_EQ) : this.append(token_1.TType.BANG); break; case '=': this.match('=') ? this.append(token_1.TType.EQ_EQ) : this.append(token_1.TType.EQ); break; case '>': this.match('=') ? this.append(token_1.TType.GREATER_EQ) : this.append(token_1.TType.GREATER); break; case '<': this.match('=') ? this.append(token_1.TType.LESS_EQ) : this.append(token_1.TType.LESS); break; case '%': if (this.match('%')) { while (this.isValid() && this.peek() != '\n') this.next(); } else if (this.match('*')) { while (this.isValid() && !(this.peek() == '*' && this.code[this.i + 2] == '%')) { if (this.curr() == '\n') this.newline(); else this.next(); } if (this.peek() != '*' && this.code[this.i + 2] != "%") { this.error("Unterminated multi-line comment."); } else { this.next(); this.next(); } } else if (this.match('=')) this.append(token_1.TType.MODULO_EQ); else this.append(token_1.TType.MODULO); break; case '"': case '\'': { let strtype = this.curr(); let text = ""; while (this.isValid() && this.peek() != strtype) { if (this.curr() == '\n') this.newline(); else this.next(); if (this.curr() == '\\') { let next = this.peek(); this.newline(); switch (next) { case '\'': text += '\''; break; case '"': text += '"'; break; case 'r': text += '\r'; break; case 'n': text += '\n'; break; case 'm': let keycode = ""; while (this.isValid() && this.isNumber(this.peek())) { this.next(); keycode += this.curr(); } text += String.fromCharCode(parseInt(keycode)); break; case 'u': if (this.peek() != '{') this.error("Expected '{' before an unicode escape code."); this.next(); let hex = ""; while (this.peek() != '}') { this.next(); hex += this.curr(); } if (this.peek() != '}') this.error("Expected '}' after an unicode escape code."); this.next(); text += String.fromCharCode(parseInt(hex, 16)); break; case 'e': text += '\x1b'; break; case '0': text += '\0'; break; case '\\': text += '\\'; break; case '\n': break; default: this.error(`Invalid escape sequence '${this.curr()}'.`); } } else text += this.curr(); } if (this.peek() != strtype) { this.error("Unterminated string."); } this.next(); this.append(token_1.TType.STRING, text); } break; case ' ': case '\r': case '\t': break; case '\n': this.newline(); continue; default: this.error(`Unexpected character ${char}.`); break; } } this.next(); } this.append(token_1.TType.END); return this.tokens; } append(type, value = "") { this.tokens.push(new token_1.Token(this.line, this.col, this.file, type, value)); } isValid() { return this.i < this.code.length; } isNumber(c) { return c >= '0' && c <= '9'; } isAlpha(c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; } isAlphaNum(c) { return this.isNumber(c) || this.isAlpha(c); } error(message) { this.hasError = true; console.log(`<${this.file}> [ ${this.line} : ${this.col} ] ${message}`); } curr() { return this.code[this.i]; } peek() { return this.code[this.i + 1]; } match(char) { if (!this.isValid()) return false; if (this.code[this.i + 1] != char) return false; this.next(); return true; } next() { this.i++; this.col++; } newline() { this.i++; this.line++; this.col = 1; } } exports.Lexer = Lexer;