diff options
author | buraq <buraq@epfl.ch> | 2004-05-26 18:07:35 +0000 |
---|---|---|
committer | buraq <buraq@epfl.ch> | 2004-05-26 18:07:35 +0000 |
commit | 8dcb4da871915fcbd55b062843332d5b9c2e0ac8 (patch) | |
tree | f6cc698e4549c3c0db1c534ffd456ba79929d93a | |
parent | 02a6574294afe8355be2654e96ec9ec5f540306c (diff) | |
download | scala-8dcb4da871915fcbd55b062843332d5b9c2e0ac8.tar.gz scala-8dcb4da871915fcbd55b062843332d5b9c2e0ac8.tar.bz2 scala-8dcb4da871915fcbd55b062843332d5b9c2e0ac8.zip |
pretty-print + whitespace trimming
-rw-r--r-- | config/list/library.lst | 1 | ||||
-rw-r--r-- | sources/scala/tools/scalac/ast/parser/MarkupParser.scala | 64 | ||||
-rw-r--r-- | sources/scala/tools/scalac/ast/parser/Parser.scala | 2 | ||||
-rw-r--r-- | sources/scala/tools/scalac/ast/parser/Scanner.scala | 8 | ||||
-rw-r--r-- | sources/scala/xml/PrettyPrinter.scala | 194 | ||||
-rw-r--r-- | sources/scala/xml/Utility.scala | 53 | ||||
-rw-r--r-- | sources/scalac/CompilerCommand.java | 5 | ||||
-rw-r--r-- | sources/scalac/Global.java | 2 | ||||
-rw-r--r-- | sources/scalac/util/Names.java | 1 | ||||
-rw-r--r-- | test/files/jvm/xmlLiterals.check | 43 | ||||
-rw-r--r-- | test/files/jvm/xmlLiterals.scala | 4 |
11 files changed, 288 insertions, 89 deletions
diff --git a/config/list/library.lst b/config/list/library.lst index bbf8cdcc38..0bc4c3cf8f 100644 --- a/config/list/library.lst +++ b/config/list/library.lst @@ -163,6 +163,7 @@ xml/EntityRef.scala xml/FactoryAdapter.scala xml/Node.scala xml/NodeSeq.scala +xml/PrettyPrinter.scala xml/ProcInstr.scala xml/Text.scala xml/Utility.scala diff --git a/sources/scala/tools/scalac/ast/parser/MarkupParser.scala b/sources/scala/tools/scalac/ast/parser/MarkupParser.scala index 19ecbf1194..765adc3c93 100644 --- a/sources/scala/tools/scalac/ast/parser/MarkupParser.scala +++ b/sources/scala/tools/scalac/ast/parser/MarkupParser.scala @@ -19,7 +19,7 @@ import scala.collection.mutable.Buffer; import scalac.symtab.Modifiers; package scala.tools.scalac.ast.parser { -class MarkupParser( unit:Unit, s:Scanner, p:Parser ) { +class MarkupParser( unit:Unit, s:Scanner, p:Parser, preserveWS:boolean ) { import Tokens.{EMPTY, LBRACE, RBRACE} ; import scala.tools.scalac.ast.{TreeList => myTreeList} @@ -160,7 +160,7 @@ class MarkupParser( unit:Unit, s:Scanner, p:Parser ) { Predef.Array[Tree]( t, _emptyMap( pos ) ) } else { Predef.Array[Tree]( t, _emptyMap( pos ), make.Typed( - pos, makeXMLseq(pos, args), make.Ident(pos, TypeNames.WILDCARD_STAR))) + pos, makeXMLseq(pos, args ), make.Ident(pos, TypeNames.WILDCARD_STAR))) }; make.Apply( pos, _scala_xml_Elem( pos ), constrArgs ) } @@ -174,9 +174,9 @@ class MarkupParser( unit:Unit, s:Scanner, p:Parser ) { make.New( pos, constr ); }; // create scala.xml.Text here <: scala.xml.Node - def makeText( pos: int, txt:String ):Tree = + def makeText( pos: int, txt:String ):Tree = { makeText( pos, gen.mkStringLit( pos, txt )); - + } // create def makeComment( pos: int, comment:scala.xml.Comment ):Tree = makeComment( pos, gen.mkStringLit( pos, comment.text )); @@ -265,30 +265,40 @@ class MarkupParser( unit:Unit, s:Scanner, p:Parser ) { if( null == res ) ts else res; } + def isEmptyText(t:Tree) = t match { + case Tree$Literal(atree.AConstant.STRING("")) => true; + case _ => false; + } def makeXMLseq( pos:int, args:Array[Tree] ) = { - val blocArr = new Array[Tree] ( 1 + args.length ); + val ts = new TreeList(); + //val blocArr = new Array[Tree] ( 1 + args.length ); val constr = _scala_collection_mutable_ArrayBuffer( pos ); val n = p.fresh(); val nIdent = make.Ident(pos, n); - blocArr( 0 ) = make.ValDef(pos, Modifiers.MUTABLE, n, Tree.Empty, - make.New( pos, constr )); + //blocArr( 0 ) + ts.append( make.ValDef(pos, Modifiers.MUTABLE, n, Tree.Empty, + make.New( pos, constr ))); var i = 0; while( i < args.length ) { val ipos = args(i).pos; - val t = make.Apply( ipos, - make.Select( ipos, nIdent, _append ), - Predef.Array[Tree]( convertToText( args( i ) ))); + if( !isEmptyText( args( i ))) { + ts.append( + make.Apply( ipos, + make.Select( ipos, nIdent, _append ), + Predef.Array[Tree]( convertToText( args( i ) ))) + ) + } i = i + 1; - blocArr( i ) = t } - make.Block( pos, blocArr, nIdent ); + make.Block( pos, ts.toArray(), nIdent ); } def makeXMLseqPat( pos:int, args:Array[Tree] ) = { make.Apply( pos, _scala_Seq( pos ), args ); } - def makeXML(pos:int,n:Name,args:Array[Tree],attrMap:ListMap[Name,Tree]):Tree = { + /** @todo: create map here directly */ + def makeXML(pos:int,n:Name,args:Array[Tree],attrMap:ListMap[Name,Tree]):Tree={ var t = makeXML( pos, n, args ); if( attrMap.isEmpty ) { t @@ -309,6 +319,29 @@ class MarkupParser( unit:Unit, s:Scanner, p:Parser ) { } } + def trimWS( str:String ) = { + val sb = new StringBuffer(); + val z:Seq[Char] = str; + var ws = false; + for( val c <- z ) { + if( s.xIsSpace( c ) ) { + if( !ws ) { + ws = true; sb.append( ' ' ) + } + } else { + ws = false; + sb.append( c ); + } + } + if( ' ' == sb.charAt( 0 ) ) { + sb.deleteCharAt( 0 ) + } + val len = sb.length(); + if( len > 0 && s.xIsSpace( sb.charAt( len-1 ) )) { + sb.setLength( len - 1 ) + } + sb.toString(); + } /** xLiteral = xExpr { xExpr } * @return Scala representation of this xml literal @@ -452,7 +485,10 @@ class MarkupParser( unit:Unit, s:Scanner, p:Parser ) { ts.append( makeEntityRef( s.pos, n )); } case _ => // text content - ts.append( makeText( s.pos, s.xText )); + var text = s.xText; + if( !preserveWS ) text = trimWS( text ); + if( text.length() > 0 ) + ts.append( makeText( s.pos, text )); } } } diff --git a/sources/scala/tools/scalac/ast/parser/Parser.scala b/sources/scala/tools/scalac/ast/parser/Parser.scala index 54f33cc900..dac75db0d0 100644 --- a/sources/scala/tools/scalac/ast/parser/Parser.scala +++ b/sources/scala/tools/scalac/ast/parser/Parser.scala @@ -38,7 +38,7 @@ class Parser(unit: Unit) { /** the markup parser */ - val xmlp = new MarkupParser( unit, s, this ); + val xmlp = new MarkupParser( unit, s, this, unit.global.xmlPreserveWS ); /** the tree factory */ diff --git a/sources/scala/tools/scalac/ast/parser/Scanner.scala b/sources/scala/tools/scalac/ast/parser/Scanner.scala index 9b3646c032..8acc2d78b2 100644 --- a/sources/scala/tools/scalac/ast/parser/Scanner.scala +++ b/sources/scala/tools/scalac/ast/parser/Scanner.scala @@ -942,7 +942,7 @@ class Scanner(_unit: Unit) extends TokenData { /* unit.global.xmlMarkup && */ ( token == IDENTIFIER )&&( name == LT ); } - def xIsSpace = ch match { + def xIsSpace( ch:char ) = ch match { case ' ' | '\t' | CR | LF => true case _ => false; } @@ -950,13 +950,13 @@ class Scanner(_unit: Unit) extends TokenData { /** skip optional space S? */ def xSpaceOpt = { - while( xIsSpace ) { xNext; } + while( xIsSpace( ch ) ) { xNext; } } /** scan [3] S ::= (#x20 | #x9 | #xD | #xA)+ */ def xSpace = { - if( xIsSpace ) { + if( xIsSpace( ch ) ) { xNext; xSpaceOpt } else { xSyntaxError("whitespace expected"); @@ -1168,7 +1168,7 @@ class Scanner(_unit: Unit) extends TokenData { def xProcInstr:scala.xml.ProcInstr = { val sb:StringBuffer = new StringBuffer(); val n = xName; - if( xIsSpace ) { + if( xIsSpace( ch ) ) { xSpace; while( true ) { if( ch=='?' && { sb.append( ch ); xNext; ch == '>' } ) { diff --git a/sources/scala/xml/PrettyPrinter.scala b/sources/scala/xml/PrettyPrinter.scala new file mode 100644 index 0000000000..edee411acf --- /dev/null +++ b/sources/scala/xml/PrettyPrinter.scala @@ -0,0 +1,194 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2004, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +** $Id$ +\* */ + +package scala.xml ; + +import java.lang.StringBuffer ; /* Java dependency! */ +import scala.collection.Map ; + +/** Utility functions for processing instances of bound and not bound XML +** classes, as well as escaping text nodes +**/ + +class PrettyPrinter( width:Int, step:Int ) { + + class BrokenException() extends java.lang.Exception(); + + class Item ; + case object Break extends Item { + override def toString() = "\\"; + }; + case class Box( col:Int, s:String ) extends Item; + case class Para( s:String ) extends Item; + + var items:List[Item] = Nil; + + var cur = 0; + + def reset() = { + cur = 0; + items = Nil; + } + + /** try to cut at whitespace */ + def cut( s:String, ind:Int ):List[Item] = { + val tmp = width - cur; + if( s.length() < tmp ) + return List(Box(ind,s)); + val sb = new StringBuffer(); + var i = s.indexOf(' '); + if( i > tmp ) throw new BrokenException(); // cannot break + + var last = i::Nil; + while( i < tmp ) { + last = i::last; + i = s.indexOf(' ', i ); + } + var res:List[Item] = Nil; + while( Nil != last ) try { + val b = Box( ind, s.substring( 0, last.head )); + cur = ind; + res = b :: Break :: cut( s.substring( last.head, s.length()), ind ); + // backtrac + } catch { + case _:BrokenException => last = last.tail; + } + throw new BrokenException() + } + + /** try to make indented box, if possible, else para */ + def makeBox( ind:Int, s:String ) = { + if( cur < ind ) + cur == ind; + if( cur + s.length() > width ) { // fits in this line + items = Box( ind, s ) :: items; + cur = cur + s.length() + } else try { + for( val b <- cut( s, ind ).elements ) // break it up + items = b :: items + } catch { + case _:BrokenException => makePara( ind, s ); // give up, para + } + } + + // dont respect indent in para, but afterwards + def makePara( ind:Int, s:String ) = { + items = Break::Para( s )::Break::items; + cur = ind; + } + + // respect indent + def makeBreak() = { // using wrapping here... + items = Break::items; + cur = 0; + } + + def leafTag( n:Node ) = { + val sb = new StringBuffer("<"); + sb.append( n.label ); + Utility.attr2xml( n.attribute.elements, sb ); + sb.append("/>"); + sb.toString(); + } + + def startTag( n:Node ) = { + val sb = new StringBuffer("<"); + sb.append( n.label ); + Utility.attr2xml( n.attribute.elements, sb ); + sb.append('>'); + sb.toString(); + } + + /* serializes an instance of Node to a string that contains well-formed XML + **/ + def toPrettyXML( n:Node ):String = { + traverse( n, 0 ); + val sb = new StringBuffer(); + var cur = 0; + //Console.println( items.reverse ); + for( val b <- items.reverse ) b match { + case Break => + sb.append('\n'); // on windows: \r\n + cur = 0; + case Box(i, s) => + while( cur < i ) { + sb.append(' '); + cur = cur + 1; + } + sb.append( s ); + case Para( s ) => + sb.append( s ); + } + sb.toString(); + } + +// def toLastWS( len:Int, s:String ) = { +// val sb = new StringBuffer(); +// while( len +// attr2xml( attribute.elements, sb ); +// sb.append('>'); +// sb.toString(); +// } + + def breakable( n:Node ):boolean = { + val it = n.child.elements; + while( it.hasNext ) + it.next match { + case _:Text | _:CharData | _:Comment | _:EntityRef | _:ProcInstr => + case _:Node => return true; + } + return false + } + /** @param tail: what we'd like to sqeeze in */ + def traverse( node:Node, ind:int ):Unit = { + node match { + + case _:Text | _:CharData | _:Comment | _:EntityRef | _:ProcInstr => + makeBox( ind, node.toString() ); + + case _:Node => + val test = node.toString(); + + if( ( test.length() < width - cur ) // all ? + &&( !breakable( node ))) { + makeBox( ind, test ); + } else { // start tag + content + end tag + //Console.println(node.label+" ind="+ind); + val stg = startTag( node ); + val endTag = "</"+node.label+">"; + val len2 = node.label.length() + 1; + + if( stg.length() < width - cur ) { // start tag fits + + makeBox( ind, stg ); + makeBreak(); + traverse( node.child.elements, ind + step ); + makeBox( ind, endTag ); + + } else if( len2 < width - cur ) { + // <start label + attrs + tag + content + end tag + makeBox( ind, stg.substring( 0, len2 )); + makeBreak(); + makeBox( ind, stg.substring( len2, stg.length() )); + makeBreak(); + traverse( node.child.elements, ind + step ); + makeBox( cur, endTag ); + } + } + } + } + + def traverse( it:Iterator[Node], ind:int ):unit = { + for( val c <- it ) { + traverse( c, ind ); + makeBreak(); + } + } + +} diff --git a/sources/scala/xml/Utility.scala b/sources/scala/xml/Utility.scala index aab2f6b63f..df95177ed3 100644 --- a/sources/scala/xml/Utility.scala +++ b/sources/scala/xml/Utility.scala @@ -72,63 +72,22 @@ object Utility { } } - /* serializes an instance of Node to a string that contains well-formed XML - **/ -/* - def toPrettyXML( n:Node, indent:int ):String = { n match { - case Text( t ) => - escape( t ); - case x:AttributedNode => - val s = new StringBuffer(); - for( val i<-List.range(0,indent) ) { - val _ = s.append(" "); - {} - } - val spaces = s.toString(); - s.append('<'); - s.append( x.label ); - if( null != x.attributes ) { - s.append( attr2xml( x.attributes.elements ) );{} - } - s.append('>'); - val childrenString = toXML( x.children.elements, 0 ); - if( indent + 2 * x.label.length() + 4 + childrenString.length() < COLS ) { - s.append( childrenString ); - s.append("</"); - s.append( x.label ); - s.append('>'); - s.append('\n'); - } else { - s.append('\n'); - val childrenString = toXML( x.children.elements, indent+1 ) ; - s.append( childrenString ); - s.append('\n'); - s.append( spaces ); - s.append("</"); - s.append( x.label ); - s.append('>'); - } - s.toString() - }} - def toXML( ch:Iterator[Node], ind:int ):String = { - ch.foldLeft ("") { (s:String,n:Node) => { - val t:String = toPrettyXML( n, ind ); s + t - }} - } -todo: better pretty printing */ - + /* def toXML( ch:Iterator[Node] ):String = { ch.foldLeft ("") { (s:String,n:Node) => s + n.toString() } } +*/ /** for a Node n, returns string representation of n.attributes **/ def attr2xml( attrib:Iterator[Pair[String, String]], sb:StringBuffer ):Unit = { for( val x <- attrib ) { sb.append( " " ); sb.append( x._1 ); - sb.append("=\""); + sb.append("="); + val sep = { if( x._2.indexOf('"') != -1 ) '\'' else '"' }; + sb.append( sep ); sb.append( x._2 ); - sb.append("\""); + sb.append( sep ); } } diff --git a/sources/scalac/CompilerCommand.java b/sources/scalac/CompilerCommand.java index 343fec3202..b47041d665 100644 --- a/sources/scalac/CompilerCommand.java +++ b/sources/scalac/CompilerCommand.java @@ -84,6 +84,7 @@ public class CompilerCommand extends CommandParser { public final BooleanOptionParser Xshortname; public final BooleanOptionParser Xmarkup; public final BooleanOptionParser Xnewmatch; + public final BooleanOptionParser XpreserveWS; //######################################################################## // Public Constructors @@ -245,6 +246,10 @@ public class CompilerCommand extends CommandParser { "Xnewmatch", "new pattern matching", false), + this.XpreserveWS = new BooleanOptionParser(this, + "XpreserveWS", "don't trim whitespace in XML literals", + false), + this.unknown_options = new UnknownOptionParser(this), this.files = new ScalaFileArgumentParser(this), diff --git a/sources/scalac/Global.java b/sources/scalac/Global.java index 74c8dd58a7..1fd795f0b9 100644 --- a/sources/scalac/Global.java +++ b/sources/scalac/Global.java @@ -61,6 +61,7 @@ public abstract class Global { public final boolean explaintypes; public final boolean uniqid; public final boolean newMatch; + public final boolean xmlPreserveWS; public final boolean printtypes; public final boolean printtokens; @@ -215,6 +216,7 @@ public abstract class Global { this.debug = args.debug.value; this.uniqid = args.uniqid.value; this.newMatch = args.Xnewmatch.value; + this.xmlPreserveWS = args.XpreserveWS.value; this.explaintypes = args.explaintypes.value; this.printtypes = args.types.value; this.printtokens = args.print.tokens; diff --git a/sources/scalac/util/Names.java b/sources/scalac/util/Names.java index 4ab401a010..b9d8ab1257 100644 --- a/sources/scalac/util/Names.java +++ b/sources/scalac/util/Names.java @@ -130,6 +130,7 @@ public class Names { public static final Name Short = Name.fromString("Short"); public static final Name String = Name.fromString("String"); public static final Name Symbol = Name.fromString("Symbol"); + public static final Name Text = Name.fromString("Text"); public static final Name Throwable = Name.fromString("Throwable"); public static final Name Try = Name.fromString("Try"); public static final Name Tuple = Name.fromString("Tuple"); diff --git a/test/files/jvm/xmlLiterals.check b/test/files/jvm/xmlLiterals.check index cf4493ca54..c68baec2d9 100644 --- a/test/files/jvm/xmlLiterals.check +++ b/test/files/jvm/xmlLiterals.check @@ -16,29 +16,28 @@ passed ok passed ok passed ok Test03Servlet +<html><head><title>ModularFormatting</title></head><body><h2>Welcome</h2><p>What follows is an example of modular formatting.</p><p><table align="center"><tr><td bgcolor="#AAAAFF" color="#222255"><h1>message</h1></td></tr></table></p><hr></hr><p>Complicated layout tasks can be encapsulated and outsourced.</p><h2>Bye!</h2></body></html> <html> - <head> - <title>ModularFormatting</title> - </head> - <body> - <h2>Welcome</h2> - <p> - What follows is an example of modular formatting. - </p> - <p> - <table align="center"> - <tr> - <td bgcolor="#AAAAFF" color="#222255"><h1> message </h1></td> - </tr> - </table> - </p> - <hr></hr> - <p> - Complicated layout tasks can be encapsulated and outsourced. - </p> - <h2>Bye!</h2> - </body> - </html> + <head> + <title>ModularFormatting</title> + </head> + <body> + <h2>Welcome</h2> + <p>What follows is an example of modular formatting.</p> + <p> + <table align="center"> + <tr> + <td bgcolor="#AAAAFF" color="#222255"> + <h1>message</h1> + </td> + </tr> + </table> + </p> + <hr></hr> + <p>Complicated layout tasks can be encapsulated and outsourced.</p> + <h2>Bye!</h2> + </body> +</html> Test04 ArrayBuffer(<foo></foo>, <bar>Text</bar>, <foo></foo>) <foo></foo> diff --git a/test/files/jvm/xmlLiterals.scala b/test/files/jvm/xmlLiterals.scala index 6054801bcf..3b20525159 100644 --- a/test/files/jvm/xmlLiterals.scala +++ b/test/files/jvm/xmlLiterals.scala @@ -219,7 +219,9 @@ object Test03Servlet { } def main( args:Array[String] ) = { - Console.println( doGetXML() ); + val x = doGetXML(); + Console.println( x ); + Console.println( new PrettyPrinter( 80, 2 ).toPrettyXML( x )); } } |