RpyPlayer/src/com/annimon/everlastingsummer/Parser.java

269 lines
8.5 KiB
Java
Raw Normal View History

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();
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;
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();
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();
String params = ""; // emotion? cloth? distance?
while (lookMatch(0, TokenType.WORD)) {
params += consume(TokenType.WORD).getText();
}
2015-04-04 15:09:43 +03:00
// Положение (left, cleft, ...)
String position = "";
if (match(TokenType.AT)) {
position = consume(TokenType.WORD).getText();
}
matchWithEffect();
ViewActivity.getInstance().sprite(whoid, params, position);
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() {
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);
}
}