rewrite Haskell parts in Scala
authorAdam Megacz <adam@megacz.com>
Sat, 22 Jan 2011 06:23:06 +0000 (22:23 -0800)
committerAdam Megacz <adam@megacz.com>
Sat, 22 Jan 2011 06:23:06 +0000 (22:23 -0800)
.gitignore [new file with mode: 0644]
Makefile
entities.java [new file with mode: 0644]
lib/edu.berkeley.sbp.jar
src/Doc.scala [new file with mode: 0644]
src/HaskellHelper.java
src/Html.scala [new file with mode: 0644]
src/Main.scala [new file with mode: 0644]
src/ScalaHelper.java [new file with mode: 0644]
src/TreeToString.java [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..7cba725
--- /dev/null
@@ -0,0 +1,2 @@
+lib/edu.berkeley.sbp.jar
+wix.jar
index 523c05b..a1373e3 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -11,18 +11,22 @@ ghc     = $(ghcroot)/compiler/ghc-inplace -fallow-undecidable-instances -fallow-
 ghc    += -fglasgow-exts -cpp -hidir $(pwd)/build/hi -i$(pwd)/build/hi -odir $(pwd)/build/class/ 
 ghclibs = $(ghcroot)/rts/HSrts.jar:$(ghcroot)/libraries/base/HSbase.jar:$(ghcroot)/libraries/stm/HSstm.jar
 
 ghc    += -fglasgow-exts -cpp -hidir $(pwd)/build/hi -i$(pwd)/build/hi -odir $(pwd)/build/class/ 
 ghclibs = $(ghcroot)/rts/HSrts.jar:$(ghcroot)/libraries/base/HSbase.jar:$(ghcroot)/libraries/stm/HSstm.jar
 
-java =  java -Xmx800m
-java += $(profile) -cp src:$(ghclibs):$(sbp)/edu.berkeley.sbp.jar:build/class
+jvmargs = -Xmx800m -XX:ThreadStackSize=160000
+
+java =  java $(jvmargs) $(profile) -cp src:$(ghclibs):$(sbp)/edu.berkeley.sbp.jar:build/class
+
+scala = JAVA_OPTS="$(jvmargs)" scala
 
 wix   = $(java) HaskellHelper
 
 
 wix   = $(java) HaskellHelper
 
-install: build/class/Main.class build/class/Tib.class
-       aklog hcoop.net || true
+install: wix.jar
        aklog research.cs.berkeley.edu || true
        aklog research.cs.berkeley.edu || true
-       $(wix) ~/wix/src/ ~/wix/dest/
+       $(scala) -cp wix.jar:lib/edu.berkeley.sbp.jar Main ~/docs/wix/src/ ~/docs/wix/dest/
+
+# --delete disabled until I can keep it from clobbering GArrow.v
        rsync -arL --rsync-path=/usr/sww/bin/rsync \
        rsync -arL --rsync-path=/usr/sww/bin/rsync \
-               --progress --verbose --delete \
-               /Users/megacz/wix/dest/cs.berkeley.edu/ \
+               --progress --verbose \
+               ~/docs/wix/dest/cs.berkeley.edu/ \
                login.eecs.berkeley.edu:public_html/
 
 docs:
                login.eecs.berkeley.edu:public_html/
 
 docs:
@@ -34,14 +38,14 @@ $(sbp):
 $(sbp)/edu.berkeley.sbp.jar: $(sbp)
        cd $(sbp); make edu.berkeley.sbp.jar
 
 $(sbp)/edu.berkeley.sbp.jar: $(sbp)
        cd $(sbp); make edu.berkeley.sbp.jar
 
-wix.jar: build/class/Main.class build/class/Tib.class lib/edu.berkeley.sbp.jar
-       rm -rf tmp
-       mkdir tmp
-       cd build/class; for A in $(lambdavm_jars) lib/edu.berkeley.sbp.jar; \
-         do jar xvf $$A; done
-       echo 'Main-Class: HaskellHelper' > build/manifest
-       cp src/*.g build/class/
-       cd build/class; jar cvmf ../manifest ../../wix.jar .
+wix.jar: $(shell find src -name \*.java -or -name \*.scala)
+       rm -rf build
+       mkdir build
+       javac -cp lib/edu.berkeley.sbp.jar -d build `find src -name \*.java`
+       scalac -deprecation -cp lib/edu.berkeley.sbp.jar -sourcepath src -d build `find src -name \*.scala`
+       echo 'Main-Class: Main' > build/manifest
+       cp src/*.g build/
+       cd build; jar cvmf manifest ../wix.jar .
 
 build/class/Tib.class: $(shell find src -name \*.java) lib/edu.berkeley.sbp.jar
        javac -d build/class -cp lib/edu.berkeley.sbp.jar $(shell find src -name \*.java)
 
 build/class/Tib.class: $(shell find src -name \*.java) lib/edu.berkeley.sbp.jar
        javac -d build/class -cp lib/edu.berkeley.sbp.jar $(shell find src -name \*.java)
diff --git a/entities.java b/entities.java
new file mode 100644 (file)
index 0000000..bb197a2
--- /dev/null
@@ -0,0 +1,68 @@
+            new Entity("tm",    0x2122),
+            new Entity("alef",  0x2135),
+            new Entity("leftArrow", "<--", 0x2190),
+            new Entity("rightArrow", "-->", 0x2192),
+            new Entity("leftDoubleArrow", "<==", 0x21D0),
+            new Entity("rightDoubleArrow", "==>", 0x21D2),
+            new Entity("doubleLeftRightArrow", "<==>", 0x21D4),
+            new Entity("upArrow", 0x2191),
+            new Entity("downArrow", 0x2193),
+            new Entity("upDoubleArrow", 0x21D1),
+            new Entity("downDoubleArrow", 0x21D3),
+            new Entity("forall", 0x2200),
+            new Entity("exists", 0x2203),
+            new Entity("emptySet", 0x2205),
+            new Entity("in", 0x2208),
+            new Entity("cent", 0xA2),
+            new Entity("pi", 0x220F),
+            new Entity("sigma", 0x2211),
+            new Entity("infinity", 0x221E),
+            new Entity("proportional", 0x221D),
+            new Entity("check", 0x221A),
+            new Entity("asterisk", 0x2217),
+            new Entity("minus", 0x2212),
+            new Entity("angle", 0x2220),
+            new Entity("and", 0x2227),
+            new Entity("or", 0x2228),
+            new Entity("intersection", 0x2229),
+            new Entity("union", 0x222A),
+            new Entity("integral", 0x222B),
+            new Entity("therefore", 0x2234),
+            new Entity("congruent", 0x2245),
+            new Entity("similarTo", 0x2248),
+            new Entity("identical", 0x2261),
+            new Entity("neq", 0x2260),
+            new Entity("subset", 0x2282),
+            new Entity("superset", 0x2283),
+            new Entity("notSubset", 0x2284),
+            new Entity("subsetEq", 0x2286),
+            new Entity("supersetEq", 0x2287),
+            new Entity("circlePlus", 0x2295),
+            new Entity("circleTimes", 0x2297),
+            new Entity("bottom", 0x22A5),
+            new Entity("cdot", 0x22C5),
+            new Entity("openDiamonds", 0x25CA),
+            new Entity("spade", 0x2660),
+            new Entity("clubs", 0x2663),
+            new Entity("hearts", 0x2665),
+            new Entity("diamonds", 0x2666),
+            new Entity("prime", 0x2032),
+            new Entity("reals", 0x211C),
+            new Entity("powerSet", 0x2118),
+            new Entity("overScore", 0x203E),
+            new Entity("yen", 0xA5),
+            new Entity("plusminus", 0xB1),
+            new Entity("micro", 0xB5),
+            new Entity("superScriptOne", 0xB9),
+            new Entity("superScriptTwo", 0xB2),
+            new Entity("superScriptThree", 0xB3),
+            new Entity("oneQuarter", 0xBC),
+            new Entity("oneHalf", 0xBD),
+            new Entity("threeQuarters", 0xBE),
+            new Entity("paragraphSymbol", 0xB6),
+            new Entity("times", 0xD7),
+            new Entity("daggar", 0x86),
+            new Entity("sectionSymbol", 0xA7),
+            new Entity("not", 0xAC),
+            new Entity("cr", 0x2193),
+            new Entity("dot", 0xB7),
index cfbc206..22b8acf 100644 (file)
Binary files a/lib/edu.berkeley.sbp.jar and b/lib/edu.berkeley.sbp.jar differ
diff --git a/src/Doc.scala b/src/Doc.scala
new file mode 100644 (file)
index 0000000..76e4b55
--- /dev/null
@@ -0,0 +1,396 @@
+import edu.berkeley.sbp.scala._
+
+import Html.mapToHtml
+import Html.joinStrings
+import Html.{urlEscape,htmlEscape}
+import Html.{pre,stag,tag,stag_,tag_,stag0,link}
+
+object Doc {
+
+  def concatMap[A,B](f: A => Seq[B], s:Seq[A]) : Seq[B] =
+    concat(s.map(f))
+
+  def concat[A](s:Seq[Seq[A]]) : Seq[A] =
+    s.foldLeft(Seq[A]())(_ ++ _)
+
+  def docFromTree(t:Tree) : Doc =
+    t match { case Tree(_,Seq(_,Tree(_,a))) => new Doc(new Header(), a.map(sectionFromTree)) }
+
+  def sectionFromTree(t:Tree) : Section =
+    t match {
+      case Tree("Section", seq) =>
+        seq(0) match {
+          case Tree("SectionHeader", Seq(Tree("=",e),c)) =>
+            new Section(e.length-1, textSequenceFromTree(c), paragraphsFromTrees(seq.tail))
+        }
+    }
+
+  def textSequenceFromTree (t:Tree) : Seq[Text] =
+    t match {
+      case Tree("Word",    chars          ) => Seq(new Chars(stringFromTrees(chars)))
+      case Tree("Ordinal", x              ) => Seq(new Command("ordinal", Seq(new Chars(stringFromTrees(x)))))
+      case Tree("Fraction", Seq(n,d)      ) => Seq(new Command("fraction",Seq(new Chars(stringFromTree(n)),
+                                                                              new Chars(stringFromTree(d)))))
+      case Tree("WS",     _               ) => Seq(WS)
+      case Tree("Quotes", Seq(x)          ) => Seq(new Quotes(textSequenceFromTree(x)))
+      case Tree("Pars", y                 ) => Seq(new SubPar(paragraphsFromTrees(y)))
+      case Tree("Command", Seq(x,y)       ) => Seq(new Command(stringFromTree(x), textSequenceFromTree(y)))
+      case Tree("Command",  Seq(x)        ) => Seq(new Command(stringFromTree(x), Seq()))
+      case Tree("Link",  Seq(text,link)   ) => Seq(new Link(urlFromTree(link), textSequenceFromTree(text)))
+      case Tree("Footnote", x             ) => Seq(new Footnote(concatMap(textSequenceFromTree,x)))
+      case Tree("Keyword", x              ) => Seq(new Keyword(concatMap(textSequenceFromTree,x)))
+      case Tree("Math", x                 ) => Seq(new Math(stringFromTrees(x)))
+      case Tree("Italic",  Seq(x)         ) => Seq(new Styled(Italic        , textSequenceFromTree(x)))
+      case Tree("Bold",  Seq(x)           ) => Seq(new Styled(Bold          , textSequenceFromTree(x)))
+      case Tree("Highlight",  Seq(x)      ) => Seq(new Styled(Highlight     , textSequenceFromTree(x)))
+      case Tree("TT", x                   ) => Seq(new Styled(TT            , concatMap(textSequenceFromTree,x)))
+      case Tree("Strikethrough", x        ) => Seq(new Styled(Strikethrough , concatMap(textSequenceFromTree,x)))
+      case Tree("Superscript", x          ) => Seq(new Styled(Superscript   , concatMap(textSequenceFromTree,x)))
+      case Tree("Subscript", x            ) => Seq(new Styled(Subscript     , concatMap(textSequenceFromTree,x)))
+      case Tree("Underline", x            ) => Seq(new Styled(Underline     , concatMap(textSequenceFromTree,x)))
+      case Tree("(e)",  _)                  => Seq(new GlyphText(Euro))
+      case Tree("(r)",  _)                  => Seq(new GlyphText(CircleR))
+      case Tree("(c)",  _)                  => Seq(new GlyphText(CircleC))
+      case Tree("(tm)",  _)                 => Seq(new GlyphText(TradeMark))
+      case Tree("--",  _)                   => Seq(new GlyphText(Emdash))
+      case Tree("<-",  _)                   => Seq(new GlyphText(LeftArrow))
+      case Tree("<=",  _)                   => Seq(new GlyphText(DoubleLeftArrow))
+      case Tree("=>",  _)                   => Seq(new GlyphText(DoubleRightArrow))
+      case Tree("<=>",  _)                  => Seq(new GlyphText(DoubleLeftRightArrow))
+      case Tree("<->",  _)                  => Seq(new GlyphText(LeftRightArrow))
+      case Tree("^o",  _)                   => Seq(new GlyphText(Degree))
+      case Tree("...",  _)                  => Seq(new GlyphText(Ellipsis))
+      case Tree("Text",   ts)               => concat(ts.map(textSequenceFromTree))
+      case Tree("",    Seq())               => Seq()
+      case t => throw new RuntimeException("unable to create [Text] from " + t)
+    }
+
+  def hostFromTree(t:Tree) : Host =
+    t match {
+      case Tree("IP", Seq(Tree(_,a),Tree(_,b),Tree(_,c),Tree(_,d))) =>
+        new HostIP(intFromTrees(a), intFromTrees(b), intFromTrees(c), intFromTrees(d))
+      case Tree("DNS", parts) =>
+        new HostDNS(parts.map( (t:Tree) => t match { case Tree(_, c) => stringFromTrees(c) }))
+    }
+
+  def urlFromTree(t:Tree) : URL =
+    t match {
+      case Tree("URL", stuff)                            => urlFromTrees(stuff)
+      case Tree("Email", Seq(Tree("username", un),host)) => new URLEmail(stringFromTrees(un), hostFromTree(host))
+      case Tree("Path",stuff)                            => new URLPath(stuff.map(fromUrlChar).foldLeft("")(_ + _))
+    }
+
+  def urlFromTrees(t:Seq[Tree]) : URL =
+    t match {
+      case Seq(Tree(_,method), login, host, port, rest @_*) =>
+        new URLNormal(stringFromTrees(method),
+                      None,
+                      hostFromTree(host),
+                      port match { case Tree("Port",port) => {
+                                     val q = stringFromTrees(port)
+                                     if (q.equals("")) None else Some(java.lang.Integer.parseInt(q))
+                                   }
+                                   case _ => None },
+                      rest match { case Seq(Tree("Path",p), x@_*) => p.map(fromUrlChar).foldLeft("")(_ + _)
+                                   case _ => "" },
+                      rest match { case Seq(_ , Tree("Path",r), x@_*) => Some(stringFromTrees(r))
+                                   case _ => None })
+    }
+
+  //fromUrlChar (Tree "%" [(Tree a [] _),(Tree b [] _)] _) = chr $ (fst $ head $ readHex (a++b))
+  // FIXME: problem here is the "/" vs "%2F" issue, so we "leave urls urlencoded"
+  def fromUrlChar(t:Tree) : String =
+    t match {
+      case Tree("%", Seq(Tree(a,Seq()),Tree(b,Seq()))) => "%"+a+b
+      case Tree(x,y) =>
+        if (x.length==1) x
+        else throw new RuntimeException("could not parse as URL char: " + t)
+    }
+
+  def paragraphsFromTrees(ts:Seq[Tree]) : Seq[Paragraph] =
+    consolidate(concatMap(paragraphsFromTree,ts))
+
+  def paragraphsFromTree(t:Tree) : Seq[Paragraph] =
+    consolidate (t match {
+      case Tree("Verbatim",       Seq(indent,v)) => Seq(new P(    Seq(new Verbatim(unindent(indent,unverbate(v))))))
+      case Tree("TextParagraph",  Seq(Tree(_,text))) => Seq(new P(concatMap(textSequenceFromTree,text)))
+      case Tree("Pars",           pars         ) => concatMap(paragraphsFromTree,pars)
+      case Tree("HR",             _            ) => Seq(HR)
+      case Tree("OL",             a            ) =>
+        Seq(new OL(a.map( (t:Tree) => t match { case Tree("LI",x) => paragraphsFromTrees(x)})))
+      case Tree("UL",             a            ) =>
+        Seq(new UL(a.map( (t:Tree) => t match { case Tree("LI",x) => paragraphsFromTrees(x)})))
+      case Tree("",               _            ) => Seq()
+      case Tree("Blockquote",     pars         ) => Seq(Blockquote(paragraphsFromTrees(pars)))
+      case _ => throw new RuntimeException("unable to create [Paragraph] from " + t)
+    })
+
+  def unverbate (t:Tree) : String =
+    t match {
+      case Tree("Verbatim",x) => x.map(unverbate).foldLeft("")(_ + _)
+      case Tree("VerbatimBrace",Seq(x,y)) => unverbate(x)+" "+unverbate(y)
+      case Tree(t,Seq()) => t
+    }
+
+  def unindent (t:Tree,v:String) : String =
+    t match {
+      case Tree("I", indent) => unindent_(indent.length+1, v)
+    }
+
+  private def unindent_ (i:Int,v:String) : String =
+    if (v.length==0)    ""
+    else if (v.charAt(0) == '\n') "\n"+unindent_(i, drop_(i, v.substring(1)))
+    else v.charAt(0)+unindent_(i, v.substring(1))
+
+  private def drop_(n:Int, x:String) : String = {
+    val x_ : Seq[Char] = x;
+    if (n==0) x
+    else (x_ match {
+      case Seq('\n', r@_*) => x
+      case Seq()           => ""
+      case Seq(a, b@_*)    => drop_(n-1, x.substring(1))
+    })
+  }
+
+  def consolidate(x:Seq[Paragraph]) : Seq[Paragraph] =
+    x match {
+      case Seq() => Seq()
+      case Seq(a) => Seq(a)
+      case Seq(OL(Seq()), x@_*) => consolidate(x)
+      case Seq(UL(Seq()), x@_*) => consolidate(x)
+      case Seq(OL(a),     OL(b), x@_*) => consolidate(Seq(OL(a++b))++x)
+      case Seq(UL(a),     UL(b), x@_*) => consolidate(Seq(UL(a++b))++x)
+      case Seq(a, b @_*) => Seq(a)++consolidate(b)
+    }
+
+  def intFromTrees(t:Seq[Tree]) : Int =
+    java.lang.Integer.parseInt(stringFromTrees(t))
+  def stringFromTree(t:Tree) : String =
+    t match { case Tree(h,c) => h++concatMap(stringFromTree,c) }
+  def stringFromTrees(ts:Seq[Tree]) : String =
+    ts.map(stringFromTree).foldLeft("")(_ + _)
+
+}
+
+class Doc (val header:Header, val sections:Seq[Section]) extends ToHtml {
+  override def toHtml =
+     "<!-- This document was AUTOMATICALLY GENERATED from wix source -->\n"+
+     "<!--    it is probably not a wise idea to edit it directly     -->\n\n"+
+     "<html>\n"+
+     "<head>\n"+
+     Html.style+
+     // FIXME: title tag
+     "</head>\n"+
+     "<body>\n"+   // tell jsmath we will escape stuff manually
+     Html.jsMath+  // FIXME: only put this in if math appears on the page
+     "<center><table><tr><td width=600>\n"+
+     mapToHtml(sections)+
+     "<br><br>\n"+
+     "<table width=100% class=footer><tr><td align=left>"+
+     "<img src='"+Html.printIconBase64+"'></td>"+
+     "<td align=right><span class='signature'>rendered from "+
+     "<a href=http://www.megacz.com/software/wix>"+
+     "W<span style='vertical-align:-20%'>I</span>X</a></span></div></td></tr></table>\n"+
+     "</td></tr></table></center>\n"+
+     "</body></html>"
+}
+
+class Header()
+
+class Section (val level:Int, val header:Seq[Text], val paragraphs:Seq[Paragraph]) extends ToHtml {
+  def toHtml = "\n<h"+((level+1))+">\n"+(mapToHtml(header))+"\n</h"+((level+1))+">\n"+(mapToHtml(paragraphs)) }
+
+abstract class Paragraph extends ToHtml
+  case class P          (val body :Seq[Text]                  ) extends Paragraph { override def toHtml = stag_("p",body) }
+  case object HR                                                       extends Paragraph { override def toHtml = stag("hr") }
+  case class OL         (val items:Seq[Seq[Paragraph]] ) extends Paragraph {
+    override def toHtml = stag0("ol", items.map( (s:Seq[Paragraph]) => stag_("li", s) ).foldLeft("")(_ + _)) }
+  case class UL         (val items:Seq[Seq[Paragraph]] ) extends Paragraph {
+    override def toHtml = stag0("ul", items.map( (s:Seq[Paragraph]) => stag_("li", s) ).foldLeft("")(_ + _)) }
+  case class Blockquote (val body :Seq[Paragraph]             ) extends Paragraph {
+    override def toHtml = 
+      "\n<table class=blockquote border=0 cellpadding=5px>\n"+
+      "<tr><td valign=top><image src='"+Html.quoteIconBase64+"'></td>\n"+
+      "<td class=warn>\n"+
+      mapToHtml(body)+
+      "</td></tr></table>\n"
+  }
+
+abstract class Style
+  case object TT extends Style
+  case object Underline extends Style
+  case object Superscript extends Style
+  case object Subscript extends Style
+  case object Strikethrough extends Style
+  case object Italic extends Style
+  case object Bold extends Style
+  case object Highlight extends Style
+
+abstract class Text extends ToHtml
+  case object WS extends Text { def toHtml = " " }
+  case class Chars(val body:String) extends Text { def toHtml = htmlEscape(body) }
+  case class Quotes(val body:Seq[Text]) extends Text { def toHtml = "&#8220;"+mapToHtml(body)+"&#8221;" }
+  case class GlyphText(val body:Glyph) extends Text { override def toHtml = body.toHtml }
+  case class Math(val body:String) extends Text { override def toHtml = "<span class=math>" +body+ "</span>" }
+  case class Verbatim(val body:String) extends Text { override def toHtml = pre(body) }
+  case class Link(val url:URL, val body:Seq[Text]) extends Text { override def toHtml = link(url.toString, body) }
+  case class Footnote(val body:Seq[Text]) extends Text { override def toHtml = throw new Exception() }
+  case class Keyword(val body:Seq[Text]) extends Text { override def toHtml = tag_("tt", body) }
+  case class SubPar(val body:Seq[Paragraph]) extends Text { override def toHtml = stag_("p", body) }
+  case class Styled(val style:Style, val body:Seq[Text]) extends Text {
+    override def toHtml =
+      style match {
+        case Underline =>       tag_("u", body)
+        case TT =>              tag_("tt", body)
+        case Italic =>          tag_("i", body)
+        case Strikethrough =>   tag_("strike", body)
+        case Superscript =>     tag_("sup", body)
+        case Subscript =>       tag_("sub", body)
+        case Bold =>            tag_("b", body)
+        case Highlight =>       "<span class=highlight>"+mapToHtml(body)+"</span>"
+      }
+  }
+  case class Command(val command:String, val body:Seq[Text]) extends Text {
+    override def toHtml = 
+      command match {
+        case "comment" =>     ""
+        case "url"     =>     "<tt>"+link(mapToHtml(body),body)+"</tt>"
+        case "WiX"     =>     "W<span style='vertical-align:-20%'>I</span>X"
+        case "TeX"     =>     "T<span style='vertical-align:-20%'>E</span>X"
+        case "red"     =>     "<font color=red>"+mapToHtml(body)+"</font>"
+        case "orange"  =>     "<font color=orange>"+mapToHtml(body)+"</font>"
+        case "green"   =>     "<font color=green>"+mapToHtml(body)+"</font>"
+        case "sc"      =>     "<sc>"+mapToHtml(body)+"</sc>"
+        case "image"   =>     "<img src='"+mapToHtml(body)+"'/>"
+        case "imagec"  =>     "<center><img src='"+mapToHtml(body)+"'/></center>"
+        case "image2"  =>     "<img width=180px src='"+mapToHtml(body)+"'/>"
+        case "image3"  =>     "<img width=200px src='"+mapToHtml(body)+"'/>"
+        case "image4"  =>     "<center><img width=550px src='"+mapToHtml(body)+"'/></center>"
+        case "warn"    =>     "\n<div class=warn>\n<table border=0 cellpadding=5px>\n"+
+                              "<tr><td valign=top><image src='"+Html.warnIconBase64+"'></td>\n"+
+                              "<td class=warn>\n"+
+                              mapToHtml(body)+
+                              "</td></tr></table></div>\n"
+        case "announce" =>    "\n<div class=announce>\n<table border=0 cellpadding=5px>\n"+
+                              "<tr><td valign=top></td>\n"+
+                              "<td class=warn>\n"+
+                              mapToHtml(body)+
+                              "</td></tr></table></div>\n"
+        case "br"      =>     "\n<br/>\n"
+        case "cent"    =>     "&#189;"
+        case "euro"    =>     "&#8364;"
+        // gross hack
+        case "ordinal"     => {
+          val x = mapToHtml(body)
+          if      (x.charAt(x.length-1) == '1') x+"<sup>"+"st"+"</sup>"
+          else if (x.charAt(x.length-1) == '2') x+"<sup>"+"nd"+"</sup>"
+          else if (x.charAt(x.length-1) == '3') x+"<sup>"+"rd"+"</sup>"
+          else                                  x+"<sup>"+"th"+"</sup>"
+        }
+        // TO DO: use "unicode vulgar fractions" here
+        // directional quotes: see http://www.dwheeler.com/essays/quotes-in-html.html
+        /*    u'1/2' : u'\u00BD',
+        //    u'1/4' : u'\u00BC',
+        //    u'3/4' : u'\u00BE',
+        //    u'1/3' : u'\u2153',
+        //    u'2/3' : u'\u2154',
+        //    u'1/5' : u'\u2155',
+        //    u'2/5' : u'\u2156',
+        //    u'3/5' : u'\u2157',
+        //    u'4/5' : u'\u2158',
+        //    u'1/6' : u'\u2159',
+        //    u'5/6' : u'\u215A',
+        //    u'1/8' : u'\u215B',
+        //    u'3/8' : u'\u215C',
+        //    u'5/8' : u'\u215D',
+        //    u'7/8' : u'\u215E',
+        */
+        case "fraction"           => "<sup>"++body(0).toHtml++"</sup>"++"/"++"<sub>"++body(1).toHtml++"</sub>"
+        case "rfc"                => "<tt><a href=http://tools.ietf.org/html/rfc"+mapToHtml(body)+">RFC"+mapToHtml(body)+"</a></tt>"
+        case "keystroke:command"  => "&#x2318;"
+        case "keystroke:shift"    => "&#x21E7;"
+        case "keystroke:option"   => "&#x2325;"
+        case "keystroke:control"  => "&#x2303;"
+        case "keystroke:capslock" => "&#x21EA;"
+        case "keystroke:apple"    => "&#xF8FF;"
+        case _                    => throw new RuntimeException("unsupported command " + command)
+      }
+  }
+
+
+abstract class Glyph extends ToHtml
+  case object Euro extends Glyph { override def toHtml = "&#8364;" }
+  case object CircleR extends Glyph { override def toHtml = "&#162;" }
+  case object CircleC extends Glyph { override def toHtml = "&#174;" }
+  case object TradeMark extends Glyph { override def toHtml = "&#8482;" }
+  case object ServiceMark extends Glyph { override def toHtml = "&#8482;" }
+  case object Emdash extends Glyph { override def toHtml = "&mdash;" }
+  case object Ellipsis extends Glyph { override def toHtml = "&#0133;"  /* &cdots;? */ }
+  case object Cent extends Glyph { override def toHtml = "&#189;" }
+  case object Daggar extends Glyph { override def toHtml = "&#8224;" }
+  case object DoubleDaggar extends Glyph { override def toHtml = "&#8225;" }
+  case object Clover extends Glyph { override def toHtml = "&#8984;" }
+  case object Flat extends Glyph { override def toHtml = "&#8918;" }
+  case object Natural extends Glyph { override def toHtml = "&#8919;" }
+  case object Sharp extends Glyph { override def toHtml = "&#8920;" }
+  case object CheckMark extends Glyph { override def toHtml = "&#10003;" }
+  case object XMark extends Glyph { override def toHtml = "&#10007;" }
+  case object LeftArrow extends Glyph { override def toHtml = "&larr;" }
+  case object RightArrow extends Glyph { override def toHtml = "&rarr;" }
+  case object DoubleLeftArrow extends Glyph { override def toHtml = "&#;"  /* FIXME */ }
+  case object DoubleRightArrow extends Glyph { override def toHtml = "&#;"  /* FIXME */ }
+  case object DoubleLeftRightArrow extends Glyph { override def toHtml = "&#;"  /* FIXME */ }
+  case object LeftRightArrow extends Glyph { override def toHtml = "&#;"  /* FIXME */ }
+  case object Degree extends Glyph { override def toHtml = "&#;"  /* FIXME */ }
+
+class Login(val user:String, val password:Option[String]) {
+  override def toString =
+    password match {
+      case None    => user
+      case Some(x) => user+":"+urlEscape(x) }
+}
+
+abstract class URL
+  case class URLPath(val path:String) extends URL { override def toString = path }
+  case class URLEmail(val user:String, val host:Host) extends URL { override def toString = "mailto:"+user+"@"+host }
+  case class URLNormal(val method:String,
+                       val login:Option[Login],
+                       val host:Host,
+                       val port:Option[Int],
+                       val path:String,
+                       val ref:Option[String]) extends URL {
+    override def toString =
+     method+"://"+
+     (login match {
+       case None => ""
+       case Some(log) => log+"@" })+
+     host+
+     "/"+
+     path+
+     (ref match {
+       case None => ""
+       case Some("") => ""
+       case Some(x) => "#"+x })
+
+  }
+
+
+abstract class Host
+  case class HostIP(val ip0:Int, val ip1:Int, val ip2:Int, val ip3:Int) extends Host {
+    override def toString = ip0+"."+ip1+"."+ip2+"."+ip3 }
+  case class HostDNS(val parts:Seq[String]) extends Host {
+    override def toString = joinStrings(parts, ".") }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
index a9efb30..4e3b2c6 100644 (file)
@@ -91,6 +91,7 @@ public class HaskellHelper {
                 }
                 System.out.println(ANSI.green("copying: "+f.getPath()));
                 File dest_ = new File(outdir.getAbsolutePath()+File.separatorChar+suffix+"-");
                 }
                 System.out.println(ANSI.green("copying: "+f.getPath()));
                 File dest_ = new File(outdir.getAbsolutePath()+File.separatorChar+suffix+"-");
