/* global TokenType, ViewActivity, TextUtils */ function Parser(tokens) { this.tokens = tokens; this.tokensCount = tokens.length; this.position = 0; this.lastPosition = 0; this.labels = {}; } Parser.prototype.EOF = new Token("", TokenType.EOF); Parser.prototype.getInstance = function () { return this; }; Parser.prototype.next = function () { this.lastPosition = this.position; var terminal = false; var counter = 0; do { try { terminal = this.statement(); } catch (re) { console.log("Parser.next() " + re); if (this.tokens.length === 0) return; } // антизацикливание counter++; if (counter >= 1000) { this.position++; counter = 0; } } while (!terminal); }; Parser.prototype.findFrom = function(from, which, step) { var pos = from; while (true) { pos += step; if (pos < 0 || pos >= this.tokensCount) break; var token = this.tokens[pos]; if (which === token.getType()) return pos; } // Возвращаем текущее значение во избежание лишних проверок. return from; }; Parser.prototype.statement = function() { var token = this.get(0); if (this.match(token, TokenType.COMMAND)) return this.command(); if (this.match(token, TokenType.SCENE)) return this.scene(); if (this.match(token, TokenType.PLAY)) return this.play(); // if (this.match(token, TokenType.QUEUE)) return this.queue(); // if (this.match(token, TokenType.STOP)) return this.stop(); if (this.match(token, TokenType.SHOW)) return this.show(); if (this.match(token, TokenType.HIDE)) return this.hide(); // if (this.match(token, TokenType.JUMP)) return this.jump(); // if (this.match(token, TokenType.IF)) return this.ifStatement(); /*if (this.lookMatch(1, TokenType.COLON)) { // menu: if (this.match(token, TokenType.MENU)) return this.menu(); // Остаток от меню выбора. Пропускаем до появления ENDMENU. if (this.match(token, TokenType.TEXT)) { if (this.hasEndMenu) this.position += this.skipMenu(); return false; } // Остаток от условного выражения. Пропускаем до появления ENDIF. if (this.match(token, TokenType.ELSE)) { if (this.hasEndIf) this.position += this.skipIf(); return false; } }*/ // Текст с именем автора реплики. if (this.lookMatch(1, TokenType.TEXT) && this.match(token, TokenType.WORD)) { var whoid = token.getText(); ViewActivity.getInstance().text(whoid, this.consume(TokenType.TEXT).getText()); return true; } // Обычный текст. if (this.match(token, TokenType.TEXT)) { ViewActivity.getInstance().text(token.getText()); return true; } if (this.match(token, TokenType.EOF)) { //ViewActivity.getInstance().finish(); return true; } if (this.match(token, TokenType.WINDOW)) { if (this.match(TokenType.SHOW)) return ViewActivity.getInstance().windowShow(this.matchWithEffect()); else if (this.match(TokenType.HIDE)) return ViewActivity.getInstance().windowHide(this.matchWithEffect()); return false; } if (!TextUtils.isEmpty(this.matchWithEffect())) return false; this.position++; return false; }; Parser.prototype.command = function() { var token = this.get(0); if (this.match(token, TokenType.RENPY_PAUSE)) { this.consume(TokenType.LPAREN); var pause = this.consumeDouble(); var hard = false; if (!this.lookMatch(0, TokenType.RPAREN)) { this.consume(TokenType.WORD); // hard this.consume(TokenType.EQ); hard = this.consumeBoolean(); } this.consume(TokenType.RPAREN); ViewActivity.getInstance().pause(Math.floor(1000 * pause), hard); return true; } if (this.match(token, TokenType.RENPY_SAY)) { this.consume(TokenType.LPAREN); var whoid = this.consume(TokenType.WORD).getText(); // TODO: this.consume(TokenType.COMMA) var text = this.consume(TokenType.TEXT).getText(); // TODO: this.consume(TokenType.COMMA) this.consume(TokenType.WORD); // interact this.consume(TokenType.EQ); var interact = this.consumeBoolean(); this.consume(TokenType.RPAREN); ViewActivity.getInstance().text(whoid, text); return interact; } if (this.match(token, TokenType.PERSISTENT_SPRITE_TIME)) { this.consume(TokenType.EQ); this.consume(TokenType.TEXT); return false; } if (this.match(token, TokenType.PROLOG_TIME) || this.match(token, TokenType.DAY_TIME) || this.match(token, TokenType.SUNSET_TIME) || this.match(token, TokenType.NIGHT_TIME)) { this.consume(TokenType.LPAREN); this.consume(TokenType.RPAREN); return false; } if (this.match(token, TokenType.MAKE_NAMES_KNOWN) || this.match(token, TokenType.MAKE_NAMES_UNKNOWN)) { this.consume(TokenType.LPAREN); this.consume(TokenType.RPAREN); if (token.getType() === TokenType.MAKE_NAMES_KNOWN) { ViewActivity.getInstance().makeNamesKnown(); } else ViewActivity.getInstance().makeNamesUnknown(); return false; } if (this.match(token, TokenType.SET_NAME)) { this.consume(TokenType.LPAREN); var whoid = this.consume(TokenType.TEXT).getText(); // TODO: this.consume(TokenType.COMMA) var name = this.consume(TokenType.TEXT).getText(); this.consume(TokenType.RPAREN); ViewActivity.getInstance().meet(whoid, name); return false; } // Карта if (this.match(token, TokenType.DISABLE_ALL_ZONES)) { this.consume(TokenType.LPAREN); this.consume(TokenType.RPAREN); ViewActivity.getInstance().disableAllZones(); return false; } if (this.match(token, TokenType.DISABLE_CURRENT_ZONE)) { this.consume(TokenType.LPAREN); this.consume(TokenType.RPAREN); ViewActivity.getInstance().disableCurrentZone(); return false; } if (this.match(token, TokenType.RESET_ZONE)) { this.consume(TokenType.LPAREN); var zone = this.consume(TokenType.TEXT).getText(); this.consume(TokenType.RPAREN); ViewActivity.getInstance().resetZone(zone); return false; } if (this.match(token, TokenType.SET_ZONE)) { this.consume(TokenType.LPAREN); var zone = this.consume(TokenType.TEXT).getText(); // TODO: this.consume(TokenType.COMMA) var label = this.consume(TokenType.TEXT).getText(); this.consume(TokenType.RPAREN); ViewActivity.getInstance().setZone(zone, label); return false; } if (this.match(token, TokenType.SHOW_MAP)) { this.consume(TokenType.LPAREN); this.consume(TokenType.RPAREN); ViewActivity.getInstance().showMap(); return true; } /*if (this.match(token, TokenType.WORD)) { if (this.match(TokenType.EQ)) { // variable = expression Variables.setVariable(token.getText(), expression().eval()); return false; } if (this.lookMatch(1, TokenType.EQ) && this.match(TokenType.PLUS)) { // variable += expression this.consume(TokenType.EQ); var varValue = Variables.getVariable(token.getText()); Variables.setVariable(token.getText(), varValue + expression().eval()); return false; } if (this.lookMatch(1, TokenType.EQ) && this.match(TokenType.MINUS)) { // variable -= expression this.consume(TokenType.EQ); var varValue = Variables.getVariable(token.getText()); Variables.setVariable(token.getText(), varValue - expression().eval()); return false; } }*/ return false; }; Parser.prototype.scene = function() { var type = ""; if (this.match(TokenType.BG)) type = "bg"; else if (this.match(TokenType.CG)) type = "cg"; else if (this.match(TokenType.ANIM)) type = "anim"; var name = this.consume(TokenType.WORD).getText(); var effect = this.matchWithEffect(); ViewActivity.getInstance().background(type, name, effect); return true; }; Parser.prototype.show = function() { var whoid = this.consume(TokenType.WORD).getText(); var params = ""; // emotion? cloth? distance? while (this.lookMatch(0, TokenType.WORD)) { var text = this.consume(TokenType.WORD).getText(); params += text; if (text.equals("close") || text.equals("far")) break; } // Положение (left, cleft, ...) var position = ""; if (this.match(TokenType.AT)) { position = this.consume(TokenType.WORD).getText(); } // Псевдоним (для показа одно и того же спрайта в разных местах) var alias = ""; if (this.match(TokenType.AS)) { alias = this.consume(TokenType.WORD).getText(); } var effect = this.matchWithEffect(); ViewActivity.getInstance().sprite(whoid, params, position, alias, effect); return false; }; Parser.prototype.hide = function() { var whoid = this.consume(TokenType.WORD).getText(); var effect = this.matchWithEffect(); ViewActivity.getInstance().hideSprite(whoid, effect); return false; }; Parser.prototype.play = function() { if (this.match(TokenType.MUSIC)) return this.playMusic(); if (this.match(TokenType.AMBIENCE)) return this.playAmbience(); if (this.lookMatch(0, TokenType.SOUND) || this.lookMatch(0, TokenType.SOUNDLOOP)) { return this.playSound(); } return false; }; Parser.prototype.playMusic = function() { var name = this.consumeMusicName(); var fade = this.matchFade(); ViewActivity.getInstance().music(name, fade); return false; }; Parser.prototype.playSound = function() { var loop = false; if (this.match(TokenType.SOUND)) loop = false; else if (this.match(TokenType.SOUNDLOOP)) loop = true; var name = this.consumeMusicName(); var fade = this.matchFade(); ViewActivity.getInstance().sound(name, loop, fade); return false; }; Parser.prototype.playAmbience = function() { var name = this.consumeMusicName(); var fade = this.matchFade(); return false; }; Parser.prototype.consumeMusicName = function() { var name = ""; if (this.lookMatch(1, TokenType.LBRACKET)) { // music_list["music"] this.consume(TokenType.WORD); this.consume(TokenType.LBRACKET); name = this.consume(TokenType.TEXT).getText(); this.consume(TokenType.RBRACKET); } else if (this.lookMatch(0, TokenType.TEXT)) { name = this.consume(TokenType.TEXT).getText(); } else { name = this.consume(TokenType.WORD).getText(); } return name; }; Parser.prototype.matchFade = function() { var result = new FadeInfo(); if (this.match(TokenType.FADEIN)) { result.fadeIn = true; result.duration = this.consumeDouble(); } else if (this.match(TokenType.FADEOUT)) { result.fadeOut = true; result.duration = this.consumeDouble(); } return result; }; Parser.prototype.matchWithEffect = function() { if (this.match(TokenType.WITH)) { return this.consume(TokenType.WORD).getText(); } return ""; }; Parser.prototype.consumeDouble = function() { return parseFloat(this.consume(TokenType.NUMBER).getText()); }; Parser.prototype.consumeBoolean = function() { return "true" === (this.consume(TokenType.NUMBER).getText().toLowerCase()); }; Parser.prototype.matchWithEffect = function() { if (this.match(TokenType.WITH)) { return this.consume(TokenType.WORD).getText(); } return ""; }; Parser.prototype.match_1 = function(type) { if (this.get(0).getType() !== type) return false; this.position++; return true; }; Parser.prototype.match_2 = function(token, type) { if (type !== token.getType()) return false; this.position++; return true; }; Parser.prototype.match = function(arg1, arg2) { if (arguments.length === 1) return this.match_1(arg1); else return this.match_2(arg1, arg2); }; Parser.prototype.consume = function(type) { if (this.get(0).getType() !== type) throw "Ожидался токен " + type + "."; return this.tokens[this.position++]; }; Parser.prototype.lookMatch = function(pos, type) { return (type === this.get(pos).getType()); }; Parser.prototype.get = function(offset) { if (this.position + offset >= this.tokensCount) return this.EOF; return this.tokens[this.position + offset]; };