271 lines
8.6 KiB
Java
271 lines
8.6 KiB
Java
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;
|
||
}
|
||
|
||
// Текст с именем автора реплики.
|
||
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();
|
||
String params = ""; // emotion? cloth? distance?
|
||
while (lookMatch(0, TokenType.WORD)) {
|
||
final String text = consume(TokenType.WORD).getText();
|
||
if (text.equals("close") || text.equals("far")) break;
|
||
params += text;
|
||
}
|
||
// Положение (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 >= tokens.size()) return EOF;
|
||
return tokens.get(position + offset);
|
||
}
|
||
}
|