001package org.hl7.fhir.dstu2016may.utils;
002
003import org.hl7.fhir.dstu2016may.model.ExpressionNode;
004import org.hl7.fhir.dstu2016may.model.ExpressionNode.SourceLocation;
005import org.hl7.fhir.exceptions.FHIRException;
006import org.hl7.fhir.utilities.Utilities;
007
008// shared lexer for concrete syntaxes 
009// - FluentPath
010// - Mapping language
011
012public class FHIRLexer {
013  public class FHIRLexerException extends FHIRException {
014
015    public FHIRLexerException() {
016      super();
017    }
018
019    public FHIRLexerException(String message, Throwable cause) {
020      super(message, cause);
021    }
022
023    public FHIRLexerException(String message) {
024      super(message);
025    }
026
027    public FHIRLexerException(Throwable cause) {
028      super(cause);
029    }
030
031  }
032  private String path;
033  private int cursor;
034  private int currentStart;
035  private String current;
036  private SourceLocation currentLocation;
037  private SourceLocation currentStartLocation;
038  private int id;
039
040  public FHIRLexer(String source) throws FHIRLexerException {
041    this.path = source;
042    currentLocation = new SourceLocation(1, 1);
043    next();
044  }
045  public String getCurrent() {
046    return current;
047  }
048  public SourceLocation getCurrentLocation() {
049    return currentLocation;
050  }
051
052  public boolean isConstant(boolean incDoubleQuotes) {
053    return current.charAt(0) == '\'' || (incDoubleQuotes && current.charAt(0) == '"') || current.charAt(0) == '@' || current.charAt(0) == '%' || current.charAt(0) == '-' || (current.charAt(0) >= '0' && current.charAt(0) <= '9') || current.equals("true") || current.equals("false") || current.equals("{}");
054  }
055
056  public boolean isStringConstant() {
057    return current.charAt(0) == '\'' || current.charAt(0) == '"';
058  }
059
060  public String take() throws FHIRLexerException {
061    String s = current;
062    next();
063    return s;
064  }
065
066  public boolean isToken() {
067    if (Utilities.noString(current))
068      return false;
069
070    if (current.startsWith("$"))
071      return true;
072
073    if (current.equals("*") || current.equals("**"))
074      return true;
075
076    if ((current.charAt(0) >= 'A' && current.charAt(0) <= 'Z') || (current.charAt(0) >= 'a' && current.charAt(0) <= 'z')) {
077      for (int i = 1; i < current.length(); i++) 
078        if (!( (current.charAt(1) >= 'A' && current.charAt(1) <= 'Z') || (current.charAt(1) >= 'a' && current.charAt(1) <= 'z') ||
079            (current.charAt(1) >= '0' && current.charAt(1) <= '9')))
080          return false;
081      return true;
082    }
083    return false;
084  }
085
086  public FHIRLexerException error(String msg) {
087    return error(msg, currentLocation.toString());
088  }
089
090  public FHIRLexerException error(String msg, String location) {
091    return new FHIRLexerException("Error in "+path+" at "+location+": "+msg);
092  }
093
094  public void next() throws FHIRLexerException {
095    current = null;
096    boolean last13 = false;
097    while (cursor < path.length() && Character.isWhitespace(path.charAt(cursor))) {
098      if (path.charAt(cursor) == '\r') {
099        currentLocation.setLine(currentLocation.getLine() + 1);
100        currentLocation.setColumn(1);
101        last13 = true;
102      } else if (!last13 && (path.charAt(cursor) == '\n')) {
103        currentLocation.setLine(currentLocation.getLine() + 1);
104        currentLocation.setColumn(1);
105        last13 = false;
106      } else {
107        last13 = false;
108        currentLocation.setColumn(currentLocation.getColumn() + 1);
109      }
110      cursor++;
111    }
112    currentStart = cursor;
113    currentStartLocation = currentLocation;
114    if (cursor < path.length()) {
115      char ch = path.charAt(cursor);
116      if (ch == '!' || ch == '>' || ch == '<' || ch == ':' || ch == '-' || ch == '=')  {
117        cursor++;
118        if (cursor < path.length() && (path.charAt(cursor) == '=' || path.charAt(cursor) == '~' || path.charAt(cursor) == '-')) 
119          cursor++;
120        current = path.substring(currentStart, cursor);
121      } else if (ch >= '0' && ch <= '9') {
122          cursor++;
123        boolean dotted = false;
124        while (cursor < path.length() && ((path.charAt(cursor) >= '0' && path.charAt(cursor) <= '9') || (path.charAt(cursor) == '.') && !dotted)) {
125          if (path.charAt(cursor) == '.')
126            dotted = true;
127          cursor++;
128        }
129        if (path.charAt(cursor-1) == '.')
130          cursor--;
131        current = path.substring(currentStart, cursor);
132      }  else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
133        while (cursor < path.length() && ((path.charAt(cursor) >= 'A' && path.charAt(cursor) <= 'Z') || (path.charAt(cursor) >= 'a' && path.charAt(cursor) <= 'z') || 
134            (path.charAt(cursor) >= '0' && path.charAt(cursor) <= '9') || path.charAt(cursor) == '_')) 
135          cursor++;
136        current = path.substring(currentStart, cursor);
137      } else if (ch == '%') {
138        cursor++;
139        if (cursor < path.length() && (path.charAt(cursor) == '"')) {
140          cursor++;
141          while (cursor < path.length() && (path.charAt(cursor) != '"'))
142            cursor++;
143          cursor++;
144        } else
145        while (cursor < path.length() && ((path.charAt(cursor) >= 'A' && path.charAt(cursor) <= 'Z') || (path.charAt(cursor) >= 'a' && path.charAt(cursor) <= 'z') || 
146            (path.charAt(cursor) >= '0' && path.charAt(cursor) <= '9') || path.charAt(cursor) == ':' || path.charAt(cursor) == '-'))
147          cursor++;
148        current = path.substring(currentStart, cursor);
149      } else if (ch == '/') {
150        cursor++;
151        if (cursor < path.length() && (path.charAt(cursor) == '/')) {
152          cursor++;
153          while (cursor < path.length() && !((path.charAt(cursor) == '\r') || path.charAt(cursor) == '\n')) 
154            cursor++;
155        }
156        current = path.substring(currentStart, cursor);
157      } else if (ch == '$') {
158        cursor++;
159        while (cursor < path.length() && (path.charAt(cursor) >= 'a' && path.charAt(cursor) <= 'z'))
160          cursor++;
161        current = path.substring(currentStart, cursor);
162      } else if (ch == '{') {
163        cursor++;
164        ch = path.charAt(cursor);
165        if (ch == '}')
166          cursor++;
167        current = path.substring(currentStart, cursor);
168      } else if (ch == '"'){
169        cursor++;
170        boolean escape = false;
171        while (cursor < path.length() && (escape || path.charAt(cursor) != '"')) {
172          if (escape)
173            escape = false;
174          else 
175            escape = (path.charAt(cursor) == '\\');
176          cursor++;
177        }
178        if (cursor == path.length())
179          throw error("Unterminated string");
180        cursor++;
181        current = "\""+path.substring(currentStart+1, cursor-1)+"\"";
182      } else if (ch == '\''){
183        cursor++;
184        char ech = ch;
185        boolean escape = false;
186        while (cursor < path.length() && (escape || path.charAt(cursor) != ech)) {
187          if (escape)
188            escape = false;
189          else 
190            escape = (path.charAt(cursor) == '\\');
191          cursor++;
192        }
193        if (cursor == path.length())
194          throw error("Unterminated string");
195        cursor++;
196        current = path.substring(currentStart, cursor);
197        if (ech == '\'')
198          current = "\'"+current.substring(1, current.length() - 1)+"\'";
199      } else if (ch == '@'){
200        cursor++;
201        while (cursor < path.length() && isDateChar(path.charAt(cursor)))
202          cursor++;          
203        current = path.substring(currentStart, cursor);
204      } else { // if CharInSet(ch, ['.', ',', '(', ')', '=', '$']) then
205        cursor++;
206        current = path.substring(currentStart, cursor);
207      }
208    }
209  }
210
211
212  private boolean isDateChar(char ch) {
213    return ch == '-' || ch == ':' || ch == 'T' || ch == '+' || ch == 'Z' || Character.isDigit(ch);
214  }
215  public boolean isOp() {
216    return ExpressionNode.Operation.fromCode(current) != null;
217  }
218  public boolean done() {
219    return currentStart >= path.length();
220  }
221  public int nextId() {
222    id++;
223    return id;
224  }
225  public SourceLocation getCurrentStartLocation() {
226    return currentStartLocation;
227  }
228  
229  // special case use
230  public void setCurrent(String current) {
231    this.current = current;
232  }
233
234  public boolean hasComment() {
235    return !done() && current.startsWith("//");
236  }
237  public boolean hasToken(String kw) {
238      return !done() && kw.equals(current);
239  }
240  public void token(String kw) throws FHIRLexerException {
241    if (!kw.equals(current)) 
242      throw error("Found \""+current+"\" expecting \""+kw+"\"");
243    next();
244  }
245  public String readConstant(String desc) throws FHIRLexerException {
246    if (!isStringConstant())
247      throw error("Found "+current+" expecting \"["+desc+"]\"");
248
249    return processConstant(take());
250  }
251
252  public String processConstant(String s) throws FHIRLexerException {
253    StringBuilder b = new StringBuilder();
254    int i = 1;
255    while (i < s.length()-1) {
256      char ch = s.charAt(i);
257      if (ch == '\\') {
258        i++;
259        switch (s.charAt(i)) {
260        case 't': 
261          b.append('\t');
262          break;
263        case 'r':
264          b.append('\r');
265          break;
266        case 'n': 
267          b.append('\n');
268          break;
269        case 'f': 
270          b.append('\f');
271          break;
272        case '\'':
273          b.append('\'');
274          break;
275        case '\\': 
276          b.append('\\');
277          break;
278        case '/': 
279          b.append('\\');
280          break;
281        case 'u':
282          i++;
283          int uc = Integer.parseInt(s.substring(i, i+4), 16);
284          b.append((char) uc);
285          i = i + 4;
286          break;
287        default:
288          throw new FHIRLexerException("Unknown character escape \\"+s.charAt(i));
289        }
290      } else {
291        b.append(ch);
292        i++;
293      }
294    }
295    return b.toString();
296
297  }
298  public void skipToken(String token) throws FHIRLexerException {
299    if (getCurrent().equals(token))
300      next();
301    
302  }
303  public String takeDottedToken() throws FHIRLexerException {
304    StringBuilder b = new StringBuilder();
305    b.append(take());
306    while (!done() && getCurrent().equals(".")) {
307      b.append(take());
308      b.append(take());
309    }
310    return b.toString();
311  }
312  
313  void skipComments() throws FHIRLexerException {
314    while (!done() && hasComment())
315      next();
316  }
317
318}