cookeylang-ts
Version:
A dynamic, interpreted language.
282 lines (281 loc) • 13 kB
JavaScript
"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;