RpyPlayerWeb/public_html/resources/js/Lexer.js
2015-12-06 13:49:35 +02:00

229 lines
6.6 KiB
JavaScript

/* 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);
};