2015-04-04 15:09:43 +03:00
|
|
|
|
package com.annimon.everlastingsummer;
|
|
|
|
|
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import android.text.TextUtils;
|
|
|
|
|
import android.util.Log;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @author aNNiMON
|
|
|
|
|
*/
|
|
|
|
|
public final class Parser {
|
|
|
|
|
|
|
|
|
|
private static final Token EOF = new Token("", TokenType.EOF);
|
|
|
|
|
|
|
|
|
|
private static Parser instance;
|
|
|
|
|
|
|
|
|
|
public static Parser parse(List<Token> tokens) {
|
|
|
|
|
instance = new Parser(tokens);
|
|
|
|
|
return instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static Parser getInstance() {
|
|
|
|
|
return instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final List<Token> tokens;
|
|
|
|
|
private int position;
|
|
|
|
|
|
|
|
|
|
public Parser(List<Token> tokens) {
|
|
|
|
|
this.tokens = tokens;
|
|
|
|
|
position = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void next() {
|
|
|
|
|
// Команды разделяются на терминальные и нетерминальные.
|
|
|
|
|
// Нетерминальные подготавливают сцену к выводу.
|
|
|
|
|
// Терминальные выводят всё на экран и ожидают следующего вызова.
|
|
|
|
|
boolean terminal = false;
|
|
|
|
|
int counter = 0;
|
|
|
|
|
do {
|
|
|
|
|
try {
|
|
|
|
|
terminal = statement();
|
|
|
|
|
} catch (RuntimeException re) {
|
|
|
|
|
Log.e("Parser", re.getMessage(), re);
|
|
|
|
|
}
|
|
|
|
|
// антизацикливание
|
|
|
|
|
counter++;
|
|
|
|
|
if (counter >= 1000) {
|
|
|
|
|
position++;
|
|
|
|
|
counter = 0;
|
|
|
|
|
}
|
|
|
|
|
} while (!terminal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
if (match(token, TokenType.STOP)) return stop();
|
|
|
|
|
if (match(token, TokenType.SHOW)) return show();
|
|
|
|
|
|
2015-04-04 15:46:54 +03:00
|
|
|
|
if (match(token, TokenType.HIDE)) {
|
|
|
|
|
ViewActivity.getInstance().hideSprite(consume(TokenType.WORD).getText());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (match(token, TokenType.RETURN) || match(token, TokenType.EOF)) {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (TextUtils.isEmpty(matchWithEffect())) return false;
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
ViewActivity.getInstance().pause((int)(1000 * pause));
|
|
|
|
|
consume(TokenType.RPAREN);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean show() {
|
|
|
|
|
final String whoid = consume(TokenType.WORD).getText();
|
|
|
|
|
final String emotion = lookMatch(0, TokenType.WORD) ? consume(TokenType.WORD).getText() : "";
|
|
|
|
|
final String cloth = lookMatch(0, TokenType.WORD) ? consume(TokenType.WORD).getText() : "";
|
|
|
|
|
final String distance = lookMatch(0, TokenType.WORD) ? consume(TokenType.WORD).getText() : "";
|
|
|
|
|
// Положение (left, cleft, ...)
|
|
|
|
|
String position = "";
|
|
|
|
|
if (match(TokenType.AT)) {
|
|
|
|
|
position = consume(TokenType.WORD).getText();
|
|
|
|
|
}
|
|
|
|
|
matchWithEffect();
|
|
|
|
|
ViewActivity.getInstance().sprite(whoid, emotion, cloth, distance, position);
|
|
|
|
|
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() {
|
|
|
|
|
consume(TokenType.WORD);
|
|
|
|
|
consume(TokenType.LBRACKET);
|
|
|
|
|
final String name = consume(TokenType.TEXT).getText();
|
|
|
|
|
consume(TokenType.RBRACKET);
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
final String name = consume(TokenType.WORD).getText();
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private double consumeDouble() {
|
|
|
|
|
return Double.parseDouble(consume(TokenType.NUMBER).getText());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
if (position + offset >= tokens.size()) return EOF;
|
|
|
|
|
return tokens.get(position + offset);
|
|
|
|
|
}
|
|
|
|
|
}
|