+                new File(dest_.getParent()).mkdirs();
                 FileOutputStream fos = new FileOutputStream(dest_);
                 FileInputStream fis = new FileInputStream(f);
                 byte[] buf = new byte[1024];
                 FileOutputStream fos = new FileOutputStream(dest_);
                 FileInputStream fis = new FileInputStream(f);
                 byte[] buf = new byte[1024];
@@ -115,6 +116,7 @@ public class HaskellHelper {
             Class.forName("Main").
                 getMethod("main", new Class[] { String[].class }).
                 invoke(null, new Object[] { new String[] { f.getAbsolutePath() } });
             Class.forName("Main").
                 getMethod("main", new Class[] { String[].class }).
                 invoke(null, new Object[] { new String[] { f.getAbsolutePath() } });
+                try {
             new File(new File(outPath).getParent()).mkdirs();
             PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(outPath+"+")));
             pw.println(ret);
             new File(new File(outPath).getParent()).mkdirs();
             PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(outPath+"+")));
             pw.println(ret);
@@ -122,7 +124,6 @@ public class HaskellHelper {
             pw.close();
             File dest = new File(outPath);
             if (dest.exists()) {
             pw.close();
             File dest = new File(outPath);
             if (dest.exists()) {
-                try {
                     Process p = Runtime.getRuntime().exec(new String[] {
                             "diff",
                             "-Bub",
                     Process p = Runtime.getRuntime().exec(new String[] {
                             "diff",
                             "-Bub",
@@ -138,13 +139,13 @@ public class HaskellHelper {
                         /*else System.out.println(ANSI.blue(s));*/
                     }
                     p.waitFor();
                         /*else System.out.println(ANSI.blue(s));*/
                     }
                     p.waitFor();
-                } catch (Exception e) {
-                    e.printStackTrace();
-                }
             }
             new File(outPath+"+").renameTo(dest);
             if (dest.lastModified() <= f.lastModified())
                 dest.setLastModified(f.lastModified()+1);
             }
             new File(outPath+"+").renameTo(dest);
             if (dest.lastModified() <= f.lastModified())
                 dest.setLastModified(f.lastModified()+1);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
         }
     }
 
         }
     }
 
