229 lines
6.6 KiB
JavaScript
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 = this.next(); this.buffer += ('\n'); continue;
|
|
case 't': ch = this.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);
|
|
};
|