/* * 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); } }