diff --git a/src/Html.scala b/src/Html.scala
new file mode 100644 (file)
index 0000000..8ffdea3
--- /dev/null
@@ -0,0 +1,216 @@
+trait ToHtml {
+  def toHtml : String
+}
+
+object Html {
+
+  def style : String =
+    "\n<style>\n"+
+    " h1, h2, h3, h4 { font-family: 'Trebuchet MS', arial, verdana, sans-serif; width: 100% }\n"+
+    " h1 { font-size: 20pt; border-top: black 1px solid; }\n"+
+    " h2 { font-size: 16pt; border-top: silver 1px solid; }\n"+
+    " h3 { font-size: 12pt; }\n"+
+    " TH, TD, P, LI, DIV, SPAN {\n"+
+    "     font-family: verdana, arial, sans-serif;\n"+
+    "     font-size: 12px;  \n"+
+    "     text-decoration:none; \n"+
+    " }\n"+
+    " LI { margin-top: 5px; }\n"+
+    " body { color: #333333; }\n"+
+    " blockquote { font-style: italic; width: 100% }\n"+
+    " div.warn { border: 1px solid #ff; border-top: 5px solid #ff; background-color: #fbb; color: white; }\n"+
+    " td.warn { color: black; }\n"+
+    " div.announce { border: 1px solid green; background-color: #bfb; color: white; }\n"+
+    " td.announce { color: black; }\n"+
+    " div.footer {\n"+
+    "   color: gray;\n"+
+    "   border-top: 1px solid silver;\n"+
+    "   font-size: 10px;\n"+
+    " }\n"+
+    " table.blockquote { margin: 5px; border: 1px #e6ddcb solid; background-color: #fbf2e0; width: 100% }\n"+
+    " a:link { text-decoration: none; color: blue; border-bottom:1px dotted; }\n"+
+    " a:visited { text-decoration: none; color: purple; border-bottom:1px dotted; }\n"+
+    " a:active { text-decoration: none; color: red; border-bottom:1px solid; }\n"+
+    " a:hover { text-decoration: none; border-bottom:1px solid; }\n"+
+    " table.footer { border-top: silver solid 1px; }\n"+
+    " span.signature { color: #bbb;  }\n"+
+    " .signature a:link { color: #aaf;  }\n"+
+    " .signature a:visited { color: #faa;  }\n"+
+    " .signature a:hover { color: blue; border-bottom: 1px solid blue;  }\n"+
+    " span.highlight { background: yellow; color: black; padding: 3px }\n"+
+    " div.pre {\n"+
+    "     text-align: left;\n"+
+    "     font-family: monospace;\n"+
+    "     border-style: none;\n"+
+    "     border-width: 2px 2px 2px 2px;\n"+
+    "     border-color: #6666aa;\n"+
+    "     color: #FFFFFF;\n"+
+    "     background-color: #333333;\n"+
+    "     margin-right: 25px;\n"+
+    "     margin-left: 25px;\n"+
+    "     padding: 10px;\n"+
+    "  }\n"+
+    "</style>\n"
+
+  def quoteIconBase64 = 
+    "data:image/png;base64,"+
+    "iVBORw0KGgoAAAANSUhEUgAAABAAAAAVCAYAAABPPm7SAAAABmJLR0QA/wD/AP+gvaeTAAAA"+
+    "CXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1wQPAx0rP5obpAAAAWJJREFUOMvdlKFvg0AU"+
+    "xr8tVZ1cbU+04lUuwc2WypMX9AxiomoJ/RNYqmaaYKpb5kBys1UkWBCdKLqVs8zwFnrQmYol"+
+    "+xLC8X55H/e+wAENhWGoPM/zT6eThQ4lSWJf5EEQuAAqAJXv+95vXCkVtpwZuq4bmM1pmlrM"+
+    "LctKWzuwbTsBUNX3lph3Njfd6/WZ9vv9iHkYhsrkPa21zQ9aa9v3fRsAFovFK9cMPgIApdT7"+
+    "eDz+RB1Y1XEBwEVe54ZbXKm/N7haNwCQ5zmtVqtnE0op49lspgFgPp+/dfF/EGIPAAaDgZBS"+
+    "xgBwPB7vd7vdIwD0+/2v5ry8juNYmgY/P1GWZQ9sQER3XOcwD4fDkA2IqGiNwN8+ERVNY/Pt"+
+    "RFQIIcozg+Vy+VKW5dDcMmuz2ThFURAATKfTj7MQ1+v1Ezc7jrMVQpTmocOjOY6znUwmRSvR"+
+    "KIpknud0KfEoimSWZQ/N2jfhtb1AvGklDQAAAABJRU5ErkJggg=="
+
+  def warnIconBase64 = 
+    "data:image/png;base64,"+
+    "iVBORw0KGgoAAAANSUhEUgAAABQAAAAREAYAAACN1FD9AAAABmJLR0QA/wD/AP+gvaeTAAAA"+
+    "CXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAUAAAAEQDeTN6UAAAD/ElEQVRIx8WUbUyV"+
+    "ZRjHf+chxDi8yZcDa0mKh/wSuNE5eNYHZBiRwoespayEkdgx22hmjB1glTlenDYlMccAX1cb"+
+    "CDFlg+aQ1JGEazGWpixdbbweDucctPF2PNxXX3pw00gL09+X59m1+77+v+d+nueCBRJ+ECAs"+
+    "78dnAPp2nLEDtMdpPwMYZhfaf8GU1gLsixwdBRDRr7lRAHmxT0zs+UKAuKmhXgDfGleAYZVh"+
+    "lXynC/Z1AYyeX+IFiLjw+MwWAxDQuBqgNVsX+iNta/nWcnV0vD+9Ir1CndDre68AfDH42PzW"+
+    "2QDW79UFxkxh6WHp8o5a5lznXCfJ/nPX867nSYArO7AssEyOjOwE8GfGZwC84Pu3edrDLgzK"+
+    "AFj06q7DAPt/1evBK4szizMlpXy4tqO2g/MHa9sm2ibwP23M78zvFE0rBAg4U24DqFpieAqA"+
+    "6kd+cjuiAAqu6Sfn/n55/fJ6CZOc6YHpATmkNWgNWoOIZtSMmlFE1d9ae2utvDt21ZRiSpHV"+
+    "+r43LwFkBT4ysehigKgDvxcC3P5QD5qxNtU11anj8hfhg+GD4YMixipjlbFK5pjyHok8Enn3"+
+    "m7yyG2DAEdIFYLy9YMHDLoBjK/WA8fhkb7JXtcqMnJbT4tJFYlpiWmJaRKJio2KjYu8KSp9K"+
+    "UAly2ZtpSbWkql/0PrsuAJR3/2cx65cA1qujRgBV6HpWy9fy5dydOz2JPYlqQO4hwZ/gT/CL"+
+    "mB1mh9kh93Enq2tP1x7V4wo0pBnS5KehVwBmPjA7AcxtDy2mBQIYPmk/B/CDb26MxGw5teWU"+
+    "+krmIdWaak21iiR2JHYkdsi83G7KtmRb1HG9b+PHAK2lJAEQ9EDBt88C5BybGyOLQgtCC8Su"+
+    "gkesI1axzRd8Mvdk7slckeoN1RuqN8wvODsyVDNUIxaXP0SFKHlfz8l4GSDTfd+B6Tdh5QCh"+
+    "hUUvAlR8o9eDI4qSipLkJcOEqdvUzaX5HmzSNGmaNMFN+037TTvgxInzb96QKTovOo/LRmuJ"+
+    "r8QnFr3+2esAB/IX9wAEjd+3cfc+gD2euTFSv2zzss0SIdnTG6c3SqU8AFulrdJWKRJSElIS"+
+    "UiLia/Y1+5r/YcNHM9tntku1e2KFZ4VHAvXcwnyAksY5MfNvAOYdQ+8BzHytL5warDtRd2I2"+
+    "QjWNh46HqjR11LvGu0aVqUOei56LYlD7PRWeCmlWpe4p95R87h66Yb9hV+7+o71BvUHKoWyj"+
+    "jlGHSlbPOTc5N6kkFTqybWSbeksxfHb4rGqd9Q3nDOdI5lRHTXtNu6rWc/vfAJjoXHoNYOl6"+
+    "Q70CaFEpYwAZhof+m/5nvv0UoDFLW/UagCXuSQvdSzwAiTv/BGXg1AxNKyCeAAAAAElFTkSu"+
+    "QmCC"
+
+  def printIconBase64 = 
+    "data:image/png;base64,"+
+    "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAOCAMAAADHVLbdAAAALHRFWHRDcmVhdGlvbiBUaW1l"+
+    "AEZyaSAxOSBTZXAgMjAwMyAxODozOTozMiAtMDAwME2jAt8AAAAHdElNRQfTCRMRKABXeznM"+
+    "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAACRQTFRF////AAAA7+/v"+
+    "3t7ezs7OtbW1ra2tlJSUnJycEBAQKSkphISEbGtEogAAAAF0Uk5TAEDm2GYAAABLSURBVHja"+
+    "nY1BEsAgCAMDKm31//81qB3l6h4Y4mYQQNvAmXNv7W/gytcTRj6pppQHNWoW6JWS13Ix86yr"+
+    "O7MEPkDWuWLPK/7hoYEOxksDsk8eppEAAAAASUVORK5CYII="
+
+  def emailIconBase64 =
+    "data:image/png;base64,"+
+    "iVBORw0KGgoAAAANSUhEUgAAABUAAAAOCAMAAAD32Kf8AAAALHRFWHRDcmVhdGlvbiBUaW1l"+
+    "AEZyaSAxOSBTZXAgMjAwMyAxODo0MjowOSAtMDAwMBDwv7IAAAAHdElNRQfTCRMRKhqYL6I0"+
+    "AAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAADBQTFRF////AAAAhISE"+
+    "9/fv9/f3////CAgI7+/v1tbOxsa9tbWtpaWclJSMlJSUe3t7a2trxDv8WgAAAAF0Uk5TAEDm"+
+    "2GYAAABiSURBVHjabc9JEoAgDETRpNOKs/e/rSEYF8jfhHpQFAi6JHpHBg5VOdIj+KeXkgO9"+
+    "tUj/Bldo3WdRnktt3fbjgoWKK5EKsunkyryCn86Oxsj/UZqKEkFmNF+mvieFdSK16wFr7QK5"+
+    "tASqkwAAAABJRU5ErkJggg=="
+
+  def pdfIconBase64 = 
+    "data:image/png;base64,"+
+    "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAK3RFWHRDcmVhdGlvbiBUaW1l"+
+    "AFRodSA2IE5vdiAyMDAzIDE1OjMwOjAwIC0wMDAwSwt8PwAAAAd0SU1FB9MLBg8fD1x8/t4A"+
+    "AAAJcEhZcwAACxIAAAsSAdLdfvwAAAAEZ0FNQQAAsY8L/GEFAAAAQlBMVEX///9zc3Nra2uE"+
+    "hITGxsaUlJScnJx7e3uMjIz////39/fv7+/n5+fe3t69vb1jY2OlpaXW1ta1tbVaWlqtra3O"+
+    "zs5w48BYAAAAAXRSTlMAQObYZgAAAIdJREFUeNpNz1ESBBEMBNCRwSCJMOT+V10WW9vKz6uO"+
+    "iusa0R137ShxjImwHsnSMCbEX6dPiIje+SUKC6hav8CbAQlZD/RmJ1Tdz6oTwETMfAByK0hT"+
+    "1kgH7E1phFfjdvS2JlJpN8RAyEHMhgJgaexJTBPyA1JiGoD0BXgg2H8wcFs3/jDvPB+sOwir"+
+    "+o6iKQAAAABJRU5ErkJggg=="
+
+  def jsMath =
+    "<script> jsMath = { showFontWarnings: false } </script>\n"+
+    "<script src='/jsmath/easy/load.js'></script>\n"+
+    "<span id='tex2math_off'></span>\n"+
+    "<NOSCRIPT> <DIV STYLE='color:#CC0000; text-align:center'> "+
+    "<B>Warning: <A HREF='http://www.math.union.edu/locate/jsMath'>jsMath</A> "+
+    "requires JavaScript to process the mathematics on this page.<BR> If your "+
+    "browser supports JavaScript, be sure it is enabled.</B> </DIV> <HR> </NOSCRIPT>\n"
+
+  def urlEscape (s:Seq[Char]) =
+    s.map(urlEscapeChar).foldLeft("")(_ + _)
+
+  def htmlEscape (s:Seq[Char]) =
+    s.map(htmlEscapeChar).foldLeft("")(_ + _)
+
+  private def urlEscapeChar (c:Char) : String =
+    c match {
+      // non-alphanumerics which may appear unescaped
+      case '$'                        => "$"
+      case '-'                        => "-"
+      case '_'                        => "_"
+      case '.'                        => "."
+      case '!'                        => "!"
+      case '*'                        => "*"
+      case '\''                       => "\'"
+      case '('                        => "("
+      case ')'                        => ")"
+      case ','                        => ","
+
+      // technically these aren't allowed by RFC, but we include them anyways
+      case '/'                        => "/"
+      case ';'                        => ";"
+      case '&'                        => "&"
+      case '='                        => "="
+
+      // FIXME: this will wind up "disencoding" a %-encoded question mark
+      case '?'                        => "?"
+
+      case _ => {
+        if ((c >= 'a' && c <= 'z') ||
+            (c >= 'A' && c <= 'Z') ||
+            (c >= '0' && c <= '9'))
+          'c'+""
+        else {
+          val s = "00"+java.lang.Integer.toString(c & 0xff, 16)
+          return '%'+s.substring(s.length()-1, 2)
+        }
+      }
+    }
+
+  private def htmlEscapeChar (c:Char) =
+    c match {
+      case '<'  => "&lt;"
+      case '>'  => "&gt;"
+      case '&'  => "&amp;"
+      case '\'' => "&apos;"
+      case '\"' => "&quot;"
+      case c    => ""+c
+    }
+
+  def mapToHtml[A <: ToHtml](h:Seq[A]) : String =
+    h.map((s:A) => s.toHtml).foldLeft("")(_ + _)
+
+  def joinStrings(strings:Seq[String], separator:String) =
+    if (strings.length == 0) ""
+    else if (strings.length == 1) strings(0)
+    else strings(0)+strings.tail.map( (s:String) => separator+s ).foldLeft("")(_ + _)
+
+  def stag(t:String)                            = "\n<"+t+"></"+t+">\n"
+  def stag(t:String,  body:ToHtml)              = "\n<"+t+">\n"+body.toHtml+"\n</"+t+">\n"
+  def stag0(t:String, body:String)              = "\n<"+t+">\n"+body+"\n</"+t+">\n"
+  def tag(t:String,   body:ToHtml)              = "<"+t+">"+body.toHtml+"</"+t+">"
+  def stag_(t:String, body:Seq[ToHtml])  = "\n<"+t+">\n"+mapToHtml(body)+"\n</"+t+">\n"
+  def tag_(t:String,  body:Seq[ToHtml])  = "<"+t+">"+mapToHtml(body)+"</"+t+">"
+
+  def link(ref:String, body:Seq[ToHtml]) : String = {
+    val img = "style='vertical-align: text-bottom;' border=0 "
+    val icon = if      (ref.endsWith(".pdf"))      "<img "+img+" src='"+Html.pdfIconBase64+"'>&nbsp;"
+               else if (ref.startsWith("mailto:")) "<img "+img+" src='"+Html.emailIconBase64+"'>&nbsp;"
+               else ""
+    return "<a href='"+ref+"'>"+icon+mapToHtml(body)+"</a>"
+  }
+
+  private def pre_(c:Char) : String =
+    c match {
+      case ' '  => "&nbsp;"
+      case '\n' => "<br/>\n"
+      case a    => htmlEscapeChar(a)
+    }
+
+  def pre(x:String) : String =
+    "\n<div class=pre style='white-space:nowrap'>"+(x.map(pre_).foldLeft("")((a,b:String) => a + b))+"\n</div>\n"
+
+}
+
diff --git a/src/Main.scala b/src/Main.scala
new file mode 100644 (file)
index 0000000..2cd9e44
--- /dev/null
@@ -0,0 +1,15 @@
+import edu.berkeley.sbp.Tree
+import edu.berkeley.sbp.scala.SBP
+
+class Main extends TreeToString {
+
+  override def run(t:edu.berkeley.sbp.Tree[String]) : String =
+    Doc.docFromTree(SBP.mkTree(t)).toHtml
+}
+
+object Main {
+
+  def main(args:Array[String]) =
+    ScalaHelper.main(args, new Main())
+
+}
diff --git a/src/ScalaHelper.java b/src/ScalaHelper.java
new file mode 100644 (file)
index 0000000..85065e2
--- /dev/null
@@ -0,0 +1,154 @@
+// Copyright 2011 the Contributors, as shown in the revision logs.
+// Licensed under the Apache Public Source License 2.0 ("the License").
+// You may not use this file except in compliance with the License.
+import edu.berkeley.sbp.*;
+import edu.berkeley.sbp.misc.*;
+import edu.berkeley.sbp.util.*;
+import edu.berkeley.sbp.meta.*;
+import edu.berkeley.sbp.chr.*;
+import java.io.*;
+
+public class ScalaHelper {
+    public static boolean isNull(Object o) { return o==null; }
+
+    private static CharParser parser = null;
+    static {
+        synchronized(ScalaHelper.class) {
+            if (parser == null) {
+                try {
+                    // FIXME: bundle this into the jarfile
+                    InputStream grammarFile = ScalaHelper.class.getClassLoader().getResourceAsStream("wix.g");
+                    Tree<String> res = new CharParser(GrammarAST.getMetaGrammar())
+                        .parse(grammarFile).expand1();
+                    Union grammar = GrammarAST.buildFromAST(res, "s", new GrammarAST.ImportResolver() {
+                            public InputStream getImportStream(String filename) {
+                                return this.getClass().getClassLoader().getResourceAsStream(filename);
+                            }
+                        });
+                    parser = new CharParser(grammar);
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    }
+
+    public static Tree parseFile(String targetFile) throws Throwable {
+        Tree ret = null;
+        try {
+            Reader r = new InputStreamReader(new FileInputStream(targetFile));
+            Input input = new CharInput(new IndentingReader(r, CharAtom.left, CharAtom.right));
+            ret = parser.parse(input).expand1();
+        } catch (Throwable e) {
+            e.printStackTrace();
+            throw e;
+        }
+        if (ret==null) throw new NullPointerException("CharParser returned null");
+        return ret;
+    }
+
+    public static void main(String[] argv, TreeToString t2s) throws Throwable {
+        if (argv.length != 2) {
+            System.out.println("usage: java -jar wix.jar [-v] <indir> <outdir>");
+            // FIXME: implement this
+            System.out.println("     | java -jar wix.jar [-v] <infile>.wix");
+            System.out.println("");
+            // FIXME: implement these
+            System.out.println("   -v   print text as it is parsed (sbp.verbose=true)");
+            System.out.println("   -vv  like -v, but also dump parse tree");
+            System.out.println("   -vvv like -vv, but also dump wix tree");
+            System.exit(-1);
+            return;
+        }
+        File indir = new File(argv[0]);
+        File outdir = new File(argv[1]);
+        if (!indir.isDirectory()) {
+            process(new File(indir.getParent()), indir.getName(), outdir, t2s);
+        } else {
+            process(indir, "", outdir, t2s);
+        }
+    }
+
+    private static void process(File indir, String suffix, File outdir, TreeToString t2s) throws Throwable {
+        File f = new File(indir.getAbsolutePath()+File.separatorChar+suffix);
+        //System.out.println(f+" "+indir + " " + suffix + " " + outdir);
+        if (!f.exists()) return;
+        if (f.isDirectory()) {
+            for (String s : f.list())
+                process(indir, suffix + File.separatorChar + s, outdir, t2s);
+            return;
+        }
+        if (!f.getPath().endsWith(".wix")) {
+            boolean skip = false;
+            if (f.getName().equals(".DS_Store")) skip = true;
+            if (f.getName().equals("._.DS_Store")) skip = true;
+            if (f.getName().endsWith("-")) skip = true;
+            if (!skip) {
+                File dest = new File(outdir.getAbsolutePath()+File.separatorChar+suffix);
+                if (dest.exists() && dest.lastModified()==f.lastModified() && dest.length()==f.length()) {
+                    System.out.println(ANSI.yellow("no change: "+f.getPath()));
+                    return;
+                }
+                System.out.println(ANSI.green("copying: "+f.getPath()));
+                File dest_ = new File(outdir.getAbsolutePath()+File.separatorChar+suffix+"-");
+                new File(dest_.getParent()).mkdirs();
+                FileOutputStream fos = new FileOutputStream(dest_);
+                FileInputStream fis = new FileInputStream(f);
+                byte[] buf = new byte[1024];
+                while(true) {
+                    int numread = fis.read(buf, 0, buf.length);
+                    if (numread==-1) break;
+                    fos.write(buf, 0, numread);
+                }
+                fos.close();
+                fis.close();
+                dest_.renameTo(dest);
+                dest.setLastModified(f.lastModified());
+            }
+        } else {
+            String out = "== " + suffix + " ";
+            while(out.length() < 75) out+="=";
+            System.out.println(ANSI.yellow(out));
+            //System.out.println();
+            String outPath = outdir.getAbsolutePath()+File.separatorChar+suffix;
+            outPath = outPath.substring(0, outPath.length()-".wix".length())+".html";
+            if (new File(outPath).exists() && new File(outPath).lastModified() > f.lastModified()) return;
+
+            Tree tree = parseFile(f.getAbsolutePath());
+            String ret = t2s.run(tree);
+            try {
+                new File(new File(outPath).getParent()).mkdirs();
+                PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(outPath+"+")));
+                pw.println(ret);
+                pw.flush();
+                pw.close();
+                File dest = new File(outPath);
+                if (dest.exists()) {
+                    Process p = Runtime.getRuntime().exec(new String[] {
+                            "diff",
+                            "-Bub",
+                            dest.getAbsolutePath(),
+                            new File(outPath+"+").getAbsolutePath()
+                        });
+                    BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
+                    br.readLine();
+                    br.readLine();
+                    for(String s = br.readLine(); s != null; s = br.readLine()) {
+                        if      (s.startsWith("+")) System.out.println(ANSI.green(s));
+                        else if (s.startsWith("-")) System.out.println(ANSI.red(s));
+                        /*else System.out.println(ANSI.blue(s));*/
+                    }
+                    p.waitFor();
+                }
+                new File(outPath+"+").renameTo(dest);
+                if (dest.lastModified() <= f.lastModified())
+                    dest.setLastModified(f.lastModified()+1);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public static Object ret;
+    public static void putBack(String o) { ret = o; }
+}
diff --git a/src/TreeToString.java b/src/TreeToString.java
new file mode 100644 (file)
index 0000000..ad9235a
--- /dev/null
@@ -0,0 +1,5 @@
+import edu.berkeley.sbp.*;
+
+public interface TreeToString {
+    public abstract String run(Tree<String> t);
+}
\ No newline at end of file