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}