001package org.hl7.fhir.dstu3.utils; 002 003import org.hl7.fhir.dstu3.model.ExpressionNode; 004import org.hl7.fhir.dstu3.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 source; 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.source = 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) == '%' || 054 current.charAt(0) == '-' || current.charAt(0) == '+' || (current.charAt(0) >= '0' && current.charAt(0) <= '9') || 055 current.equals("true") || current.equals("false") || current.equals("{}"); 056 } 057 058 public boolean isStringConstant() { 059 return current.charAt(0) == '\'' || current.charAt(0) == '"'; 060 } 061 062 public String take() throws FHIRLexerException { 063 String s = current; 064 next(); 065 return s; 066 } 067 068 public int takeInt() throws FHIRLexerException { 069 String s = current; 070 if (!Utilities.isInteger(s)) 071 throw error("Found "+current+" expecting an integer"); 072 next(); 073 return Integer.parseInt(s); 074 } 075 076 public boolean isToken() { 077 if (Utilities.noString(current)) 078 return false; 079 080 if (current.startsWith("$")) 081 return true; 082 083 if (current.equals("*") || current.equals("**")) 084 return true; 085 086 if ((current.charAt(0) >= 'A' && current.charAt(0) <= 'Z') || (current.charAt(0) >= 'a' && current.charAt(0) <= 'z')) { 087 for (int i = 1; i < current.length(); i++) 088 if (!( (current.charAt(1) >= 'A' && current.charAt(1) <= 'Z') || (current.charAt(1) >= 'a' && current.charAt(1) <= 'z') || 089 (current.charAt(1) >= '0' && current.charAt(1) <= '9'))) 090 return false; 091 return true; 092 } 093 return false; 094 } 095 096 public FHIRLexerException error(String msg) { 097 return error(msg, currentLocation.toString()); 098 } 099 100 public FHIRLexerException error(String msg, String location) { 101 return new FHIRLexerException("Error at "+location+": "+msg); 102 } 103 104 public void next() throws FHIRLexerException { 105 current = null; 106 boolean last13 = false; 107 while (cursor < source.length() && Character.isWhitespace(source.charAt(cursor))) { 108 if (source.charAt(cursor) == '\r') { 109 currentLocation.setLine(currentLocation.getLine() + 1); 110 currentLocation.setColumn(1); 111 last13 = true; 112 } else if (!last13 && (source.charAt(cursor) == '\n')) { 113 currentLocation.setLine(currentLocation.getLine() + 1); 114 currentLocation.setColumn(1); 115 last13 = false; 116 } else { 117 last13 = false; 118 currentLocation.setColumn(currentLocation.getColumn() + 1); 119 } 120 cursor++; 121 } 122 currentStart = cursor; 123 currentStartLocation = currentLocation; 124 if (cursor < source.length()) { 125 char ch = source.charAt(cursor); 126 if (ch == '!' || ch == '>' || ch == '<' || ch == ':' || ch == '-' || ch == '=') { 127 cursor++; 128 if (cursor < source.length() && (source.charAt(cursor) == '=' || source.charAt(cursor) == '~' || source.charAt(cursor) == '-')) 129 cursor++; 130 current = source.substring(currentStart, cursor); 131 } else if (ch == '.' ) { 132 cursor++; 133 if (cursor < source.length() && (source.charAt(cursor) == '.')) 134 cursor++; 135 current = source.substring(currentStart, cursor); 136 } else if (ch >= '0' && ch <= '9') { 137 cursor++; 138 boolean dotted = false; 139 while (cursor < source.length() && ((source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || (source.charAt(cursor) == '.') && !dotted)) { 140 if (source.charAt(cursor) == '.') 141 dotted = true; 142 cursor++; 143 } 144 if (source.charAt(cursor-1) == '.') 145 cursor--; 146 current = source.substring(currentStart, cursor); 147 } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { 148 while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') || (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') || 149 (source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == '_')) 150 cursor++; 151 current = source.substring(currentStart, cursor); 152 } else if (ch == '%') { 153 cursor++; 154 if (cursor < source.length() && (source.charAt(cursor) == '"')) { 155 cursor++; 156 while (cursor < source.length() && (source.charAt(cursor) != '"')) 157 cursor++; 158 cursor++; 159 } else 160 while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') || (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') || 161 (source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == ':' || source.charAt(cursor) == '-')) 162 cursor++; 163 current = source.substring(currentStart, cursor); 164 } else if (ch == '/') { 165 cursor++; 166 if (cursor < source.length() && (source.charAt(cursor) == '/')) { 167 cursor++; 168 while (cursor < source.length() && !((source.charAt(cursor) == '\r') || source.charAt(cursor) == '\n')) 169 cursor++; 170 } 171 current = source.substring(currentStart, cursor); 172 } else if (ch == '$') { 173 cursor++; 174 while (cursor < source.length() && (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z')) 175 cursor++; 176 current = source.substring(currentStart, cursor); 177 } else if (ch == '{') { 178 cursor++; 179 ch = source.charAt(cursor); 180 if (ch == '}') 181 cursor++; 182 current = source.substring(currentStart, cursor); 183 } else if (ch == '"'){ 184 cursor++; 185 boolean escape = false; 186 while (cursor < source.length() && (escape || source.charAt(cursor) != '"')) { 187 if (escape) 188 escape = false; 189 else 190 escape = (source.charAt(cursor) == '\\'); 191 cursor++; 192 } 193 if (cursor == source.length()) 194 throw error("Unterminated string"); 195 cursor++; 196 current = "\""+source.substring(currentStart+1, cursor-1)+"\""; 197 } else if (ch == '\''){ 198 cursor++; 199 char ech = ch; 200 boolean escape = false; 201 while (cursor < source.length() && (escape || source.charAt(cursor) != ech)) { 202 if (escape) 203 escape = false; 204 else 205 escape = (source.charAt(cursor) == '\\'); 206 cursor++; 207 } 208 if (cursor == source.length()) 209 throw error("Unterminated string"); 210 cursor++; 211 current = source.substring(currentStart, cursor); 212 if (ech == '\'') 213 current = "\'"+current.substring(1, current.length() - 1)+"\'"; 214 } else if (ch == '@'){ 215 cursor++; 216 while (cursor < source.length() && isDateChar(source.charAt(cursor))) 217 cursor++; 218 current = source.substring(currentStart, cursor); 219 } else { // if CharInSet(ch, ['.', ',', '(', ')', '=', '$']) then 220 cursor++; 221 current = source.substring(currentStart, cursor); 222 } 223 } 224 } 225 226 227 private boolean isDateChar(char ch) { 228 return ch == '-' || ch == ':' || ch == 'T' || ch == '+' || ch == 'Z' || Character.isDigit(ch); 229 } 230 public boolean isOp() { 231 return ExpressionNode.Operation.fromCode(current) != null; 232 } 233 public boolean done() { 234 return currentStart >= source.length(); 235 } 236 public int nextId() { 237 id++; 238 return id; 239 } 240 public SourceLocation getCurrentStartLocation() { 241 return currentStartLocation; 242 } 243 244 // special case use 245 public void setCurrent(String current) { 246 this.current = current; 247 } 248 249 public boolean hasComment() { 250 return !done() && current.startsWith("//"); 251 } 252 public boolean hasToken(String kw) { 253 return !done() && kw.equals(current); 254 } 255 public boolean hasToken(String... names) { 256 if (done()) 257 return false; 258 for (String s : names) 259 if (s.equals(current)) 260 return true; 261 return false; 262 } 263 264 public void token(String kw) throws FHIRLexerException { 265 if (!kw.equals(current)) 266 throw error("Found \""+current+"\" expecting \""+kw+"\""); 267 next(); 268 } 269 270 public String readConstant(String desc) throws FHIRLexerException { 271 if (!isStringConstant()) 272 throw error("Found "+current+" expecting \"["+desc+"]\""); 273 274 return processConstant(take()); 275 } 276 277 public String processConstant(String s) throws FHIRLexerException { 278 StringBuilder b = new StringBuilder(); 279 int i = 1; 280 while (i < s.length()-1) { 281 char ch = s.charAt(i); 282 if (ch == '\\') { 283 i++; 284 switch (s.charAt(i)) { 285 case 't': 286 b.append('\t'); 287 break; 288 case 'r': 289 b.append('\r'); 290 break; 291 case 'n': 292 b.append('\n'); 293 break; 294 case 'f': 295 b.append('\f'); 296 break; 297 case '\'': 298 b.append('\''); 299 break; 300 case '\\': 301 b.append('\\'); 302 break; 303 case '/': 304 b.append('\\'); 305 break; 306 case 'u': 307 i++; 308 int uc = Integer.parseInt(s.substring(i, i+4), 16); 309 b.append((char) uc); 310 i = i + 4; 311 break; 312 default: 313 throw new FHIRLexerException("Unknown character escape \\"+s.charAt(i)); 314 } 315 } else { 316 b.append(ch); 317 i++; 318 } 319 } 320 return b.toString(); 321 322 } 323 public void skipToken(String token) throws FHIRLexerException { 324 if (getCurrent().equals(token)) 325 next(); 326 327 } 328 public String takeDottedToken() throws FHIRLexerException { 329 StringBuilder b = new StringBuilder(); 330 b.append(take()); 331 while (!done() && getCurrent().equals(".")) { 332 b.append(take()); 333 b.append(take()); 334 } 335 return b.toString(); 336 } 337 338 void skipComments() throws FHIRLexerException { 339 while (!done() && hasComment()) 340 next(); 341 } 342 343}