/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.bunjlabs.fuga.router;
import java.io.IOException;
import java.io.Reader;
import java.util.regex.Pattern;
public class Tokenizer {
private static final Pattern METHOD_PATTERN = Pattern.compile("GET|POST|PUT|PATCH|TRACE|DELETE|HEAD|OPTIONS");
private static final Pattern INTEGER_PATTERN = Pattern.compile("([1-9][0-9]+)|[0-9]");
protected static final int TK_EOF = -1;
protected static final int TK_ERROR = -2;
protected static final int TK_NOTHING = -4;
protected static final int TK_USE = -10;
protected static final int TK_INCLUDE = -11;
protected static final int TK_HOST = -12;
protected static final int TK_METHOD = -20;
protected static final int TK_PATTERN = -21;
protected static final int TK_WORD = -22;
protected static final int TK_STRCONST = -23;
protected static final int TK_INTEGER = -24;
private final Reader r;
private int curr = -1;
protected int ttype = TK_NOTHING;
protected int line = 1;
protected int column = 1;
protected String sval = null;
public Tokenizer(Reader r) {
this.r = r;
try {
skip();
} catch (IOException ex) {
}
}
public int next() throws RoutesMapSyntaxException {
return ttype = nextToken();
}
private int nextToken() throws RoutesMapSyntaxException {
sval = null;
try {
for (;;) {
switch (curr) {
case -1:
return TK_EOF;
case '\r':
case '\n':
incLine();
break;
case ' ':
case '\f':
case '\t':
skip();
break;
case '/':
comment();
break;
case '@':
pattern();
return TK_PATTERN;
case '(':
case ')':
case '{':
case '}':
case ',':
case ':':
int c = curr;
skip();
return c;
case '"':
constant();
return TK_STRCONST;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return integer();
default: {
if (!currIsWord()) {
char ct = (char) curr;
skip();
throw new RoutesMapSyntaxException(this, "Unexpected symbol: " + ct);
}
return word();
}
}
}
} catch (IOException ex) {
sval = ex.getLocalizedMessage();
return TK_ERROR;
}
}
private int integer() throws RoutesMapSyntaxException, IOException {
StringBuilder sb = new StringBuilder();
while (currIsDigit()) {
sb.append((char) curr);
skip();
}
sval = sb.toString();
if (!INTEGER_PATTERN.matcher(sval).matches()) {
return word(sval);
}
return TK_INTEGER;
}
private void constant() throws RoutesMapSyntaxException, IOException {
StringBuilder sb = new StringBuilder();
skip();
while (curr != '"') {
switch (curr) {
case -1:
case '\n':
case '\r':
throw new RoutesMapSyntaxException(this, "Unfinished string");
case '\\':
skip();
if (curr == 'n') {
sb.append('\n');
} else if (curr == 'r') {
sb.append('\r');
} else if (curr == 't') {
sb.append('\t');
} else if (curr == 'f') {
sb.append('\f');
} else if (curr == '\\') {
sb.append('\\');
} else {
throw new RoutesMapSyntaxException(this, "Unsupported char escape sequance: \\" + ((char) curr));
}
break;
default:
sb.append((char) curr);
break;
}
skip();
}
skip();
sval = sb.toString();
}
private int word() throws IOException {
return word(null);
}
private int word(String first) throws IOException {
StringBuilder sb = new StringBuilder();
if (first != null && !first.isEmpty()) {
sb.append(first);
}
while (currIsWord()) {
sb.append((char) curr);
skip();
}
sval = sb.toString();
if (METHOD_PATTERN.matcher(sval).matches()) {
return TK_METHOD;
} else if (sval.equals("use")) {
return TK_USE;
} else if (sval.equals("include")) {
return TK_INCLUDE;
} else if (sval.equals("host")) {
return TK_HOST;
} else {
return TK_WORD;
}
}
private void pattern() throws IOException {
StringBuilder sb = new StringBuilder();
skip();
while (!currIsNewline() && !currIsWitespace()) {
sb.append((char) curr);
skip();
}
sval = sb.toString();
}
private void comment() throws IOException, RoutesMapSyntaxException {
skip();
if (curr == '*') {
/* block comment */
skip();
for (;;) {
if (curr == '*') {
skip();
if (curr == '/') {
skip();
break;
}
} else if (currIsNewline()) {
incLine();
} else {
skip();
}
}
} else {
while (!currIsNewline()) {
skip();
/* line comment */
}
}
}
private boolean currIsDigit() {
return (curr >= '0' && curr <= '9');
}
private boolean currIsWord() {
return (curr >= 'A' && curr <= 'Z')
|| (curr >= 'a' && curr <= 'z')
|| (curr >= '0' && curr <= '9')
|| curr == '_' || curr == '.';
}
private boolean currIsWitespace() {
return (curr == ' ' || curr == '\f' || curr == '\t');
}
private boolean currIsNewline() {
return (curr == '\n' || curr == '\r');
}
private void skip() throws IOException {
curr = r.read();
column++;
}
private void incLine() throws IOException, RoutesMapSyntaxException {
int old = curr;
skip();
if (currIsNewline() && curr != old) {
skip();
}
if (++line >= Integer.MAX_VALUE) {
throw new RoutesMapSyntaxException(this, "Too big input");
}
column = 1;
}
}