001    /*
002     * Copyright (C) 2012 eXo Platform SAS.
003     *
004     * This is free software; you can redistribute it and/or modify it
005     * under the terms of the GNU Lesser General Public License as
006     * published by the Free Software Foundation; either version 2.1 of
007     * the License, or (at your option) any later version.
008     *
009     * This software is distributed in the hope that it will be useful,
010     * but WITHOUT ANY WARRANTY; without even the implied warranty of
011     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012     * Lesser General Public License for more details.
013     *
014     * You should have received a copy of the GNU Lesser General Public
015     * License along with this software; if not, write to the Free
016     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018     */
019    package org.crsh.cli.impl.line;
020    
021    /**
022     * Line parser.
023     *
024     * @author Julien Viet
025     */
026    public final class LineParser {
027    
028      public static abstract class Visitor {
029        public void onChar(int index, Quoting quoting, boolean backslash, char c) { }
030        public void openStrongQuote(int index) {}
031        public void closeStrongQuote(int index) {}
032        public void openWeakQuote(int index) {}
033        public void closeWeakQuote(int index) {}
034        public void reset() {}
035      }
036    
037      /** . */
038      private static final int NORMAL = 0, WEAK_QUOTING = 1, STRONG_QUOTING = 2;
039    
040      /** . */
041      private int status = NORMAL;
042    
043      /** . */
044      private boolean escaped = false;
045    
046      /** . */
047      private int index = 0;
048    
049      /** . */
050      private final Visitor[] visitors;
051    
052      public LineParser(Visitor... visitors) {
053        this.visitors = visitors;
054      }
055    
056      public boolean crlf() {
057        if (escaped) {
058          escaped = false;
059          return false;
060        } else {
061          switch (status) {
062            case WEAK_QUOTING:
063              for (Visitor visitor : visitors) visitor.onChar(index, Quoting.WEAK, false, '\n');
064              index++;
065              return false;
066            case STRONG_QUOTING:
067              for (Visitor visitor : visitors) visitor.onChar(index, Quoting.STRONG, false, '\n');
068              index++;
069              return false;
070            default:
071              return true;
072          }
073        }
074      }
075    
076      public LineParser append(CharSequence s) {
077        int len = s.length();
078        for (int index = 0;index < len;index++) {
079          append(s.charAt(index));
080        }
081        return this;
082      }
083    
084      public LineParser append(char c) {
085        if (!escaped) {
086          switch (status) {
087            case NORMAL:
088              switch (c) {
089                case '\\':
090                  escaped = true;
091                  break;
092                case '\"':
093                  for (Visitor visitor : visitors) visitor.openWeakQuote(index);
094                  status = WEAK_QUOTING;
095                  index++;
096                  break;
097                case '\'':
098                  for (Visitor visitor : visitors) visitor.openStrongQuote(index);
099                  index++;
100                  status = STRONG_QUOTING;
101                  break;
102                default:
103                  for (Visitor visitor : visitors) visitor.onChar(index, null, false, c);
104                  index++;
105                  break;
106              }
107              break;
108            case WEAK_QUOTING:
109              switch (c) {
110                case '"':
111                  for (Visitor visitor : visitors) visitor.closeWeakQuote(index);
112                  index++;
113                  status = NORMAL;
114                  break;
115                case '\\':
116                  escaped = true;
117                  break;
118                default:
119                  for (Visitor visitor : visitors) visitor.onChar(index, Quoting.WEAK, false, c);
120                  index++;
121                  break;
122              }
123              break;
124            case STRONG_QUOTING:
125              switch (c) {
126                case '\'':
127                  for (Visitor visitor : visitors) visitor.closeStrongQuote(index);
128                  index++;
129                  status = NORMAL;
130                  break;
131                case '\\':
132                  escaped = true;
133                  break;
134                default:
135                  for (Visitor visitor : visitors) visitor.onChar(index, Quoting.STRONG, false, c);
136                  index++;
137                  break;
138              }
139              break;
140          }
141        } else {
142          switch (status) {
143            case NORMAL:
144              for (Visitor visitor : visitors) visitor.onChar(index + 1, null, true, c);
145              index += 2;
146              break;
147            case WEAK_QUOTING:
148              for (Visitor visitor : visitors) visitor.onChar(index + 1, Quoting.WEAK, true, c);
149              index += 2;
150              break;
151            case STRONG_QUOTING:
152              if (c == '\'') {
153                // Special case
154                status = NORMAL;
155                for (Visitor visitor : visitors) visitor.onChar(index, Quoting.STRONG, false, '\\');
156                for (Visitor visitor : visitors) visitor.closeStrongQuote(index + 1);
157                index += 2;
158              } else {
159                for (Visitor visitor : visitors) visitor.onChar(index + 1, Quoting.STRONG, true, c);
160                index += 2;
161              }
162              break;
163          }
164          escaped = false;
165        }
166        return this;
167      }
168    
169      public void reset() {
170        index = 0;
171        status = NORMAL;
172        escaped = false;
173        for (Visitor visitor : visitors) visitor.reset();
174      }
175    }