// Copyright (c) 2006 by Leif Frenzel <himself@leiffrenzel.de> // All rights reserved. package net.sf.eclipsefp.haskell.core.cabalmodel; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringReader; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.StringTokenizer; import net.sf.eclipsefp.haskell.core.HaskellCorePlugin; import net.sf.eclipsefp.haskell.util.PlatformUtil; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; /** <p>contains helping functionality for loading a {@link PackageDescription * PackageDescription model}.</p> * * @author Leif Frenzel */ public class PackageDescriptionLoader { public static PackageDescription load( final IFile file ) throws CoreException{ PackageDescription result = new PackageDescription(); if (file!=null && file.exists()){ if (!file.getWorkspace().isTreeLocked()){ file.refreshLocal( 0, new NullProgressMonitor() ); } try (InputStream is=file.getContents(); BufferedReader br = new BufferedReader(new InputStreamReader( is,file.getCharset() ))) { new CabalParser(result).parse(br); } catch( final IOException ioex ) { // very unlikely HaskellCorePlugin.log( "Loading cabal file", ioex ); //$NON-NLS-1$ } } return result; } public static PackageDescription load( final String content ) { PackageDescription result = new PackageDescription(); if( content != null && content.trim().length() > 0 ) { BufferedReader br = new BufferedReader( new StringReader( content ) ); try { new CabalParser(result).parse( br ); } catch( final IOException ioex ) { // very unlikely HaskellCorePlugin.log( "Loading cabal file", ioex ); //$NON-NLS-1$ } } return result; } public static PackageDescriptionStanza loadStanza( final String content ) { PackageDescription pd=load( content ); if (pd!=null && pd.getStanzas().size()>1){ return pd.getStanzas().get(1); } return null; } public static List<String> parseList(final String value,final String seps){ List<String> ret=new LinkedList<>(); if (value!=null && value.length()>0){ StringTokenizer st=new StringTokenizer( value,seps ); boolean expectVersion=false; while (st.hasMoreTokens()){ String t=st.nextToken(); if (t.length()>0){ if (isBuildDependsCharacter( t.charAt( 0 )) && ret.size()>0){ String s=ret.get( ret.size()-1); ret.set( ret.size()-1, s+" "+t ); //$NON-NLS-1$ expectVersion=true; } else if (expectVersion&& ret.size()>0){ String s=ret.get( ret.size()-1); ret.set( ret.size()-1, s+" "+t ); //$NON-NLS-1$ expectVersion=false; } else { expectVersion=false; ret.add(t); } } } } return ret; } private static boolean isBuildDependsCharacter(final char c){ return c=='<' || c=='>' || c=='=' || c=='|' || c=='&'; } public static List<String> parseList(final String value){ return parseList(value, ", "+PlatformUtil.NL ); //$NON-NLS-1$ } // helping methods ////////////////// private static class CabalParser{ private static final String BRACE_OPEN="{"; //$NON-NLS-1$ private static final String BRACE_CLOSE="}"; //$NON-NLS-1$ private static final String COLON=":"; //$NON-NLS-1$ private PackageDescriptionStanza lastStanza=null; private final LinkedList<PackageDescriptionStanza> stanzaStack=new LinkedList<>(); private int currentIndent=0; private int count=0; private int empty=0; private String field=null; private final StringBuilder fieldValue=new StringBuilder(); private ValuePosition fieldVP=null; private final PackageDescription pd; private int braces=0; /** * are we ending in NL? */ private boolean gotNLAtEnd=true; private CabalParser( final PackageDescription pd ){ this.pd=pd; } private final StringBuilder line=new StringBuilder(); private int lookup=-1; /** * yes I rewrote a readLine method to know if the file ends with NL or not * @param br the reader * @return the line read, or null of the end of the file * @throws IOException */ private String readLine( final BufferedReader br)throws IOException{ if (lookup>-1){ line.append( (char)lookup ); } while (true){ lookup=br.read(); boolean ret=false; if (lookup==-1){ if (line.length()>0){ gotNLAtEnd=false; ret=true; } else { return null; } } else if (lookup=='\r'){ lookup=br.read(); if (lookup=='\n'){ lookup=-1; } ret=true; } else if (lookup=='\n'){ ret=true; lookup=-1; } else { line.append( (char)lookup ); lookup=-1; } if (ret){ String s=line.toString(); line.setLength( 0 ); return s; } } } private void parse( final BufferedReader br ) throws IOException { String line = readLine(br); while( line != null ) { if (!isComment( line )){ if(! isEmpty( line ) ) { if (lastStanza==null){ lastStanza=new PackagePropertiesStanza(pd,count); } int indent=getIndent(line); if (indent>currentIndent && field!=null && fieldVP!=null){ addFieldLine(line, indent); } else if (line.trim().equals(BRACE_OPEN)){ braces++; } else { String trimmed=line.trim(); if (trimmed.startsWith( BRACE_CLOSE )){ braces--; addField(); popStanza(); if (trimmed.length()==BRACE_CLOSE.length()){ line=br.readLine(); continue; } line=line.substring( line.indexOf( BRACE_CLOSE )+1 ); } currentIndent=indent; addField(); if (braces==0){ while (indent<lastStanza.getIndent() && stanzaStack.size()>0){ popStanza(); } } String start=getSectionHeader( line ); CabalSyntax cs=CabalSyntax.sections.get( start.toLowerCase( Locale.ENGLISH ) ); if (cs != null){ String name=null; if (line.length()>start.length()+indent){ name=line.substring( start.length()+indent).trim(); } if (name!=null && name.endsWith( BRACE_OPEN )){ braces++; name=name.substring( 0,name.length()-1 ).trim(); } addStanza(cs,start,name); lastStanza.setIndent( indent + 2 ); //lastStanza=new PackageDescriptionStanza(cs,name,count); } else { int ix=line.indexOf(COLON ); if (ix>-1){ field=line.substring( 0,ix ).trim(); int initialIndent=ix+1; int subsequentIndent=initialIndent; if (ix<line.length()){ while (initialIndent<line.length() && Character.isWhitespace( line.charAt( initialIndent ))){ if ( line.charAt( initialIndent )=='\t'){ subsequentIndent+=3; } initialIndent++; subsequentIndent++; } fieldValue.append( line.substring( initialIndent ).trim() ); } fieldVP=new ValuePosition(); fieldVP.setStartLine( count ); fieldVP.setInitialIndent( initialIndent ); fieldVP.setSubsequentIndent( subsequentIndent ); if (lastStanza.getProperties().isEmpty()){ lastStanza.setIndent( indent ); } } } } empty=0; } else { empty++; } } line = readLine(br); count++; } if (lastStanza!=null){ addField(); while (stanzaStack.size()>0){ popStanza(); } if (!gotNLAtEnd){ lastStanza.needNL=true; } addStanza(null,null,null); } } private void addField() { if(lastStanza!=null && field!=null){ // fieldValue.length()>0 && if(fieldVP!=null){ String key=field.toLowerCase( Locale.ENGLISH ); lastStanza.getProperties().put( key, fieldValue.toString() ); fieldVP.setEndLine( count - empty); lastStanza.getPositions().put( key, fieldVP ); lastStanza.getRealNames().put( key, field ); fieldValue.setLength( 0 ); fieldVP=null; } field=null; } } private void addStanza(final CabalSyntax type,final String realTypeName,final String name){ if (type!=null && (type.equals( CabalSyntax.SECTION_IF ) || type.equals( CabalSyntax.SECTION_ELSE ))){ stanzaStack.addLast( lastStanza ); } else { lastStanza.setEndLine( count-empty ); pd.getStanzas().add(lastStanza); } lastStanza=new PackageDescriptionStanza(pd,type,name,count); lastStanza.setRealTypeName( realTypeName ); } private void popStanza(){ if (stanzaStack.size()>0){ PackageDescriptionStanza st=stanzaStack.removeLast(); lastStanza.setEndLine( count-empty ); st.getStanzas().add(lastStanza); lastStanza=st; } } private void addFieldLine(final String line,final int indent){ if (fieldValue.length()>0){ fieldValue.append( PlatformUtil.NL); } String val=line.substring( indent ) ; if (val.trim().equals( "." )){ //$NON-NLS-1$ val=""; //$NON-NLS-1$ } fieldValue.append( val.trim() ); fieldVP.setSubsequentIndent( indent ); } private static int getIndent(final String line){ int a=0; while (line.charAt( a )==' ' && a<line.length()){ a++; } return a; } private static boolean isEmpty( final String line ) { return line.trim().length() == 0; } private static boolean isComment( final String line ) { return line.trim().startsWith( "--" ); //$NON-NLS-1$ } private static String getSectionHeader(final String line ) { String section=line.trim(); int ix=section.indexOf( ' ' ); if (ix>-1){ section=section.substring( 0,ix ).trim(); } else { ix=section.indexOf( '\t' ); if (ix>-1){ section=section.substring( 0,ix ).trim(); } } return section; } } /* private static void parseOld( final BufferedReader br, final PackageDescription pd ) throws IOException { List<StanzaInfo> stanzas = new ArrayList<StanzaInfo>(); StanzaInfo lastStanza = new StanzaInfo(); stanzas.add( lastStanza ); Set<String> sections=new HashSet<String>(); for (CabalSyntax cs:CabalSyntax.values() ){ if (cs.isSectionHeader()){ sections.add(cs.getCabalName().toLowerCase()); } } int count = 0; boolean contentStarted = false; String line = br.readLine(); int empty=1; while( line != null ) { count++; if( !isComment( line ) ) { contentStarted = true; if(! isEmpty( line ) ) { if (empty>1 && isSectionHeader(line,sections)){ if (getSectionHeader(line).equals( CabalSyntax.SECTION_IF )){ } lastStanza.setEnd( count - empty ); lastStanza = new StanzaInfo(); lastStanza.setStart( count-1 ); stanzas.add( lastStanza ); } lastStanza.getContent().add( line ); lastStanza.getLines().add( new Integer(count - 1) ); empty=1; } else { empty++; } } else if( !contentStarted ) { lastStanza.setStart( lastStanza.getStart() + 1 ); } line = br.readLine(); } if( !lastStanza.getContent().isEmpty() ) { // then we had not yet a chance to set the end line lastStanza.setEnd( count ); } applyStanzas( stanzas, pd ); }*/ /*private static boolean isSectionHeader(final String line, final Set<String> sections ) { return sections.contains(getSectionHeader(line)); }*/ /*private static void applyStanzas( final List<StanzaInfo> stanzas, final PackageDescription pd ) { Iterator<StanzaInfo> it = stanzas.iterator(); while( it.hasNext() ) { StanzaInfo info = it.next(); if( !info.getContent().isEmpty() ) { pd.addStanza( create( info ) ); } } }*/ /*private static PackageDescriptionStanza create( final StanzaInfo info ) { PackageDescriptionStanza result=null; /int startLine=1; if( isExecutable( info.getContent() ) ) { result = new ExecutableStanza( getExecutableName( info.getContent() ),info.getStart(), info.getEnd() ); } else if( isLibrary( info.getContent() ) ) { result = new LibraryStanza( getLibraryName( info.getContent() ), info.getStart(), info.getEnd() ); } else { result = new GeneralStanza(info.getStart(), info.getEnd() ); startLine=0; }/ int startLine=0; if (info.getContent()!=null && info.getContent().size()>0){ String s=info.getContent().get( 0 ).trim(); for (CabalSyntax cs:CabalSyntax.values()){ if (cs.isSectionHeader()){ if (s.toLowerCase().startsWith( cs.getCabalName().toLowerCase())){ String name=s.substring( cs.getCabalName().length() ).trim(); if (name.endsWith( "{" )){ //$NON-NLS-1$ name=name.substring( 0,name.length()-1 ).trim(); } if(cs.equals( CabalSyntax.SECTION_LIBRARY )){ name=null; } startLine=1; result = new PackageDescriptionStanza(cs,name,info.getStart(), info.getEnd() ); } } } } if (result==null){ result=new GeneralStanza( info.getStart(), info.getEnd() ); } parseProperties(info,result,startLine); return result; } private static void parseProperties( final StanzaInfo info, final PackageDescriptionStanza result, final int startLine ) { String pName=null; StringBuilder pValue=new StringBuilder(); ValuePosition vp=null; for (int a=startLine;a<info.getContent().size();a++){ String s=info.getContent().get( a ); int line=info.getLines().get(a).intValue(); if (vp==null){ vp=new ValuePosition(); vp.setStartLine( line ); vp.setEndLine( line+1 ); } else { vp.setEndLine( line ); } int thisIndent=0; while (thisIndent<s.length() && Character.isWhitespace( s.charAt( thisIndent ))){ thisIndent++; } if (a==startLine){ result.setIndent( thisIndent ); } if (thisIndent>result.getIndent()){ if (thisIndent<s.length()){ if (pValue.length()>0){ pValue.append( NL); //$NON-NLS-1$ } String val=s.substring( thisIndent ) ; if (val.trim().equals( "." )){ //$NON-NLS-1$ val=""; //$NON-NLS-1$ } pValue.append(val ); vp.setSubsequentIndent( thisIndent ); } } else { if (pName!=null && pValue.length()>0){ result.getProperties().put( pName.toLowerCase(), pValue.toString() ); result.getPositions().put(pName.toLowerCase(),vp); vp=new ValuePosition(); vp.setStartLine( line ); vp.setEndLine( line +1 ); pValue.setLength( 0 ); pName=null; } int ix=s.indexOf( ":" ); //$NON-NLS-1$ if (ix>-1){ pName=s.substring( 0,ix ).trim(); ix++; int valIndent=ix; int subIndent=ix; while (ix<s.length() && Character.isWhitespace( s.charAt( ix ))){ valIndent++; subIndent++; if (s.charAt( ix )=='\t'){ subIndent+=3; } ix++; } vp.setInitialIndent( valIndent ); vp.setSubsequentIndent( subIndent ); pValue.append(s.substring( ix )); } } } if (pName!=null && pValue.length()>0){ result.getProperties().put( pName.toLowerCase(), pValue.toString() ); result.getPositions().put(pName.toLowerCase(),vp); pValue.setLength( 0 ); } } */ /*private static String getPlainName( final List<String> content ) { String result = getValue( content, CabalSyntax.FIELD_NAME ); if( result == null ) { result = "Unknown"; //$NON-NLS-1$ } return result; }*/ /*private static String getLibraryName( final List<String> content ) { return getValue( content, CabalSyntax.FIELD_NAME.toString() ); }*/ /*private static String getExecutableName( final List<String> content ) { //return getValue( content, CabalSyntax.FIELD_EXECUTABLE ); if (content.size()==0){ return null; } String s=content.get( 0 ); if (s.toLowerCase().startsWith( CabalSyntax.SECTION_EXECUTABLE.toString() )){ if (s.length()>CabalSyntax.SECTION_EXECUTABLE.toString().length() ){ return s.substring( CabalSyntax.SECTION_EXECUTABLE.toString().length() ).trim(); } return ""; //$NON-NLS-1$ } return s; }*/ /*private static String getValue( final List<String> content, final String fieldName ) { String result = null; Iterator<String> it = content.iterator(); while( result == null && it.hasNext() ) { String next = it.next(); String nextNoCase = next.trim().toLowerCase(); if( nextNoCase.startsWith( fieldName.toLowerCase() ) ) { int index = next.indexOf( ':' ) + 1; result = next.substring( index ).trim(); } } return result; }*/ /*private static boolean isLibrary( final List<String> content ) { return containsLineStart( content, CabalSyntax.SECTION_LIBRARY.getCabalName()); } private static boolean isExecutable( final List<String> content ) { return containsLineStart( content, CabalSyntax.SECTION_EXECUTABLE.getCabalName() ); } private static boolean containsLineStart( final List<String> content, final String start ) { boolean result = false; Iterator<String> it = content.iterator(); while( !result && it.hasNext() ) { String next = it.next().trim().toLowerCase(); result = next.startsWith( start.toLowerCase() ); } return result; }*/ // inner classes //////////////// /* private static class StanzaInfo { private final List<String> content = new ArrayList<String>(); private final List<Integer> lines=new ArrayList<Integer>(); private int start = 0; private int end = 1; StanzaInfo() { // NOOP } List<String> getContent() { return content; } public List<Integer> getLines() { return lines; } void setEnd( final int end ) { this.end = end; } int getEnd() { return end; } void setStart( final int start ) { this.start = start; } int getStart() { return start; } }*/ }