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 }