| /* |
| * 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. |
| */ |
| package org.apache.felix.gogo.runtime; |
| |
| import java.util.*; |
| import java.util.regex.Pattern; |
| |
| |
| public class Parser |
| { |
| |
| public static abstract class Executable extends Token |
| { |
| public Executable(Token cs) |
| { |
| super(cs); |
| } |
| } |
| |
| public static class Operator extends Executable |
| { |
| public Operator(Token cs) { |
| super(cs); |
| } |
| } |
| |
| public static class Statement extends Executable |
| { |
| private final List<Token> tokens; |
| private final List<Token> redirections; |
| |
| public Statement(Token cs, List<Token> tokens, List<Token> redirections) |
| { |
| super(cs); |
| this.tokens = tokens; |
| this.redirections = redirections; |
| } |
| |
| public List<Token> tokens() |
| { |
| return tokens; |
| } |
| |
| public List<Token> redirections() { |
| return redirections; |
| } |
| } |
| |
| /** |
| * pipe1 ; pipe2 ; ... |
| */ |
| public static class Program extends Token |
| { |
| private final List<Executable> tokens; |
| |
| public Program(Token cs, List<Executable> tokens) |
| { |
| super(cs); |
| this.tokens = tokens; |
| } |
| |
| public List<Executable> tokens() |
| { |
| return tokens; |
| } |
| } |
| |
| /** |
| * token1 | token2 | ... |
| */ |
| public static class Pipeline extends Executable |
| { |
| private final List<Executable> tokens; |
| |
| public Pipeline(Token cs, List<Executable> tokens) |
| { |
| super(cs); |
| this.tokens = tokens; |
| } |
| |
| public List<Executable> tokens() |
| { |
| return tokens; |
| } |
| |
| } |
| |
| /** |
| * ( program ) |
| */ |
| public static class Sequence extends Executable |
| { |
| private final Program program; |
| |
| public Sequence(Token cs, Program program) |
| { |
| super(cs); |
| this.program = program; |
| } |
| |
| public Program program() |
| { |
| return program; |
| } |
| } |
| |
| /** |
| * { program } |
| */ |
| public static class Closure extends Token |
| { |
| private final Program program; |
| |
| public Closure(Token cs, Program program) |
| { |
| super(cs); |
| this.program = program; |
| } |
| |
| public Program program() |
| { |
| return program; |
| } |
| } |
| |
| /** |
| * [ a b ...] |
| * [ k1=v1 k2=v2 ...] |
| */ |
| public static class Array extends Token |
| { |
| private final List<Token> list; |
| private final Map<Token, Token> map; |
| |
| public Array(Token cs, List<Token> list, Map<Token, Token> map) |
| { |
| super(cs); |
| assert list != null ^ map != null; |
| this.list = list; |
| this.map = map; |
| } |
| |
| public List<Token> list() |
| { |
| return list; |
| } |
| |
| public Map<Token, Token> map() |
| { |
| return map; |
| } |
| } |
| |
| protected final Tokenizer tz; |
| protected final LinkedList<String> stack = new LinkedList<>(); |
| protected final List<Token> tokens = new ArrayList<>(); |
| protected final List<Statement> statements = new ArrayList<>(); |
| |
| public Parser(CharSequence line) |
| { |
| this.tz = new Tokenizer(line); |
| } |
| |
| public List<Token> tokens() { |
| return Collections.unmodifiableList(tokens); |
| } |
| |
| public List<Statement> statements() { |
| Collections.sort(statements, new Comparator<Statement>() { |
| @Override |
| public int compare(Statement o1, Statement o2) { |
| return Integer.compare(o1.start, o2.start); |
| } |
| }); |
| return Collections.unmodifiableList(statements); |
| } |
| |
| public Program program() |
| { |
| List<Executable> tokens = new ArrayList<>(); |
| List<Executable> pipes = null; |
| int start = tz.index - 1; |
| while (true) |
| { |
| Statement ex; |
| Token t = next(); |
| if (t == null) |
| { |
| if (pipes != null) |
| { |
| throw new EOFError(tz.line, tz.column, "unexpected EOT while looking for a statement after |", getMissing("pipe"), "0"); |
| } |
| else |
| { |
| return new Program(whole(tokens, start), tokens); |
| } |
| } |
| if (Token.eq("}", t) || Token.eq(")", t)) |
| { |
| if (pipes != null) |
| { |
| throw new EOFError(t.line, t.column, "unexpected token '" + t + "' while looking for a statement after |", getMissing("pipe"), "0"); |
| } |
| else |
| { |
| push(t); |
| return new Program(whole(tokens, start), tokens); |
| } |
| } |
| else |
| { |
| push(t); |
| ex = statement(); |
| } |
| t = next(); |
| if (t == null || Token.eq(";", t) || Token.eq("\n", t) || Token.eq("&", t) || Token.eq("&&", t) || Token.eq("||", t)) |
| { |
| if (pipes != null) |
| { |
| pipes.add(ex); |
| tokens.add(new Pipeline(whole(pipes, start), pipes)); |
| pipes = null; |
| } |
| else |
| { |
| tokens.add(ex); |
| } |
| if (t == null) |
| { |
| return new Program(whole(tokens, start), tokens); |
| } |
| else { |
| tokens.add(new Operator(t)); |
| } |
| } |
| else if (Token.eq("|", t) || Token.eq("|&", t)) |
| { |
| if (pipes == null) |
| { |
| pipes = new ArrayList<>(); |
| } |
| pipes.add(ex); |
| pipes.add(new Operator(t)); |
| } |
| else |
| { |
| if (pipes != null) |
| { |
| pipes.add(ex); |
| tokens.add(new Pipeline(whole(pipes, start), pipes)); |
| pipes = null; |
| } |
| else |
| { |
| tokens.add(ex); |
| } |
| push(t); |
| } |
| } |
| } |
| |
| protected void push(Token t) { |
| tz.push(t); |
| } |
| |
| protected Token next() { |
| boolean pushed = tz.pushed != null; |
| Token token = tz.next(); |
| if (!pushed && token != null) { |
| tokens.add(token); |
| } |
| return token; |
| } |
| |
| public Sequence sequence() |
| { |
| Token start = start("(", "sequence"); |
| expectNotNull(); |
| Program program = program(); |
| Token end = end(")"); |
| return new Sequence(whole(start, end), program); |
| } |
| |
| public Closure closure() |
| { |
| Token start = start("{", "closure"); |
| expectNotNull(); |
| Program program = program(); |
| Token end = end("}"); |
| return new Closure(whole(start, end), program); |
| } |
| |
| private static final Pattern redirNoArg = Pattern.compile("[0-9]?>&[0-9-]|[0-9-]?<&[0-9-]"); |
| private static final Pattern redirArg = Pattern.compile("[0-9&]?>|[0-9]?>>|[0-9]?<|[0-9]?<>|<<<"); |
| private static final Pattern redirHereDoc = Pattern.compile("<<-?"); |
| |
| public Statement statement() |
| { |
| List<Token> tokens = new ArrayList<>(); |
| List<Token> redirs = new ArrayList<>(); |
| boolean needRedirArg = false; |
| int start = tz.index; |
| while (true) |
| { |
| Token t = next(); |
| if (t == null |
| || Token.eq("\n", t) |
| || Token.eq(";", t) |
| || Token.eq("&", t) |
| || Token.eq("&&", t) |
| || Token.eq("||", t) |
| || Token.eq("|", t) |
| || Token.eq("|&", t) |
| || Token.eq("}", t) |
| || Token.eq(")", t) |
| || Token.eq("]", t)) |
| { |
| if (needRedirArg) |
| { |
| throw new EOFError(tz.line, tz.column, "Expected file name for redirection", "redir", "foo"); |
| } |
| push(t); |
| break; |
| } |
| if (Token.eq("{", t)) |
| { |
| push(t); |
| tokens.add(closure()); |
| } |
| else if (Token.eq("[", t)) |
| { |
| push(t); |
| tokens.add(array()); |
| } |
| else if (Token.eq("(", t)) |
| { |
| push(t); |
| tokens.add(sequence()); |
| } |
| else if (needRedirArg) |
| { |
| redirs.add(t); |
| needRedirArg = false; |
| } |
| else if (redirNoArg.matcher(t).matches()) |
| { |
| redirs.add(t); |
| } |
| else if (redirArg.matcher(t).matches()) |
| { |
| redirs.add(t); |
| needRedirArg = true; |
| } |
| else if (redirHereDoc.matcher(t).matches()) |
| { |
| redirs.add(t); |
| redirs.add(tz.readHereDoc(t.charAt(t.length() - 1) == '-')); |
| } |
| else |
| { |
| tokens.add(t); |
| } |
| } |
| Statement statement = new Statement(whole(tokens, start), tokens, redirs); |
| statements.add(statement); |
| return statement; |
| } |
| |
| public Array array() |
| { |
| Token start = start("[", "array"); |
| Boolean isMap = null; |
| List<Token> list = new ArrayList<>(); |
| Map<Token, Token> map = new LinkedHashMap<>(); |
| while (true) |
| { |
| Token key = next(); |
| if (key == null) |
| { |
| throw new EOFError(tz.line, tz.column, "unexpected EOT", getMissing(), "]"); |
| } |
| if (Token.eq("]", key)) |
| { |
| push(key); |
| break; |
| } |
| if (Token.eq("\n", key)) |
| { |
| continue; |
| } |
| if (Token.eq("{", key) || Token.eq(";", key) || Token.eq("&", key) || Token.eq("&&", key) || Token.eq("||", key) |
| || Token.eq("|", key) || Token.eq("|&", key) || Token.eq(")", key) || Token.eq("}", key) || Token.eq("=", key)) |
| { |
| throw new SyntaxError(key.line(), key.column(), "unexpected token '" + key + "' while looking for array key"); |
| } |
| if (Token.eq("(", key)) |
| { |
| push(key); |
| key = sequence(); |
| } |
| if (Token.eq("[", key)) |
| { |
| push(key); |
| key = array(); |
| } |
| if (isMap == null) |
| { |
| Token n = next(); |
| if (n == null) |
| { |
| throw new EOFError(tz.line, tz.column, "unexpected EOF while looking for array token", getMissing(), "]"); |
| } |
| isMap = Token.eq("=", n); |
| push(n); |
| } |
| if (isMap) |
| { |
| expect("="); |
| Token val = next(); |
| if (val == null) |
| { |
| throw new EOFError(tz.line, tz.column, "unexpected EOF while looking for array value", getMissing(), "0"); |
| } |
| else if (Token.eq(";", val) || Token.eq("&", val) || Token.eq("&&", val) || Token.eq("||", val) || Token.eq("|", val) || Token.eq("|&", val) |
| || Token.eq(")", key) || Token.eq("}", key) || Token.eq("=", key)) |
| { |
| throw new SyntaxError(key.line(), key.column(), "unexpected token '" + key + "' while looking for array value"); |
| } |
| else if (Token.eq("[", val)) |
| { |
| push(val); |
| val = array(); |
| } |
| else if (Token.eq("(", val)) |
| { |
| push(val); |
| val = sequence(); |
| } |
| else if (Token.eq("{", val)) |
| { |
| push(val); |
| val = closure(); |
| } |
| map.put(key, val); |
| } |
| else |
| { |
| list.add(key); |
| } |
| } |
| Token end = end("]"); |
| if (isMap == null || !isMap) |
| { |
| return new Array(whole(start, end), list, null); |
| } |
| else |
| { |
| return new Array(whole(start, end), null, map); |
| } |
| } |
| |
| protected void expectNotNull() |
| { |
| Token t = next(); |
| if (t == null) |
| { |
| throw new EOFError(tz.line, tz.column, |
| "unexpected EOT", |
| getMissing(), "0"); |
| } |
| push(t); |
| } |
| |
| private String getMissing() { |
| return getMissing(null); |
| } |
| |
| private String getMissing(String additional) { |
| StringBuilder sb = new StringBuilder(); |
| LinkedList<String> stack = this.stack; |
| if (additional != null) { |
| stack = new LinkedList<>(stack); |
| stack.addLast(additional); |
| } |
| String last = null; |
| int nb = 0; |
| for (String cur : stack) { |
| if (last == null) { |
| last = cur; |
| nb = 1; |
| } else if (last.equals(cur)) { |
| nb++; |
| } else { |
| if (sb.length() > 0) { |
| sb.append(" "); |
| } |
| sb.append(last); |
| if (nb > 1) { |
| sb.append("(").append(nb).append(")"); |
| } |
| last = cur; |
| nb = 1; |
| } |
| } |
| if (sb.length() > 0) { |
| sb.append(" "); |
| } |
| sb.append(last); |
| if (nb > 1) { |
| sb.append("(").append(nb).append(")"); |
| } |
| return sb.toString(); |
| } |
| |
| protected Token start(String str, String missing) { |
| stack.addLast(missing); |
| return expect(str); |
| } |
| |
| protected Token end(String str) { |
| Token t = expect(str); |
| stack.removeLast(); |
| return t; |
| } |
| |
| protected Token expect(String str) |
| { |
| Token start = next(); |
| if (start == null) |
| { |
| throw new EOFError(tz.line, tz.column, |
| "unexpected EOT looking for '" + str + "", |
| getMissing(), str); |
| } |
| if (!Token.eq(str, start)) |
| { |
| throw new SyntaxError(start.line, start.column, "expected '" + str + "' but got '" + start.toString() + "'"); |
| } |
| return start; |
| } |
| |
| protected Token whole(List<? extends Token> tokens, int index) |
| { |
| if (tokens.isEmpty()) |
| { |
| index = Math.min(index, tz.text().length()); |
| return tz.text().subSequence(index, index); |
| } |
| Token b = tokens.get(0); |
| Token e = tokens.get(tokens.size() - 1); |
| return whole(b, e); |
| } |
| |
| protected Token whole(Token b, Token e) |
| { |
| return tz.text.subSequence(b.start - tz.text.start, e.start + e.length() - tz.text.start); |
| } |
| |
| } |