/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
// DWB14: parser loops if // comment at start of program
// DWB15: allow program to have trailing ';'
package org.apache.karaf.shell.support.parsing;
import java.util.ArrayList;
import java.util.List;
public class GogoParser {
int current = 0;
String text;
boolean escaped;
static final String SPECIAL = "<;|{[\"'$`(=";
List<List<List<String>>> program;
List<List<String>> statements;
List<String> statement;
int cursor;
int start = -1;
int c0;
int c1;
int c2;
int c3;
public GogoParser(String text, int cursor) {
this.text = text;
this.cursor = cursor;
}
public void ws() {
// derek: BUGFIX: loop if comment at beginning of input
//while (!eof() && isWhitespace(peek())) {
while (!eof() && (!escaped && isWhitespace(peek()) || current == 0)) {
if (current != 0 || !escaped && isWhitespace(peek())) {
current++;
}
if (peek() == '/' && current < text.length() - 2
&& text.charAt(current + 1) == '/') {
comment();
}
if (current == 0) {
break;
}
}
}
private boolean isWhitespace(char ch) {
return ch != '\n' && Character.isWhitespace(ch);
}
private void comment() {
while (!eof() && peek() != '\n' && peek() != '\r') {
next();
}
}
public boolean eof() {
return current >= text.length();
}
public char peek() {
return peek(false);
}
char peek(boolean increment) {
escaped = false;
if (eof()) {
return 0;
}
int last = current;
char c = text.charAt(current++);
if (c == '\\') {
escaped = true;
if (eof()) {
throw new RuntimeException("Eof found after \\");
}
c = text.charAt(current++);
switch (c) {
case 't':
c = '\t';
break;
case '\r':
case '\n':
c = ' ';
break;
case 'b':
c = '\b';
break;
case 'f':
c = '\f';
break;
case 'n':
c = '\n';
break;
case 'r':
c = '\r';
break;
case 'u':
c = unicode();
current += 4;
break;
default:
// We just take the next character literally
// but have the escaped flag set, important for {},[] etc
}
}
if (cursor > last && cursor <= current) {
c0 = program != null ? program.size() : 0;
c1 = statements != null ? statements.size() : 0;
c2 = statement != null ? statement.size() : 0;
c3 = (start >= 0) ? current - start : 0;
}
if (!increment) {
current = last;
}
return c;
}
public List<List<List<String>>> program() {
program = new ArrayList<List<List<String>>>();
ws();
if (!eof()) {
program.add(pipeline());
while (peek() == ';' || peek() == '\n') {
current++;
List<List<String>> pipeline = pipeline();
program.add(pipeline);
}
}
if (!eof()) {
throw new RuntimeException("Program has trailing text: " + context(current));
}
List<List<List<String>>> p = program;
program = null;
return p;
}
CharSequence context(int around) {
return text.subSequence(Math.max(0, current - 20), Math.min(text.length(),
current + 4));
}
public List<List<String>> pipeline() {
statements = new ArrayList<List<String>>();
statements.add(statement());
while (peek() == '|') {
current++;
ws();
if (!eof()) {
statements.add(statement());
}
else {
statements.add(new ArrayList<String>());
break;
}
}
List<List<String>> s = statements;
statements = null;
return s;
}
public List<String> statement() {
statement = new ArrayList<String>();
statement.add(value());
while (!eof()) {
ws();
if (peek() == '|' || peek() == ';' || peek() == '\n') {
break;
}
if (!eof()) {
statement.add(messy());
}
}
List<String> s = statement;
statement = null;
return s;
}
public String messy()
{
start = current;
char c = peek();
if (c > 0 && SPECIAL.indexOf(c) < 0) {
current++;
try {
while (!eof()) {
c = peek();
if (!escaped && (c == ';' || c == '|' || c == '\n' || isWhitespace(c))) {
break;
}
next();
}
return text.substring(start, current);
} finally {
start = -1;
}
}
else {
return value();
}
}
public int position() {
return current;
}
public int cursorArgumentIndex() {
return c2;
}
public int argumentPosition() {
return c3;
}
public String value() {
ws();
start = current;
try {
char c = next();
if (!escaped) {
switch (c) {
case '{':
return text.substring(start, find('}', '{'));
case '(':
return text.substring(start, find(')', '('));
case '[':
return text.substring(start, find(']', '['));
case '<':
return text.substring(start, find('>', '<'));
case '=':
return text.substring(start, current);
case '"':
case '\'':
quote(c);
break;
}
}
// Some identifier or number
while (!eof()) {
c = peek();
if (!escaped) {
if (isWhitespace(c) || c == ';' || c == '|' || c == '=') {
break;
}
else if (c == '{') {
next();
find('}', '{');
}
else if (c == '(') {
next();
find(')', '(');
}
else if (c == '<') {
next();
find('>', '<');
}
else if (c == '[') {
next();
find(']', '[');
}
else if (c == '\'' || c == '"') {
next();
quote(c);
next();
}
else {
next();
}
}
else {
next();
}
}
return text.substring(start, current);
} finally {
start = -1;
}
}
public boolean escaped() {
return escaped;
}
public char next() {
return peek(true);
}
char unicode() {
if (current + 4 > text.length()) {
throw new IllegalArgumentException("Unicode \\u escape at eof at pos ..."
+ context(current) + "...");
}
String s = text.subSequence(current, current + 4).toString();
int n = Integer.parseInt(s, 16);
return (char) n;
}
int find(char target, char deeper) {
int start = current;
int level = 1;
while (level != 0) {
if (eof()) {
throw new RuntimeException("Eof found in the middle of a compound for '"
+ target + deeper + "', begins at " + context(start));
}
char c = next();
if (!escaped) {
if (c == target) {
level--;
} else {
if (c == deeper) {
level++;
} else {
if (c == '"') {
quote('"');
} else {
if (c == '\'') {
quote('\'');
}
else {
if (c == '`') {
quote('`');
}
}
}
}
}
}
}
return current;
}
int quote(char which) {
while (!eof() && (peek() != which || escaped)) {
next();
}
return current++;
}
CharSequence findVar() {
int start = current;
char c = peek();
if (c == '{') {
next();
int end = find('}', '{');
return text.subSequence(start, end);
}
if (c == '(') {
next();
int end = find(')', '(');
return text.subSequence(start, end);
}
if (Character.isJavaIdentifierPart(c)) {
while (c == '$') {
c = next();
}
while (!eof() && (Character.isJavaIdentifierPart(c) || c == '.') && c != '$') {
next();
c = peek();
}
return text.subSequence(start, current);
}
throw new IllegalArgumentException(
"Reference to variable does not match syntax of a variable: "
+ context(start));
}
public String toString() {
return "..." + context(current) + "...";
}
public String unescape() {
StringBuilder sb = new StringBuilder();
while (!eof()) {
sb.append(next());
}
return sb.toString();
}
}