--- /dev/null
+package org.simantics.scl.compiler.commands;
+
+import gnu.trove.map.hash.THashMap;
+import gnu.trove.procedure.TObjectProcedure;
+import gnu.trove.set.hash.THashSet;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.simantics.scl.compiler.common.names.Name;
+import org.simantics.scl.compiler.constants.StringConstant;
+import org.simantics.scl.compiler.elaboration.expressions.EApply;
+import org.simantics.scl.compiler.elaboration.expressions.EBlock;
+import org.simantics.scl.compiler.elaboration.expressions.EConstant;
+import org.simantics.scl.compiler.elaboration.expressions.EExternalConstant;
+import org.simantics.scl.compiler.elaboration.expressions.ELiteral;
+import org.simantics.scl.compiler.elaboration.expressions.EVariable;
+import org.simantics.scl.compiler.elaboration.expressions.Expression;
+import org.simantics.scl.compiler.elaboration.expressions.Variable;
+import org.simantics.scl.compiler.elaboration.expressions.block.GuardStatement;
+import org.simantics.scl.compiler.elaboration.expressions.block.Statement;
+import org.simantics.scl.compiler.environment.AbstractLocalEnvironment;
+import org.simantics.scl.compiler.environment.Environment;
+import org.simantics.scl.compiler.environment.LocalEnvironment;
+import org.simantics.scl.compiler.environment.specification.EnvironmentSpecification;
+import org.simantics.scl.compiler.errors.CompilationError;
+import org.simantics.scl.compiler.errors.Locations;
+import org.simantics.scl.compiler.internal.codegen.utils.NameMangling;
+import org.simantics.scl.compiler.internal.parsing.exceptions.SCLSyntaxErrorException;
+import org.simantics.scl.compiler.internal.parsing.parser.SCLParserImpl;
+import org.simantics.scl.compiler.internal.parsing.utils.LaxUTF8Reader;
+import org.simantics.scl.compiler.internal.parsing.utils.MemoReader;
+import org.simantics.scl.compiler.module.ImportDeclaration;
+import org.simantics.scl.compiler.module.repository.ImportFailure;
+import org.simantics.scl.compiler.module.repository.ImportFailureException;
+import org.simantics.scl.compiler.module.repository.ModuleRepository;
+import org.simantics.scl.compiler.runtime.RuntimeEnvironment;
+import org.simantics.scl.compiler.top.ExpressionEvaluator;
+import org.simantics.scl.compiler.top.LocalStorage;
+import org.simantics.scl.compiler.top.SCLExpressionCompilationException;
+import org.simantics.scl.compiler.types.TCon;
+import org.simantics.scl.compiler.types.Type;
+import org.simantics.scl.compiler.types.Types;
+import org.simantics.scl.runtime.SCLContext;
+import org.simantics.scl.runtime.function.Function;
+import org.simantics.scl.runtime.function.FunctionImpl2;
+import org.simantics.scl.runtime.reporting.DelegatingSCLReportingHandler;
+import org.simantics.scl.runtime.reporting.SCLReporting;
+import org.simantics.scl.runtime.reporting.SCLReportingHandler;
+import org.simantics.scl.runtime.tuple.Tuple0;
+
+
+public class CommandSession {
+
+ ModuleRepository moduleRepository;
+ SCLReportingHandler defaultHandler;
+
+ RuntimeEnvironment runtimeEnvironment;
+ ValueToStringConverter valueToStringConverter;
+
+ ArrayList<CommandSessionImportEntry> importEntries = new ArrayList<CommandSessionImportEntry>();
+
+ THashMap<String,Object> variableValues = new THashMap<String,Object>();
+ THashMap<String,Type> variableTypes = new THashMap<String,Type>();
+
+ PrintStream fileOutput;
+
+ private static final String CONTEXT_MODULE = "Expressions/Context";
+ private static final TCon CONTEXT_TYPE = Types.con(CONTEXT_MODULE, "Context");
+ private static final Name CONTEXT_GET = Name.create(CONTEXT_MODULE, "contextGet");
+
+ public CommandSession(ModuleRepository moduleRepository, SCLReportingHandler handler) {
+ this.moduleRepository = moduleRepository;
+ this.defaultHandler = new PrintDecorator(
+ handler == null ? SCLReportingHandler.DEFAULT : handler);
+ updateRuntimeEnvironment(true);
+ }
+
+ private static EnvironmentSpecification createEnvironmentSpecification(Collection<CommandSessionImportEntry> importEntries) {
+ EnvironmentSpecification spec = new EnvironmentSpecification();
+ spec.importModule("Builtin", "");
+ spec.importModule("StandardLibrary", "");
+ spec.importModule("Expressions/Context", null);
+ for(CommandSessionImportEntry entry : importEntries)
+ if(!entry.disabled && !entry.hasError)
+ spec.importModule(entry.moduleName, entry.localName);
+ return spec;
+ }
+
+ public void updateRuntimeEnvironment(boolean clearErrorsFlags) {
+ if(clearErrorsFlags)
+ for(CommandSessionImportEntry entry : importEntries)
+ entry.hasError = false;
+ EnvironmentSpecification environmentSpecification = createEnvironmentSpecification(importEntries);
+
+ runtimeEnvironment = null;
+ try {
+ try {
+ runtimeEnvironment = moduleRepository.createRuntimeEnvironment(
+ environmentSpecification,
+ getClass().getClassLoader());
+ } catch(ImportFailureException e) {
+ THashSet<String> failedModules = new THashSet<String>();
+ for(ImportFailure failure : e.failures) {
+ failedModules.add(failure.moduleName);
+ defaultHandler.printError(failure.toString());
+ if(failure.reason instanceof CompilationError[])
+ for(CompilationError error : (CompilationError[])failure.reason) {
+ defaultHandler.printError(" " + error.description);
+ }
+ }
+ for(CommandSessionImportEntry entry : importEntries)
+ if(failedModules.contains(entry.moduleName))
+ entry.hasError = true;
+ environmentSpecification = createEnvironmentSpecification(importEntries);
+ try {
+ runtimeEnvironment = moduleRepository.createRuntimeEnvironment(
+ environmentSpecification,
+ getClass().getClassLoader());
+ } catch (ImportFailureException e1) {
+ for(ImportFailure failure : e1.failures)
+ defaultHandler.printError(failure.toString());
+ }
+ }
+ } catch(RuntimeException e) {
+ e.printStackTrace();
+ throw e;
+ }
+ valueToStringConverter = new ValueToStringConverter(runtimeEnvironment);
+ }
+
+ public RuntimeEnvironment getRuntimeEnvironment() {
+ return runtimeEnvironment;
+ }
+
+ public ModuleRepository getModuleRepository() {
+ return moduleRepository;
+ }
+
+ private static class CancelExecution extends RuntimeException {
+ private static final long serialVersionUID = -6925642906311538873L;
+ }
+
+ private LocalStorage localStorage = new LocalStorage() {
+ @Override
+ public void store(String name, Object value, Type type) {
+ variableValues.put(name, value);
+ variableTypes.put(name, type);
+ }
+ };
+
+ private static class LocalFunction {
+ final Function function;
+ final Type type;
+
+ public LocalFunction(Function function, Type type) {
+ this.function = function;
+ this.type = type;
+ }
+ }
+
+ private static final THashMap<String, LocalFunction> LOCAL_FUNCTIONS = new THashMap<String, LocalFunction>();
+ static {
+ LOCAL_FUNCTIONS.put("runFromFile", new LocalFunction(new FunctionImpl2<CommandSession, String, Tuple0>() {
+ @Override
+ public Tuple0 apply(final CommandSession commandSession, String fileName) {
+ SCLContext context = SCLContext.getCurrent();
+ commandSession.runFromFile(fileName, (SCLReportingHandler)context.get(SCLReportingHandler.REPORTING_HANDLER));
+ return Tuple0.INSTANCE;
+ }
+ }, Types.functionE(Types.STRING, Types.PROC, Types.UNIT)));
+ LOCAL_FUNCTIONS.put("runTest", new LocalFunction(new FunctionImpl2<CommandSession, String, Tuple0>() {
+ @Override
+ public Tuple0 apply(final CommandSession commandSession, String fileName) {
+ SCLContext context = SCLContext.getCurrent();
+ SCLReportingHandler handler = (SCLReportingHandler)context.get(SCLReportingHandler.REPORTING_HANDLER);
+ try {
+ BufferedReader reader = new BufferedReader(new LaxUTF8Reader(fileName));
+ try {
+ new TestScriptExecutor(commandSession, reader, handler).execute();
+ } finally {
+ reader.close();
+ }
+ } catch(IOException e) {
+ handler.printError(e.getMessage());
+ }
+ return Tuple0.INSTANCE;
+ }
+ }, Types.functionE(Types.STRING, Types.PROC, Types.UNIT)));
+ LOCAL_FUNCTIONS.put("reset", new LocalFunction(new FunctionImpl2<CommandSession, Tuple0, Tuple0>() {
+ @Override
+ public Tuple0 apply(CommandSession commandSession, Tuple0 _) {
+ commandSession.removeTransientImports();
+ commandSession.removeVariables();
+ commandSession.moduleRepository.getSourceRepository().checkUpdates();
+ commandSession.updateRuntimeEnvironment(true);
+ return Tuple0.INSTANCE;
+ }
+ }, Types.functionE(Types.UNIT, Types.PROC, Types.UNIT)));
+ LOCAL_FUNCTIONS.put("variables", new LocalFunction(new FunctionImpl2<CommandSession, Tuple0, List<String>>() {
+ @Override
+ public List<String> apply(CommandSession commandSession, Tuple0 _) {
+ ArrayList<String> result = new ArrayList<String>(commandSession.variableTypes.keySet());
+ Collections.sort(result);
+ return result;
+ }
+ }, Types.functionE(Types.PUNIT, Types.PROC, Types.list(Types.STRING))));
+ LOCAL_FUNCTIONS.put("startPrintingToFile", new LocalFunction(new FunctionImpl2<CommandSession, String, Tuple0>() {
+ @Override
+ public Tuple0 apply(final CommandSession commandSession, String fileName) {
+ try {
+ if(commandSession.fileOutput != null) {
+ commandSession.fileOutput.close();
+ SCLReporting.printError("Printing to file was already enabled. Stopped the previous printing.");
+ }
+ commandSession.fileOutput = new PrintStream(fileName, "UTF-8");
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ return Tuple0.INSTANCE;
+ }
+ }, Types.functionE(Types.STRING, Types.PROC, Types.UNIT)));
+ LOCAL_FUNCTIONS.put("startAppendingToFile", new LocalFunction(new FunctionImpl2<CommandSession, String, Tuple0>() {
+ @Override
+ public Tuple0 apply(final CommandSession commandSession, String fileName) {
+ try {
+ if(commandSession.fileOutput != null) {
+ commandSession.fileOutput.close();
+ SCLReporting.printError("Printing to file was already enabled. Stopped the previous printing.");
+ }
+ FileOutputStream stream = new FileOutputStream(fileName, true);
+ commandSession.fileOutput = new PrintStream(stream, false, "UTF-8");
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ return Tuple0.INSTANCE;
+ }
+ }, Types.functionE(Types.STRING, Types.PROC, Types.UNIT)));
+ LOCAL_FUNCTIONS.put("stopPrintingToFile", new LocalFunction(new FunctionImpl2<CommandSession, Tuple0, Tuple0>() {
+ @Override
+ public Tuple0 apply(final CommandSession commandSession, Tuple0 _) {
+ if(commandSession.fileOutput != null) {
+ commandSession.fileOutput.close();
+ commandSession.fileOutput = null;
+ }
+ return Tuple0.INSTANCE;
+ }
+ }, Types.functionE(Types.PUNIT, Types.PROC, Types.UNIT)));
+ }
+
+ private LocalEnvironment createLocalEnvironment() {
+ return new AbstractLocalEnvironment() {
+ Variable contextVariable = new Variable("context", CONTEXT_TYPE);
+ @Override
+ public Expression resolve(Environment environment, String localName) {
+ Type type = variableTypes.get(localName);
+ if(type != null)
+ return new EApply(
+ new EConstant(environment.getValue(CONTEXT_GET), type),
+ new EVariable(contextVariable),
+ new ELiteral(new StringConstant(localName))
+ );
+ LocalFunction localFunction = LOCAL_FUNCTIONS.get(localName);
+ if(localFunction != null) {
+ return new EExternalConstant(
+ localFunction.function.apply(CommandSession.this),
+ localFunction.type);
+ }
+ return null;
+ }
+ @Override
+ protected Variable[] getContextVariables() {
+ return new Variable[] { contextVariable };
+ }
+ @Override
+ public void forNames(TObjectProcedure<String> proc) {
+ for(String name : variableTypes.keySet())
+ proc.execute(name);
+ for(String name : LOCAL_FUNCTIONS.keySet())
+ proc.execute(name);
+ }
+ };
+ }
+
+ protected void removeTransientImports() {
+ ArrayList<CommandSessionImportEntry> newEntries = new ArrayList<CommandSessionImportEntry>(importEntries.size());
+ for(CommandSessionImportEntry entry : importEntries)
+ if(entry.persistent)
+ newEntries.add(entry);
+ importEntries = newEntries;
+ }
+
+ public THashMap<String,Type> localNamesForContentProposals() {
+ THashMap<String,Type> result = new THashMap<String,Type>();
+ for(Map.Entry<String,LocalFunction> entry : LOCAL_FUNCTIONS.entrySet())
+ result.put(entry.getKey(), entry.getValue().type);
+ result.putAll(variableTypes);
+ return result;
+ }
+
+ private CompiledCommand compile(Expression expression) throws SCLExpressionCompilationException {
+ LocalEnvironment localEnvironment = createLocalEnvironment();
+ if(runtimeEnvironment == null)
+ throw new SCLExpressionCompilationException(new CompilationError[] {
+ new CompilationError("Compilation failed: imports in the current environment have failed.")
+ });
+ ExpressionEvaluator evaluator = new ExpressionEvaluator(runtimeEnvironment, localStorage, expression);
+ Function command = (Function)evaluator
+ .localEnvironment(localEnvironment)
+ .decorateExpression(true)
+ .eval();
+ return new CompiledCommand(command, evaluator.getType());
+ }
+
+ class PrintDecorator extends DelegatingSCLReportingHandler {
+ public PrintDecorator(SCLReportingHandler baseHandler) {
+ super(baseHandler);
+ }
+
+ @Override
+ public void print(String text) {
+ super.print(text);
+ if(fileOutput != null)
+ fileOutput.println(text);
+ }
+
+ @Override
+ public void printCommand(String command) {
+ super.printCommand(command);
+ if(fileOutput != null)
+ fileOutput.println("> " + command);
+ }
+
+ @Override
+ public void printError(String error) {
+ super.printError(error);
+ if(fileOutput != null)
+ fileOutput.println(error);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void execute(MemoReader reader, Expression expression, final SCLReportingHandler handler) {
+ SCLContext context = SCLContext.getCurrent();
+ Object oldPrinter = context.put(SCLReportingHandler.REPORTING_HANDLER, handler);
+ try {
+ CompiledCommand command;
+ try {
+ handler.printCommand(reader.extractString(expression.location));
+ command = compile(expression);
+ } catch (SCLExpressionCompilationException e) {
+ CompilationError[] errors = ((SCLExpressionCompilationException)e).getErrors();
+ for(CompilationError error : errors) {
+ if(error.location != Locations.NO_LOCATION)
+ handler.printError(reader.locationUnderlining(error.location));
+ handler.printError(error.description);
+ }
+ throw new CancelExecution();
+ }
+ reader.forgetEverythingBefore(Locations.endOf(expression.location));
+
+ Object resultValue = command.command.apply(variableValues);
+ String resultString = toString(resultValue, command.type);
+ if(!resultString.isEmpty())
+ handler.print(resultString);
+ } catch(Exception e) {
+ if(!(e instanceof CancelExecution)) {
+ if(e instanceof InterruptedException)
+ handler.printError("Execution interrupted.");
+ else
+ formatException(handler, e);
+ }
+ throw new CancelExecution();
+ } finally {
+ context.put(SCLReportingHandler.REPORTING_HANDLER, oldPrinter);
+ }
+ }
+
+ private String toString(Object value, Type type) {
+ if(type.equals(Types.UNIT))
+ return "";
+ try {
+ return valueToStringConverter.show(value, type);
+ } catch (SCLExpressionCompilationException e) {
+ return "<value of type " + type + ">";
+ }
+ }
+
+ class CommandParser extends SCLParserImpl {
+ SCLReportingHandler handler;
+ MemoReader reader;
+ public CommandParser(SCLReportingHandler handler, MemoReader reader) {
+ super(reader);
+ this.reader = reader;
+ this.handler = handler;
+ }
+
+ EBlock currentBlock;
+ void finishBlock() {
+ if(currentBlock != null) {
+ checkInterrupted();
+ LinkedList<Statement> statements = currentBlock.getStatements();
+ currentBlock.location = Locations.combine(
+ statements.getFirst().location,
+ statements.getLast().location);
+ execute(reader, currentBlock, handler);
+ currentBlock = null;
+ }
+ }
+ @Override
+ protected Object reduceStatementCommand() {
+ Statement statement = (Statement)get(0);
+ if(statement.mayBeRecursive()) {
+ if(currentBlock == null)
+ currentBlock = new EBlock();
+ currentBlock.addStatement(statement);
+ }
+ else {
+ finishBlock();
+ checkInterrupted();
+ if(statement instanceof GuardStatement)
+ execute(reader, ((GuardStatement)statement).value, handler);
+ else {
+ EBlock block = new EBlock();
+ block.addStatement(statement);
+ block.location = statement.location;
+ execute(reader, block, handler);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected Object reduceImportCommand() {
+ finishBlock();
+ checkInterrupted();
+
+ ImportDeclaration importDeclaration = (ImportDeclaration)get(0);
+ handler.printCommand(reader.extractString(importDeclaration.location));
+ new CommandSessionImportEntry(importDeclaration.moduleName,
+ importDeclaration.localName).addTo(importEntries);
+ updateRuntimeEnvironment(false);
+ return null;
+ }
+ }
+
+ private void checkInterrupted() {
+ if(Thread.interrupted()) {
+ defaultHandler.printError("Execution interrupted.");
+ throw new CancelExecution();
+ }
+ }
+
+ public void execute(Reader commandReader, SCLReportingHandler handler) {
+ if(handler == null)
+ handler = defaultHandler;
+ else if (!(handler instanceof PrintDecorator))
+ handler = new PrintDecorator(handler);
+ CommandParser parser = new CommandParser(handler, new MemoReader(commandReader));
+ try {
+ parser.parseCommands();
+ parser.finishBlock();
+ } catch(CancelExecution e) {
+ } catch(SCLSyntaxErrorException e) {
+ handler.printCommand(parser.reader.getLastCommand());
+ if(e.location != Locations.NO_LOCATION)
+ handler.printError(parser.reader.locationUnderlining(e.location));
+ handler.printError(e.getMessage());
+ } catch(Exception e) {
+ if(e instanceof InterruptedException)
+ handler.printError("Execution interrupted.");
+ else
+ formatException(handler, e);
+ }
+ }
+
+ public void execute(String command) {
+ execute(new StringReader(command), null);
+ }
+
+ public void execute(String command, SCLReportingHandler handler) {
+ execute(new StringReader(command), handler);
+ }
+
+ public CompilationError[] validate(String command) {
+ return CompilationError.EMPTY_ARRAY;
+ /*try {
+ compile(command);
+ return CompilationError.EMPTY_ARRAY;
+ } catch(SCLExpressionCompilationException e) {
+ return e.getErrors();
+ }*/
+ }
+
+ private static final String THIS_CLASS_NAME = CommandSession.class.getName();
+
+ public static void formatException(
+ SCLReportingHandler handler,
+ Throwable e) {
+ formatException(handler, null, e);
+ }
+
+ private static void formatException(
+ SCLReportingHandler handler,
+ StackTraceElement[] enclosingTrace,
+ Throwable e) {
+ StackTraceElement[] elements = e.getStackTrace();
+ Throwable cause = e.getCause();
+ if(cause != null) {
+ formatException(handler, elements, cause);
+ handler.printError("Rethrown as ");
+ }
+ handler.printError(e.toString());
+ int endPos = elements.length;
+ if(enclosingTrace != null) {
+ int p = enclosingTrace.length;
+ while(endPos > 0 && p > 0 && elements[endPos-1].equals(enclosingTrace[p-1])) {
+ --p;
+ --endPos;
+ }
+ }
+ else {
+ for(int i=0;i<endPos;++i) {
+ StackTraceElement element = elements[i];
+ if(element.getMethodName().equals("execute") &&
+ element.getClassName().equals(THIS_CLASS_NAME)) {
+ endPos = i;
+ while(endPos > 0) {
+ element = elements[endPos-1];
+ String className = element.getClassName();
+ if(className.startsWith("org.simantics.scl.compiler.top.SCLExpressionCompiler")
+ //|| element.getClassName().startsWith("org.simantics.scl.compiler.interpreted.")
+ || className.startsWith("org.simantics.scl.runtime.function.FunctionImpl")
+ //|| className.startsWith("tempsclpackage")
+ )
+ --endPos;
+ else
+ break;
+ }
+ break;
+ }
+ }
+ }
+ for(int i=0;i<endPos;++i) {
+ StringBuilder b = new StringBuilder();
+ StackTraceElement element = elements[i];
+ String className = element.getClassName();
+ if(className.equals("org.simantics.scl.compiler.interpreted.IApply")
+ || className.equals("org.simantics.scl.compiler.interpreted.ILet")
+ || className.startsWith("tempsclpackage"))
+ continue;
+ if(className.startsWith("org.simantics.scl.compiler.interpreted.ILambda")) {
+ b.append("\tat command line\n");
+ continue;
+ }
+ String methodName = element.getMethodName();
+ if(className.startsWith("org.simantics.scl.runtime.function.FunctionImpl") &&
+ methodName.equals("applyArray"))
+ continue;
+ String fileName = element.getFileName();
+ if("_SCL_Closure".equals(fileName))
+ continue;
+ b.append("\tat ");
+ if("_SCL_Module".equals(fileName)
+ || "_SCL_TypeClassInstance".equals(fileName))
+ b.append(NameMangling.demangle(methodName))
+ .append('(').append(element.getLineNumber()).append(')');
+ else
+ b.append(element);
+ handler.printError(b.toString());
+ }
+ }
+
+ public void setVariable(String name, Type type, Object value) {
+ variableValues.put(name, value);
+ variableTypes.put(name, type);
+ }
+
+ public Object getVariableValue(String name) {
+ return variableValues.get(name);
+ }
+
+ public Type getVariableType(String name) {
+ return variableTypes.get(name);
+ }
+
+ public void removeVariable(String name) {
+ variableValues.remove(name);
+ variableTypes.remove(name);
+ }
+
+ public void removeVariables() {
+ variableValues.clear();
+ variableTypes.clear();
+ }
+
+ public Set<String> getVariables() {
+ return variableTypes.keySet();
+ }
+
+ public ArrayList<CommandSessionImportEntry> getImportEntries() {
+ return importEntries;
+ }
+
+ public void setImportEntries(
+ ArrayList<CommandSessionImportEntry> importEntries) {
+ this.importEntries = importEntries;
+ updateRuntimeEnvironment(true);
+ }
+
+ public void runFromFile(String fileName, SCLReportingHandler handler) {
+ try {
+ Reader reader = new LaxUTF8Reader(fileName);
+ try {
+ execute(reader, handler);
+ } finally {
+ reader.close();
+ }
+ } catch(IOException e) {
+ formatException(handler, e);
+ }
+ }
+}