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