915158bc8139a99a5a831cb1b8400be9fbc46143
[sbp.git] / src / edu / berkeley / sbp / tib / Tib.java
1 // Copyright 2005 the Contributors, as shown in the revision logs.
2 // Licensed under the Apache Public Source License 2.0 ("the License").
3 // You may not use this file except in compliance with the License.
4
5 package edu.berkeley.sbp.tib;
6 import edu.berkeley.sbp.*;
7 import edu.berkeley.sbp.misc.*;
8 import java.util.*;
9 import java.io.*;
10
11 // TODO: multiple {{ }} for superquotation
12 // TODO: strings
13 // TODO: comments
14
15 /**
16  *   A slow, ugly, inefficient, inelegant, ad-hoc parser for TIB files.
17  *
18  *   Despite being inelegant, this parser keeps all internal state on
19  *   the stack (the only heap objects created are those which are
20  *   returned).
21  *
22  *   This was written as an ad-hoc parser to facilitate
23  *   experimentation with the TIB spec.  Once the spec is finalized it
24  *   should probably be rewritten using a parser-generator, if
25  *   possible (it is unclear whether or not the associated grammar is
26  *   context-free).
27  */
28 public class Tib implements Token.Stream<CharToken> {
29
30     public Tib(String s) throws IOException, Invalid { this(new StringReader(s)); }
31     public Tib(Reader r) throws IOException, Invalid { this(new BufferedReader(r)); }
32     public Tib(InputStream is) throws IOException, Invalid { this(new BufferedReader(new InputStreamReader(is))); }
33     public Tib(BufferedReader br) throws IOException, Invalid {
34         cur = parse(br);
35         System.out.println("\rparsing: \"" + cur.toString(0, -1) + "\"");
36     }
37
38     private Block cur;
39     private String s = null;
40     int pos = 0;
41     int spos = 0;
42
43     int _row = 0;
44     int _col = 0;
45     public CharToken next() throws IOException {
46         if (cur==null) return null;
47         if (s != null) {
48             if (spos < s.length()) {
49                 char c = s.charAt(spos++);
50                 if (c=='\n') { _row++; _col = 0; }
51                 else _col++;
52                 return new CharToken(c, _row, _col);
53             }
54             s = null;
55         }
56         if (pos >= cur.size()) {
57             pos = cur.iip+1;
58             cur = cur.parent;
59             if (cur==null) return null;
60             return CharToken.right(_row, _col);
61         }
62         Object o = cur.child(pos++);
63         if (o instanceof String) {
64             spos = 0;
65             s = (String)o;
66             return next();
67         }
68         if (o instanceof Block) {
69             Block b = (Block)o;
70             _row = b.row;
71             _col = b.col;
72         }
73         if (((Block)o).isLiteral()) {
74             spos = 0;
75             s = ((Block.Literal)o).text();
76             return next();
77         }
78         cur = (Block)o;
79         pos = 0;
80         return CharToken.left(_row, _col);
81     }
82
83     public static Block parse(BufferedReader br) throws Invalid, IOException {
84         int row=0, col=0;
85         try {
86             boolean blankLine = false;
87             Block top = new Block.Root();
88             for(String s = br.readLine(); s != null; s = br.readLine()) {
89                 col = 0;
90                 while (s.length() > 0 &&
91                        s.charAt(0) == ' ' &&
92                        (!(top instanceof Block.Literal) || col < top.col)) { col++; s = s.substring(1); }
93                 if ((top instanceof Block.Literal) && col >= top.col) { top.add(s); continue; }
94                 if (s.length()==0)                                    { blankLine = true; continue; }
95                 while (col < top.col) {
96                     if (s.startsWith("{}") && top instanceof Block.Literal && ((Block.Literal)top).braceCol == col) break;
97                     blankLine = false;
98                     top = top.closeIndent();
99                 }
100                 if (s.startsWith("{}")) {
101                     int bc = col;
102                     boolean append = top instanceof Block.Literal && ((Block.Literal)top).braceCol == bc;
103                     s = s.substring(2);
104                     col += 2;
105                     while (s.length() > 0 && s.charAt(0) == ' ' && !(append && col >= top.col) ) { col++; s = s.substring(1); }
106                     if (append) top.add(s); else (top = new Block.Literal(top, row, col, bc)).add(s);
107                     continue;
108                 }
109                 while (s.length() > 0 && s.charAt(s.length()-1)==' ') { s = s.substring(0, s.length()-1); }
110                 if (col > top.col) top = new Block.Indent(top, row, col);
111                 else if (blankLine) { top = top.closeIndent(); top = new Block.Indent(top, row, col); }
112                 blankLine = false;
113                 for(int i=0; i<s.length(); i++) {
114                     top.add(s.charAt(i));
115                     switch(s.charAt(i)) {
116                         case '{':  top = new Block.Brace(top, row, col);   break;
117                         case '}':  top = top.closeBrace();                 break;
118                     }
119                 }
120                 top.add(' ');
121                 top.finishWord();
122             }
123             // FIXME
124             Block ret = top;
125             while (ret.parent != null) ret = ret.parent;
126             return ret;
127         } catch (InternalException ie) {
128             throw new Invalid(ie, row, col);
129         }
130     }
131
132     public static class Block /*implements Token*/ {
133                       Block  parent;
134         public  final int    row;
135         public  final int    col;
136         public final int iip;
137         private final Vector children = new Vector();
138         private       String pending  = "";
139
140         //public Location getLocation() { return /*new Location.Cartesian(row, col)*/null; }
141         public int    size() { return children.size(); }
142         public Object child(int i) { return children.elementAt(i); }
143         public boolean isLiteral() {  return false; }
144
145         protected Block(int row, int col)                {
146             this.row=row;
147             this.col=col;
148             this.iip = -1;
149             parent = null;
150         }
151         public    Block(Block parent, int row, int col)  {
152             this.row=row;
153             this.col=col;
154             this.iip = parent.size();
155             (this.parent = parent).add(this);
156         }
157
158         public void    add(String s)        { children.addElement(s); }
159         public void    add(char c)          {
160             if (c == '{' || c == '}') { finishWord(); return; }
161             if (c != ' ') { pending += c; return; }
162             if (pending.length() > 0) { finishWord(); add(" "); return; }
163             if (size()==0) return;
164             if (child(size()-1).equals(" ")) return;
165             add(" ");
166             return;
167         }
168
169         public void    add(Block  b)        { children.addElement(b); }
170         public Block   promote()            { parent.parent.replaceLast(this); return close(); }
171         public Object  lastChild()          { return children.lastElement(); }
172         public Block   lastChildAsBlock()   { return (Block)lastChild(); }
173         public void    replaceLast(Block b) { children.setElementAt(b, children.size()-1); b.parent = this; }
174
175         public void    finishWord()         { if (pending.length() > 0) { add(pending); pending = ""; } }
176
177         public Block   closeBrace()         { throw new InternalException("attempt to closeBrace() a "+getClass().getName()); }
178         public Block   closeIndent()        { throw new InternalException("attempt to closeIndent() a "+getClass().getName()); }
179         public Block   close() {
180             while(size() > 0 && child(size()-1).equals(" ")) children.setSize(children.size()-1);
181             if (size()==0) throw new InternalException("PARSER BUG: attempt to close an empty block (should never happen)");
182             if (size() > 1 || !(lastChild() instanceof Block)) return parent;
183             return lastChildAsBlock().promote();
184         }
185         public String toString() { return toString(80); }
186         public String toString(int justificationLimit) { return toString(0, 80); }
187         protected String toString(int indent, int justificationLimit) {
188             StringBuffer ret = new StringBuffer();
189             StringBuffer line = new StringBuffer();
190             for(int i=0; i<children.size(); i++) {
191                 Object o = children.elementAt(i);
192                 if (i>0 && children.elementAt(i-1) instanceof Block && justificationLimit!=-1) ret.append("\n");
193                 if (o instanceof Block) {
194                     ret.append(justify(line.toString(), indent, justificationLimit));
195                     line.setLength(0);
196                     if (justificationLimit==-1) {
197                         ret.append("{");
198                         ret.append(((Block)o).toString(indent+2, justificationLimit));
199                         ret.append("}");
200                     } else {
201                         ret.append(((Block)o).toString(indent+2, justificationLimit));
202                     }
203                 } else {
204                     line.append(o.toString());
205                 }
206             }
207             ret.append(justify(line.toString(), indent, justificationLimit));
208             return ret.toString();
209         }
210
211         private static class Root extends Block {
212             public Root() { super(0, Integer.MIN_VALUE); }
213             public Block close() { throw new InternalException("attempted to close top block"); }
214             public String toString(int justificationLimit) { return toString(-2, justificationLimit); }
215         }
216
217         private static class Brace  extends Block {
218             public Brace(Block parent, int row, int col) { super(parent, row, col); }
219             public Block closeBrace() { return super.close(); }
220         }
221     
222         private static class Indent extends Block {
223             public Indent(Block parent, int row, int col) { super(parent, row, col); }
224             public Block closeIndent() { return super.close(); }
225         }
226
227         private static class Literal extends Block {
228             private StringBuffer content = new StringBuffer();
229             public final int braceCol;
230             public    Literal(Block parent, int row, int col, int braceCol) { super(parent,row,col); this.braceCol = braceCol; }
231             public    boolean isLiteral() {  return true; }
232             public    int size() { return 1; }
233             public    Object child(int i) { return i==0 ? content.toString() : null; }
234             public    Block  close() { return parent; }
235             public    Block  closeIndent() { return close(); }
236             public    void   add(String s) { if (content.length()>0) content.append('\n'); content.append(s); }
237             public    String text() { return content.toString(); }
238             protected String toString(int indent, int justificationLimit) {
239                 StringBuffer ret = new StringBuffer();
240                 String s = content.toString();
241                 while(s.length() > 0) {
242                     int nl = s.indexOf('\n');
243                     if (nl==-1) nl = s.length();
244                     ret.append(spaces(indent));
245                     ret.append("{}  ");
246                     ret.append(s.substring(0, nl));
247                     s = s.substring(Math.min(s.length(),nl+1));
248                     ret.append('\n');
249                 }
250                 return ret.toString();
251             }
252         }
253     }
254
255
256     // Exceptions //////////////////////////////////////////////////////////////////////////////
257
258     private static class InternalException extends RuntimeException { public InternalException(String s) { super(s); } }
259     public static class Invalid extends /*IOException*/RuntimeException {
260         public Invalid(InternalException ie, int row, int col) {
261             super(ie.getMessage() + " at " + row + ":" + col);
262         }
263     }
264
265     // Testing //////////////////////////////////////////////////////////////////////////////
266
267     public static void main(String[] s) throws Exception { System.out.println(parse(new BufferedReader(new InputStreamReader(System.in))).toString(-1)); }
268     
269     // Utilities //////////////////////////////////////////////////////////////////////////////
270
271     public static String spaces(int i) { if (i<=0) return ""; return " " + spaces(i-1); }
272
273     private static String justify(String s, int indent, int justificationLimit) {
274         if (s.length() == 0) return "";
275         if (justificationLimit==-1) return s;
276         StringBuffer ret = new StringBuffer();
277         while(s.length() > 0) {
278             if (s.charAt(0) == ' ') { s = s.substring(1); continue; }
279             ret.append(spaces(indent));
280             int i = s.indexOf(' ');
281             if (i==-1) i = s.length();
282             while(s.indexOf(' ', i+1) != -1 && s.indexOf(' ', i+1) < justificationLimit-indent) i = s.indexOf(' ', i+1);
283             if (s.length() + indent < justificationLimit) i = s.length();
284             ret.append(s.substring(0, i));
285             s = s.substring(i);
286             ret.append('\n');
287         }
288         return ret.toString();
289     }
290
291 }
292