2015-04-04 15:09:43 +03:00
|
|
|
|
package com.annimon.everlastingsummer;
|
|
|
|
|
|
2015-04-13 14:42:17 +03:00
|
|
|
|
import java.util.HashMap;
|
2015-04-04 15:09:43 +03:00
|
|
|
|
import java.util.List;
|
2015-04-13 14:42:17 +03:00
|
|
|
|
import java.util.Map;
|
2015-04-15 23:34:58 +03:00
|
|
|
|
import com.annimon.everlastingsummer.ast.*;
|
2015-04-16 14:40:37 +03:00
|
|
|
|
import com.annimon.everlastingsummer.ast.BinaryExpression.Operator;
|
2015-04-04 15:09:43 +03:00
|
|
|
|
import android.text.TextUtils;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @author aNNiMON
|
|
|
|
|
*/
|
|
|
|
|
public final class Parser {
|
|
|
|
|
|
|
|
|
|
private static final Token EOF = new Token("", TokenType.EOF);
|
|
|
|
|
|
|
|
|
|
private static Parser instance;
|
|
|
|
|
|
2015-04-23 14:22:13 +03:00
|
|
|
|
public static Parser parse(List<Token> tokens, SaveInfo save) {
|
|
|
|
|
if (save == null) instance = new Parser(tokens);
|
|
|
|
|
else instance = new Parser(tokens, save);
|
2015-04-04 15:09:43 +03:00
|
|
|
|
return instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Parser getInstance() {
|
|
|
|
|
return instance;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-14 19:26:21 +03:00
|
|
|
|
public static void release() {
|
|
|
|
|
if (instance != null) {
|
|
|
|
|
instance.tokens.clear();
|
|
|
|
|
instance.labels.clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-04-04 15:09:43 +03:00
|
|
|
|
|
|
|
|
|
private final List<Token> tokens;
|
2015-04-10 22:33:28 +03:00
|
|
|
|
private final int tokensCount;
|
2015-05-03 14:33:24 +03:00
|
|
|
|
private int position, lastPosition;
|
2015-04-04 15:09:43 +03:00
|
|
|
|
|
2015-04-13 14:42:17 +03:00
|
|
|
|
private final Map<String, Integer> labels;
|
2015-04-16 18:54:25 +03:00
|
|
|
|
/** Оптимизация, чтобы каждый раз не искать endmenu/endif,
|
2015-04-14 21:57:44 +03:00
|
|
|
|
* если их попросту нет в необработанном сценарии */
|
2015-04-16 18:54:25 +03:00
|
|
|
|
private boolean hasEndMenu, hasEndIf;
|
2015-04-13 14:42:17 +03:00
|
|
|
|
|
2015-04-04 15:09:43 +03:00
|
|
|
|
public Parser(List<Token> tokens) {
|
2015-04-23 14:22:13 +03:00
|
|
|
|
this(tokens, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Parser(List<Token> tokens, SaveInfo info) {
|
|
|
|
|
this(tokens, info.getPosition());
|
|
|
|
|
Variables.setVariables(info.getVariables());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Parser(List<Token> tokens, int position) {
|
2015-04-04 15:09:43 +03:00
|
|
|
|
this.tokens = tokens;
|
2015-04-10 22:33:28 +03:00
|
|
|
|
tokensCount = tokens.size();
|
2015-04-23 14:22:13 +03:00
|
|
|
|
this.position = position;
|
2015-05-03 14:33:24 +03:00
|
|
|
|
lastPosition = position;
|
2015-04-13 14:42:17 +03:00
|
|
|
|
labels = new HashMap<String, Integer>();
|
2015-04-14 21:57:44 +03:00
|
|
|
|
hasEndMenu = false;
|
2015-04-16 18:54:25 +03:00
|
|
|
|
hasEndIf = false;
|
2015-04-14 21:57:44 +03:00
|
|
|
|
preScan();
|
2015-04-15 23:34:58 +03:00
|
|
|
|
Variables.init();
|
2015-04-04 15:09:43 +03:00
|
|
|
|
}
|
|
|
|
|
|
2015-04-10 22:33:28 +03:00
|
|
|
|
public List<Token> getTokens() {
|
|
|
|
|
return tokens;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int getTokensCount() {
|
|
|
|
|
return tokensCount;
|
|
|
|
|
}
|
2015-05-03 14:33:24 +03:00
|
|
|
|
|
|
|
|
|
public int getLastPosition() {
|
|
|
|
|
return lastPosition;
|
|
|
|
|
}
|
2015-04-10 22:33:28 +03:00
|
|
|
|
|
|
|
|
|
public int getPosition() {
|
|
|
|
|
return position;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setPosition(int position) {
|
|
|
|
|
this.position = position;
|
|
|
|
|
next();
|
|
|
|
|
}
|
2015-04-24 16:32:02 +03:00
|
|
|
|
|
|
|
|
|
public void jumpLabel(String label) {
|
|
|
|
|
if (labels.containsKey(label)) {
|
|
|
|
|
position = labels.get(label);
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-04-10 22:33:28 +03:00
|
|
|
|
|
2015-04-04 15:09:43 +03:00
|
|
|
|
public void next() {
|
2015-05-03 14:33:24 +03:00
|
|
|
|
lastPosition = position;
|
2015-04-04 15:09:43 +03:00
|
|
|
|
// Команды разделяются на терминальные и нетерминальные.
|
|
|
|
|
// Нетерминальные подготавливают сцену к выводу.
|
|
|
|
|
// Терминальные выводят всё на экран и ожидают следующего вызова.
|
|
|
|
|
boolean terminal = false;
|
|
|
|
|
int counter = 0;
|
|
|
|
|
do {
|
|
|
|
|
try {
|
|
|
|
|
terminal = statement();
|
|
|
|
|
} catch (RuntimeException re) {
|
2015-04-16 23:22:46 +03:00
|
|
|
|
if (Logger.DEBUG) Logger.log("Parser.next()", re);
|
2015-04-16 19:41:52 +03:00
|
|
|
|
if (tokens.isEmpty()) return;
|
2015-04-04 15:09:43 +03:00
|
|
|
|
}
|
|
|
|
|
// антизацикливание
|
|
|
|
|
counter++;
|
|
|
|
|
if (counter >= 1000) {
|
|
|
|
|
position++;
|
|
|
|
|
counter = 0;
|
|
|
|
|
}
|
|
|
|
|
} while (!terminal);
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-10 23:39:51 +03:00
|
|
|
|
public void prevScene() {
|
|
|
|
|
// Для перехода к предыдущей сцене, следует два раза найти токен SCENE,
|
|
|
|
|
// т.к. при первом поиске мы найдём лишь текущую.
|
|
|
|
|
final int currentScene = find(TokenType.SCENE, -1);
|
|
|
|
|
final int pos = findFrom(currentScene, TokenType.SCENE, -1);
|
|
|
|
|
// Если не нашли - перематываем на начало.
|
|
|
|
|
if (pos == position) position = 0;
|
|
|
|
|
else position = pos;
|
|
|
|
|
next();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void nextScene() {
|
|
|
|
|
position = find(TokenType.SCENE, 1);
|
|
|
|
|
next();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int find(TokenType which, int step) {
|
|
|
|
|
return findFrom(position, which, step);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int findFrom(int from, TokenType which, int step) {
|
|
|
|
|
int pos = from;
|
|
|
|
|
while (true) {
|
|
|
|
|
pos += step;
|
|
|
|
|
if (pos < 0 || pos >= tokensCount) break;
|
|
|
|
|
|
|
|
|
|
final Token token = tokens.get(pos);
|
|
|
|
|
if (which == token.getType()) return pos;
|
|
|
|
|
}
|
|
|
|
|
// Возвращаем текущее значение во избежание лишних проверок.
|
|
|
|
|
return from;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-04 15:09:43 +03:00
|
|
|
|
private boolean statement() {
|
|
|
|
|
// http://www.renpy.org/wiki/renpy/doc/reference/The_Ren%27Py_Language#Grammar_Rules
|
|
|
|
|
final Token token = get(0);
|
|
|
|
|
if (match(token, TokenType.COMMAND)) return command();
|
|
|
|
|
if (match(token, TokenType.SCENE)) return scene();
|
|
|
|
|
if (match(token, TokenType.PLAY)) return play();
|
2015-05-03 13:33:24 +03:00
|
|
|
|
if (match(token, TokenType.QUEUE)) return queue();
|
2015-04-04 15:09:43 +03:00
|
|
|
|
if (match(token, TokenType.STOP)) return stop();
|
|
|
|
|
if (match(token, TokenType.SHOW)) return show();
|
2015-05-03 19:19:34 +03:00
|
|
|
|
if (match(token, TokenType.HIDE)) return hide();
|
2015-04-04 15:46:54 +03:00
|
|
|
|
|
2015-04-13 14:42:17 +03:00
|
|
|
|
if (match(token, TokenType.JUMP)) return jump();
|
2015-04-15 23:40:18 +03:00
|
|
|
|
if (match(token, TokenType.IF)) return ifStatement();
|
2015-04-13 14:42:17 +03:00
|
|
|
|
|
2015-04-12 00:53:29 +03:00
|
|
|
|
if (lookMatch(1, TokenType.COLON)) {
|
|
|
|
|
// menu:
|
|
|
|
|
if (match(token, TokenType.MENU)) return menu();
|
|
|
|
|
|
|
|
|
|
// Остаток от меню выбора. Пропускаем до появления ENDMENU.
|
|
|
|
|
if (match(token, TokenType.TEXT)) {
|
2015-04-16 18:11:38 +03:00
|
|
|
|
if (hasEndMenu) position += skipMenu();
|
2015-04-12 00:53:29 +03:00
|
|
|
|
return false;
|
|
|
|
|
}
|
2015-04-16 00:00:27 +03:00
|
|
|
|
|
|
|
|
|
// Остаток от условного выражения. Пропускаем до появления ENDIF.
|
|
|
|
|
if (match(token, TokenType.ELSE)) {
|
2015-04-16 18:54:25 +03:00
|
|
|
|
if (hasEndIf) position += skipIf();
|
2015-04-16 00:00:27 +03:00
|
|
|
|
return false;
|
|
|
|
|
}
|
2015-04-12 00:53:29 +03:00
|
|
|
|
}
|
|
|
|
|
|
2015-04-04 15:09:43 +03:00
|
|
|
|
// Текст с именем автора реплики.
|
|
|
|
|
if (lookMatch(1, TokenType.TEXT) && match(token, TokenType.WORD)) {
|
|
|
|
|
final String whoid = token.getText();
|
|
|
|
|
ViewActivity.getInstance().text(whoid, consume(TokenType.TEXT).getText());
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Обычный текст.
|
|
|
|
|
if (match(token, TokenType.TEXT)) {
|
|
|
|
|
ViewActivity.getInstance().text(token.getText());
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-06 23:49:36 +03:00
|
|
|
|
if (match(token, TokenType.EOF)) {
|
2015-04-04 15:09:43 +03:00
|
|
|
|
ViewActivity.getInstance().finish();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (match(token, TokenType.WINDOW)) {
|
|
|
|
|
if (match(TokenType.SHOW))
|
|
|
|
|
ViewActivity.getInstance().windowShow();
|
|
|
|
|
else if (match(TokenType.HIDE))
|
|
|
|
|
ViewActivity.getInstance().windowHide();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-06 17:07:47 +03:00
|
|
|
|
if (!TextUtils.isEmpty(matchWithEffect())) return false;
|
2015-04-04 15:09:43 +03:00
|
|
|
|
|
|
|
|
|
position++;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Парсинг команд на языке Python. Начинаются на $.
|
|
|
|
|
* Поддерживается только самое нужное, весь питон я вам не интерпретирую.
|
|
|
|
|
*/
|
|
|
|
|
private boolean command() {
|
|
|
|
|
final Token token = get(0);
|
|
|
|
|
|
|
|
|
|
if (match(token, TokenType.RENPY_PAUSE)) {
|
|
|
|
|
consume(TokenType.LPAREN);
|
|
|
|
|
final double pause = consumeDouble();
|
2015-05-03 13:48:28 +03:00
|
|
|
|
boolean hard = false;
|
|
|
|
|
if (!lookMatch(0, TokenType.RPAREN)) {
|
|
|
|
|
consume(TokenType.WORD); // hard
|
|
|
|
|
consume(TokenType.EQ);
|
|
|
|
|
hard = consumeBoolean();
|
|
|
|
|
}
|
2015-04-04 15:09:43 +03:00
|
|
|
|
consume(TokenType.RPAREN);
|
2015-05-03 13:48:28 +03:00
|
|
|
|
ViewActivity.getInstance().pause((int)(1000 * pause), hard);
|
2015-04-04 15:09:43 +03:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2015-05-03 13:56:05 +03:00
|
|
|
|
|
|
|
|
|
if (match(token, TokenType.RENPY_SAY)) {
|
|
|
|
|
consume(TokenType.LPAREN);
|
|
|
|
|
final String whoid = consume(TokenType.WORD).getText();
|
|
|
|
|
// TODO: consume(TokenType.COMMA)
|
|
|
|
|
final String text = consume(TokenType.TEXT).getText();
|
|
|
|
|
// TODO: consume(TokenType.COMMA)
|
|
|
|
|
consume(TokenType.WORD); // interact
|
|
|
|
|
consume(TokenType.EQ);
|
|
|
|
|
final boolean interact = consumeBoolean();
|
|
|
|
|
consume(TokenType.RPAREN);
|
|
|
|
|
ViewActivity.getInstance().text(whoid, text);
|
|
|
|
|
return interact;
|
|
|
|
|
}
|
2015-04-04 15:09:43 +03:00
|
|
|
|
|
|
|
|
|
if (match(token, TokenType.PERSISTENT_SPRITE_TIME)) {
|
|
|
|
|
consume(TokenType.EQ);
|
|
|
|
|
consume(TokenType.TEXT);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (match(token, TokenType.PROLOG_TIME) ||
|
|
|
|
|
match(token, TokenType.DAY_TIME) ||
|
|
|
|
|
match(token, TokenType.SUNSET_TIME) ||
|
|
|
|
|
match(token, TokenType.NIGHT_TIME)) {
|
|
|
|
|
consume(TokenType.LPAREN);
|
|
|
|
|
consume(TokenType.RPAREN);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-18 17:15:06 +03:00
|
|
|
|
if (match(token, TokenType.MAKE_NAMES_KNOWN) ||
|
|
|
|
|
match(token, TokenType.MAKE_NAMES_UNKNOWN)) {
|
|
|
|
|
consume(TokenType.LPAREN);
|
|
|
|
|
consume(TokenType.RPAREN);
|
|
|
|
|
if (token.getType() == TokenType.MAKE_NAMES_KNOWN) {
|
|
|
|
|
ViewActivity.getInstance().makeNamesKnown();
|
|
|
|
|
} else ViewActivity.getInstance().makeNamesUnknown();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-18 17:42:01 +03:00
|
|
|
|
if (match(token, TokenType.SET_NAME)) {
|
|
|
|
|
consume(TokenType.LPAREN);
|
|
|
|
|
final String whoid = consume(TokenType.TEXT).getText();
|
|
|
|
|
// TODO: consume(TokenType.COMMA)
|
|
|
|
|
final String name = consume(TokenType.TEXT).getText();
|
|
|
|
|
consume(TokenType.RPAREN);
|
|
|
|
|
ViewActivity.getInstance().meet(whoid, name);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-24 16:59:48 +03:00
|
|
|
|
// Карта
|
|
|
|
|
if (match(token, TokenType.DISABLE_ALL_ZONES)) {
|
|
|
|
|
consume(TokenType.LPAREN);
|
|
|
|
|
consume(TokenType.RPAREN);
|
|
|
|
|
ViewActivity.getInstance().disableAllZones();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (match(token, TokenType.DISABLE_CURRENT_ZONE)) {
|
|
|
|
|
consume(TokenType.LPAREN);
|
|
|
|
|
consume(TokenType.RPAREN);
|
|
|
|
|
ViewActivity.getInstance().disableCurrentZone();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2015-04-24 17:10:32 +03:00
|
|
|
|
if (match(token, TokenType.RESET_ZONE)) {
|
|
|
|
|
consume(TokenType.LPAREN);
|
|
|
|
|
final String zone = consume(TokenType.TEXT).getText();
|
|
|
|
|
consume(TokenType.RPAREN);
|
|
|
|
|
ViewActivity.getInstance().resetZone(zone);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2015-04-24 16:59:48 +03:00
|
|
|
|
if (match(token, TokenType.SET_ZONE)) {
|
|
|
|
|
consume(TokenType.LPAREN);
|
|
|
|
|
final String zone = consume(TokenType.TEXT).getText();
|
|
|
|
|
// TODO: consume(TokenType.COMMA)
|
|
|
|
|
final String label = consume(TokenType.TEXT).getText();
|
|
|
|
|
consume(TokenType.RPAREN);
|
|
|
|
|
ViewActivity.getInstance().setZone(zone, label);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (match(token, TokenType.SHOW_MAP)) {
|
|
|
|
|
consume(TokenType.LPAREN);
|
|
|
|
|
consume(TokenType.RPAREN);
|
|
|
|
|
ViewActivity.getInstance().showMap();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-16 00:07:40 +03:00
|
|
|
|
if (match(token, TokenType.WORD)) {
|
|
|
|
|
if (match(TokenType.EQ)) {
|
|
|
|
|
// variable = expression
|
|
|
|
|
Variables.setVariable(token.getText(), expression().eval());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2015-04-16 20:29:28 +03:00
|
|
|
|
if (lookMatch(1, TokenType.EQ) && match(TokenType.PLUS)) {
|
|
|
|
|
// variable += expression
|
|
|
|
|
consume(TokenType.EQ);
|
|
|
|
|
final double varValue = Variables.getVariable(token.getText());
|
|
|
|
|
Variables.setVariable(token.getText(), varValue + expression().eval());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (lookMatch(1, TokenType.EQ) && match(TokenType.MINUS)) {
|
|
|
|
|
// variable -= expression
|
|
|
|
|
consume(TokenType.EQ);
|
|
|
|
|
final double varValue = Variables.getVariable(token.getText());
|
|
|
|
|
Variables.setVariable(token.getText(), varValue - expression().eval());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2015-04-16 00:07:40 +03:00
|
|
|
|
}
|
|
|
|
|
|
2015-04-04 15:09:43 +03:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean scene() {
|
|
|
|
|
String type;
|
|
|
|
|
if (match(TokenType.BG)) type = "bg";
|
|
|
|
|
else if (match(TokenType.CG)) type = "cg";
|
|
|
|
|
else if (match(TokenType.ANIM)) type = "anim";
|
|
|
|
|
else type = "";
|
|
|
|
|
|
|
|
|
|
final String name = consume(TokenType.WORD).getText();
|
|
|
|
|
final String effect = matchWithEffect();
|
|
|
|
|
ViewActivity.getInstance().background(type, name, effect);
|
2015-05-04 16:18:07 +03:00
|
|
|
|
return true;
|
2015-04-04 15:09:43 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean show() {
|
|
|
|
|
final String whoid = consume(TokenType.WORD).getText();
|
2015-04-06 17:07:18 +03:00
|
|
|
|
String params = ""; // emotion? cloth? distance?
|
|
|
|
|
while (lookMatch(0, TokenType.WORD)) {
|
2015-04-06 23:22:50 +03:00
|
|
|
|
final String text = consume(TokenType.WORD).getText();
|
|
|
|
|
params += text;
|
2015-04-07 13:03:08 +03:00
|
|
|
|
if (text.equals("close") || text.equals("far")) break;
|
2015-04-06 17:07:18 +03:00
|
|
|
|
}
|
2015-04-04 15:09:43 +03:00
|
|
|
|
// Положение (left, cleft, ...)
|
|
|
|
|
String position = "";
|
|
|
|
|
if (match(TokenType.AT)) {
|
|
|
|
|
position = consume(TokenType.WORD).getText();
|
|
|
|
|
}
|
2015-04-16 21:29:38 +03:00
|
|
|
|
// Псевдоним (для показа одно и того же спрайта в разных местах)
|
|
|
|
|
String alias = "";
|
|
|
|
|
if (match(TokenType.AS)) {
|
|
|
|
|
alias = consume(TokenType.WORD).getText();
|
|
|
|
|
}
|
2015-05-03 19:19:34 +03:00
|
|
|
|
final String effect = matchWithEffect();
|
|
|
|
|
ViewActivity.getInstance().sprite(whoid, params, position, alias, effect);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean hide() {
|
|
|
|
|
final String whoid = consume(TokenType.WORD).getText();
|
|
|
|
|
final String effect = matchWithEffect();
|
|
|
|
|
ViewActivity.getInstance().hideSprite(whoid, effect);
|
2015-04-04 15:09:43 +03:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean play() {
|
|
|
|
|
if (match(TokenType.MUSIC)) return playMusic();
|
|
|
|
|
if (match(TokenType.AMBIENCE)) return playAmbience();
|
|
|
|
|
if (lookMatch(0, TokenType.SOUND) || lookMatch(0, TokenType.SOUNDLOOP)) {
|
|
|
|
|
return playSound();
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean playMusic() {
|
2015-05-04 16:28:12 +03:00
|
|
|
|
final String name = consumeMusicName();
|
2015-04-04 15:09:43 +03:00
|
|
|
|
final FadeInfo fade = matchFade();
|
|
|
|
|
ViewActivity.getInstance().music(name, fade);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean playSound() {
|
|
|
|
|
boolean loop = false;
|
|
|
|
|
if (match(TokenType.SOUND)) loop = false;
|
|
|
|
|
else if (match(TokenType.SOUNDLOOP)) loop = true;
|
|
|
|
|
|
2015-05-04 18:15:36 +03:00
|
|
|
|
final String name = consumeMusicName();
|
2015-04-04 15:09:43 +03:00
|
|
|
|
final FadeInfo fade = matchFade();
|
|
|
|
|
ViewActivity.getInstance().sound(name, loop, fade);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean playAmbience() {
|
|
|
|
|
// Ambient не реализован, но парсится.
|
|
|
|
|
final String name = consume(TokenType.WORD).getText();
|
|
|
|
|
final FadeInfo fade = matchFade();
|
|
|
|
|
// ViewActivity.getInstance().sound(name, loop, fade);
|
|
|
|
|
return false;
|
2015-05-03 13:33:24 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean queue() {
|
|
|
|
|
if (match(TokenType.MUSIC)) return queueMusic();
|
|
|
|
|
if (match(TokenType.SOUND)) return queueSound();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean queueMusic() {
|
2015-05-04 16:40:25 +03:00
|
|
|
|
final String name = consumeMusicName();
|
2015-05-03 13:33:24 +03:00
|
|
|
|
ViewActivity.getInstance().addMusicToQueue(name);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean queueSound() {
|
2015-05-04 18:15:36 +03:00
|
|
|
|
final String name = consumeMusicName();
|
2015-05-03 13:33:24 +03:00
|
|
|
|
ViewActivity.getInstance().addSoundToQueue(name);
|
|
|
|
|
return false;
|
2015-04-04 15:09:43 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean stop() {
|
|
|
|
|
if (match(TokenType.MUSIC)) {
|
|
|
|
|
final FadeInfo fade = matchFade();
|
|
|
|
|
ViewActivity.getInstance().stopMusic(fade);
|
|
|
|
|
}
|
|
|
|
|
if (match(TokenType.SOUND) || match(TokenType.SOUNDLOOP)) {
|
|
|
|
|
final FadeInfo fade = matchFade();
|
|
|
|
|
ViewActivity.getInstance().stopSound(fade);
|
|
|
|
|
}
|
|
|
|
|
if (match(TokenType.AMBIENCE)) {
|
|
|
|
|
final FadeInfo fade = matchFade();
|
|
|
|
|
// ViewActivity.getInstance().stopMusic(fade);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-12 00:20:02 +03:00
|
|
|
|
private boolean menu() {
|
|
|
|
|
// menu: title?
|
|
|
|
|
consume(TokenType.COLON);
|
|
|
|
|
|
|
|
|
|
String title = null;
|
|
|
|
|
if (lookMatch(0, TokenType.TEXT) && !lookMatch(1, TokenType.COLON)) {
|
|
|
|
|
title = consume(TokenType.TEXT).getText();
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-14 21:57:44 +03:00
|
|
|
|
if (!hasEndMenu) return false;
|
2015-04-12 00:20:02 +03:00
|
|
|
|
// Ищем элементы выбора
|
|
|
|
|
final Menu menu = new Menu(title);
|
|
|
|
|
int pos = 0;
|
2015-04-12 00:39:05 +03:00
|
|
|
|
int level = 1; // уровень вложенности меню
|
|
|
|
|
while (true) {
|
|
|
|
|
// Расчёт уровня меню.
|
|
|
|
|
if (lookMatch(pos, TokenType.MENU) && lookMatch(pos + 1, TokenType.COLON)) {
|
|
|
|
|
level++;
|
2015-04-12 00:20:02 +03:00
|
|
|
|
pos++;
|
|
|
|
|
}
|
2015-04-12 00:39:05 +03:00
|
|
|
|
if (lookMatch(pos, TokenType.ENDMENU)) {
|
|
|
|
|
level--;
|
|
|
|
|
// Завершаем работу по достижению ENDMENU первого уровня.
|
|
|
|
|
if (level <= 0) break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (level == 1) {
|
|
|
|
|
// Добавляем только пункты из меню первого уровня.
|
|
|
|
|
if (lookMatch(pos, TokenType.TEXT) && lookMatch(pos + 1, TokenType.COLON)) {
|
|
|
|
|
menu.addItem(get(pos).getText(), position + pos + 2);
|
|
|
|
|
pos++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (lookMatch(pos, TokenType.EOF)) return false;
|
2015-04-12 00:20:02 +03:00
|
|
|
|
pos++;
|
2015-04-12 00:39:05 +03:00
|
|
|
|
}
|
2015-04-12 00:20:02 +03:00
|
|
|
|
|
|
|
|
|
ViewActivity.getInstance().menu(menu);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-13 14:42:17 +03:00
|
|
|
|
private boolean jump() {
|
|
|
|
|
final String labelName = consume(TokenType.WORD).getText();
|
2015-04-24 16:32:02 +03:00
|
|
|
|
jumpLabel(labelName);
|
2015-04-13 14:42:17 +03:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-15 23:40:18 +03:00
|
|
|
|
private boolean ifStatement() {
|
2015-04-16 14:00:56 +03:00
|
|
|
|
final Expression condition = expression();
|
2015-04-15 23:40:18 +03:00
|
|
|
|
consume(TokenType.COLON);
|
|
|
|
|
|
2015-04-16 18:54:25 +03:00
|
|
|
|
if (!hasEndIf) return false;
|
2015-04-15 23:40:18 +03:00
|
|
|
|
if (condition.eval() == 0) {
|
|
|
|
|
// Если условие не верно, пропускаем блок до следующего ENDIF
|
|
|
|
|
int pos = 0;
|
|
|
|
|
int level = 1; // уровень вложенности блока
|
|
|
|
|
while (true) {
|
|
|
|
|
// Расчёт уровня блока.
|
|
|
|
|
if (lookMatch(pos, TokenType.IF)) {
|
|
|
|
|
level++;
|
|
|
|
|
pos++;
|
|
|
|
|
}
|
2015-04-16 00:00:27 +03:00
|
|
|
|
if (lookMatch(pos, TokenType.ELSE)) {
|
|
|
|
|
level--;
|
|
|
|
|
pos += 2; // пропускаем ELSE и двоеточие
|
|
|
|
|
// Завершаем работу по достижению ELSE первого уровня.
|
|
|
|
|
if (level <= 0) break;
|
|
|
|
|
}
|
2015-04-15 23:40:18 +03:00
|
|
|
|
if (lookMatch(pos, TokenType.ENDIF)) {
|
|
|
|
|
level--;
|
|
|
|
|
// Завершаем работу по достижению ENDIF первого уровня.
|
|
|
|
|
if (level <= 0) break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (lookMatch(pos, TokenType.EOF)) return false;
|
|
|
|
|
pos++;
|
|
|
|
|
}
|
|
|
|
|
position += pos;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-15 23:34:58 +03:00
|
|
|
|
private Expression expression() {
|
2015-04-16 17:31:15 +03:00
|
|
|
|
return orTest();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Expression orTest() {
|
|
|
|
|
Expression expression = andTest();
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
if (match(TokenType.OR)) {
|
|
|
|
|
expression = new BinaryExpression(Operator.BOOLEAN_OR, expression, andTest());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return expression;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Expression andTest() {
|
|
|
|
|
Expression expression = notTest();
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
if (match(TokenType.AND)) {
|
|
|
|
|
expression = new BinaryExpression(Operator.BOOLEAN_AND, expression, notTest());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return expression;
|
2015-04-16 15:23:10 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Expression notTest() {
|
|
|
|
|
if (match(TokenType.NOT)) {
|
|
|
|
|
return new ValueExpression( notTest().eval() != 0 ? 0 : 1 );
|
|
|
|
|
}
|
2015-04-16 17:33:09 +03:00
|
|
|
|
return comparison();
|
2015-04-16 14:55:51 +03:00
|
|
|
|
}
|
|
|
|
|
|
2015-04-16 17:33:09 +03:00
|
|
|
|
private Expression comparison() {
|
2015-04-16 14:55:51 +03:00
|
|
|
|
Expression expression = additive();
|
|
|
|
|
|
2015-04-16 17:33:09 +03:00
|
|
|
|
while (true) {
|
|
|
|
|
if (lookMatch(1, TokenType.EQ)) {
|
|
|
|
|
if (match(TokenType.EQ)) {
|
|
|
|
|
// ==
|
2015-04-16 19:41:52 +03:00
|
|
|
|
consume(TokenType.EQ);
|
2015-04-16 17:33:09 +03:00
|
|
|
|
expression = new BinaryExpression(Operator.EQUALS, expression, additive());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (match(TokenType.GT)) {
|
|
|
|
|
// >=
|
2015-04-16 19:41:52 +03:00
|
|
|
|
consume(TokenType.EQ);
|
2015-04-16 17:33:09 +03:00
|
|
|
|
expression = new BinaryExpression(Operator.GTEQ, expression, additive());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (match(TokenType.LT)) {
|
|
|
|
|
// <=
|
2015-04-16 19:41:52 +03:00
|
|
|
|
consume(TokenType.EQ);
|
2015-04-16 17:33:09 +03:00
|
|
|
|
expression = new BinaryExpression(Operator.LTEQ, expression, additive());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (match(TokenType.EXCL)) {
|
|
|
|
|
// !=
|
2015-04-16 19:41:52 +03:00
|
|
|
|
consume(TokenType.EQ);
|
2015-04-16 17:33:09 +03:00
|
|
|
|
expression = new BinaryExpression(Operator.NOTEQUALS, expression, additive());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2015-04-16 14:55:51 +03:00
|
|
|
|
}
|
2015-04-16 17:33:09 +03:00
|
|
|
|
|
|
|
|
|
if (match(TokenType.LT)) {
|
|
|
|
|
expression = new BinaryExpression(Operator.LT, expression, additive());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (match(TokenType.GT)) {
|
|
|
|
|
expression = new BinaryExpression(Operator.GT, expression, additive());
|
|
|
|
|
continue;
|
2015-04-16 14:55:51 +03:00
|
|
|
|
}
|
2015-04-16 17:33:09 +03:00
|
|
|
|
break;
|
2015-04-16 14:55:51 +03:00
|
|
|
|
}
|
|
|
|
|
return expression;
|
2015-04-16 14:40:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Expression additive() {
|
|
|
|
|
Expression expression = unary();
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
if (match(TokenType.PLUS)) {
|
|
|
|
|
expression = new BinaryExpression(Operator.ADD, expression, unary());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (match(TokenType.MINUS)) {
|
|
|
|
|
expression = new BinaryExpression(Operator.SUBTRACT, expression, unary());
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return expression;
|
2015-04-16 13:51:13 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Expression unary() {
|
|
|
|
|
if (match(TokenType.MINUS)) {
|
|
|
|
|
return new ValueExpression( -primary().eval() );
|
|
|
|
|
}
|
|
|
|
|
if (match(TokenType.PLUS)) {
|
|
|
|
|
return primary();
|
|
|
|
|
}
|
2015-04-15 23:34:58 +03:00
|
|
|
|
return primary();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Expression primary() {
|
|
|
|
|
final Token current = get(0);
|
2015-04-15 23:36:59 +03:00
|
|
|
|
if (match(current, TokenType.NUMBER)) {
|
|
|
|
|
return new ValueExpression( Double.parseDouble(current.getText()) );
|
|
|
|
|
}
|
2015-04-15 23:34:58 +03:00
|
|
|
|
if (match(current, TokenType.WORD)) {
|
|
|
|
|
return new VariableExpression(current.getText());
|
|
|
|
|
}
|
2015-04-16 20:50:14 +03:00
|
|
|
|
if (match(current, TokenType.LPAREN)) {
|
|
|
|
|
Expression expr = expression();
|
|
|
|
|
match(TokenType.RPAREN);
|
|
|
|
|
return expr;
|
|
|
|
|
}
|
2015-04-15 23:34:58 +03:00
|
|
|
|
throw new RuntimeException();
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-14 21:57:44 +03:00
|
|
|
|
private void preScan() {
|
2015-04-13 14:42:17 +03:00
|
|
|
|
// Сканируем все метки, для быстрого перехода командой jump.
|
2015-04-14 21:57:44 +03:00
|
|
|
|
// А также определяем параметры для оптимизации.
|
2015-04-13 14:42:17 +03:00
|
|
|
|
for (int i = 0; i < tokensCount - 2; i++) {
|
2015-04-14 21:57:44 +03:00
|
|
|
|
final TokenType current = tokens.get(i).getType();
|
|
|
|
|
if (current == TokenType.ENDMENU) {
|
|
|
|
|
hasEndMenu = true;
|
2015-04-16 18:54:25 +03:00
|
|
|
|
} else if (current == TokenType.ENDIF) {
|
|
|
|
|
hasEndIf = true;
|
2015-04-14 21:57:44 +03:00
|
|
|
|
} else if ( (current == TokenType.LABEL) &&
|
2015-04-13 14:42:17 +03:00
|
|
|
|
(tokens.get(i + 2).getType() == TokenType.COLON) ) {
|
|
|
|
|
// label word :
|
|
|
|
|
final Token token = tokens.get(i + 1);
|
|
|
|
|
if (token.getType() == TokenType.WORD) {
|
|
|
|
|
// Добавляем позицию команды, следующей после метки.
|
|
|
|
|
labels.put(token.getText(), i + 3);
|
|
|
|
|
}
|
|
|
|
|
i += 2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-16 18:11:38 +03:00
|
|
|
|
private int skipMenu() {
|
|
|
|
|
int pos = 0;
|
|
|
|
|
int level = 1; // уровень вложенности меню
|
|
|
|
|
while (true) {
|
|
|
|
|
// Расчёт уровня меню.
|
|
|
|
|
if (lookMatch(pos, TokenType.MENU) && lookMatch(pos + 1, TokenType.COLON)) {
|
|
|
|
|
level++;
|
2015-04-16 19:41:52 +03:00
|
|
|
|
pos += 2;
|
2015-04-16 18:11:38 +03:00
|
|
|
|
}
|
|
|
|
|
if (lookMatch(pos, TokenType.ENDMENU)) {
|
2015-04-16 19:41:52 +03:00
|
|
|
|
pos++;
|
2015-04-16 18:11:38 +03:00
|
|
|
|
level--;
|
|
|
|
|
// Завершаем работу по достижению ENDMENU первого уровня.
|
|
|
|
|
if (level <= 0) break;
|
|
|
|
|
}
|
2015-04-23 11:16:35 +03:00
|
|
|
|
if (lookMatch(pos, TokenType.EOF)) return 0;
|
2015-04-16 18:11:38 +03:00
|
|
|
|
pos++;
|
|
|
|
|
}
|
|
|
|
|
return pos;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-16 18:54:25 +03:00
|
|
|
|
private int skipIf() {
|
|
|
|
|
int pos = 0;
|
|
|
|
|
int level = 1;
|
|
|
|
|
while (true) {
|
|
|
|
|
if (lookMatch(pos, TokenType.IF)) level++;
|
|
|
|
|
else if (lookMatch(pos, TokenType.ENDIF)) {
|
2015-04-16 19:41:52 +03:00
|
|
|
|
pos++;
|
2015-04-16 18:54:25 +03:00
|
|
|
|
level--;
|
|
|
|
|
if (level <= 0) break;
|
2015-04-23 11:16:35 +03:00
|
|
|
|
} else if (lookMatch(pos, TokenType.EOF)) return 0;
|
2015-04-16 18:54:25 +03:00
|
|
|
|
pos++;
|
|
|
|
|
}
|
|
|
|
|
return pos;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-04 15:09:43 +03:00
|
|
|
|
|
|
|
|
|
private double consumeDouble() {
|
|
|
|
|
return Double.parseDouble(consume(TokenType.NUMBER).getText());
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-03 13:48:28 +03:00
|
|
|
|
private boolean consumeBoolean() {
|
|
|
|
|
return "true".equalsIgnoreCase(consume(TokenType.WORD).getText());
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-04 16:28:12 +03:00
|
|
|
|
private String consumeMusicName() {
|
|
|
|
|
final String name;
|
|
|
|
|
if (lookMatch(1, TokenType.LBRACKET)) {
|
|
|
|
|
// music_list["music"]
|
|
|
|
|
consume(TokenType.WORD);
|
|
|
|
|
consume(TokenType.LBRACKET);
|
|
|
|
|
name = consume(TokenType.TEXT).getText();
|
|
|
|
|
consume(TokenType.RBRACKET);
|
|
|
|
|
} else if (lookMatch(0, TokenType.TEXT)) {
|
|
|
|
|
name = consume(TokenType.TEXT).getText();
|
|
|
|
|
} else {
|
|
|
|
|
name = consume(TokenType.WORD).getText();
|
|
|
|
|
}
|
|
|
|
|
return name;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-04 15:09:43 +03:00
|
|
|
|
private FadeInfo matchFade() {
|
|
|
|
|
final FadeInfo result = new FadeInfo();
|
|
|
|
|
if (match(TokenType.FADEIN)) {
|
|
|
|
|
result.setIn(true);
|
|
|
|
|
result.setDuration(consumeDouble());
|
|
|
|
|
} else if (match(TokenType.FADEOUT)) {
|
|
|
|
|
result.setOut(true);
|
|
|
|
|
result.setDuration(consumeDouble());
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String matchWithEffect() {
|
|
|
|
|
if (match(TokenType.WITH)) {
|
|
|
|
|
return consume(TokenType.WORD).getText();
|
|
|
|
|
}
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private boolean match(TokenType type) {
|
|
|
|
|
if (get(0).getType() != type) return false;
|
|
|
|
|
position++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean match(Token token, TokenType type) {
|
|
|
|
|
if (type != token.getType()) return false;
|
|
|
|
|
position++;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Token consume(TokenType type) {
|
|
|
|
|
if (get(0).getType() != type) throw new RuntimeException("Ожидался токен " + type + ".");
|
|
|
|
|
return tokens.get(position++);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean lookMatch(int pos, TokenType type) {
|
|
|
|
|
return (type == get(pos).getType());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Token get(int offset) {
|
2015-04-10 22:33:28 +03:00
|
|
|
|
if (position + offset >= tokensCount) return EOF;
|
2015-04-04 15:09:43 +03:00
|
|
|
|
return tokens.get(position + offset);
|
|
|
|
|
}
|
|
|
|
|
}
|