/** * Copyright (c) 2015, Lucee Assosication Switzerland. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * */ package lucee.transformer.util; import java.util.ArrayList; import lucee.commons.digest.HashUtil; import lucee.commons.io.SystemUtil; import lucee.transformer.Position; /** * this class is a Parser String optimized for the transfomer (CFML Parser) */ public class SourceCode { public static final short AT_LEAST_ONE_SPACE=0; public static final short ZERO_OR_MORE_SPACE=1; protected int pos=0; protected final char[] text; protected final char[] lcText; protected final Integer[] lines; // TODO to int[] private final boolean writeLog; private final int dialect; /** * Constructor of the class * @param text * @param charset */ public SourceCode(String strText,boolean writeLog, int dialect) { this.text=strText.toCharArray(); this.dialect=dialect; lcText=new char[text.length]; ArrayList<Integer> arr=new ArrayList<Integer>(); for(int i=0;i<text.length;i++) { pos=i; if(text[i]=='\n') { arr.add(new Integer(i)); lcText[i]=' '; } else if(text[i]=='\r') { if(isNextRaw('\n')){ lcText[i++]=' '; } arr.add(new Integer(i)); lcText[i]=' '; } else if(text[i]=='\t') lcText[i]=' '; else lcText[i]=Character.toLowerCase(text[i]); } pos=0; arr.add(new Integer(text.length)); lines=arr.toArray(new Integer[arr.size()]); this.writeLog=writeLog; } /** * returns if the internal pointer is not on the last positions */ public boolean hasNext() { return pos+1<lcText.length; } /** * moves the internal pointer to the next position, no check if the next position is still valid */ public void next(){ pos++; } /** * moves the internal pointer to the previous position, no check if the next position is still valid */ public void previous(){ pos--; } /** * returns the character of the current position of the internal pointer */ public char getCurrent() { return text[pos]; } /** * returns the lower case representation of the character of the current position */ public char getCurrentLower() { return lcText[pos]; } /** * returns the character at the given position */ public char charAt(int pos) { return text[pos]; } /** * returns the character at the given position as lower case representation */ public char charAtLower(int pos) { return lcText[pos]; } /** * is the character at the next position the same as the character provided by the input parameter */ public boolean isNext(char c) { if(!hasNext()) return false; return lcText[pos+1]==c; } private boolean isNextRaw(char c) { if(!hasNext()) return false; return text[pos+1]==c; } /** * is the character at the current position (internal pointer) in the range of the given input characters? * @param left lower value. * @param right upper value. */ public boolean isCurrentBetween(char left, char right) { if(!isValidIndex()) return false; return lcText[pos]>=left && lcText[pos]<=right; } /** * returns if the character at the current position (internal pointer) is a valid variable character */ public boolean isCurrentVariableCharacter() { if(!isValidIndex()) return false; return isCurrentLetter() || isCurrentNumber() || isCurrent('$') || isCurrent('_'); } /** * returns if the current character is a letter (a-z,A-Z) * @return is a letter */ public boolean isCurrentLetter() { if(!isValidIndex()) return false; return lcText[pos]>='a' && lcText[pos]<='z'; } /** * returns if the current character is a number (0-9) * @return is a letter */ public boolean isCurrentNumber() { if(!isValidIndex()) return false; return lcText[pos]>='0' && lcText[pos]<='9'; } /** * retuns if the current character (internal pointer) is a valid special sign (_, $, Pound Symbol, Euro Symbol) */ public boolean isCurrentSpecial() { if(!isValidIndex()) return false; return lcText[pos]=='_' || lcText[pos]=='$' || lcText[pos]==SystemUtil.CHAR_EURO || lcText[pos]==SystemUtil.CHAR_POUND; } /** * is the current character (internal pointer) the same as the given */ public boolean isCurrent(char c) { if(!isValidIndex()) return false; return lcText[pos]==c; } /** * forward the internal pointer plus one if the next character is the same as the given input */ public boolean forwardIfCurrent(char c) { if(isCurrent(c)) { pos++; return true; } return false; } /** * returns if the current character (internal pointer) and the following are the same as the given input */ public boolean isCurrent(String str) { if(pos+str.length()>lcText.length) return false; for(int i=str.length()-1;i>=0;i--) { if(str.charAt(i)!=lcText[pos+i]) return false; } return true; /*char[] c=str.toCharArray(); // @ todo not shure for length if(pos+c.length>text.length) return false; for(int i=c.length-1;i>=0;i--) { if(c[i]!=lcText[pos+i]) return false; } return true;*/ } /** * forwards if the current character (internal pointer) and the following are the same as the given input */ public boolean forwardIfCurrent(String str) { boolean is=isCurrent(str); if(is)pos+=str.length(); return is; } /** * @param str string to check against current position * @param startWithSpace if true there must be whitespace at the current position * @return does the criteria match? */ public boolean forwardIfCurrent(String str, boolean startWithSpace) { if(!startWithSpace) return forwardIfCurrent(str); int start=pos; if(!removeSpace())return false; if(!forwardIfCurrent(str)){ pos=start; return false; } return true; } /** * @param str string to check against current position * @param startWithSpace if true there must be whitespace at the current position * @param followedByNoVariableCharacter the character following the string must be a none variable character (!a-z,A-Z,0-9,_$) (not eaten) * @return does the criteria match? */ public boolean forwardIfCurrent(String str, boolean startWithSpace, boolean followedByNoVariableCharacter) { int start=pos; if(startWithSpace && !removeSpace())return false; if(!forwardIfCurrent(str)){ pos=start; return false; } if(followedByNoVariableCharacter && isCurrentVariableCharacter()) { pos=start; return false; } return true; } /** * forwards if the current character (internal pointer) and the following are the same as the given input, followed by a none word character */ public boolean forwardIfCurrentAndNoWordAfter(String str) { int c=pos; if(forwardIfCurrent(str)) { if(!isCurrentBetween('a','z') && !isCurrent('_'))return true; } pos=c; return false; } /** * forwards if the current character (internal pointer) and the following are the same as the given input, followed by a none word character or a number */ public boolean forwardIfCurrentAndNoVarExt(String str) { int c=pos; if(forwardIfCurrent(str)) { if(!isCurrentBetween('a','z') &&!isCurrentBetween('0','9') && !isCurrent('_'))return true; } pos=c; return false; } /** * Gibt zurueck ob first den folgenden Zeichen entspricht, gefolgt von Leerzeichen und second. * @param first Erste Zeichen zum Vergleich (Vor den Leerzeichen). * @param second Zweite Zeichen zum Vergleich (Nach den Leerzeichen). * @return Gibt zurueck ob die eingegebenen Werte dem Inhalt beim aktuellen Stand des Zeigers entsprechen. */ public boolean isCurrent(String first,char second) { int start=pos; if(!forwardIfCurrent(first)) return false; removeSpace(); boolean rtn=isCurrent(second); pos=start; return rtn; } /** * Gibt zurueck ob first den folgenden Zeichen entspricht, gefolgt von Leerzeichen und second. * @param first Erstes Zeichen zum Vergleich (Vor den Leerzeichen). * @param second Zweites Zeichen zum Vergleich (Nach den Leerzeichen). * @return Gibt zurueck ob die eingegebenen Werte dem Inhalt beim aktuellen Stand des Zeigers entsprechen. */ public boolean isCurrent(char first,char second) { int start=pos; if(!forwardIfCurrent(first)) return false; removeSpace(); boolean rtn=isCurrent(second); pos=start; return rtn; } /** * Gibt zurueck ob first den folgenden Zeichen entspricht, * gefolgt von Leerzeichen und second, * wenn ja wird der Zeiger um die Laenge der uebereinstimmung nach vorne gestellt. * @param first Erste Zeichen zum Vergleich (Vor den Leerzeichen). * @param second Zweite Zeichen zum Vergleich (Nach den Leerzeichen). * @return Gibt zurueck ob der Zeiger vorwaerts geschoben wurde oder nicht. */ public boolean forwardIfCurrent(String first,char second) { int start=pos; if(!forwardIfCurrent(first)) return false; removeSpace(); boolean rtn=forwardIfCurrent(second); if(!rtn)pos=start; return rtn; } /** * Gibt zurueck ob ein Wert folgt und vor und hinterher Leerzeichen folgen. * @param before Definition der Leerzeichen vorher. * @param val Gefolgter Wert der erartet wird. * @param after Definition der Leerzeichen nach dem Wert. * @return Gibt zurueck ob der Zeiger vorwaerts geschoben wurde oder nicht. */ public boolean forwardIfCurrent(short before, String val,short after) { int start=pos; // space before if(before==AT_LEAST_ONE_SPACE) { if(!removeSpace()) return false; } else removeSpace(); // value if(!forwardIfCurrent(val)) { setPos(start); return false; } // space after if(after==AT_LEAST_ONE_SPACE) { if(!removeSpace()) { setPos(start); return false; } } else removeSpace(); return true; } /** * Gibt zurueck ob first den folgenden Zeichen entspricht, * gefolgt von Leerzeichen und second, * wenn ja wird der Zeiger um die Laenge der uebereinstimmung nach vorne gestellt. * @param first Erste Zeichen zum Vergleich (Vor den Leerzeichen). * @param second Zweite Zeichen zum Vergleich (Nach den Leerzeichen). * @return Gibt zurueck ob der Zeiger vorwaerts geschoben wurde oder nicht. */ public boolean forwardIfCurrent(char first,char second) { int start=pos; if(!forwardIfCurrent(first)) return false; removeSpace(); boolean rtn=forwardIfCurrent(second); if(!rtn)pos=start; return rtn; } /** * Gibt zurueck ob first den folgenden Zeichen entspricht, gefolgt von Leerzeichen und second. * @param first Erste Zeichen zum Vergleich (Vor den Leerzeichen). * @param second Zweite Zeichen zum Vergleich (Nach den Leerzeichen). * @return Gibt zurueck ob die eingegebenen Werte dem Inhalt beim aktuellen Stand des Zeigers entsprechen. */ public boolean isCurrent(String first,String second) { int start=pos; if(!forwardIfCurrent(first)) return false; removeSpace(); boolean rtn=isCurrent(second); pos=start; return rtn; } /** * Gibt zurueck ob first den folgenden Zeichen entspricht, * gefolgt von Leerzeichen und second, * wenn ja wird der Zeiger um die Laenge der uebereinstimmung nach vorne gestellt. * @param first Erste Zeichen zum Vergleich (Vor den Leerzeichen). * @param second Zweite Zeichen zum Vergleich (Nach den Leerzeichen). * @return Gibt zurueck ob der Zeiger vorwaerts geschoben wurde oder nicht. */ public boolean forwardIfCurrent(String first,String second) { int start=pos; if(!forwardIfCurrent(first)) return false; if(!removeSpace()){ pos=start; return false; } boolean rtn=forwardIfCurrent(second); if(!rtn)pos=start; return rtn; } public boolean forwardIfCurrent(String first,String second,String third) { int start=pos; if(!forwardIfCurrent(first)) return false; if(!removeSpace()){ pos=start; return false; } if(!forwardIfCurrent(second)){ pos=start; return false; } if(!removeSpace()){ pos=start; return false; } boolean rtn=forwardIfCurrent(third); if(!rtn)pos=start; return rtn; } public boolean forwardIfCurrent(String first,String second,String third, boolean startWithSpace) { if(!startWithSpace) return forwardIfCurrent(first, second, third); int start=pos; if(!removeSpace())return false; if(!forwardIfCurrent(first,second,third)){ pos=start; return false; } return true; } public boolean forwardIfCurrent(String first,String second,String third, boolean startWithSpace, boolean followedByNoVariableCharacter) { int start=pos; if(startWithSpace && !removeSpace())return false; if(!forwardIfCurrent(first,second,third)){ pos=start; return false; } if(followedByNoVariableCharacter && isCurrentVariableCharacter()) { pos=start; return false; } return true; } public boolean forwardIfCurrent(String first,String second, boolean startWithSpace, boolean followedByNoVariableCharacter) { int start=pos; if(startWithSpace && !removeSpace())return false; if(!forwardIfCurrent(first,second)){ pos=start; return false; } if(followedByNoVariableCharacter && isCurrentVariableCharacter()) { pos=start; return false; } return true; } public boolean forwardIfCurrent(String first,String second,String third, String forth) { int start=pos; if(!forwardIfCurrent(first)) return false; if(!removeSpace()){ pos=start; return false; } if(!forwardIfCurrent(second)){ pos=start; return false; } if(!removeSpace()){ pos=start; return false; } if(!forwardIfCurrent(third)){ pos=start; return false; } if(!removeSpace()){ pos=start; return false; } boolean rtn=forwardIfCurrent(forth); if(!rtn)pos=start; return rtn; } /** * Gibt zurueck ob sich vor dem aktuellen Zeichen Leerzeichen befinden. * @return Gibt zurueck ob sich vor dem aktuellen Zeichen Leerzeichen befinden. */ public boolean hasSpaceBefore() { return pos > 0 && lcText[pos - 1] == ' '; } public boolean hasNLBefore() { int index=0; while(pos-(++index) >= 0){ if(text[pos - index] == '\n')return true; if(text[pos - index] == '\r')return true; if(lcText[pos - index] != ' ') return false; } return false; } /** * Stellt den Zeiger nach vorne, wenn er sich innerhalb von Leerzeichen befindet, * bis die Leerzeichen fertig sind. * @return Gibt zurueck ob der Zeiger innerhalb von Leerzeichen war oder nicht. */ public boolean removeSpace() { int start=pos; while(pos<lcText.length && lcText[pos]==' ') { pos++; } return (start<pos); } public void revertRemoveSpace() { while(hasSpaceBefore()){ previous(); } } public String removeAndGetSpace() { int start=pos; while(pos<lcText.length && lcText[pos]==' ') { pos++; } return substring(start,pos-start); } /** * Stellt den internen Zeiger an den Anfang der naechsten Zeile, * gibt zurueck ob eine weitere Zeile existiert oder ob es bereits die letzte Zeile war. * @return Existiert eine weitere Zeile. */ public boolean nextLine() { while(isValidIndex() && text[pos]!='\n' && text[pos]!='\r') { next(); } if(!isValidIndex()) return false; if(text[pos]=='\n') { next(); return isValidIndex(); } if(text[pos]=='\r') { next(); if(isValidIndex() && text[pos]=='\n') { next(); } return isValidIndex(); } return false; } /** * Gibt eine Untermenge des CFMLString als Zeichenkette zurueck, * ausgehend von start bis zum Ende des CFMLString. * @param start Von wo aus die Untermege ausgegeben werden soll. * @return Untermenge als Zeichenkette */ public String substring(int start) { return substring(start,lcText.length-start); } /** * Gibt eine Untermenge des CFMLString als Zeichenkette zurueck, * ausgehend von start mit einer maximalen Laenge count. * @param start Von wo aus die Untermenge ausgegeben werden soll. * @param count Wie lange die zurueckgegebene Zeichenkette maximal sein darf. * @return Untermenge als Zeichenkette. */ public String substring(int start, int count) { return String.valueOf(text,start,count); } /** * Gibt eine Untermenge des CFMLString als Zeichenkette in Kleinbuchstaben zurueck, * ausgehend von start bis zum Ende des CFMLString. * @param start Von wo aus die Untermenge ausgegeben werden soll. * @return Untermenge als Zeichenkette in Kleinbuchstaben. */ public String substringLower(int start) { return substringLower(start,lcText.length-start); } /** * Gibt eine Untermenge des CFMLString als Zeichenkette in Kleinbuchstaben zurueck, * ausgehend von start mit einer maximalen Laenge count. * @param start Von wo aus die Untermenge ausgegeben werden soll. * @param count Wie lange die zurueckgegebene Zeichenkette maximal sein darf. * @return Untermenge als Zeichenkette in Kleinbuchstaben. */ public String substringLower(int start, int count) { return String.valueOf(lcText,start,count); } /** * Gibt eine Untermenge des CFMLString als CFMLString zurueck, * ausgehend von start bis zum Ende des CFMLString. * @param start Von wo aus die Untermenge ausgegeben werden soll. * @return Untermenge als CFMLString */ public SourceCode subCFMLString(int start) { return subCFMLString(start,text.length-start); } /** * return a subset of the current SourceCode * @param start start position of the new subset. * @param count length of the new subset. * @return subset of the SourceCode as new SourcCode */ public SourceCode subCFMLString(int start, int count) { return new SourceCode(String.valueOf(text,start,count),writeLog,dialect); } /** Gibt den CFMLString als String zurueck. * @see java.lang.Object#toString() */ @Override public String toString() { return new String(this.text); } /** * Gibt die aktuelle Position des Zeigers innerhalb des CFMLString zurueck. * @return Position des Zeigers */ public int getPos() { return pos; } /** * Setzt die Position des Zeigers innerhalb des CFMLString, ein ungueltiger index wird ignoriert. * @param pos Position an die der Zeiger gestellt werde soll. */ public void setPos(int pos) { this.pos= pos; } /** * Gibt die aktuelle Zeile zurueck in der der Zeiger des CFMLString steht. * @return Zeilennummer */ public int getLine() { return getLine(pos); } public Position getPosition() { return getPosition(pos); } public Position getPosition(int pos) { int line=0; int posAtStart=0; for(int i=0;i<lines.length;i++) { if(pos<=lines[i].intValue()) { line=i+1; if(i>0)posAtStart=lines[i-1].intValue(); break; } } if(line==0) throw new RuntimeException("syntax error"); int column=pos-posAtStart; return new Position(line,column,pos); } /** * Gibt zurueck in welcher Zeile die angegebene Position ist. * @param pos Position von welcher die Zeile erfragt wird * @return Zeilennummer */ public int getLine(int pos) { for(int i=0;i<lines.length;i++) { if(pos<=lines[i].intValue()) return i+1; } return lines.length; } /** * Gibt die Stelle in der aktuelle Zeile zurueck, in welcher der Zeiger steht. * @return Position innerhalb der Zeile. */ public int getColumn() { return getColumn(pos); } /** * Gibt die Stelle in der Zeile auf die pos zeigt zurueck. * @param pos Position von welcher die Zeile erfragt wird * @return Position innerhalb der Zeile. */ public int getColumn(int pos) { int line=getLine(pos)-1; if(line==0) return pos+1; return pos-lines[line-1].intValue(); } /** * Gibt die Zeile auf welcher der Zeiger steht als String zurueck. * @return Zeile als Zeichenkette */ public String getLineAsString() { return getLineAsString(getLine(pos)); } /** * Gibt die angegebene Zeile als String zurueck. * @param line Zeile die zurueck gegeben werden soll * @return Zeile als Zeichenkette */ public String getLineAsString(int line) { int index=line-1; if(lines.length<=index) return null; int max=lines[index].intValue(); int min=0; if(index!=0) min=lines[index-1].intValue()+1; if(min<max && max-1<lcText.length) return this.substring(min, max-min); return ""; } /** * Gibt zurueck ob der Zeiger auf dem letzten Zeichen steht. * @return Gibt zurueck ob der Zeiger auf dem letzten Zeichen steht. */ public boolean isLast() { return pos==lcText.length-1; } /** * Gibt zurueck ob der Zeiger nach dem letzten Zeichen steht. * @return Gibt zurueck ob der Zeiger nach dem letzten Zeichen steht. */ public boolean isAfterLast() { return pos>=lcText.length; } /** * Gibt zurueck ob der Zeiger einen korrekten Index hat. * @return Gibt zurueck ob der Zeiger einen korrekten Index hat. */ public boolean isValidIndex() { return pos<lcText.length && pos>-1; } /** * Gibt zurueck, ausgehend von der aktuellen Position, * wann das naechste Zeichen folgt das gleich ist wie die Eingabe, * falls keines folgt wird -1 zurueck gegeben. * Gross- und Kleinschreibung der Zeichen werden igoriert. * @param c gesuchtes Zeichen * @return Zeichen das gesucht werden soll. */ public int indexOfNext(char c) { for(int i=pos;i<lcText.length;i++) { if(lcText[i]==c) return i; } return -1; } /** * Gibt das letzte Wort das sich vor dem aktuellen Zeigerstand befindet zurueck, * falls keines existiert wird null zurueck gegeben. * @return Word vor dem aktuellen Zeigerstand. */ public String lastWord() { int size = 1; while (pos - size > 0 && lcText[pos - size] == ' ') { size++; } while (pos - size > 0 && lcText[pos - size] != ' ' && lcText[pos - size] != ';') { size++; } return this.substring((pos - size + 1), (pos - 1)); } /** * Gibt die Laenge des CFMLString zurueck. * @return Laenge des CFMLString. */ public int length() { return lcText.length; } /** * Prueft ob das uebergebene Objekt diesem Objekt entspricht. * @param o Object zum vergleichen. * @return Ist das uebergebene Objekt das selbe wie dieses. */ @Override public boolean equals(Object o) { if(!(o instanceof SourceCode))return false; return o.toString().equals(this.toString()); } public boolean getWriteLog() { return writeLog; } public String getText() { return new String(text); } public String id() { return HashUtil.create64BitHashAsString(getText()); } public int getDialect() { return dialect; } }