UNPKG

@tsukiroku/tiny

Version:
236 lines (235 loc) 7.89 kB
"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;