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 tokens) { instance = new Parser(tokens); return instance; } public static Parser getInstance() { return instance; } private final List tokens; private int position; public Parser(List 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.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 >= tokens.size()) return EOF; return tokens.get(position + offset); } }