1 package org.simantics.scl.compiler.commands;
3 import java.io.BufferedReader;
4 import java.io.FileNotFoundException;
5 import java.io.FileOutputStream;
6 import java.io.IOException;
7 import java.io.PrintStream;
9 import java.io.StringReader;
10 import java.io.UnsupportedEncodingException;
11 import java.util.ArrayList;
12 import java.util.Collection;
13 import java.util.Collections;
14 import java.util.List;
18 import org.simantics.scl.compiler.common.names.Names;
19 import org.simantics.scl.compiler.constants.StringConstant;
20 import org.simantics.scl.compiler.dynamic.SafeDynamic;
21 import org.simantics.scl.compiler.elaboration.expressions.EApply;
22 import org.simantics.scl.compiler.elaboration.expressions.EBlock;
23 import org.simantics.scl.compiler.elaboration.expressions.EConstant;
24 import org.simantics.scl.compiler.elaboration.expressions.EExternalConstant;
25 import org.simantics.scl.compiler.elaboration.expressions.ELiteral;
26 import org.simantics.scl.compiler.elaboration.expressions.EVariable;
27 import org.simantics.scl.compiler.elaboration.expressions.Expression;
28 import org.simantics.scl.compiler.elaboration.expressions.Variable;
29 import org.simantics.scl.compiler.elaboration.expressions.block.GuardStatement;
30 import org.simantics.scl.compiler.elaboration.expressions.block.Statement;
31 import org.simantics.scl.compiler.environment.AbstractLocalEnvironment;
32 import org.simantics.scl.compiler.environment.Environment;
33 import org.simantics.scl.compiler.environment.LocalEnvironment;
34 import org.simantics.scl.compiler.environment.specification.EnvironmentSpecification;
35 import org.simantics.scl.compiler.errors.CompilationError;
36 import org.simantics.scl.compiler.errors.ErrorSeverity;
37 import org.simantics.scl.compiler.errors.Locations;
38 import org.simantics.scl.compiler.internal.codegen.utils.NameMangling;
39 import org.simantics.scl.compiler.internal.parsing.exceptions.SCLSyntaxErrorException;
40 import org.simantics.scl.compiler.internal.parsing.parser.SCLParserImpl;
41 import org.simantics.scl.compiler.internal.parsing.utils.LaxUTF8Reader;
42 import org.simantics.scl.compiler.internal.parsing.utils.MemoReader;
43 import org.simantics.scl.compiler.module.ImportDeclaration;
44 import org.simantics.scl.compiler.module.repository.ImportFailure;
45 import org.simantics.scl.compiler.module.repository.ImportFailureException;
46 import org.simantics.scl.compiler.module.repository.ModuleRepository;
47 import org.simantics.scl.compiler.module.repository.UpdateListener;
48 import org.simantics.scl.compiler.runtime.RuntimeEnvironment;
49 import org.simantics.scl.compiler.top.ExpressionEvaluator;
50 import org.simantics.scl.compiler.top.LocalStorage;
51 import org.simantics.scl.compiler.top.SCLExpressionCompilationException;
52 import org.simantics.scl.compiler.types.Type;
53 import org.simantics.scl.compiler.types.Types;
54 import org.simantics.scl.runtime.SCLContext;
55 import org.simantics.scl.runtime.function.Function;
56 import org.simantics.scl.runtime.function.FunctionImpl2;
57 import org.simantics.scl.runtime.reporting.DelegatingSCLReportingHandler;
58 import org.simantics.scl.runtime.reporting.SCLReporting;
59 import org.simantics.scl.runtime.reporting.SCLReportingHandler;
60 import org.simantics.scl.runtime.tuple.Tuple0;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
64 import gnu.trove.map.hash.THashMap;
65 import gnu.trove.procedure.TObjectProcedure;
66 import gnu.trove.set.hash.THashSet;
69 public class CommandSession {
71 private static final Logger LOGGER = LoggerFactory.getLogger(CommandSession.class);
73 ModuleRepository moduleRepository;
74 SCLReportingHandler defaultHandler;
76 RuntimeEnvironment runtimeEnvironment;
77 ValueToStringConverter valueToStringConverter;
79 ArrayList<CommandSessionImportEntry> importEntries = new ArrayList<CommandSessionImportEntry>();
81 THashMap<String,Object> variableValues = new THashMap<String,Object>();
82 THashMap<String,Type> variableTypes = new THashMap<String,Type>();
84 PrintStream fileOutput;
85 private UpdateListener dependenciesListener;
88 * Only checks the commands for compilation errors but does not run them.
90 private boolean validateOnly;
92 public CommandSession(ModuleRepository moduleRepository) {
93 this(moduleRepository, SCLReporting.getCurrentReportingHandler());
96 public CommandSession(ModuleRepository moduleRepository, SCLReportingHandler handler) {
97 this.moduleRepository = moduleRepository;
98 this.defaultHandler = new PrintDecorator(
99 handler == null ? SCLReportingHandler.DEFAULT : handler);
100 updateRuntimeEnvironment(true);
103 private static EnvironmentSpecification createEnvironmentSpecification(Collection<CommandSessionImportEntry> importEntries) {
104 EnvironmentSpecification spec = new EnvironmentSpecification();
105 spec.importModule("Builtin", "");
106 spec.importModule("StandardLibrary", "");
107 spec.importModule("Expressions/Context", null);
108 for(CommandSessionImportEntry entry : importEntries)
109 if(!entry.disabled && !entry.hasError)
110 spec.importModule(entry.moduleName, entry.localName);
114 public void updateRuntimeEnvironment(boolean clearErrorsFlags) {
116 for(CommandSessionImportEntry entry : importEntries)
117 entry.hasError = false;
118 EnvironmentSpecification environmentSpecification = createEnvironmentSpecification(importEntries);
120 runtimeEnvironment = null;
122 if(dependenciesListener != null)
123 dependenciesListener.stopListening();
125 runtimeEnvironment = moduleRepository.createRuntimeEnvironment(
126 environmentSpecification,
127 getClass().getClassLoader(),
128 dependenciesListener);
129 } catch(ImportFailureException e) {
130 THashSet<String> failedModules = new THashSet<String>();
131 for(ImportFailure failure : e.failures) {
132 failedModules.add(failure.moduleName);
133 defaultHandler.printError(failure.toString());
134 if(failure.reason instanceof CompilationError[])
135 for(CompilationError error : (CompilationError[])failure.reason) {
136 if(error.severity != ErrorSeverity.WARNING)
137 defaultHandler.printError(" " + error.description);
140 for(CommandSessionImportEntry entry : importEntries)
141 if(failedModules.contains(entry.moduleName))
142 entry.hasError = true;
143 environmentSpecification = createEnvironmentSpecification(importEntries);
145 runtimeEnvironment = moduleRepository.createRuntimeEnvironment(
146 environmentSpecification,
147 getClass().getClassLoader()); // no listener here, because should listen also failed modules
148 } catch (ImportFailureException e1) {
149 for(ImportFailure failure : e1.failures)
150 defaultHandler.printError(failure.toString());
153 } catch(RuntimeException e) {
154 LOGGER.error("updateRuntimeEnvironment(clearErrorsFlags={}) failed", clearErrorsFlags, e);
157 valueToStringConverter = new ValueToStringConverter(runtimeEnvironment);
160 public RuntimeEnvironment getRuntimeEnvironment() {
161 return runtimeEnvironment;
164 public ModuleRepository getModuleRepository() {
165 return moduleRepository;
168 private static class CancelExecution extends RuntimeException {
169 private static final long serialVersionUID = -6925642906311538873L;
172 private LocalStorage localStorage = new LocalStorage() {
174 public void store(String name, Object value, Type type) {
175 variableValues.put(name, value);
176 variableTypes.put(name, type);
180 private static class LocalFunction {
181 final Function function;
184 public LocalFunction(Function function, Type type) {
185 this.function = function;
190 private static final THashMap<String, LocalFunction> LOCAL_FUNCTIONS = new THashMap<String, LocalFunction>();
192 LOCAL_FUNCTIONS.put("runFromFile", new LocalFunction(new FunctionImpl2<CommandSession, String, Tuple0>() {
194 public Tuple0 apply(final CommandSession commandSession, String fileName) {
195 SCLContext context = SCLContext.getCurrent();
196 commandSession.runFromFile(fileName, (SCLReportingHandler)context.get(SCLReportingHandler.REPORTING_HANDLER));
197 return Tuple0.INSTANCE;
199 }, Types.functionE(Types.STRING, Types.PROC, Types.UNIT)));
200 LOCAL_FUNCTIONS.put("runTest", new LocalFunction(new FunctionImpl2<CommandSession, String, Tuple0>() {
202 public Tuple0 apply(final CommandSession commandSession, String fileName) {
203 SCLContext context = SCLContext.getCurrent();
204 SCLReportingHandler handler = (SCLReportingHandler)context.get(SCLReportingHandler.REPORTING_HANDLER);
206 BufferedReader reader = new BufferedReader(new LaxUTF8Reader(fileName));
208 new TestScriptExecutor(commandSession, reader, handler).execute();
212 } catch(IOException e) {
213 handler.printError(e.getMessage());
215 return Tuple0.INSTANCE;
217 }, Types.functionE(Types.STRING, Types.PROC, Types.UNIT)));
218 LOCAL_FUNCTIONS.put("reset", new LocalFunction(new FunctionImpl2<CommandSession, Tuple0, Tuple0>() {
220 public Tuple0 apply(CommandSession commandSession, Tuple0 dummy) {
221 commandSession.removeTransientImports();
222 commandSession.removeVariables();
223 commandSession.moduleRepository.getSourceRepository().checkUpdates();
224 commandSession.updateRuntimeEnvironment(true);
225 return Tuple0.INSTANCE;
227 }, Types.functionE(Types.UNIT, Types.PROC, Types.UNIT)));
228 LOCAL_FUNCTIONS.put("variables", new LocalFunction(new FunctionImpl2<CommandSession, Tuple0, List<String>>() {
230 public List<String> apply(CommandSession commandSession, Tuple0 dummy) {
231 ArrayList<String> result = new ArrayList<String>(commandSession.variableTypes.keySet());
232 Collections.sort(result);
235 }, Types.functionE(Types.PUNIT, Types.PROC, Types.list(Types.STRING))));
236 LOCAL_FUNCTIONS.put("startPrintingToFile", new LocalFunction(new FunctionImpl2<CommandSession, String, Tuple0>() {
238 public Tuple0 apply(final CommandSession commandSession, String fileName) {
240 if(commandSession.fileOutput != null) {
241 commandSession.fileOutput.close();
242 SCLReporting.printError("Printing to file was already enabled. Stopped the previous printing.");
244 commandSession.fileOutput = new PrintStream(fileName, "UTF-8");
245 } catch (FileNotFoundException e) {
246 throw new RuntimeException(e);
247 } catch (UnsupportedEncodingException e) {
248 throw new RuntimeException(e);
250 return Tuple0.INSTANCE;
252 }, Types.functionE(Types.STRING, Types.PROC, Types.UNIT)));
253 LOCAL_FUNCTIONS.put("startAppendingToFile", new LocalFunction(new FunctionImpl2<CommandSession, String, Tuple0>() {
255 public Tuple0 apply(final CommandSession commandSession, String fileName) {
257 if(commandSession.fileOutput != null) {
258 commandSession.fileOutput.close();
259 SCLReporting.printError("Printing to file was already enabled. Stopped the previous printing.");
261 FileOutputStream stream = new FileOutputStream(fileName, true);
262 commandSession.fileOutput = new PrintStream(stream, false, "UTF-8");
263 } catch (FileNotFoundException e) {
264 throw new RuntimeException(e);
265 } catch (UnsupportedEncodingException e) {
266 throw new RuntimeException(e);
268 return Tuple0.INSTANCE;
270 }, Types.functionE(Types.STRING, Types.PROC, Types.UNIT)));
271 LOCAL_FUNCTIONS.put("stopPrintingToFile", new LocalFunction(new FunctionImpl2<CommandSession, Tuple0, Tuple0>() {
273 public Tuple0 apply(final CommandSession commandSession, Tuple0 dummy) {
274 if(commandSession.fileOutput != null) {
275 commandSession.fileOutput.close();
276 commandSession.fileOutput = null;
278 return Tuple0.INSTANCE;
280 }, Types.functionE(Types.PUNIT, Types.PROC, Types.UNIT)));
283 private LocalEnvironment createLocalEnvironment() {
284 return new AbstractLocalEnvironment() {
285 Variable contextVariable = new Variable("context", Names.Expressions_Context_Context);
287 public Expression resolve(Environment environment, String localName) {
288 Type type = variableTypes.get(localName);
291 new EConstant(environment.getValue(Names.Expressions_Context_contextGet), type),
292 new EVariable(contextVariable),
293 new ELiteral(new StringConstant(localName))
295 LocalFunction localFunction = LOCAL_FUNCTIONS.get(localName);
296 if(localFunction != null) {
297 return new EExternalConstant(
298 localFunction.function.apply(CommandSession.this),
304 protected Variable[] getContextVariables() {
305 return new Variable[] { contextVariable };
308 public void forNames(TObjectProcedure<String> proc) {
309 for(String name : variableTypes.keySet())
311 for(String name : LOCAL_FUNCTIONS.keySet())
317 protected void removeTransientImports() {
318 ArrayList<CommandSessionImportEntry> newEntries = new ArrayList<CommandSessionImportEntry>(importEntries.size());
319 for(CommandSessionImportEntry entry : importEntries)
321 newEntries.add(entry);
322 importEntries = newEntries;
325 public THashMap<String,Type> localNamesForContentProposals() {
326 THashMap<String,Type> result = new THashMap<String,Type>();
327 for(Map.Entry<String,LocalFunction> entry : LOCAL_FUNCTIONS.entrySet())
328 result.put(entry.getKey(), entry.getValue().type);
329 result.putAll(variableTypes);
333 private CompiledCommand compile(Expression expression) throws SCLExpressionCompilationException {
334 LocalEnvironment localEnvironment = createLocalEnvironment();
335 if(runtimeEnvironment == null)
336 throw new SCLExpressionCompilationException(new CompilationError[] {
337 new CompilationError("Compilation failed: imports in the current environment have failed.")
339 ExpressionEvaluator evaluator = new ExpressionEvaluator(runtimeEnvironment, localStorage, expression);
340 Function command = (Function)evaluator
341 .localEnvironment(localEnvironment)
342 .decorateExpression(true)
343 .validateOnly(validateOnly)
345 return new CompiledCommand(command, evaluator.getType());
348 class PrintDecorator extends DelegatingSCLReportingHandler {
349 public PrintDecorator(SCLReportingHandler baseHandler) {
354 public void print(String text) {
356 if(fileOutput != null)
357 fileOutput.println(text);
361 public void printCommand(String command) {
362 super.printCommand(command);
363 if(fileOutput != null)
364 fileOutput.println("> " + command);
368 public void printError(String error) {
369 super.printError(error);
370 if(fileOutput != null)
371 fileOutput.println(error);
375 @SuppressWarnings("unchecked")
376 private void execute(MemoReader reader, Expression expression, final SCLReportingHandler handler) {
377 SCLContext context = SCLContext.getCurrent();
378 Object oldPrinter = context.put(SCLReportingHandler.REPORTING_HANDLER, handler);
380 CompiledCommand command;
382 handler.printCommand(reader.extractString(expression.location));
383 command = compile(expression);
384 } catch (SCLExpressionCompilationException e) {
387 CompilationError[] errors = e.getErrors();
388 for(CompilationError error : errors) {
389 if(error.location != Locations.NO_LOCATION)
390 handler.printError(reader.locationUnderlining(error.location));
391 handler.printError(error.description);
393 throw new CancelExecution();
395 reader.forgetEverythingBefore(Locations.endOf(expression.location));
398 Object resultValue = command.command.apply(variableValues);
399 String resultString = toString(resultValue, command.type);
400 if(!resultString.isEmpty())
401 handler.print(resultString);
403 } catch(Exception e) {
406 if(!(e instanceof CancelExecution)) {
407 if(e instanceof InterruptedException)
408 handler.printError("Execution interrupted.");
410 formatException(handler, e);
412 throw new CancelExecution();
414 context.put(SCLReportingHandler.REPORTING_HANDLER, oldPrinter);
418 private String toString(Object value, Type type) {
419 if(type.equals(Types.UNIT))
422 return valueToStringConverter.show(value, type);
423 } catch (SCLExpressionCompilationException e) {
424 return "<value of type " + type + ">";
428 class CommandParser extends SCLParserImpl {
429 SCLReportingHandler handler;
431 public CommandParser(SCLReportingHandler handler, MemoReader reader) {
433 this.reader = reader;
434 this.handler = handler;
439 if(currentBlock != null) {
441 currentBlock.location = Locations.combine(
442 currentBlock.getFirst().location,
443 currentBlock.getLast().location);
444 execute(reader, currentBlock, handler);
449 protected Object reduceStatementCommand() {
450 Statement statement = (Statement)get(0);
451 if(statement.mayBeRecursive()) {
452 if(currentBlock == null)
453 currentBlock = new EBlock();
454 currentBlock.addStatement(statement);
459 if(statement instanceof GuardStatement)
460 execute(reader, ((GuardStatement)statement).value, handler);
462 EBlock block = new EBlock();
463 block.addStatement(statement);
464 block.location = statement.location;
465 execute(reader, block, handler);
472 protected Object reduceImportCommand() {
476 ImportDeclaration importDeclaration = (ImportDeclaration)get(0);
477 handler.printCommand(reader.extractString(importDeclaration.location));
478 new CommandSessionImportEntry(importDeclaration.moduleName,
479 importDeclaration.localName).addTo(importEntries);
480 updateRuntimeEnvironment(false);
485 private void checkInterrupted() {
486 if(Thread.interrupted()) {
487 defaultHandler.printError("Execution interrupted.");
488 throw new CancelExecution();
492 private CompilationError[] validate(Reader commandReader) {
493 CommandParser parser = new CommandParser(defaultHandler, new MemoReader(commandReader));
496 parser.parseCommands();
497 parser.finishBlock();
498 return CompilationError.EMPTY_ARRAY;
499 } catch(SCLExpressionCompilationException e) {
500 return e.getErrors();
501 } catch(SCLSyntaxErrorException e) {
502 return new CompilationError[] { new CompilationError(e.location, e.getMessage()) };
503 } catch(Exception e) {
504 return new CompilationError[] { new CompilationError(Locations.NO_LOCATION, e.getMessage()) };
506 validateOnly = false;
510 public void execute(Reader commandReader, SCLReportingHandler handler) {
512 handler = defaultHandler;
513 else if (!(handler instanceof PrintDecorator))
514 handler = new PrintDecorator(handler);
515 CommandParser parser = new CommandParser(handler, new MemoReader(commandReader));
517 parser.parseCommands();
518 parser.finishBlock();
519 } catch(CancelExecution e) {
520 } catch(SCLSyntaxErrorException e) {
521 handler.printCommand(parser.reader.getLastCommand());
522 if(e.location != Locations.NO_LOCATION)
523 handler.printError(parser.reader.locationUnderlining(e.location));
524 handler.printError(e.getMessage());
525 } catch (Exception | AssertionError e) {
526 if(e instanceof InterruptedException)
527 handler.printError("Execution interrupted.");
529 formatException(handler, e);
533 public void execute(String command) {
534 execute(new StringReader(command), null);
537 public void execute(String command, SCLReportingHandler handler) {
538 execute(new StringReader(command), handler);
541 private static final String THIS_CLASS_NAME = CommandSession.class.getName();
543 public static void formatException(
544 SCLReportingHandler handler,
546 formatException(handler, null, e);
549 private static void formatException(
550 SCLReportingHandler handler,
551 StackTraceElement[] enclosingTrace,
553 StackTraceElement[] elements = e.getStackTrace();
554 Throwable cause = e.getCause();
556 formatException(handler, elements, cause);
557 handler.printError("Rethrown as ");
559 handler.printError(e.toString());
560 int endPos = elements.length;
561 if(enclosingTrace != null) {
562 int p = enclosingTrace.length;
563 while(endPos > 0 && p > 0 && elements[endPos-1].equals(enclosingTrace[p-1])) {
569 for(int i=0;i<endPos;++i) {
570 StackTraceElement element = elements[i];
571 if(element.getMethodName().equals("execute") &&
572 element.getClassName().equals(THIS_CLASS_NAME)) {
575 element = elements[endPos-1];
576 String className = element.getClassName();
577 if(className.startsWith("org.simantics.scl.compiler.top.SCLExpressionCompiler")
578 //|| element.getClassName().startsWith("org.simantics.scl.compiler.interpreted.")
579 || className.startsWith("org.simantics.scl.runtime.function.FunctionImpl")
580 //|| className.startsWith("tempsclpackage")
590 for(int i=0;i<endPos;++i) {
591 StringBuilder b = new StringBuilder();
592 StackTraceElement element = elements[i];
593 String className = element.getClassName();
594 if(className.equals("org.simantics.scl.compiler.interpreted.IApply")
595 || className.equals("org.simantics.scl.compiler.interpreted.ILet")
596 || className.startsWith("tempsclpackage"))
598 if(className.startsWith("org.simantics.scl.compiler.interpreted.ILambda")) {
599 b.append("\tat command line\n");
602 String methodName = element.getMethodName();
603 if(className.startsWith("org.simantics.scl.runtime.function.FunctionImpl") &&
604 methodName.equals("applyArray"))
606 String fileName = element.getFileName();
607 if("_SCL_Closure".equals(fileName))
610 if("_SCL_Module".equals(fileName)
611 || "_SCL_TypeClassInstance".equals(fileName))
614 .append(NameMangling.demangle(methodName))
615 .append('(').append(element.getLineNumber()).append(')');
618 handler.printError(b.toString());
622 public void setVariable(String name, Type type, Object value) {
623 variableValues.put(name, value);
624 variableTypes.put(name, type);
627 public void setVariable(String name, SafeDynamic typeAndValue) {
628 variableValues.put(name, typeAndValue.value);
629 variableTypes.put(name, typeAndValue.type_);
632 public Object getVariableValue(String name) {
633 return variableValues.get(name);
636 public Type getVariableType(String name) {
637 return variableTypes.get(name);
640 public SafeDynamic getVariableValueAndType(String name) {
641 Type type = variableTypes.get(name);
644 Object value = variableValues.get(name);
645 return new SafeDynamic(type, value);
648 public void removeVariable(String name) {
649 variableValues.remove(name);
650 variableTypes.remove(name);
653 public void removeVariables() {
654 variableValues.clear();
655 variableTypes.clear();
658 public Set<String> getVariables() {
659 return variableTypes.keySet();
662 public ArrayList<CommandSessionImportEntry> getImportEntries() {
663 return importEntries;
666 public void setImportEntries(
667 ArrayList<CommandSessionImportEntry> importEntries) {
668 this.importEntries = importEntries;
669 updateRuntimeEnvironment(true);
672 public void runFromFile(String fileName, SCLReportingHandler handler) {
674 Reader reader = new LaxUTF8Reader(fileName);
676 execute(reader, handler);
680 } catch(IOException e) {
681 formatException(handler, e);
685 public static CompilationError[] validate(ModuleRepository moduleRepository,StringReader commandReader) {
686 CommandSession session = new CommandSession(moduleRepository, null);
687 return session.validate(commandReader);
690 public static CompilationError[] validate(ModuleRepository moduleRepository,String command) {
691 return validate(moduleRepository, new StringReader(command));
694 public CompilationError[] validate(String command) {
695 return validate(new StringReader(command));
698 public void setDependenciesListener(UpdateListener dependenciesListener) {
699 this.dependenciesListener = dependenciesListener;