@tsukiroku/tiny
Version:
Tiny interpreter
236 lines (235 loc) • 7.89 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const Tiny = __importStar(require("../../index"));
class Lexer {
input;
options;
filename;
position = 0;
readPosition = 0;
ch = '';
column = 0;
line = 1;
lineStart = 1;
messages;
constructor(input, options, filename) {
this.input = input;
this.options = options;
this.filename = filename;
this.messages = Tiny.localization(options);
this.readChar();
}
curr() {
return {
line: this.line,
column: this.column - this.lineStart,
};
}
readChar() {
if (this.readPosition >= this.input.length)
this.ch = '\0';
else
this.ch = this.input[this.readPosition];
this.position = this.readPosition;
this.readPosition += 1;
this.column += 1;
}
readIdentifier() {
let position = this.position;
if (!this.isDigit(this.ch)) {
while (this.isLetter(this.ch) || /[0-9]/.test(this.ch))
this.readChar();
const literal = this.input.substring(position, this.position);
return {
type: Tiny.fromLiteral(literal),
literal: literal,
...this.curr(),
};
}
else {
Tiny.printError({
...this.curr(),
message: this.messages.parseError.invalidIdentifier,
}, this.filename, this.options.stderr, {
...this.options,
});
return {
type: Tiny.TokenType.EOF,
literal: 'EOF',
...this.curr(),
};
}
}
readNumber() {
const position = this.position;
let float = false;
while (this.isDigit(this.ch)) {
if (this.ch === '.') {
if (float) {
Tiny.printError({
...this.curr(),
message: this.messages.parseError.invalidNumber,
}, this.filename, this.options.stderr, {
...this.options,
});
return {
type: Tiny.TokenType.EOF,
literal: 'EOF',
...this.curr(),
};
}
float = true;
}
this.readChar();
}
return {
type: Tiny.TokenType.NUMBER,
literal: this.input.substring(position, this.position),
...this.curr(),
};
}
replaceAll(str, ...args) {
return args.reduce((acc, curr) => acc.replaceAll(curr.find, curr.replace), str);
}
readString(tok) {
let position = this.position + 1;
while (this.peekChar() !== tok && this.ch !== '\0')
this.readChar();
if (this.ch === '\0') {
Tiny.printError({
...this.curr(),
message: Tiny.errorFormatter(this.messages.parseError.invalidString, this.input.substring(position - 1, this.position)),
}, this.filename, this.options.stderr, {
...this.options,
});
return {
type: Tiny.TokenType.EOF,
literal: 'EOF',
...this.curr(),
};
}
this.readChar();
return {
type: Tiny.TokenType.STRING,
literal: this.replaceAll(this.input.substring(position, this.position), { find: '\\"', replace: '"' }, { find: "\\'", replace: "'" }, { find: '\\\\', replace: '\\' }, { find: '\\0', replace: '\0' }, { find: '\\b', replace: '\b' }, { find: '\\f', replace: '\f' }, { find: '\\v', replace: '\v' }, { find: '\\n', replace: '\n' }, { find: '\\r', replace: '\r' }, { find: '\\t', replace: '\t' }),
...this.curr(),
};
}
skipWhitespace() {
while (this.ch === ' ' || this.ch === '\n' || this.ch === '\r' || this.ch === '\t') {
if (this.ch === '\n') {
this.line += 1;
this.lineStart = this.column;
}
this.readChar();
}
}
peekChar() {
if (this.readPosition >= this.input.length)
return '\0';
return this.input[this.readPosition];
}
readComment() {
let position = this.position;
while (this.ch !== '\0' && this.ch !== '\n')
this.readChar();
this.line += 1;
return {
type: Tiny.TokenType.COMMENT,
literal: this.input.substring(position, this.position).slice(1).trim(),
...this.curr(),
};
}
checkToken(token, next) {
if (this.ch === token && !next)
return true;
if (this.ch === token && this.peekChar() === next) {
this.readChar();
return true;
}
return false;
}
check(tests) {
for (const test of tests) {
if (this.checkToken(test.curr, test.next ?? null)) {
let token = {
type: Tiny.TokenType.ILLEGAL,
literal: this.ch,
...this.curr(),
};
if (test.tokenType)
token = {
type: test.tokenType,
literal: this.ch,
...this.curr(),
};
else if (test.stringToken)
token = this.readString(test.stringToken);
else if (test.commentToken)
token = this.readComment();
else if (test.token)
token = test.token;
if (test.readChar)
this.readChar();
return token;
}
}
return {
type: Tiny.TokenType.ILLEGAL,
literal: this.ch,
...this.curr(),
};
}
nextToken() {
let token;
this.skipWhitespace();
token = this.check(Tiny.tokens);
if (token.type === Tiny.TokenType.ILLEGAL) {
if (this.isLetter(this.ch))
token = this.readIdentifier();
else if (this.isDigit(this.ch))
token = this.readNumber();
else {
token = {
type: Tiny.TokenType.ILLEGAL,
literal: this.ch,
...this.curr(),
};
this.readChar();
}
return token;
}
this.readChar();
return token;
}
isLetter(ch) {
return /[a-zA-Z]|_/g.test(ch);
}
isDigit(ch) {
return /[\d]|\./g.test(ch);
}
}
exports.default = Lexer;