/* global TokenType */ function Lexer(input) { this.input = input; this.tokens = []; this.length = input.length; this.pos = 0; this.buffer = ""; } Lexer.prototype.OPERATOR_CHARS = "=+-<>()[]!$:"; Lexer.prototype.OPERATOR_TYPES = [ TokenType.EQ, TokenType.PLUS, TokenType.MINUS, TokenType.LT, TokenType.GT, TokenType.LPAREN, TokenType.RPAREN, TokenType.LBRACKET, TokenType.RBRACKET, TokenType.EXCL, TokenType.COMMAND, TokenType.COLON ]; Lexer.prototype.KEYWORDS = { "play" : TokenType.PLAY, "queue" : TokenType.QUEUE, "stop" : TokenType.STOP, "music" : TokenType.MUSIC, "ambience" : TokenType.AMBIENCE, "sound" : TokenType.SOUND, "sound_loop" : TokenType.SOUNDLOOP, "fadein" : TokenType.FADEIN, "fadeout" : TokenType.FADEOUT, "scene" : TokenType.SCENE, "anim" : TokenType.ANIM, "bg" : TokenType.BG, "cg" : TokenType.CG, "at" : TokenType.AT, "as" : TokenType.AS, "define" : TokenType.DEFINE, "window" : TokenType.WINDOW, "hide" : TokenType.HIDE, "show" : TokenType.SHOW, "with" : TokenType.WITH, "return" : TokenType.RETURN, "menu" : TokenType.MENU, "endmenu" : TokenType.ENDMENU, "jump" : TokenType.JUMP, "label" : TokenType.LABEL, "if" : TokenType.IF, "else" : TokenType.ELSE, "endif" : TokenType.ENDIF, "or" : TokenType.OR, "and" : TokenType.AND, "not" : TokenType.NOT, "renpy.pause" : TokenType.RENPY_PAUSE, "renpy.say" : TokenType.RENPY_SAY, "persistent.sprite_time" : TokenType.PERSISTENT_SPRITE_TIME, "prolog_time" : TokenType.PROLOG_TIME, "day_time" : TokenType.DAY_TIME, "sunset_time" : TokenType.SUNSET_TIME, "night_time" : TokenType.NIGHT_TIME, "make_names_known" : TokenType.MAKE_NAMES_KNOWN, "make_names_unknown" : TokenType.MAKE_NAMES_UNKNOWN, "set_name" : TokenType.SET_NAME, "meet" : TokenType.SET_NAME, "disable_all_zones" : TokenType.DISABLE_ALL_ZONES, "disable_current_zone" : TokenType.DISABLE_CURRENT_ZONE, "reset_zone" : TokenType.RESET_ZONE, "set_zone" : TokenType.SET_ZONE, "show_map" : TokenType.SHOW_MAP }; Lexer.prototype.getTokens = function () { return this.tokens; }; Lexer.prototype.process = function () { this.pos = 0; while (this.pos < this.length) { this.tokenize(); } return this; }; Lexer.prototype.tokenize = function () { this.skipWhitespaces(); var ch = this.peek(0); if (ch.match(/[a-z]/i)) { // Слово (ключевое слово или команда) this.tokenizeWord(); } else if (ch.match(/[0-9]/i)) { // Число this.tokenizeNumber(); } else if (ch === '"' || ch === '\'') { // Текст в "кавычках" или 'одинарных' this.tokenizeText(ch); } else if (ch === '#') { this.tokenizeComment(); } else { // Операторы и спецсимволы this.tokenizeOperator(); } }; Lexer.prototype.tokenizeWord = function () { var ch = this.peek(0); // Строка в юникоде u"текст" или u'текст' if (ch === 'u') { var textStartChar = this.peek(1); if (textStartChar === '"' || textStartChar === '\'') { this.next(); // u this.tokenizeText(textStartChar); return; } } this.clearBuffer(); while (ch.match(/[a-z0-9_\.]/i)) { this.buffer += (ch); ch = this.next(); } var word = this.buffer; var key = word.toLowerCase(); if (key in this.KEYWORDS) { this.addToken(this.KEYWORDS[key]); } else { this.addToken(TokenType.WORD, word); } }; Lexer.prototype.tokenizeNumber = function () { var ch = this.peek(0); this.clearBuffer(); var decimal = false; while (true) { // Целое или вещественное число. if (ch === '.') { // Пропускаем десятичные точки, если они уже были в числе. if (!decimal) this.buffer += (ch); decimal = true; ch = this.next(); continue; } else if (!ch.match(/[0-9]/i)) { break; } this.buffer += (ch); ch = this.next(); } this.addToken(TokenType.NUMBER, this.buffer); }; Lexer.prototype.tokenizeOperator = function () { var ch = this.peek(0); var index = this.OPERATOR_CHARS.indexOf(ch); if (index !== -1) { this.addToken(this.OPERATOR_TYPES[index]); } this.next(); }; Lexer.prototype.tokenizeText = function (textStartChar) { this.clearBuffer(); var ch = this.next(); // пропускаем открывающую кавычку while (true) { if (ch === textStartChar) break; if (ch === '\0') break; // не закрыта кавычка, но мы добавим то, что есть if (ch === '\\') { ch = this.next(); switch (ch) { case 'n': ch = next(); this.buffer += ('\n'); continue; case 't': ch = next(); this.buffer += ('\t'); continue; default: if (ch === textStartChar) { ch = this.next(); this.buffer += ('"'); continue; } } this.buffer += ('\\'); continue; } this.buffer += (ch); ch = this.next(); } this.next(); // пропускаем закрывающую кавычку this.addToken(TokenType.TEXT, this.buffer); }; Lexer.prototype.tokenizeComment = function () { var ch = this.peek(0); while ("\n\r\0".indexOf(ch) === -1) { ch = this.next(); } }; Lexer.prototype.skipWhitespaces = function () { var ch = this.peek(0); while (ch !== '\0' && ch.match(/\s/)) { ch = this.next(); } }; Lexer.prototype.addToken_1 = function (type) { this.addToken_2(type, ""); }; Lexer.prototype.addToken_2 = function (type, text) { this.tokens.push(new Token(text, type)); }; Lexer.prototype.addToken = function (arg1, arg2) { if (arguments.length === 1) this.addToken_1(arg1); else this.addToken_2(arg1, arg2); }; Lexer.prototype.clearBuffer = function () { this.buffer = ""; }; Lexer.prototype.next = function () { this.pos++; if (this.pos >= this.length) return '\0'; return this.input.charAt(this.pos); }; Lexer.prototype.peek = function (relativePosition) { var tempPos = this.pos + relativePosition; if (tempPos >= this.length) return '\0'; return this.input.charAt(tempPos); };