/******************************************************************************* * Copyright (c) 2009-2013 CWI * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI * * Tijs van der Storm - Tijs.van.der.Storm@cwi.nl * * Mark Hills - Mark.Hills@cwi.nl (CWI) *******************************************************************************/ package org.rascalmpl.library.util; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.io.StringWriter; import java.io.Writer; import java.net.URI; import java.net.URISyntaxException; import org.rascalmpl.interpreter.ConsoleRascalMonitor; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.interpreter.IEvaluator; import org.rascalmpl.interpreter.IEvaluatorContext; import org.rascalmpl.interpreter.env.GlobalEnvironment; import org.rascalmpl.interpreter.env.ModuleEnvironment; import org.rascalmpl.interpreter.load.SourceLocationListContributor; import org.rascalmpl.interpreter.load.StandardLibraryContributor; import org.rascalmpl.interpreter.result.IRascalResult; import org.rascalmpl.interpreter.result.Result; import org.rascalmpl.interpreter.utils.LimitedResultWriter.IOLimitReachedException; import org.rascalmpl.interpreter.utils.RuntimeExceptionFactory; import org.rascalmpl.library.Prelude; import org.rascalmpl.library.experiments.Compiler.RVM.Interpreter.RascalExecutionContext; import org.rascalmpl.library.experiments.Compiler.RVM.Interpreter.RascalRuntimeException; import org.rascalmpl.library.experiments.Compiler.RVM.Interpreter.ToplevelType; import org.rascalmpl.library.lang.rascal.syntax.RascalParser; import org.rascalmpl.parser.Parser; import org.rascalmpl.parser.gtd.io.InputConverter; import org.rascalmpl.parser.gtd.result.action.IActionExecutor; import org.rascalmpl.parser.gtd.result.out.DefaultNodeFlattener; import org.rascalmpl.parser.uptr.UPTRNodeFactory; import org.rascalmpl.parser.uptr.action.NoActionExecutor; import org.rascalmpl.repl.LimitedLineWriter; import org.rascalmpl.repl.LimitedWriter; import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.URIUtil; import org.rascalmpl.value.IBool; import org.rascalmpl.value.IConstructor; import org.rascalmpl.value.IInteger; import org.rascalmpl.value.IList; import org.rascalmpl.value.IListWriter; import org.rascalmpl.value.IMap; import org.rascalmpl.value.INode; import org.rascalmpl.value.ISet; import org.rascalmpl.value.ISourceLocation; import org.rascalmpl.value.IString; import org.rascalmpl.value.ITuple; import org.rascalmpl.value.IValue; import org.rascalmpl.value.IValueFactory; import org.rascalmpl.value.io.StandardTextWriter; import org.rascalmpl.value.type.Type; import org.rascalmpl.values.ValueFactoryFactory; import org.rascalmpl.values.uptr.ITree; import org.rascalmpl.values.uptr.RascalValueFactory; import org.rascalmpl.values.uptr.TreeAdapter; public class Reflective { protected final IValueFactory values; private Evaluator cachedEvaluator; private int robin = 0; protected final Prelude prelude; private static final int maxCacheRounds = 500; public Reflective(IValueFactory values){ super(); this.values = values; prelude = new Prelude(values); } public IValue getRascalClasspath(IEvaluatorContext ctx) { return values.string(ctx.getConfiguration().getRascalJavaClassPathProperty()); } IEvaluator<?> getDefaultEvaluator(PrintWriter stdout, PrintWriter stderr) { GlobalEnvironment heap = new GlobalEnvironment(); ModuleEnvironment root = heap.addModule(new ModuleEnvironment(ModuleEnvironment.SHELL_MODULE, heap)); IValueFactory vf = ValueFactoryFactory.getValueFactory(); Evaluator evaluator = new Evaluator(vf, stderr, stdout, root, heap); evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); evaluator.setMonitor(new ConsoleRascalMonitor()); return evaluator; } public IList evalCommands(IList commands, ISourceLocation loc, IEvaluatorContext ctx) { StringWriter out = new StringWriter(); StringWriter err = new StringWriter(); IListWriter result = values.listWriter(); IEvaluator<?> evaluator = getDefaultEvaluator(new PrintWriter(out), new PrintWriter(err)); int outOffset = 0; int errOffset = 0; for (IValue v: commands) { String errOut = ""; boolean exc = false; Result<IValue> x = null; try { x = evaluator.eval(evaluator.getMonitor(), ((IString)v).getValue(), loc); } catch (Throwable e) { errOut = err.getBuffer().substring(errOffset); errOffset += errOut.length(); errOut += e.getMessage(); exc = true; } String output = out.getBuffer().substring(outOffset); outOffset += output.length(); if (!exc) { errOut += err.getBuffer().substring(errOffset); errOffset += errOut.length(); } String s; try { s = printResult(x); ITuple tuple = values.tuple(values.string(s), values.string(output), values.string(errOut)); result.append(tuple); } catch (IOException e) { continue; } } IList results = result.done(); return results; } private final static int LINE_LIMIT = 200; private final static int CHAR_LIMIT = LINE_LIMIT * 20; private String printResult(IRascalResult result) throws IOException { if (result == null) { return ""; } StringWriter sw = new StringWriter(); PrintWriter out = new PrintWriter(sw); IValue value = result.getValue(); if (value == null) { return ""; } Type type = result.getType(); StandardTextWriter indentedPrettyPrinter = new StandardTextWriter(); if (type.isAbstractData() && type.isSubtypeOf(RascalValueFactory.Tree)) { out.print(type.toString()); out.print(": "); // we first unparse the tree out.print("`"); TreeAdapter.yield((IConstructor)result.getValue(), true, out); out.println("`"); // write parse tree out one a single line for reference out.print("Tree: "); StandardTextWriter singleLinePrettyPrinter = new StandardTextWriter(false); try (Writer wrt = new LimitedWriter(out, CHAR_LIMIT)) { singleLinePrettyPrinter.write(value, wrt); } catch (IOLimitReachedException e) { // ignore since this is what we wanted } } else { out.print(type.toString()); out.print(": "); // limit both the lines and the characters try (Writer wrt = new LimitedWriter(new LimitedLineWriter(out,LINE_LIMIT), CHAR_LIMIT)) { indentedPrettyPrinter.write(value, wrt); } catch (IOLimitReachedException e) { // ignore since this is what we wanted } } out.flush(); return sw.toString(); } // REFLECT -- copy in ReflectiveCompiled public IValue parseCommand(IString str, ISourceLocation loc, IEvaluatorContext ctx) { IEvaluator<?> evaluator = ctx.getEvaluator(); return evaluator.parseCommand(evaluator.getMonitor(), str.getValue(), loc); } // REFLECT -- copy in ReflectiveCompiled public IValue parseCommands(IString str, ISourceLocation loc, IEvaluatorContext ctx) { IEvaluator<?> evaluator = ctx.getEvaluator(); return evaluator.parseCommands(evaluator.getMonitor(), str.getValue(), loc); } // REFLECT -- copy in ReflectiveCompiled public IValue parseModuleAndFragments(ISourceLocation loc, IEvaluatorContext ctx) { try { Evaluator ownEvaluator = getPrivateEvaluator(ctx); return ownEvaluator.parseModuleAndFragments(ownEvaluator.getMonitor(), loc); } catch (IOException e) { throw RuntimeExceptionFactory.io(values.string(e.getMessage()), null, null); } catch (Throwable e) { throw RuntimeExceptionFactory.javaException(e, null, null); } } private Evaluator getPrivateEvaluator(IEvaluatorContext ctx) { if (cachedEvaluator == null || robin++ > maxCacheRounds) { robin = 0; IEvaluator<?> callingEval = ctx.getEvaluator(); GlobalEnvironment heap = new GlobalEnvironment(); ModuleEnvironment root = heap.addModule(new ModuleEnvironment("$parser$", heap)); cachedEvaluator = new Evaluator(callingEval.getValueFactory(), callingEval.getStdErr(), callingEval.getStdOut(), root, heap); // Update the classpath so it is the same as in the context interpreter. cachedEvaluator.getConfiguration().setRascalJavaClassPathProperty(ctx.getConfiguration().getRascalJavaClassPathProperty()); // clone the classloaders for (ClassLoader loader : ctx.getEvaluator().getClassLoaders()) { cachedEvaluator.addClassLoader(loader); } } return cachedEvaluator; } // REFLECT -- copy in ReflectiveCompiled public IValue parseModuleAndFragments(IString str, ISourceLocation loc, IEvaluatorContext ctx) { Evaluator ownEvaluator = getPrivateEvaluator(ctx); return ownEvaluator.parseModuleAndFragments(ownEvaluator.getMonitor(), str.getValue().toCharArray(), loc); } // REFLECT -- copy in ReflectiveCompiled public IValue parseModuleAndFragments(ISourceLocation loc, final IList searchPath, IEvaluatorContext ctx) { final Evaluator ownEvaluator = getPrivateEvaluator(ctx); // add the given locations to the search path SourceLocationListContributor contrib = new SourceLocationListContributor("reflective", searchPath); ownEvaluator.addRascalSearchPathContributor(contrib); try { return ownEvaluator.parseModuleAndFragments(ownEvaluator.getMonitor(), loc); } catch (IOException e) { throw RuntimeExceptionFactory.io(values.string(e.getMessage()), null, null); } catch (Throwable e) { throw RuntimeExceptionFactory.javaException(e, null, null); } finally { ownEvaluator.removeSearchPathContributor(contrib); } } protected char[] getResourceContent(ISourceLocation location) throws IOException{ char[] data; Reader textStream = null; URIResolverRegistry resolverRegistry = URIResolverRegistry.getInstance(); try { textStream = resolverRegistry.getCharacterReader(location); data = InputConverter.toChar(textStream); } finally{ if(textStream != null){ textStream.close(); } } return data; } public IValue parseModule(ISourceLocation loc) { ITree tree = (ITree) parseModuleWithSpaces(loc); if (TreeAdapter.isAmb(tree)) { return tree; } ITree top = TreeAdapter.getStartTop(tree); return top; } public IValue parseModuleWithSpaces(ISourceLocation loc) { IActionExecutor<ITree> actions = new NoActionExecutor(); try { return new RascalParser().parse(Parser.START_MODULE, loc.getURI(), getResourceContent(loc), actions, new DefaultNodeFlattener<IConstructor, ITree, ISourceLocation>(), new UPTRNodeFactory(true)); } catch (IOException e) { throw RuntimeExceptionFactory.io(values.string(e.getMessage()), null, null); } } public IValue parseNamedModuleWithSpaces(IString modulePath, IEvaluatorContext ctx){ ISourceLocation moduleLoc = ctx.getEvaluator().getRascalResolver().resolveModule(modulePath.getValue()); if(moduleLoc == null){ throw RascalRuntimeException.io(values.string("Module " + modulePath.getValue() + " not found"), null); } return parseModuleWithSpaces(moduleLoc); } // REFLECT -- copy in ReflectiveCompiled public IValue getModuleLocation(IString modulePath, IEvaluatorContext ctx) { ISourceLocation uri = ctx.getEvaluator().getRascalResolver().resolveModule(modulePath.getValue()); if (uri == null) { throw RuntimeExceptionFactory.moduleNotFound(modulePath, ctx.getCurrentAST(), null); } return uri; } // REFLECT -- copy in ReflectiveCompiled public ISourceLocation getSearchPathLocation(IString path, IEvaluatorContext ctx) { String value = path.getValue(); if (path.length() == 0) { throw RuntimeExceptionFactory.io(values.string("File not found in search path: [" + path + "]"), null, null); } if (!value.startsWith("/")) { value = "/" + value; } try { ISourceLocation uri = ctx.getEvaluator().getRascalResolver().resolvePath(value); if (uri == null) { URI parent = URIUtil.getParentURI(URIUtil.createFile(value)); if (parent == null) { // if the parent does not exist we are at the root and we look up the first path contributor: parent = URIUtil.createFile("/"); } // here we recurse on the parent to see if it might exist ISourceLocation result = getSearchPathLocation(values.string(parent.getPath()), ctx); if (result != null) { String child = URIUtil.getURIName(URIUtil.createFile(value)); return URIUtil.getChildLocation(result, child); } throw RuntimeExceptionFactory.io(values.string("File not found in search path: " + path), null, null); } return uri; } catch (URISyntaxException e) { throw RuntimeExceptionFactory.malformedURI(value, null, null); } } // Note -- copy in ReflectiveCompiled public IBool inCompiledMode() { return values.bool(false); } // REFLECT -- copy in ReflectiveCompiled public IValue watch(IValue tp, IValue val, IString name, IEvaluatorContext ctx){ return watch(tp, val, name, values.string(""), ctx); } protected String stripQuotes(IValue suffixVal){ String s1 = suffixVal.toString(); if(s1.startsWith("\"")){ s1 = s1.substring(1, s1.length() - 1); } return s1; } public IString diff(IValue oldVal, IValue newVal){ return values.string(idiff("", oldVal, newVal)); } private String preview(IValue v){ String s = v.toString(); if(s.length() < 80){ return s; } return s.substring(0, 76) + " ..."; } protected String idiff(String indent, IValue oldVal, IValue newVal){ if(!oldVal.getType().equals(newVal.getType())){ return indent + "old " + oldVal.getType() + ", new " + newVal.getType(); } if(oldVal.isEqual(newVal)){ return "no diff"; } if(oldVal.getType().isString()){ IString ov = (IString) oldVal; IString nv = (IString) newVal; String ldiff = (ov.length() == nv.length()) ? "" : ("string length " + ov.length() + " vs " + nv.length() + "; "); for(int i = 0; i < ov.length() && i < nv.length(); i++){ if(ov.charAt(i) != nv.charAt(i)){ return indent + ldiff + "diff at index " + i + " in " + preview(ov) + ": " + ov.charAt(i) + " vs " + nv.charAt(i) + "\n" + indent + "old: " + ov + "\n" + indent + "new: " + nv; } } } if(oldVal.getType().isSourceLocation()){ return indent + "old " + oldVal + "\n" + indent + "new " + newVal; } if(oldVal.getType().isList()){ IList ov = (IList) oldVal; IList nv = (IList) newVal; String ldiff = (ov.length() == nv.length()) ? "" : ("size " + ov.length() + " vs " + nv.length() + "; "); for(int i = 0; i < ov.length() && i < nv.length(); i++){ if(!ov.get(i).isEqual(nv.get(i))){ return indent + ldiff + "diff at index " + i + " in list " + preview(ov) + ":\n" + idiff(indent + " ", ov.get(i), nv.get(i)); } } } if(oldVal.getType().isTuple()){ ITuple ov = (ITuple) oldVal; ITuple nv = (ITuple) newVal; for(int i = 0; i < ov.arity(); i++){ if(!ov.get(i).isEqual(nv.get(i))){ return indent + "diff at index " + i + " in tuple " + preview(ov) + ":\n" + idiff(indent + " ", ov.get(i), nv.get(i)); } } } if(oldVal.getType().isSet()){ ISet ov = (ISet) oldVal; ISet nv = (ISet) newVal; String ldiff = (ov.size() == nv.size()) ? "" : ("size " + ov.size() + " vs " + nv.size() + "; "); ISet diff1 = ov.subtract(nv); String msg1 = diff1.size() == 0 ? "" : indent + "only in old set: " + diff1 + "\n"; //"; "; ISet diff2 = nv.subtract(ov); String msg2 = diff2.size() == 0 ? "" : indent + "only in new set: " + diff2; return ldiff + msg1 + msg2 + "\n"; } if(oldVal.getType().isMap()){ IMap ov = (IMap) oldVal; IMap nv = (IMap) newVal; String ldiff = (ov.size() == nv.size()) ? "" : ("size " + ov.size() + " vs " + nv.size() + "; "); IMap all = ov.join(nv); String onlyInOld = ""; String onlyInOldCurrent = ""; String onlyInNew = ""; String onlyInNewCurrent = ""; String diffVal = ""; String diffValCurrent = ""; int nDiff = 0; for(IValue key : all){ if(!nv.containsKey(key)){ if(onlyInOldCurrent.length() > 80){ onlyInOld += onlyInOldCurrent + "\n" + indent + key; onlyInOldCurrent = ""; } else { onlyInOldCurrent += " " + key; } continue; } if(!ov.containsKey(key)){ if(onlyInNewCurrent.length() > 80){ onlyInNew += onlyInNewCurrent + "\n" + indent + key; onlyInNewCurrent = ""; } else { onlyInNewCurrent += " " + key; } continue; } if(!ov.get(key).isEqual(nv.get(key))){ if(nDiff < 10){ if(diffValCurrent.length() > 80){ diffVal += diffValCurrent + "\n" + indent + key; diffValCurrent = ""; } else { diffValCurrent += " " + key; } nDiff++; } } } onlyInOld += onlyInOldCurrent; onlyInNew += onlyInNewCurrent; diffVal += diffValCurrent; String msg1 = onlyInOld.length() == 0 ? "" : "keys only in old map:" + onlyInOld + "; "; String msg2 = onlyInNew.length() == 0 ? "" : "keys only in new map:" + onlyInNew + "; "; String msg3 = diffVal.length() == 0 ? "" : "some keys with different values:" + diffVal; return indent + ldiff + msg1 + msg2 + msg3; } if(oldVal.getType().isNode()){ INode ov = (INode) oldVal; INode nv = (INode) newVal; String oldName = ov.getName(); String newName = nv.getName(); if(!oldName.equals(newName)){ return indent + "diff in function symbol: " + oldName + " vs " + newName; } int oldArity = ov.arity(); int newArity = nv.arity(); if(oldArity != newArity){ return indent + "diff in arity for function symbol " + oldName + ": "+ oldArity + " vs " + newArity; } for(int i = 0; i < oldArity; i++){ if(!ov.get(i).isEqual(nv.get(i))){ String argId = Integer.toString(i); if(ov instanceof IConstructor){ IConstructor cov = (IConstructor) ov; argId = cov.getChildrenTypes().getFieldName(i); } return indent + "diff at arg " + argId + " for function symbol " + oldName + ": " + preview(ov) + "\n" + idiff(indent + " ", ov.get(i), nv.get(i)); } } } String sOld = oldVal.toString(); if(sOld.length() > 20){ sOld = sOld.substring(0, 20) + "..."; } String sNew = newVal.toString(); if(sNew.length() > 20){ sNew = sNew.substring(0, 20) + "..."; } return indent + "old " + sOld + ", new " + sNew; } // REFLECT -- copy in ReflectiveCompiled public IValue watch(IValue tp, IValue val, IString name, IValue suffixVal, IEvaluatorContext ctx){ ISourceLocation watchLoc; String suffix = stripQuotes(suffixVal); String name1 = stripQuotes(name); String path = "watchpoints/" + (suffix.length() == 0 ? name1 : (name1 + "-" + suffix)) + ".txt"; try { watchLoc = values.sourceLocation("home", null, path, null, null); } catch (URISyntaxException e) { throw RuntimeExceptionFactory.io(values.string("Cannot create |home:///" + name1 + "|"), null, null); } prelude.writeTextValueFile(watchLoc, val); return val; } public IInteger getFingerprint(IValue val, IBool concretePatterns){ return values.integer(ToplevelType.getFingerprint(val, concretePatterns.getValue())); } public IInteger getFingerprint(IValue val, IInteger arity, IBool concretePatterns){ return values.integer(ToplevelType.getFingerprint(val, concretePatterns.getValue()) << 2 + arity.intValue()); } public IInteger getFingerprintNode(INode nd){ return values.integer(ToplevelType.getFingerprintNode(nd)); } }