|
|
@@ -0,0 +1,166 @@
|
|
|
+/*
|
|
|
+ * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
+ * @copyright Ranides Atterwim
|
|
|
+ * @license WTFPL
|
|
|
+ * @url http://ranides.net/projects/assira
|
|
|
+ */
|
|
|
+package net.ranides.assira.lexer;
|
|
|
+
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.function.BiConsumer;
|
|
|
+import java.util.function.Consumer;
|
|
|
+
|
|
|
+/**
|
|
|
+ * <p>
|
|
|
+ * Strumień przetwarzający kolejno elementy zbioru przydatny przy pisaniu
|
|
|
+ * lexerów <i>ad hoc</i>. Żadna funkcjonalność związana z obsługą gramatyki
|
|
|
+ * nie istnieje. Przydatny do przetwarzania sekwencji sterujących, kodów
|
|
|
+ * escape'ujących w stringach, wzorców podstawieniowych etc
|
|
|
+ * </p><p>
|
|
|
+ * Bardziej ściśle: do przetwarzania formatów, w których bezkontekstowe
|
|
|
+ * symbole nie są zagnieżdżane rekurencyjnie.
|
|
|
+ * </p>
|
|
|
+ * @author ranides
|
|
|
+ */
|
|
|
+public class StringStreamer {
|
|
|
+
|
|
|
+ private static final char[] EMPTY = new char[0];
|
|
|
+ private final Consumer<String> consumer;
|
|
|
+ private String tail;
|
|
|
+ private String array;
|
|
|
+ private int recent;
|
|
|
+ private int position;
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Tworzy nowy strumień, który przekazuje wyodrębnione fragmenty do podanego
|
|
|
+ * konsumenta.
|
|
|
+ * @param consumer
|
|
|
+ */
|
|
|
+ public StringStreamer(Consumer<String> consumer) {
|
|
|
+ this.tail = null;
|
|
|
+ this.array = "";
|
|
|
+ this.recent = 0;
|
|
|
+ this.position = 0;
|
|
|
+ this.consumer = consumer;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Odczytuje element ze strumienia i przesuwa kursor do przodu.
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public char get() {
|
|
|
+ return array.charAt(position++);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Odczytuje element ze strumienia nie przesuwając kursora
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public char peek() {
|
|
|
+ return array.charAt(position);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Pomija element
|
|
|
+ */
|
|
|
+ public void skip() {
|
|
|
+ position++;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void skip(int offset) {
|
|
|
+ position += offset;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Sprawdza, czy przetworzono wszystkie dostępne dane.
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public boolean end() {
|
|
|
+ return position >= array.length();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Wysyła do odbiorcy fragment o wskazanej treści
|
|
|
+ * @param data
|
|
|
+ */
|
|
|
+ public void send(String data) {
|
|
|
+ consumer.accept(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Odrzuca bieżący fragment i rozpoczyna przetwarzanie nowego od aktualnej
|
|
|
+ * pozycji kursora.
|
|
|
+ */
|
|
|
+ public void dispose() {
|
|
|
+ recent = position;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Wysyła bieżący fragment do odbiorcy, ignorując {@code left} pierwszych
|
|
|
+ * znaków oraz {@code right} ostatnich. Następnie rozpoczyna przetwarzanie
|
|
|
+ * nowego fragmentu rozpoczynającego się od aktualnej pozycji kursora.
|
|
|
+ * @param left
|
|
|
+ * @param right
|
|
|
+ */
|
|
|
+ public void flush(int left, int right) {
|
|
|
+ if(recent < position && position <= array.length()) {
|
|
|
+ send(array.substring(recent+left, position-right));
|
|
|
+ }
|
|
|
+ dispose();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Wysyła bieżący fragment do odbiorcy. Następnie rozpoczyna przetwarzanie
|
|
|
+ * nowego fragmentu od aktualnej pozycji kursora.
|
|
|
+ */
|
|
|
+ public void flush() {
|
|
|
+ flush(0, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Rozpoczyna przetwarzanie kolejnej porcji danych
|
|
|
+ * @param input
|
|
|
+ * @throws IllegalStateException jeśli strumień jest w trakcie przetwarzania
|
|
|
+ * danych.
|
|
|
+ */
|
|
|
+ public StringStreamer open(String input) {
|
|
|
+ if(!array.isEmpty()) {
|
|
|
+ throw new IllegalStateException("already opened");
|
|
|
+ }
|
|
|
+ if( null != tail ) {
|
|
|
+ array = tail + input;
|
|
|
+ tail = null;
|
|
|
+ } else {
|
|
|
+ array = input;
|
|
|
+ }
|
|
|
+ position = 0;
|
|
|
+ recent = 0;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Kończy przetwarzanie danych
|
|
|
+ */
|
|
|
+ public void close() {
|
|
|
+ if(tail != null) {
|
|
|
+ if( !array.isEmpty() ) {
|
|
|
+ throw new IllegalStateException("tail already in use");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ tail = array.substring(recent);
|
|
|
+ recent = 0;
|
|
|
+ position = 0;
|
|
|
+ array = "";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Sprawdza,czy przetwarzanie wykorzystało wszystkie przekazane dane
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public boolean pending() {
|
|
|
+ return tail != null;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|