[513] | 1 | // License: GPL. Copyright 2007 by Immanuel Scholz and others
|
---|
| 2 | package org.openstreetmap.josm.actions.search;
|
---|
| 3 |
|
---|
[2863] | 4 | import static org.openstreetmap.josm.tools.I18n.marktr;
|
---|
| 5 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 6 |
|
---|
[513] | 7 | import java.io.IOException;
|
---|
[2645] | 8 | import java.io.Reader;
|
---|
[3046] | 9 | import java.util.Arrays;
|
---|
| 10 | import java.util.List;
|
---|
[513] | 11 |
|
---|
[2645] | 12 | import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
|
---|
| 13 |
|
---|
[513] | 14 | public class PushbackTokenizer {
|
---|
[2993] | 15 |
|
---|
[3385] | 16 | public static class Range {
|
---|
[2993] | 17 | private final long start;
|
---|
| 18 | private final long end;
|
---|
| 19 |
|
---|
| 20 | public Range(long start, long end) {
|
---|
| 21 | this.start = start;
|
---|
| 22 | this.end = end;
|
---|
| 23 | }
|
---|
| 24 |
|
---|
| 25 | public long getStart() {
|
---|
| 26 | return start;
|
---|
| 27 | }
|
---|
| 28 |
|
---|
| 29 | public long getEnd() {
|
---|
| 30 | return end;
|
---|
| 31 | }
|
---|
| 32 | }
|
---|
| 33 |
|
---|
[2645] | 34 | private final Reader search;
|
---|
[513] | 35 |
|
---|
[2645] | 36 | private Token currentToken;
|
---|
| 37 | private String currentText;
|
---|
[3046] | 38 | private Long currentNumber;
|
---|
| 39 | private Long currentRange;
|
---|
[2645] | 40 | private int c;
|
---|
[513] | 41 |
|
---|
[2645] | 42 | public PushbackTokenizer(Reader search) {
|
---|
[1169] | 43 | this.search = search;
|
---|
[2645] | 44 | getChar();
|
---|
[1169] | 45 | }
|
---|
[513] | 46 |
|
---|
[2863] | 47 | public enum Token {
|
---|
| 48 | NOT(marktr("<not>")), OR(marktr("<or>")), LEFT_PARENT(marktr("<left parent>")),
|
---|
| 49 | RIGHT_PARENT(marktr("<right parent>")), COLON(marktr("<colon>")), EQUALS(marktr("<equals>")),
|
---|
[3046] | 50 | KEY(marktr("<key>")), QUESTION_MARK(marktr("<question mark>")),
|
---|
| 51 | EOF(marktr("<end-of-file>"));
|
---|
[2645] | 52 |
|
---|
[2863] | 53 | private Token(String name) {
|
---|
| 54 | this.name = name;
|
---|
| 55 | }
|
---|
| 56 |
|
---|
| 57 | private final String name;
|
---|
| 58 |
|
---|
| 59 | @Override
|
---|
| 60 | public String toString() {
|
---|
| 61 | return tr(name);
|
---|
| 62 | }
|
---|
| 63 | }
|
---|
| 64 |
|
---|
| 65 |
|
---|
[2645] | 66 | private void getChar() {
|
---|
| 67 | try {
|
---|
| 68 | c = search.read();
|
---|
| 69 | } catch (IOException e) {
|
---|
| 70 | throw new RuntimeException(e.getMessage(), e);
|
---|
| 71 | }
|
---|
| 72 | }
|
---|
| 73 |
|
---|
[2973] | 74 | private long getNumber() {
|
---|
| 75 | long result = 0;
|
---|
| 76 | while (Character.isDigit(c)) {
|
---|
| 77 | result = result * 10 + (c - '0');
|
---|
| 78 | getChar();
|
---|
| 79 | }
|
---|
| 80 | return result;
|
---|
| 81 | }
|
---|
| 82 |
|
---|
[3049] | 83 | private static final List<Character> specialChars = Arrays.asList(new Character[] {'"', ':', '(', ')', '|', '=', '?'});
|
---|
| 84 | private static final List<Character> specialCharsQuoted = Arrays.asList(new Character[] {'"'});
|
---|
[3046] | 85 |
|
---|
| 86 | private String getString(boolean quoted) {
|
---|
[3049] | 87 | List<Character> sChars = quoted ? specialCharsQuoted : specialChars;
|
---|
[3046] | 88 | StringBuilder s = new StringBuilder();
|
---|
| 89 | boolean escape = false;
|
---|
[3050] | 90 | while (c != -1 && (escape || (!sChars.contains((char)c) && (quoted || !Character.isWhitespace(c))))) {
|
---|
[3046] | 91 | if (c == '\\' && !escape) {
|
---|
| 92 | escape = true;
|
---|
| 93 | } else {
|
---|
| 94 | s.append((char)c);
|
---|
| 95 | escape = false;
|
---|
| 96 | }
|
---|
| 97 | getChar();
|
---|
| 98 | }
|
---|
| 99 | return s.toString();
|
---|
| 100 | }
|
---|
| 101 |
|
---|
| 102 | private String getString() {
|
---|
| 103 | return getString(false);
|
---|
| 104 | }
|
---|
| 105 |
|
---|
[1169] | 106 | /**
|
---|
| 107 | * The token returned is <code>null</code> or starts with an identifier character:
|
---|
| 108 | * - for an '-'. This will be the only character
|
---|
| 109 | * : for an key. The value is the next token
|
---|
| 110 | * | for "OR"
|
---|
| 111 | * ' ' for anything else.
|
---|
| 112 | * @return The next token in the stream.
|
---|
| 113 | */
|
---|
[2645] | 114 | public Token nextToken() {
|
---|
| 115 | if (currentToken != null) {
|
---|
| 116 | Token result = currentToken;
|
---|
| 117 | currentToken = null;
|
---|
| 118 | return result;
|
---|
[1169] | 119 | }
|
---|
[513] | 120 |
|
---|
[2645] | 121 | while (Character.isWhitespace(c)) {
|
---|
| 122 | getChar();
|
---|
| 123 | }
|
---|
| 124 | switch (c) {
|
---|
| 125 | case -1:
|
---|
| 126 | getChar();
|
---|
| 127 | return Token.EOF;
|
---|
| 128 | case ':':
|
---|
| 129 | getChar();
|
---|
| 130 | return Token.COLON;
|
---|
| 131 | case '=':
|
---|
| 132 | getChar();
|
---|
| 133 | return Token.EQUALS;
|
---|
| 134 | case '(':
|
---|
| 135 | getChar();
|
---|
| 136 | return Token.LEFT_PARENT;
|
---|
| 137 | case ')':
|
---|
| 138 | getChar();
|
---|
| 139 | return Token.RIGHT_PARENT;
|
---|
| 140 | case '|':
|
---|
| 141 | getChar();
|
---|
| 142 | return Token.OR;
|
---|
[2863] | 143 | case '?':
|
---|
| 144 | getChar();
|
---|
| 145 | return Token.QUESTION_MARK;
|
---|
[2645] | 146 | case '"':
|
---|
[2768] | 147 | getChar();
|
---|
[3046] | 148 | currentText = getString(true);
|
---|
[2645] | 149 | getChar();
|
---|
| 150 | return Token.KEY;
|
---|
| 151 | default:
|
---|
[3046] | 152 | String prefix = "";
|
---|
| 153 | if (c == '-') {
|
---|
[2645] | 154 | getChar();
|
---|
[3046] | 155 | if (!Character.isDigit(c))
|
---|
| 156 | return Token.NOT;
|
---|
| 157 | prefix = "-";
|
---|
[1169] | 158 | }
|
---|
[3046] | 159 | currentText = prefix + getString();
|
---|
[2645] | 160 | if ("or".equals(currentText))
|
---|
| 161 | return Token.OR;
|
---|
[3046] | 162 | try {
|
---|
| 163 | currentNumber = Long.parseLong(currentText);
|
---|
| 164 | } catch (NumberFormatException e) {
|
---|
| 165 | currentNumber = null;
|
---|
| 166 | }
|
---|
| 167 | int pos = currentText.indexOf('-', 1);
|
---|
| 168 | if (pos > 0) {
|
---|
| 169 | try {
|
---|
| 170 | currentNumber = Long.parseLong(currentText.substring(0, pos));
|
---|
| 171 | currentRange = Long.parseLong(currentText.substring(pos + 1));
|
---|
| 172 | } catch (NumberFormatException e) {
|
---|
| 173 | currentNumber = null;
|
---|
| 174 | currentRange = null;
|
---|
| 175 | }
|
---|
| 176 | }
|
---|
| 177 | return Token.KEY;
|
---|
[1169] | 178 | }
|
---|
| 179 | }
|
---|
[513] | 180 |
|
---|
[2645] | 181 | public boolean readIfEqual(Token token) {
|
---|
| 182 | Token nextTok = nextToken();
|
---|
| 183 | if (nextTok == null ? token == null : nextTok == token)
|
---|
[1169] | 184 | return true;
|
---|
[2645] | 185 | currentToken = nextTok;
|
---|
[1169] | 186 | return false;
|
---|
| 187 | }
|
---|
[513] | 188 |
|
---|
[2993] | 189 | public String readTextOrNumber() {
|
---|
[2645] | 190 | Token nextTok = nextToken();
|
---|
| 191 | if (nextTok == Token.KEY)
|
---|
| 192 | return currentText;
|
---|
| 193 | currentToken = nextTok;
|
---|
[1169] | 194 | return null;
|
---|
| 195 | }
|
---|
[513] | 196 |
|
---|
[2973] | 197 | public long readNumber(String errorMessage) throws ParseError {
|
---|
[3046] | 198 | if ((nextToken() == Token.KEY) && (currentNumber != null))
|
---|
[2973] | 199 | return currentNumber;
|
---|
| 200 | else
|
---|
| 201 | throw new ParseError(errorMessage);
|
---|
| 202 | }
|
---|
| 203 |
|
---|
[2993] | 204 | public long getReadNumber() {
|
---|
[3046] | 205 | return (currentNumber != null) ? currentNumber : 0;
|
---|
[2993] | 206 | }
|
---|
| 207 |
|
---|
[3046] | 208 | public Range readRange(String errorMessage) throws ParseError {
|
---|
| 209 | if ((nextToken() == Token.KEY) && (currentNumber != null)) {
|
---|
| 210 | if (currentRange == null)
|
---|
| 211 | return new Range(currentNumber, currentNumber);
|
---|
| 212 | else
|
---|
| 213 | return new Range(currentNumber, currentRange);
|
---|
| 214 | } else
|
---|
| 215 | throw new ParseError(errorMessage);
|
---|
[2993] | 216 | }
|
---|
| 217 |
|
---|
[2645] | 218 | public String getText() {
|
---|
| 219 | return currentText;
|
---|
| 220 | }
|
---|
[513] | 221 | }
|
---|