diff --git a/runtime-testsuite/test/org/antlr/v4/runtime/TestCaseChangingCharStream.java b/runtime-testsuite/test/org/antlr/v4/runtime/TestCaseChangingCharStream.java new file mode 100644 index 0000000000..5cebdd8cba --- /dev/null +++ b/runtime-testsuite/test/org/antlr/v4/runtime/TestCaseChangingCharStream.java @@ -0,0 +1,49 @@ +package org.antlr.v4.runtime; + +import com.google.common.collect.Lists; +import org.junit.Test; + +import java.util.List; + +import static org.antlr.v4.runtime.CharStreams.fromString; +import static org.junit.Assert.assertEquals; + +public class TestCaseChangingCharStream { + + /** + * Helper function to return a complete list of symbols read from the stream. + * @param stream + * @return + */ + private static List readAll(CharStream stream) { + List symbols = Lists.newArrayList(); + + for (int i = 1; i <= stream.size()+1; i++) { + symbols.add( stream.LA(i) ); + } + + return symbols; + } + + @Test + public void testUpper() { + List expected = Lists.newArrayList((int)'A', (int)'B', (int)'C', (int)'D', IntStream.EOF); + + CharStream stream = CharStreams.toUpper(fromString("abcd")); + assertEquals(expected, readAll(stream)); + + stream = CharStreams.toUpper(fromString("ABCD")); + assertEquals(expected, readAll(stream)); + } + + @Test + public void testLower() { + List expected = Lists.newArrayList((int)'a', (int)'b', (int)'c', (int)'d', IntStream.EOF); + + CharStream stream = CharStreams.toLower(fromString("abcd")); + assertEquals(expected, readAll(stream)); + + stream = CharStreams.toLower(fromString("ABCD")); + assertEquals(expected, readAll(stream)); + } +} diff --git a/runtime/Java/src/org/antlr/v4/runtime/CaseChangingCharStream.java b/runtime/Java/src/org/antlr/v4/runtime/CaseChangingCharStream.java new file mode 100644 index 0000000000..d069d0188a --- /dev/null +++ b/runtime/Java/src/org/antlr/v4/runtime/CaseChangingCharStream.java @@ -0,0 +1,81 @@ +package org.antlr.v4.runtime; + +import org.antlr.v4.runtime.misc.Interval; + +/** + * This class supports case-insensitive lexing by wrapping an existing + * {@link CharStream} and forcing the lexer to see either upper or + * lowercase characters. Grammar literals should then be either upper or + * lower case such as 'BEGIN' or 'begin'. The text of the character + * stream is unaffected. Example: input 'BeGiN' would match lexer rule + * 'BEGIN' if constructor parameter upper=true but getText() would return + * 'BeGiN'. + */ +public class CaseChangingCharStream implements CharStream { + + final CharStream stream; + final boolean upper; + + /** + * Constructs a new CaseChangingCharStream wrapping the given {@link CharStream} forcing + * all characters to upper case or lower case. + * @param stream The stream to wrap. + * @param upper If true force each symbol to upper case, otherwise force to lower. + */ + public CaseChangingCharStream(CharStream stream, boolean upper) { + this.stream = stream; + this.upper = upper; + } + + @Override + public String getText(Interval interval) { + return stream.getText(interval); + } + + @Override + public void consume() { + stream.consume(); + } + + @Override + public int LA(int i) { + int c = stream.LA(i); + if (c <= 0) { + return c; + } + if (upper) { + return Character.toUpperCase(c); + } + return Character.toLowerCase(c); + } + + @Override + public int mark() { + return stream.mark(); + } + + @Override + public void release(int marker) { + stream.release(marker); + } + + @Override + public int index() { + return stream.index(); + } + + @Override + public void seek(int index) { + stream.seek(index); + } + + @Override + public int size() { + return stream.size(); + } + + @Override + public String getSourceName() { + return stream.getSourceName(); + } +} diff --git a/runtime/Java/src/org/antlr/v4/runtime/CharStreams.java b/runtime/Java/src/org/antlr/v4/runtime/CharStreams.java index 73fb875999..d0ca125fbd 100644 --- a/runtime/Java/src/org/antlr/v4/runtime/CharStreams.java +++ b/runtime/Java/src/org/antlr/v4/runtime/CharStreams.java @@ -303,4 +303,26 @@ public static CodePointCharStream fromChannel( channel.close(); } } + + /** + * Takes the stream and forces all symbols to uppercase for lexing purposes + * but leaves the original text as-is. + * + * @param in + * @return + */ + public static CharStream toUpper(CharStream in) { + return new CaseChangingCharStream(in, true); + } + + /** + * Takes the stream and forces all symbols to lowercase for lexing purposes + * but leaves the original text as-is. + * + * @param in + * @return + */ + public static CharStream toLower(CharStream in) { + return new CaseChangingCharStream(in, false); + } }