1    	package acl2s.plugin.indent;
2    	
3    	import java.util.Arrays;
4    	import java.util.HashSet;
5    	import java.util.Set;
6    	
7    	import org.eclipse.jface.text.BadLocationException;
8    	import org.eclipse.jface.text.IDocument;
9    	
10   	import acl2s.plugin.parse.LispToken;
11   	import acl2s.plugin.parse.LispTokens;
12   	
13   	public class DefaultLispIndent implements ILispIndent {
14   		protected final LispTokens tokens;
15   		protected final IDocument doc;
16   		
17   		public DefaultLispIndent(LispTokens tokens, IDocument doc) {
18   			this.tokens = tokens;
19   			this.doc = doc;
20   		}
21   	
22   		int getExistingBasicIndention(int line) throws BadLocationException {
23   			int start = doc.getLineOffset(line);
24   			int end = start + doc.getLineLength(line);
25   	
26   			int indent = 0;
27   			for (int i = start; i < end; i++) {
28   				char ch = doc.getChar(i);
29   				if (ch == ' ') {
30   					indent++;
31   				} else if (ch == '\t') {
32   					indent += 4;  // fallback;  shouldn't be tabs
33   				} else if (ch == '\n' || ch == '\r') {
34   					return -1;  // all space
35   				} else {
36   					return indent;
37   				}
38   			}
39   			return -1;  // all space & eof
40   		}
41   		
42   		public int getExistingLineIndention(int line) throws BadLocationException {
43   			if (line < 0) return 0;
44   			int start = doc.getLineOffset(line);
45   			LispToken t = tokens.getToken(tokens.getTokenIdxContaining(start));
46   			if (line > 0) {
47   				if (t.isLiteralMode(start-1,doc)) {
48   					return getExistingLineIndention(line-1);
49   				}
50   			}
51   			int end = start + doc.getLineLength(line);
52   			
53   			// hack to eliminate prompt
54   			if (t.type == LispToken.NONE_TOK && t.absoluteEnd() <= end) {
55   				start = t.absoluteEnd();
56   			}
57   			
58   			int indent = getExistingBasicIndention(line);
59   			if (indent >= 0) {
60   				return indent;
61   			} else {
62   				// nothing on line => do previous line
63   				return getExistingLineIndention(line - 1);
64   			}
65   		}
66   		
67   		// called from autoindenter, etc.
68   		public int getNextIndentionFrom(int offset) {
69   			return getNextIndentionFrom(offset, false);
70   		}
71   		
72   		protected int getNextIndentionFrom(int offset, boolean fromParam) {
73   			try {
74   				int tIdx;
75   				if (offset < doc.getLength()) {
76   					tIdx = tokens.getTokenIdxContaining(offset);
77   				} else {
78   					tIdx = tokens.getTokenCount() - 1;
79   				}
80   				LispToken t = tokens.getToken(tIdx);
81   				if (t.offset >= offset) { // if we're only preceded by space in this token
82   					if (t.inHashComment(offset - t.offset)) return -2; // no change, autoindent is fine
83   					tIdx--;
84   					if (tIdx < 0) return 0;
85   					t = tokens.getToken(tIdx);
86   				} else if (t.isLiteralMode(offset, doc)) { // newline interpreted *not* as whitespace
87   					if ((t.type == LispToken.STRING_TOK && doc.getChar(offset-1) == '~')
88   							|| t.type == LispToken.DOC_TOK) {
89   						// indent inside string if doc string or ~\n (as in fmt string)
90   						int line = doc.getLineOfOffset(offset);
91   						int lstart = doc.getLineOffset(line);
92   						while (t.offset < lstart) {
93   							// search for non-empty line to indent from
94   							// while start of string is before this line
95   							int indent = getExistingBasicIndention(line);
96   							if (indent >= 0) {
97   								return indent;
98   							}
99   							line--;
100  							lstart = doc.getLineOffset(line);
101  						}
102  						// else we've reached start of string, which is on this line
103  						if (t.type == LispToken.DOC_TOK) {
104  							return t.offset - lstart;  // to conform to ACL2 source conventions
105  						} else {
106  							return t.offset - lstart + 1;
107  						}
108  					} else {
109  						return -1; // no change, no autoindent
110  					}
111  				}
112  				
113  				int line = doc.getLineOfOffset(offset);
114  				if (!fromParam && t.absoluteEnd() < offset) {
115  					line = doc.getLineOfOffset(t.absoluteEnd());
116  				}
117  				int maxTIdx = tIdx;
118  				int lstart = doc.getLineOffset(line);
119  				int lstartTIdx = tokens.getTokenIdxContaining(lstart);
120  	
121  				while (lstart > 0 && line > 0 && 
122  						tokens.getToken(lstartTIdx).isLiteralMode(lstart-1, doc)) {
123  					line--;
124  					lstart = doc.getLineOffset(line);
125  					lstartTIdx = tokens.getTokenIdxContaining(lstart);
126  				}
127  				
128  				// hack to eliminate prompt
129  				LispToken st = tokens.getToken(lstartTIdx);
130  				if (st.type == LispToken.NONE_TOK) {
131  					lstart = st.absoluteEnd();
132  				}
133  				
134  				int balance = 0;
135  				for (tIdx = maxTIdx; tIdx >= lstartTIdx && balance < 1; tIdx--) {
136  					t = tokens.getToken(tIdx);
137  					balance += t.parenDelta();
138  					if (balance > 0) {
139  						break;
140  					}
141  				}
142  				
143  				if (balance > 0) {
144  					int indent = t.absoluteEnd() - lstart;
145  					if (tIdx + 1 <= maxTIdx) {
146  						LispToken fn = tokens.getToken(tIdx + 1);
147  						if (fn.type == LispToken.SYM_TOK) {
148  							String fName = doc.get(fn.offset,fn.length);
Event do_not_call: "java.lang.String.toLowerCase()" implicitly uses the environment's default character set, which might lead to unexpected behavior. Consider using toLowerCase(Locale locale).
149  							fName = fName.toLowerCase();
150  							if (fName.startsWith("def") || indentNameSet.contains(fName)) {
151  								return indent + 1;
152  							}
153  						}
154  						if ((fn.type == LispToken.SYM_TOK && !(doc.getChar(fn.offset) == ':')) // sym but ot keyword
155  								|| fn.type == LispToken.STRING_TOK) {
156  							if (tIdx + 2 <= maxTIdx || fromParam) { // also a function and >= 1 parameter following
157  								LispToken param1 = tokens.getToken(tIdx + 2);
158  								indent = param1.offset - lstart;
159  							}
160  						}
161  					}
162  					return indent;
163  				}
164  				
165  				if (balance == 0) return getExistingLineIndention(line);
166  				
167  				// balance < 0
168  				for (tIdx = maxTIdx; tIdx >= lstartTIdx; tIdx--) {
169  					t = tokens.getToken(tIdx);
170  					if (t.isCParen()) {
171  						break;
172  					}
173  				}
174  				tIdx = tokens.matchParenTok(tIdx);
175  				if (tIdx < 0) return 0;
176  				t = tokens.getToken(tIdx);
177  				line = doc.getLineOfOffset(Math.min(offset,t.absoluteEnd()));
178  				lstart = doc.getLineOffset(line);
179  				return getNextIndentionFrom(t.offset,true);  // RECURSION!!
180  			} catch (BadLocationException e) {
181  				// impossible
182  				e.printStackTrace();
183  				return 0;
184  			}
185  		}
186  	
187  		public void indentLine(int line) {
188  			try {
189  				int docLen = doc.getLength();
190  				int start = doc.getLineOffset(line);
191  				int space = 0;
192  				int semis = 0;
193  				boolean allSpace = true;
194  				for (;;) {
195  					if (start+space >= docLen) break;
196  					char ch = doc.getChar(start+space);
197  					if (ch != ' ' && ch != '\t') {
198  						if (ch != '\r' && ch != '\n') allSpace = false;
199  						for (;;) {
200  							int i = start+space+semis;
201  							if (i >= docLen || doc.getChar(i) != ';') break;
202  							semis++;
203  						}
204  						break;
205  					}
206  					space++;
207  				}
208  	
209  				LispToken t;
210  				if (start > 0) {
211  					t = tokens.getToken(tokens.getTokenIdxContaining(start - 1));
212  				} else if (docLen > 0) {
213  					t = tokens.getToken(tokens.getTokenIdxContaining(start));
214  				} else {
215  					t = null;
216  				}
217  				
218  				boolean toBeginning = false;
219  				if (t != null &&
220  						t.offset >= start + doc.getLineLength(line) && // if the line is all whitespace
221  						!allSpace) { // and there's text in the line (comments)
222  					if (semis == 0 || semis >= 3) {
223  						return;  // don't change indent
224  					} else {				
225  						toBeginning = semis == 1;
226  					}
227  				}
228  				
229  				if (line == 0 || toBeginning) {
230  					if (space == 0) return; // no change
231  					doc.replace(start, space, "");
232  				} else {
233  					int indent = 0;
234  					indent = getNextIndentionFrom(start-1);
235  					if (indent < 0) return;  // indent shouldn't be changed (e.g. literal mode)
236  					if (indent == space) return; // no change
237  					String text = LispAutoIndent.getSpaces(indent);
238  					doc.replace(start, space, text);
239  				}
240  			} catch (BadLocationException e) {
241  				e.printStackTrace();
242  			}
243  		}
244  		
245  		String[] indentNames = new String[] { // def* automatically indented
246  				"let", "let*", "if" /*, "cond"  NO! */ /*"mv-let", NO! */ 
247  				, "er-let*", "assign", "theorem", "encapsulate"
248  		};
249  		Set<String> indentNameSet = new HashSet<String>(Arrays.asList(indentNames));
250  		
251  		
252  	}