package org.simantics.scl.compiler.commands; 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.Names; 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.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; import gnu.trove.map.hash.THashMap; import gnu.trove.procedure.TObjectProcedure; import gnu.trove.set.hash.THashSet; public class CommandSession { ModuleRepository moduleRepository; SCLReportingHandler defaultHandler; RuntimeEnvironment runtimeEnvironment; ValueToStringConverter valueToStringConverter; ArrayList importEntries = new ArrayList(); THashMap variableValues = new THashMap(); THashMap variableTypes = new THashMap(); PrintStream fileOutput; 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 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 failedModules = new THashSet(); 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 LOCAL_FUNCTIONS = new THashMap(); static { LOCAL_FUNCTIONS.put("runFromFile", new LocalFunction(new FunctionImpl2() { @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() { @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() { @Override public Tuple0 apply(CommandSession commandSession, Tuple0 dummy) { 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>() { @Override public List apply(CommandSession commandSession, Tuple0 dummy) { ArrayList result = new ArrayList(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() { @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() { @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() { @Override public Tuple0 apply(final CommandSession commandSession, Tuple0 dummy) { 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", Names.Expressions_Context_Context); @Override public Expression resolve(Environment environment, String localName) { Type type = variableTypes.get(localName); if(type != null) return new EApply( new EConstant(environment.getValue(Names.Expressions_Context_contextGet), 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 proc) { for(String name : variableTypes.keySet()) proc.execute(name); for(String name : LOCAL_FUNCTIONS.keySet()) proc.execute(name); } }; } protected void removeTransientImports() { ArrayList newEntries = new ArrayList(importEntries.size()); for(CommandSessionImportEntry entry : importEntries) if(entry.persistent) newEntries.add(entry); importEntries = newEntries; } public THashMap localNamesForContentProposals() { THashMap result = new THashMap(); for(Map.Entry 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 ""; } } 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 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 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 getVariables() { return variableTypes.keySet(); } public ArrayList getImportEntries() { return importEntries; } public void setImportEntries( ArrayList 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); } } }