001package org.hl7.fhir.dstu3.utils; 002 003/*- 004 * #%L 005 * org.hl7.fhir.dstu3 006 * %% 007 * Copyright (C) 2014 - 2019 Health Level 7 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023 024import org.hl7.fhir.dstu3.model.ExpressionNode; 025import org.hl7.fhir.dstu3.model.ExpressionNode.SourceLocation; 026import org.hl7.fhir.exceptions.FHIRException; 027import org.hl7.fhir.utilities.Utilities; 028 029// shared lexer for concrete syntaxes 030// - FluentPath 031// - Mapping language 032 033public class FHIRLexer { 034 public class FHIRLexerException extends FHIRException { 035 036 public FHIRLexerException() { 037 super(); 038 } 039 040 public FHIRLexerException(String message, Throwable cause) { 041 super(message, cause); 042 } 043 044 public FHIRLexerException(String message) { 045 super(message); 046 } 047 048 public FHIRLexerException(Throwable cause) { 049 super(cause); 050 } 051 052 } 053 private String source; 054 private int cursor; 055 private int currentStart; 056 private String current; 057 private SourceLocation currentLocation; 058 private SourceLocation currentStartLocation; 059 private int id; 060 061 public FHIRLexer(String source) throws FHIRLexerException { 062 this.source = source; 063 currentLocation = new SourceLocation(1, 1); 064 next(); 065 } 066 public String getCurrent() { 067 return current; 068 } 069 public SourceLocation getCurrentLocation() { 070 return currentLocation; 071 } 072 073 public boolean isConstant(boolean incDoubleQuotes) { 074 return current.charAt(0) == '\'' || (incDoubleQuotes && current.charAt(0) == '"') || current.charAt(0) == '@' || current.charAt(0) == '%' || 075 current.charAt(0) == '-' || current.charAt(0) == '+' || (current.charAt(0) >= '0' && current.charAt(0) <= '9') || 076 current.equals("true") || current.equals("false") || current.equals("{}"); 077 } 078 079 public boolean isStringConstant() { 080 return current.charAt(0) == '\'' || current.charAt(0) == '"'; 081 } 082 083 public String take() throws FHIRLexerException { 084 String s = current; 085 next(); 086 return s; 087 } 088 089 public int takeInt() throws FHIRLexerException { 090 String s = current; 091 if (!Utilities.isInteger(s)) 092 throw error("Found "+current+" expecting an integer"); 093 next(); 094 return Integer.parseInt(s); 095 } 096 097 public boolean isToken() { 098 if (Utilities.noString(current)) 099 return false; 100 101 if (current.startsWith("$")) 102 return true; 103 104 if (current.equals("*") || current.equals("**")) 105 return true; 106 107 if ((current.charAt(0) >= 'A' && current.charAt(0) <= 'Z') || (current.charAt(0) >= 'a' && current.charAt(0) <= 'z')) { 108 for (int i = 1; i < current.length(); i++) 109 if (!( (current.charAt(1) >= 'A' && current.charAt(1) <= 'Z') || (current.charAt(1) >= 'a' && current.charAt(1) <= 'z') || 110 (current.charAt(1) >= '0' && current.charAt(1) <= '9'))) 111 return false; 112 return true; 113 } 114 return false; 115 } 116 117 public FHIRLexerException error(String msg) { 118 return error(msg, currentLocation.toString()); 119 } 120 121 public FHIRLexerException error(String msg, String location) { 122 return new FHIRLexerException("Error at "+location+": "+msg); 123 } 124 125 public void next() throws FHIRLexerException { 126 current = null; 127 boolean last13 = false; 128 while (cursor < source.length() && Character.isWhitespace(source.charAt(cursor))) { 129 if (source.charAt(cursor) == '\r') { 130 currentLocation.setLine(currentLocation.getLine() + 1); 131 currentLocation.setColumn(1); 132 last13 = true; 133 } else if (!last13 && (source.charAt(cursor) == '\n')) { 134 currentLocation.setLine(currentLocation.getLine() + 1); 135 currentLocation.setColumn(1); 136 last13 = false; 137 } else { 138 last13 = false; 139 currentLocation.setColumn(currentLocation.getColumn() + 1); 140 } 141 cursor++; 142 } 143 currentStart = cursor; 144 currentStartLocation = currentLocation; 145 if (cursor < source.length()) { 146 char ch = source.charAt(cursor); 147 if (ch == '!' || ch == '>' || ch == '<' || ch == ':' || ch == '-' || ch == '=') { 148 cursor++; 149 if (cursor < source.length() && (source.charAt(cursor) == '=' || source.charAt(cursor) == '~' || 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 current = source.substring(currentStart, cursor); 157 } else if (ch >= '0' && ch <= '9') { 158 cursor++; 159 boolean dotted = false; 160 while (cursor < source.length() && ((source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || (source.charAt(cursor) == '.') && !dotted)) { 161 if (source.charAt(cursor) == '.') 162 dotted = true; 163 cursor++; 164 } 165 if (source.charAt(cursor-1) == '.') 166 cursor--; 167 current = source.substring(currentStart, cursor); 168 } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { 169 while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') || (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') || 170 (source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == '_')) 171 cursor++; 172 current = source.substring(currentStart, cursor); 173 } else if (ch == '%') { 174 cursor++; 175 if (cursor < source.length() && (source.charAt(cursor) == '"')) { 176 cursor++; 177 while (cursor < source.length() && (source.charAt(cursor) != '"')) 178 cursor++; 179 cursor++; 180 } else 181 while (cursor < source.length() && ((source.charAt(cursor) >= 'A' && source.charAt(cursor) <= 'Z') || (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z') || 182 (source.charAt(cursor) >= '0' && source.charAt(cursor) <= '9') || source.charAt(cursor) == ':' || source.charAt(cursor) == '-')) 183 cursor++; 184 current = source.substring(currentStart, cursor); 185 } else if (ch == '/') { 186 cursor++; 187 if (cursor < source.length() && (source.charAt(cursor) == '/')) { 188 cursor++; 189 while (cursor < source.length() && !((source.charAt(cursor) == '\r') || source.charAt(cursor) == '\n')) 190 cursor++; 191 } 192 current = source.substring(currentStart, cursor); 193 } else if (ch == '$') { 194 cursor++; 195 while (cursor < source.length() && (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z')) 196 cursor++; 197 current = source.substring(currentStart, cursor); 198 } else if (ch == '{') { 199 cursor++; 200 ch = source.charAt(cursor); 201 if (ch == '}') 202 cursor++; 203 current = source.substring(currentStart, cursor); 204 } else if (ch == '"'){ 205 cursor++; 206 boolean escape = false; 207 while (cursor < source.length() && (escape || source.charAt(cursor) != '"')) { 208 if (escape) 209 escape = false; 210 else 211 escape = (source.charAt(cursor) == '\\'); 212 cursor++; 213 } 214 if (cursor == source.length()) 215 throw error("Unterminated string"); 216 cursor++; 217 current = "\""+source.substring(currentStart+1, cursor-1)+"\""; 218 } else if (ch == '\''){ 219 cursor++; 220 char ech = ch; 221 boolean escape = false; 222 while (cursor < source.length() && (escape || source.charAt(cursor) != ech)) { 223 if (escape) 224 escape = false; 225 else 226 escape = (source.charAt(cursor) == '\\'); 227 cursor++; 228 } 229 if (cursor == source.length()) 230 throw error("Unterminated string"); 231 cursor++; 232 current = source.substring(currentStart, cursor); 233 if (ech == '\'') 234 current = "\'"+current.substring(1, current.length() - 1)+"\'"; 235 } else if (ch == '@'){ 236 cursor++; 237 while (cursor < source.length() && isDateChar(source.charAt(cursor))) 238 cursor++; 239 current = source.substring(currentStart, cursor); 240 } else { // if CharInSet(ch, ['.', ',', '(', ')', '=', '$']) then 241 cursor++; 242 current = source.substring(currentStart, cursor); 243 } 244 } 245 } 246 247 248 private boolean isDateChar(char ch) { 249 return ch == '-' || ch == ':' || ch == 'T' || ch == '+' || ch == 'Z' || Character.isDigit(ch); 250 } 251 public boolean isOp() { 252 return ExpressionNode.Operation.fromCode(current) != null; 253 } 254 public boolean done() { 255 return currentStart >= source.length(); 256 } 257 public int nextId() { 258 id++; 259 return id; 260 } 261 public SourceLocation getCurrentStartLocation() { 262 return currentStartLocation; 263 } 264 265 // special case use 266 public void setCurrent(String current) { 267 this.current = current; 268 } 269 270 public boolean hasComment() { 271 return !done() && current.startsWith("//"); 272 } 273 public boolean hasToken(String kw) { 274 return !done() && kw.equals(current); 275 } 276 public boolean hasToken(String... names) { 277 if (done()) 278 return false; 279 for (String s : names) 280 if (s.equals(current)) 281 return true; 282 return false; 283 } 284 285 public void token(String kw) throws FHIRLexerException { 286 if (!kw.equals(current)) 287 throw error("Found \""+current+"\" expecting \""+kw+"\""); 288 next(); 289 } 290 291 public String readConstant(String desc) throws FHIRLexerException { 292 if (!isStringConstant()) 293 throw error("Found "+current+" expecting \"["+desc+"]\""); 294 295 return processConstant(take()); 296 } 297 298 public String processConstant(String s) throws FHIRLexerException { 299 StringBuilder b = new StringBuilder(); 300 int i = 1; 301 while (i < s.length()-1) { 302 char ch = s.charAt(i); 303 if (ch == '\\') { 304 i++; 305 switch (s.charAt(i)) { 306 case 't': 307 b.append('\t'); 308 break; 309 case 'r': 310 b.append('\r'); 311 break; 312 case 'n': 313 b.append('\n'); 314 break; 315 case 'f': 316 b.append('\f'); 317 break; 318 case '\'': 319 b.append('\''); 320 break; 321 case '\\': 322 b.append('\\'); 323 break; 324 case '/': 325 b.append('\\'); 326 break; 327 case 'u': 328 i++; 329 int uc = Integer.parseInt(s.substring(i, i+4), 16); 330 b.append((char) uc); 331 i = i + 4; 332 break; 333 default: 334 throw new FHIRLexerException("Unknown character escape \\"+s.charAt(i)); 335 } 336 } else { 337 b.append(ch); 338 i++; 339 } 340 } 341 return b.toString(); 342 343 } 344 public void skipToken(String token) throws FHIRLexerException { 345 if (getCurrent().equals(token)) 346 next(); 347 348 } 349 public String takeDottedToken() throws FHIRLexerException { 350 StringBuilder b = new StringBuilder(); 351 b.append(take()); 352 while (!done() && getCurrent().equals(".")) { 353 b.append(take()); 354 b.append(take()); 355 } 356 return b.toString(); 357 } 358 359 void skipComments() throws FHIRLexerException { 360 while (!done() && hasComment()) 361 next(); 362 } 363 364}