blob: 8388b27212c31755f90cdacb6c5cffecd64016db [file] [log] [blame]
/*
* 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.console.completer;
import java.util.ArrayList;
import java.util.List;
public class Parser
{
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 Parser(String text, int cursor)
{
this.text = text;
this.cursor = cursor;
}
void ws()
{
// derek: BUGFIX: loop if comment at beginning of input
//while (!eof() && Character.isWhitespace(peek())) {
while (!eof() && (!escaped && Character.isWhitespace(peek()) || current == 0))
{
if (current != 0 || !escaped && Character.isWhitespace(peek()))
{
current++;
}
if (peek() == '/' && current < text.length() - 2
&& text.charAt(current + 1) == '/')
{
comment();
}
if (current == 0)
{
break;
}
}
}
private void comment()
{
while (!eof() && peek() != '\n' && peek() != '\r')
{
next();
}
}
boolean eof()
{
return current >= text.length();
}
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 \\"); // derek
}
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() == ';')
{
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() == ';')
{
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 == '|' || Character.isWhitespace(c)))
{
break;
}
next();
}
return text.substring(start, current);
} finally {
start = -1;
}
}
else
{
return value();
}
}
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 (Character.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;
}
}
boolean escaped()
{
return escaped;
}
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();
}
}