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

290 lines
9.0 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 final int tokensCount;
private int position;
public Parser(List<Token> tokens) {
this.tokens = tokens;
tokensCount = tokens.size();
position = 0;
}
public List<Token> getTokens() {
return tokens;
}
public int getTokensCount() {
return tokensCount;
}
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
next();
}
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;
}
// Текст с именем автора реплики.
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.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();
String params = ""; // emotion? cloth? distance?
while (lookMatch(0, TokenType.WORD)) {
final String text = consume(TokenType.WORD).getText();
params += text;
if (text.equals("close") || text.equals("far")) break;
}
// Положение (left, cleft, ...)
String position = "";
if (match(TokenType.AT)) {
position = consume(TokenType.WORD).getText();
}
matchWithEffect();
ViewActivity.getInstance().sprite(whoid, params, 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 >= tokensCount) return EOF;
return tokens.get(position + offset);
}
}