1    	package acl2s.plugin.editors;
2    	
3    	import java.util.ArrayList;
4    	import java.util.Iterator;
5    	import java.util.List;
6    	
7    	import org.eclipse.jface.text.BadLocationException;
8    	import org.eclipse.jface.text.ITextViewer;
9    	import org.eclipse.jface.text.contentassist.CompletionProposal;
10   	import org.eclipse.jface.text.contentassist.ContextInformation;
11   	import org.eclipse.jface.text.contentassist.ContextInformationValidator;
12   	import org.eclipse.jface.text.contentassist.ICompletionProposal;
13   	import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
14   	import org.eclipse.jface.text.contentassist.IContextInformation;
15   	import org.eclipse.jface.text.contentassist.IContextInformationValidator;
16   	
17   	import acl2s.lib.contentassist.*;
18   	import acl2s.lib.contentassist.FileDictionary.CharacterTreeFindResult;
19   	import acl2s.lib.parse.Parser;
20   	import acl2s.lib.session.IResultInfo;
21   	import acl2s.lib.session.SessionException;
22   	import acl2s.plugin.Acl2sPlugin;
23   	import acl2s.plugin.editors.lisp.LispDocument;
24   	import acl2s.plugin.parse.LispToken;
25   	import acl2s.plugin.parse.LispTokens;
26   	import acl2s.uilib.session.InteractiveSessionManager;
27   	
28   	public class LispCompletionProcessor implements IContentAssistProcessor {
29   		private final char[] PROPOSAL_ACTIVATION_CHARS = new char[] { '(', ':' };
30   		private String lastExceptionMessage;
31   		private IContextInformationValidator validator;
32   		private final boolean useContext = false;
33   	
34   		public LispCompletionProcessor() {
35   			lastExceptionMessage = "";
36   			getContextInformationValidator();
37   		}
38   	
39   		private CharacterTreeFindResult getFunParams(String prefix,
40   				LispDocument doc, int offset, List<String> completions) {
41   			LispTokens toks = doc.getTokens();
42   			// head backwards to first defun or defmacro or name of an indexed macro
43   			LispToken t = null;
44   			int offs = offset, ind = 0;
45   			do {
46   				ind = toks.prevSexpToken(offs);
47   				t = toks.getToken(ind);
48   				offs = t.offset;
49   	
50   				try {
51   					String sym = (t.type == LispToken.SYM_TOK) ? doc.get(t.offset,
52   							t.length) : null;
53   	
54   					if (sym != null) {
55   						t = toks.getToken(ind + 1);
56   						String name = doc.get(t.offset, t.length);
57   						CharacterTreeFindResult r = WorkspaceDictionary.findExact(
58   								doc, name,
59   								FileDictionary.CharacterTreeNodeType.FUNTYPE);
60   						if (r != null) {
61   							for (String s : r.signature) {
62   								if (s != null && s.toLowerCase().startsWith(prefix)
63   										&& !completions.contains(s))
64   									completions.add(s);
65   							}
66   							return r; // function name
67   						}
68   					}
69   				} catch (BadLocationException ble) {
70   					return null;
71   				} catch (IndexOutOfBoundsException iob) {
72   					// fine, just continue
73   				}
74   			} while (t != null && offs > 0 && t.ctx != null);
75   			return null;
76   		}
77   	
78   		private void getLetBindings(String prefix, LispDocument doc, int offset,
79   				List<String> completions) {
80   			LispTokens toks = doc.getTokens();
81   			// head backwards to each let and then add the bindings that match the
82   			// prefix.
83   			// (let ((name symbol) (name2 (sexp))) ...)
84   			LispToken t = null, n = null, m = null;
85   			int offs = offset, ind = 0;
86   			boolean let = false;
87   			do {
88   				ind = toks.prevSexpToken(offs);
89   				t = toks.getToken(ind);
Event deref: Dereferencing "t".
Also see events: [already_used]
90   				offs = t.offset;
91   	
92   				try {
At conditional (1): (t.type == 115): Taking false branch.
93   					String sym = (t.type == LispToken.SYM_TOK) ? doc.get(t.offset,
94   							t.length) : null;
95   	
At conditional (2): (sym != null): Taking false branch.
96   					let = sym != null
97   							&& (sym.equalsIgnoreCase("let")
98   									|| sym.equalsIgnoreCase("let*") || sym
99   									.equalsIgnoreCase("letrec"));
At conditional (3): let: Taking false branch.
100  					if (let) {
101  						let = true;
102  						// get to first symbol
103  						while ((n = toks.getToken(ind + 1)).type == LispToken.OPAREN_TOK)
104  							++ind;
105  						// add first completion
106  						if (n != null && n.type == LispToken.SYM_TOK) {
107  							sym = doc.get(n.offset, n.length);
108  							if (sym.toLowerCase().startsWith(prefix.toLowerCase())
109  									&& !completions.contains(sym))
110  								completions.add(sym);
111  						}
112  						m = toks.getToken(ind);
113  						while (m.type != LispToken.CPAREN_TOK) {
114  							ind = toks.nextSexpToken(m.offset);
115  							m = toks.getToken(ind);
116  							if (m.type == LispToken.OPAREN_TOK
117  									&& (n = toks.getToken(ind + 1)).type == LispToken.SYM_TOK) {
118  								sym = doc.get(n.offset, n.length);
119  								if (sym.toLowerCase().startsWith(
120  										prefix.toLowerCase())
121  										&& !completions.contains(sym))
122  									completions.add(sym);
123  							}
124  						}
125  					}
126  				} catch (BadLocationException ble) {
127  					return;
128  				} catch (IndexOutOfBoundsException iob) {
129  					// fine, just continue
130  				}
Event already_used: "t" was already used as non-null.
Also see events: [deref]
At conditional (4): (t != null): Taking false branch.
131  			} while (t != null && offs > 0 && t.ctx != null);
132  		}
133  	
134  		private void addContextDocType(String prefix, LispDocument document,
135  				FileDictionary.CharacterTreeNodeType type,
136  				ArrayList<String> completions,
137  				ArrayList<IContextInformation> contexts) {
138  			String typeString;
139  			ArrayList<CharacterTreeFindResult> dictFind = WorkspaceDictionary.find(
140  					document, prefix, type);
141  			for (int i = 0; i < dictFind.size(); ++i) {
142  				CharacterTreeFindResult res = dictFind.get(i);
143  				if (!completions.contains(res.symbol)) {
144  					typeString = "";
145  					completions.add(res.symbol);
146  					if (useContext) {
147  						switch (res.type) {
148  						case DEFMACRO: {
149  							if (res.signature != null && res.signature.length != 0) {
150  								StringBuffer sb = new StringBuffer(res.signature[0]);
151  								for (int j = 1; j < res.signature.length; ++j)
152  									sb.append(" ").append(res.signature[j]);
153  								typeString = String.format(
154  										"(defmacro %s (%s) ...)", res.symbol, sb);
155  							} else {
156  								typeString = String.format("(defmacro %s () ...)",
157  										res.symbol);
158  							}
159  						}
160  							break;
161  						case DEFUN: {
162  							if (res.signature != null && res.signature.length != 0) {
163  								StringBuffer sb = new StringBuffer(res.signature[0]);
164  								for (int j = 1; j < res.signature.length; ++j)
165  									sb.append(" ").append(res.signature[j]);
166  								typeString = String.format("(defun %s (%s) ...)",
167  										res.symbol, sb);
168  							} else {
169  								typeString = String.format("(defun %s () ...)",
170  										res.symbol);
171  							}
172  						}
173  							break;
174  						case DEFCONST: {
175  							typeString = "defconst";
176  						}
177  							break;
178  						case DEFTHM: {
179  							typeString = "defthm";
180  						}
181  							break;
182  						}
183  						contexts.add(new ContextInformation(Acl2sPlugin
184  								.getImage("icons/f_blue.gif"), res.symbol,
185  								typeString));
186  					}
187  				}
188  			}
189  		}
190  	
191  		public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer,
192  				int offset) {
193  			ArrayList<String> completions = new ArrayList<String>();
194  			ArrayList<IContextInformation> contexts = new ArrayList<IContextInformation>();
195  			LispToken tok = computeCompletionsAndInfo(viewer, offset, completions,
196  					contexts);
197  	
198  			if (tok == null)
199  				return null;
200  	
201  			ICompletionProposal[] results = new CompletionProposal[completions
202  					.size()];
203  			Iterator<String> itr = completions.iterator();
204  			Iterator<IContextInformation> citr = contexts.iterator();
205  			int i = 0;
206  			while (itr.hasNext()) {
207  				String replace = itr.next();
208  				results[i] = new CompletionProposal(replace, tok.offset,
209  						tok.length, replace.length());
210  				if (useContext) {
211  					IContextInformation info = citr.next();
212  					getContextInformationValidator().install(info, viewer,
213  							tok.offset);
214  				}
215  				++i;
216  			}
217  			return results;
218  		}
219  	
220  		public IContextInformation[] computeContextInformation(ITextViewer viewer,
221  				int offset) {
222  			ArrayList<String> completions = new ArrayList<String>();
223  			ArrayList<IContextInformation> contexts = new ArrayList<IContextInformation>();
224  			computeCompletionsAndInfo(viewer, offset, completions, contexts);
225  			return contexts.toArray(new ContextInformation[contexts.size()]);
226  		}
227  	
228  		private LispToken computeCompletionsAndInfo(ITextViewer viewer, int offset,
229  				ArrayList<String> completions,
230  				ArrayList<IContextInformation> contexts) {
231  			try {
232  				LispDocument document = (LispDocument) viewer.getDocument();
233  				LispTokens toks = document.getTokens();
234  				int tokInd = toks.getTokenIdxContaining(offset - 1);
235  				LispToken tok = toks.getToken(tokInd);
236  				if (tok.absoluteEnd() < offset - 1 || tok.type != LispToken.SYM_TOK)
237  					return null;
238  	
239  				// need to know if this is a function, command or variable.
240  				// '(', ':', ' ' respective previous characters. '\0' if no prev
241  				// char (no completions)
242  				char tokPrevChar = (tok.offset != 0) ? document
243  						.getChar(tok.offset - 1) : ' ';
244  				if (tokInd > 0) {
245  					LispToken prevTok = toks.getToken(tokInd - 1);
246  					if (prevTok.type == LispToken.OPAREN_TOK)
247  						tokPrevChar = '(';
248  					else if (tokPrevChar != ':')
249  						tokPrevChar = ' ';
250  				}
251  	
252  				String prefix = document.get(tok.offset, tok.length);
253  				String prefixfixed = (prefix.startsWith("|")) ? prefix : prefix
254  						.toUpperCase();
255  				int fromInd = 0;
256  	
257  				switch (tokPrevChar) {
258  				case '(': {
259  					// TODO First add functions in local context
260  	
261  					// Next add functions in file (don't care if it's defined after
262  					// this line. Not our problem)
263  					addContextDocType(prefix, document,
264  							FileDictionary.CharacterTreeNodeType.FUNTYPE,
265  							completions, contexts);
266  					// Next add functions in base ACL2 definition
267  					addContextDocType(prefix, null,
268  							FileDictionary.CharacterTreeNodeType.FUNTYPE,
269  							completions, contexts);
270  					fromInd = completions.size();
271  	
272  					// add functions that exist in the open session
273  					try {
274  						InteractiveSessionManager manager = document.getSession()
275  								.getManager();
276  						if (manager.isAlive()) {
277  							IResultInfo r = manager
278  									.systemCmd2("(acl2s::events-with-prefix \""
279  											+ prefixfixed
280  											+ "\" (acl2::w acl2::state) 'DEFUN)");
281  							Parser.parseListOfSymbols(r.getOutput(), completions, 0);
282  							if (useContext) {
283  							for (int i = fromInd; i < completions.size(); ++i) {
284  								contexts.add(new ContextInformation(completions
285  										.get(i), "ACL2 Session defun"));
286  							}
287  							fromInd = completions.size();
288  							}
289  	
290  							r = manager.systemCmd2("(acl2s::events-with-prefix \""
291  									+ prefixfixed
292  									+ "\" (acl2::w acl2::state) 'DEFMACRO)");
293  							Parser.parseListOfSymbols(r.getOutput(), completions, 0);
294  							if (useContext) {
295  							for (int i = fromInd; i < completions.size(); ++i) {
296  								contexts.add(new ContextInformation(completions
297  										.get(i), "ACL2 Session defmacro"));
298  							}
299  							}
300  						}
301  					} catch (SessionException sde) {
302  						// fine
303  					}
304  				}
305  					break;
306  				case ':': // possible commands (context-specific)
307  					// TODO Complete
308  					break;
309  				case ' ': // local let bindings and parameters (context-specific) or
310  							// defconsts
311  				{
312  					// First add the function (or macro) parameters
313  					CharacterTreeFindResult funname = getFunParams(prefix,
314  							document, offset, completions);
315  					if (useContext) {
316  						StringBuilder sb = new StringBuilder(
317  								String
318  										.format(
319  												"(%s ",
320  												(funname.type == FileDictionary.CharacterTreeNodeType.DEFUN ? "defun"
321  														: "defmacro")));
322  						for (int i = 0; i < completions.size(); ++i) {
323  							if (i != 0) {
324  								sb.append(' ');
325  							}
326  							sb.append(completions.get(i));
327  						}
328  						sb.append(')');
329  						
330  						for (int i = fromInd; i < completions.size(); ++i) {
331  							contexts.add(new ContextInformation(completions.get(i),
332  									String.format("Parameter of %s", sb)));
333  						}
334  						fromInd = completions.size();
335  					}
336  	
337  					// Next add the let bindings (deep to shallow)
338  					getLetBindings(prefix, document, offset, completions);
339  					if (useContext) {
340  						for (int i = fromInd; i < completions.size(); ++i) {
341  							contexts.add(new ContextInformation(completions.get(i),
342  									"Let binding"));
343  						}
344  					}
345  					fromInd = completions.size();
346  	
347  					// Next add the defconsts
348  					addContextDocType(prefix, document,
349  							FileDictionary.CharacterTreeNodeType.DEFCONST,
350  							completions, contexts);
351  	
352  					// Next add the defconsts in the base ACL2 session
353  					addContextDocType(prefix, null,
354  							FileDictionary.CharacterTreeNodeType.DEFCONST,
355  							completions, contexts);
356  					fromInd = contexts.size();
357  	
358  					// finally add defconsts that exist in the open session
359  					try {
360  						InteractiveSessionManager manager = document.getSession()
361  								.getManager();
362  						if (manager.isAlive()) {
363  							IResultInfo r = manager
364  									.systemCmd2("(acl2s::constants-with-prefix \""
365  											+ prefix.toUpperCase()
366  											+ "\" (acl2::w acl2::state))");
367  							Parser.parseListOfSymbols(r.getOutput(), completions, 0);
368  							if (useContext) {
369  								for (int i = fromInd; i < completions.size(); ++i) {
370  									contexts.add(new ContextInformation(completions
371  											.get(i), "ACL2 Session defconst"));
372  								}
373  							}
374  						}
375  					} catch (SessionException sbe) {
376  						// fine
377  					}
378  				}
379  					break;
380  				default:
381  					break;
382  				}
383  				return tok;
384  			} catch (Exception e) {
385  				contexts.clear();
386  				completions.clear();
387  				lastExceptionMessage = e.getMessage();
388  			}
389  			return null;
390  		}
391  	
392  		public char[] getCompletionProposalAutoActivationCharacters() {
393  			return PROPOSAL_ACTIVATION_CHARS;
394  		}
395  	
396  		public char[] getContextInformationAutoActivationCharacters() {
397  			return PROPOSAL_ACTIVATION_CHARS;
398  		}
399  	
400  		public IContextInformationValidator getContextInformationValidator() {
401  			if (useContext) {
402  				if (validator == null) {
403  					validator = new ContextInformationValidator(this);
404  				}
405  				return validator;
406  			}
407  			return null;
408  		}
409  	
410  		public String getErrorMessage() {
411  			return lastExceptionMessage;
412  		}
413  	
414  	}