1 package acl2s.lib.session;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.InputStreamReader;
6 import java.io.OutputStreamWriter;
7 import java.io.Serializable;
8 import java.util.ArrayList;
9 import java.util.HashMap;
10 import java.util.Map;
11 import java.util.Set;
12 import java.util.concurrent.locks.Lock;
13 import java.util.concurrent.locks.ReentrantLock;
14
15 import org.peterd.util.Misc;
16 import org.peterd.util.io.AsyncWriter;
17 import org.peterd.util.io.LineReader;
18 import org.peterd.util.process.InterruptibleProcess;
19
20 import acl2s.lib.parse.IPackageDefinition;
21 import acl2s.lib.parse.IParseContext;
22 import acl2s.lib.parse.PackageDefinition;
23 import acl2s.lib.parse.ParseContext;
24 import acl2s.lib.parse.ParseException;
25 import acl2s.lib.parse.obj.Cons;
26 import acl2s.lib.parse.obj.Int;
27 import acl2s.lib.parse.obj.LispObject;
28 import acl2s.lib.parse.obj.NotListException;
29 import acl2s.lib.parse.obj.Num;
30 import acl2s.lib.parse.obj.Rat;
31 import acl2s.lib.parse.obj.Str;
32 import acl2s.lib.parse.obj.Sym;
33 import acl2s.lib.parse.obj.Zero;
34 import acl2s.lib_driver.IAcl2sLibDriver;
35 import acl2s.lib_driver.IAcl2sLibErrorHandler;
36 import acl2s.lib_driver.IAcl2sLibThreadManager;
37
38 public class InteractiveSession {
39 protected InterruptibleProcess proc;
40 protected boolean busy;
41 protected Lock outReadLock;
42 protected AsyncWriter acl2Input;
43 protected LineReader acl2Output;
44 protected StatusData status;
45 protected EnvData env;
46 protected EnvData oldEnv;
47 protected final IAcl2sLibErrorHandler errorHandler;
48 protected final IAcl2sLibThreadManager threadMgr;
49 protected final ISessionConfig config;
50 protected final File workingDir;
51 protected final Killer killer;
52 protected long secret;
53 protected boolean interruptPending;
54 protected String[] cmdLine;
55 protected HashMap<String,IPackageDefinition> pkgDefs = new HashMap<String, IPackageDefinition>();
56
57 public InteractiveSession(IAcl2sLibDriver driver, ISessionConfig config, File workingDir) {
58 proc = null;
59 this.config = config;
60 this.workingDir = workingDir;
61 errorHandler = driver.getErrorHandler();
62 threadMgr = driver.getThreadManager();
63 killer = new Killer();
64 pkgDefs.putAll(BootstrapParseContext.pkgs);
65 }
66
67 public synchronized void beginInit() throws IOException, IllegalStateException {
68 if (proc != null) throw new IllegalStateException();
69 cmdLine = config.getBaseConfig().getCmdLine();
70 proc = InterruptibleProcess.exec(cmdLine, workingDir, config.getBaseConfig(), true);
71 acl2Input = new AsyncWriter(new OutputStreamWriter(proc.getStdin())).fork();
|
Event do_not_call: |
"java.io.InputStreamReader(java.io.InputStream arg1)" implicitly uses the environment's default character set, which might lead to unexpected behavior. Consider using InputStreamReader(InputStream in, String charsetName). |
72 acl2Output = new LineReader(new InputStreamReader(proc.getStdout()));
73 busy = true;
74 interruptPending = false;
75 outReadLock = new ReentrantLock();
76 status = new StatusData();
77 status.success = true; // so that startup appears to succeed
78 oldEnv = new EnvData();
79 env = null;
80 secret = Misc.random.nextLong() & 0xfffffffffffffffL;
81
82 // Initial input stuff
83 String[] startup = config.getStartup(secret);
84 if (startup != null) {
85 for (int i = 0; i < startup.length; i++) {
86 acl2Input.write(startup[i]);
87 }
88 }
89 }
90
91 public synchronized IResultInfo fullInit() throws IllegalStateException, IOException, SessionDeadException {
92 beginInit();
93 try {
94 return finishCmd();
95 } catch (SessionNotBusyException e) {
96 // shouldn't happen
97 throw new IllegalStateException(e);
98 }
99 }
100
101 public void dispose() {
102 kill();
103 }
104
105 public void close() throws SessionStateException {
106 close(1000);
107 }
108
109 public synchronized void close(int millis) throws SessionStateException {
110 if (proc == null) return;
111 if (busy) throw new SessionBusyException();
112 if (env == null) throw new SessionStateException();
113 try {
114 acl2Input.write("#.\n(acl2s::clear-acl2s-password " + secret + " acl2::state)\n(acl2::value :q)\n(acl2::good-bye)\n");
115 acl2Input.close();
116 busy = true;
117 } catch (IOException ioe) {
118 errorHandler.warning("Error while closing ACL2 session.", ioe);
119 } finally {
120 try {
121 proc.waitFor(millis);
122 } catch (Exception e) {
123 proc.kill();
124 }
125 proc = null;
126 }
127 }
128
129 public synchronized boolean interrupted() {
130 return interruptPending || status.interrupted;
131 }
132
133 public synchronized void interrupt() throws SessionDeadException, SessionNotBusyException {
134 if (proc == null) throw new SessionDeadException();
135 if (!busy && env != null) throw new SessionNotBusyException();
136 if (interrupted()) return;
137 try {
138 proc.interrupt();
139 busy = true;
140 interruptPending = true;
141 } catch (IOException ioe) {
142 errorHandler.error("Error while interrupting ACL2 session.", ioe);
143 proc.kill();
144 proc = null;
145 return;
146 }
147 }
148
149 /*
150 public void abort() throws SessionDeadException, SessionStateException {
151 miscInput("#.");
152 }
153
154 public synchronized void interruptOrAbort() throws SessionDeadException {
155 try {
156 if (busy || env == null) {
157 interrupt();
158 } else if (env == null || env.inWormhole()) {
159 abort();
160 }
161 } catch (SessionStateException sse) {
162 // shouldn't happen
163 }
164 }
165 //*/
166
167 public synchronized boolean isAlive() {
168 return proc != null;
169 }
170
171 // call this if you already have the mutex or don't mind waiting
172 public synchronized void kill() {
173 try {
174 close();
175 } catch (SessionStateException e) {
176 if (proc != null) {
177 proc.kill();
178 proc = null;
179 }
180 }
181 }
182
183 class Killer implements Runnable {
184 public void run() {
185 kill();
186 }
187 }
188
189 // call this if you don't want to wait for this mutex
190 public void forkKill() {
191 threadMgr.delayedExec(killer, 0, true);
192 }
193
194 public Runnable getKiller() { return killer; }
195
196
197 static final Sym WITH_LIMIT = Sym.bootstrapCreate("WITH-PROVER-TIME-LIMIT");
198 static final int LIMIT_DENOM = 1000;
199 static final float LIMIT_MAX = Integer.MAX_VALUE / LIMIT_DENOM;
200
201 public synchronized IResultInfo systemCmd(LispObject cmd, float proverTimeLimit)
202 throws SessionDeadException, SessionStateException {
203 if (proverTimeLimit <= 0f || proverTimeLimit >= LIMIT_MAX) throw new IllegalArgumentException();
204
205 Num limit = Rat.create((int)(proverTimeLimit * LIMIT_DENOM) + 1, LIMIT_DENOM);
206 return systemCmd(new Cons(WITH_LIMIT,
207 new Cons(limit,
208 new Cons(cmd, Sym.nilInstance))));
209 }
210
211 public synchronized IResultInfo systemCmd(LispObject cmd)
212 throws SessionDeadException, SessionStateException {
213 return systemCmd(cmd.toString(env.getParseContext()));
214 }
215
216 public synchronized IResultInfo systemCmd(String cmd, float proverTimeLimit)
217 throws SessionDeadException, SessionStateException {
218 if (proverTimeLimit <= 0f || proverTimeLimit >= LIMIT_MAX) throw new IllegalArgumentException();
219
220 String num = Integer.toString((int)(proverTimeLimit * LIMIT_DENOM) + 1, env.getReadBase());
221 String denom = Integer.toString(LIMIT_DENOM, env.getReadBase());
222 return systemCmd("(ACL2::WITH-PROVER-TIME-LIMIT " + num + "/" + denom + " " + cmd + ")");
223 }
224
225 public synchronized IResultInfo systemCmd(String cmd)
226 throws SessionDeadException, SessionStateException {
227 userCmd(cmd);
228 return finishCmd();
229 }
230
231 public synchronized void userCmd(String s)
232 throws SessionDeadException, SessionStateException {
233 if (s == null) {
234 status = new StatusData();
235 return;
236 }
237 if (proc == null) throw new SessionDeadException();
238 if (busy) throw new SessionBusyException();
239 if (env == null) throw new SessionStateException("Not accepting commands at the moment.");
240 status = new StatusData();
241 oldEnv = env;
242 env = null;
243 try {
244 acl2Input.write(s);
245 acl2Input.write('\n');
246 //acl2Input.flush();
247 busy = true;
248 } catch (IOException ioe) {
249 errorHandler.error("Error while writing command input to ACL2.", ioe);
250 kill();
251 }
252 }
253
254 public synchronized void miscInput(LispObject obj) throws SessionDeadException, SessionStateException {
255 miscInput(obj.toString());
256 }
257
258 public synchronized void miscInput(String s) throws SessionDeadException, SessionStateException {
259 if (proc == null) throw new SessionDeadException();
260 if (busy) throw new SessionBusyException();
261 if (env != null) throw new SessionStateException("Not expecting non-command input.");
262 try {
263 acl2Input.write(s);
264 acl2Input.write('\n');
265 //acl2Input.flush();
266 busy = true;
267 } catch (IOException ioe) {
268 errorHandler.error("Error while writing misc input to ACL2.", ioe);
269 kill();
270 }
271 }
272
273 public synchronized IResultInfo finishCmd() throws SessionDeadException, SessionNotBusyException {
274 if (!busy) {
275 if (isAlive()) {
276 throw new SessionNotBusyException();
277 } else {
278 throw new SessionDeadException();
279 }
280 }
281 StringBuilder ret = new StringBuilder();
282 String s;
283 for (;;) {
284 s = readLineUntilPrompt();
285 if (s == null) return new Result(ret.toString(),status);
286 ret.append(s);
287 }
288 }
289
290 private transient final ArrayList<String> lines = new ArrayList<String>();
291 public String[] readLinesUntilPrompt(int max) {
292 String first = readLineUntilPrompt();
293 if (first == null) return null;
294
295 lines.clear();
296 lines.add(first);
297 for (;;) {
298 if (max > 0 && lines.size() >= max) break;
299 if (!acl2Output.ready()) {
300 try {
301 Thread.sleep(100);
302 } catch (InterruptedException e) {
303 // ignore
304 }
305 if (!acl2Output.ready()) {
306 break;
307 }
308 }
309 String line = readLineUntilPrompt();
310 if (line == null) {
311 break;
312 } else {
313 lines.add(line);
314 }
315 }
316 return lines.toArray(new String[lines.size()]);
317 }
318
319 static final String SUCCESS = "${SuCcEsS}$\n";
320 static final int SUCCESS_LEN = SUCCESS.length();
321
322 static final String BEGIN_PKG = "$DeFpKg{$\n";
323 static final int BEGIN_PKG_LEN = BEGIN_PKG.length();
324 static final String END_PKG = "$DeFpKg}$\n";
325 static final int END_PKG_LEN = END_PKG.length();
326
327 static final String BEGIN_ENV = "$EnViRoNmEnT{$\n";
328 static final int BEGIN_ENV_LEN = BEGIN_ENV.length();
329 static final String MIDDLE_ENV = "$EnViRoNmEnT$";
330 static final int MIDDLE_ENV_LEN = MIDDLE_ENV.length();
331 static final String END_ENV = "$EnViRoNmEnT}$\n";
332 static final int END_ENV_LEN = END_ENV.length();
333
334 static final String READ_OBJECT = "${ReAd-ObJeCt}$\n";
335 static final int READ_OBJECT_LEN = READ_OBJECT.length();
336
337 private transient final StringBuilder buf = new StringBuilder(100);
338 public String readLineUntilPrompt() {
339 if (!outReadLock.tryLock()) throw new IllegalStateException("Multiple threads trying to read.");
340 try {
341 String s;
342 int start;
343 synchronized (this) {
344 if (proc == null) throw new SessionDeadException();
345 if (!busy) return null;
346 }
347 boolean lineRead;
348 buf.setLength(0);
349 lineRead = acl2Output.readLineTo(buf);
350 if (!lineRead) throw new SessionDeadException();
351 buf.append('\n');
352 s = buf.toString();
353 synchronized (this) {
354 if (interruptPending && MyBaseConfig.isConsoleInterrupt(s)) {
355 interruptPending = false;
356 status.interrupted = true;
357 }
358 }
359
360 if (s.endsWith("$\n")) { // optimization: check for common suffix
361
362 if (s.endsWith(SUCCESS)) {
363 synchronized (this) {
364 status.success = true;
365 }
366 s = s.substring(0,s.length() - SUCCESS_LEN);
367 }
368
369 if (s.endsWith(BEGIN_ENV)) {
370 start = s.length() - BEGIN_ENV_LEN;
371 // slow but rare:
372 while (!s.endsWith(END_ENV)) {
373 lineRead = acl2Output.readLineTo(buf);
374 if (!lineRead) throw new SessionDeadException();
375 buf.append('\n');
376 s = buf.toString();
377 }
378 int end = s.indexOf(MIDDLE_ENV);
379 if (end < start + BEGIN_ENV_LEN) {
380 errorHandler.error("Bad environment output from ACL2. Aborting session.", null);
381 throw new SessionDeadException();
382 }
383 String sub = s.substring(start + BEGIN_ENV_LEN,end);
384 synchronized (this) {
385 env = new EnvData();
386 try {
387 LispObject o = LispObject.canonicalParseFirst(sub);
388
389 processPrompt(o);
390 } catch (ParseException pe) {
391 errorHandler.error("Erroroneous environment output from ACL2. Aborting session.", pe);
392 throw new SessionDeadException();
393 }
394 String pr = s.substring(end + MIDDLE_ENV_LEN, s.length() - END_ENV_LEN);
395 env.setPrompt(pr);
396 }
397
398 s = s.substring(0,start);
399 }
400
401 if (s.endsWith(BEGIN_PKG)) {
402 start = s.length() - BEGIN_PKG_LEN;
403 // slow but rare enough:
404 while (!s.endsWith(END_PKG)) {
405 lineRead = acl2Output.readLineTo(buf);
406 if (!lineRead) throw new SessionDeadException();
407 buf.append('\n');
408 s = buf.toString();
409 }
410
411 String defStr = s.substring(start + BEGIN_PKG_LEN, s.length() - END_PKG_LEN);
412
413 try {
414 LispObject defObj = LispObject.canonicalParseFirst(defStr);
415 Cons defCons = (Cons) defObj;
416 String pkgName = ((Str)defCons.car).value;
417 LispObject[] importObjs = Cons.listToArray(defCons.cdr);
418 Sym[] imports = new Sym[importObjs.length];
419 System.arraycopy(importObjs, 0, imports, 0, importObjs.length);
420 IPackageDefinition def = new PackageDefinition(pkgName, imports);
421 synchronized (this) {
422 pkgDefs.put(pkgName, def);
423 }
424 } catch (ClassCastException e) {
425 errorHandler.error("Erroroneous package definition output from ACL2. Aborting session.", e);
426 throw new SessionDeadException();
427 } catch (ArrayStoreException e) {
428 errorHandler.error("Erroroneous package definition output from ACL2. Aborting session.", e);
429 throw new SessionDeadException();
430 } catch (ParseException e) {
431 errorHandler.error("Erroroneous package definition output from ACL2. Aborting session.", e);
432 throw new SessionDeadException();
433 } catch (NotListException e) {
434 errorHandler.error("Erroroneous package definition output from ACL2. Aborting session.", e);
435 throw new SessionDeadException();
436 }
437
438 s = s.substring(0,start);
439 }
440
441 if (s.endsWith(READ_OBJECT)) {
442 s = s.substring(0,s.length() - READ_OBJECT_LEN);
443 synchronized (this) {
444 if (!interruptPending) {
445 busy = false;
446 notifyAll();
447 } else {
448 s = s + "\n(interruption arrived after completion of above)";
449
450 // if win32 SBCL with my ctrl+c hack, poke it to get interrupt to happen
451 //acl2Input.write("#+sbcl-ctrlc-hack (acl2::mv (acl2::time-limit4-reached-p \"foo\") :invisible acl2::state)\n");
452 }
453 }
454 }
455 }
456
457 return s;
458 } catch (SessionDeadException sde) {
459 synchronized (this) {
460 status.interrupted = true;
461 status.success = false;
462 if (proc != null) {
463 proc.kill();
464 proc = null;
465 }
466 }
467 return null;
468 } catch (IOException ioe) {
469 synchronized (this) {
470 if (proc == null) return null;
471 status.interrupted = true;
472 status.success = false;
473 kill();
474 }
475 errorHandler.error("Error while reading ACL2 output.", ioe);
476 return null;
477 } finally {
478 outReadLock.unlock();
479 }
480 }
481
482 protected synchronized void processPrompt(LispObject obj) throws ParseException {
483 env.fromObjAndPkgDefs(obj, pkgDefs);
484 status.superDone = env.superDone;
485 status.extendedWorld = env.superDone > oldEnv.superDone;
486 if (env.ldLevel > oldEnv.ldLevel) { // another way success can happen
487 status.success = true;
488 }
489 }
490
491 public long getSecret() { return secret; }
492 public IEnvInfo getEnvInfo() { return env; }
493 public IStatusInfo getStatusInfo() { return status; }
494
495 static class StatusData implements IStatusInfo, Serializable {
496 private static final long serialVersionUID = 3L;
497
498 boolean extendedWorld = false;
499 boolean interrupted = false;
500 boolean success = false;
501 int superDone;
502
503 public boolean wasInterrupted() {
504 return interrupted;
505 }
506
507 public boolean wasSuccess() {
508 return success;
509 }
510
511 public boolean extendedLogicalState() {
512 return extendedWorld;
513 }
514
515 public int getSuperDoneSize() {
516 return superDone;
517 }
518 }
519
520 static Set<String> bootstrapPkgs = BootstrapParseContext.instance.getBootstrapPackages();
521
522 static class EnvData extends ParseContext implements IEnvInfo, Serializable {
523 private static final long serialVersionUID = 2L;
524
525 String prompt;
526 int ldLevel;
527 int superDone;
528 int superUndone;
529 int eventIndex;
530 boolean inWormhole;
531 boolean inRawMode;
532
533 EnvData() {
534 ldLevel = 1;
535 superDone = 1;
536 superUndone = 0;
537 pkgs = new HashMap<String, IPackageDefinition>();
538 }
539
540 void fromObjAndPkgDefs(LispObject obj, Map<String,IPackageDefinition> pkgDefs) throws ParseException {
541 try {
542 Cons lst = (Cons)obj;
543 defltPackage = ((Str)lst.car).value;
544
545 lst = (Cons)(lst.cdr);
546 ldLevel = ((Int)lst.car).v.intValue();
547
548 lst = (Cons)(lst.cdr);
549 inWormhole = !(lst.car instanceof Zero);
550
551 lst = (Cons)(lst.cdr);
552 inRawMode = !(lst.car instanceof Zero);
553
554 lst = (Cons)(lst.cdr);
555 LispObject[] pkgNames = Cons.listToArray(lst.car);
556
557 pkgs.clear();
558 for (LispObject o : pkgNames) {
559 String pkgName = ((Str)o).value;
560 pkgs.put(pkgName, pkgDefs.get(pkgName));
561 }
562
563 lst = (Cons)(lst.cdr);
564 eventIndex = ((Int)lst.car).v.intValue();
565
566 lst = (Cons)(lst.cdr);
567 superDone = ((Int)lst.car).v.intValue();
568
569 lst = (Cons)(lst.cdr);
570 superUndone = ((Int)lst.car).v.intValue();
571 } catch (ClassCastException cce) {
572 throw new ParseException("Invalid prompt data.");
573 } catch (NotListException e) {
574 throw new ParseException("Invalid prompt data.");
575 }
576 }
577
578 void setPrompt(String s) {
579 prompt = s;
580 }
581
582 public int getLdLevel() {
583 return ldLevel;
584 }
585
586 public String getPakage() {
587 return getDefaultPackage();
588 }
589
590 public String getPrompt() {
591 return prompt;
592 }
593
594 public int getLastEventIndex() {
595 return eventIndex;
596 }
597
598 public boolean inWormhole() {
599 return inWormhole;
600 }
601
602 public boolean inRawMode() {
603 return inRawMode;
604 }
605
606 public int getSuperDoneSize() {
607 return superDone;
608 }
609
610 public int getSuperUndoneSize() {
611 return superUndone;
612 }
613
614 public IParseContext getParseContext() {
615 return this;
616 }
617
618 public void forgetRedo() {
619 superUndone = 0;
620 }
621 }
622
623 static class Result implements IResultInfo {
624 private static final long serialVersionUID = 2L;
625
626 final String output;
627 final boolean interrupted;
628 final boolean success;
629 final boolean affectedWorld;
630 final int superDone;
631
632 public Result(String output, StatusData p) {
633 this.output = output;
634 this.interrupted = p.interrupted;
635 this.success = p.success;
636 this.affectedWorld = p.extendedWorld;
637 this.superDone = p.superDone;
638 }
639
640 public String getOutput() {
641 return output;
642 }
643
644 public boolean wasInterrupted() {
645 return interrupted;
646 }
647
648 public boolean wasSuccess() {
649 return success;
650 }
651
652 public boolean extendedLogicalState() {
653 return affectedWorld;
654 }
655
656 public int getSuperDoneSize() {
657 return superDone;
658 }
659 }
660
661 public String[] getCmdLine() {
662 return cmdLine;
663 }
664
665 public ISessionConfig getConfig() {
666 return config;
667 }
668 }