package org.sugarj.driver;
import static org.sugarj.common.FileCommands.toWindowsPath;
import static org.sugarj.common.Log.log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.spoofax.interpreter.core.InterpreterException;
import org.spoofax.interpreter.library.IOAgent;
import org.spoofax.interpreter.terms.IStrategoTerm;
import org.spoofax.jsglr.client.InvalidParseTableException;
import org.spoofax.jsglr.client.SGLR;
import org.spoofax.jsglr.shared.BadTokenException;
import org.spoofax.jsglr.shared.SGLRException;
import org.spoofax.jsglr.shared.TokenExpectedException;
import org.strategoxt.HybridInterpreter;
import org.strategoxt.lang.Context;
import org.strategoxt.lang.StrategoException;
import org.strategoxt.lang.StrategoExit;
import org.strategoxt.strj.main_strj_0_0;
import org.sugarj.AbstractBaseProcessor;
import org.sugarj.common.ATermCommands;
import org.sugarj.common.Environment;
import org.sugarj.common.FileCommands;
import org.sugarj.common.FilteringIOAgent;
import org.sugarj.common.Log;
import org.sugarj.common.path.Path;
import org.sugarj.driver.caching.ModuleKey;
import org.sugarj.driver.caching.ModuleKeyCache;
import org.sugarj.driver.transformations.extraction.extract_editor_0_0;
import org.sugarj.driver.transformations.extraction.extract_str_0_0;
import org.sugarj.driver.transformations.renaming.rename_rules_0_2;
import org.sugarj.stdlib.StdLib;
/**
* This class provides methods for various SDF commands. Each
* SDF command is represented by a separate method of a similar
* name.
*
* @author Sebastian Erdweg <seba at informatik uni-marburg de>
*/
public class STRCommands {
private static IOAgent strjIOAgent = new FilteringIOAgent(Log.CORE | Log.TRANSFORM,
Pattern.quote("[ strj | info ]") + ".*",
Pattern.quote("[ strj | error ] Compilation failed") + ".*",
Pattern.quote("[ strj | warning ] Nullary constructor") + ".*");
private final static Pattern STR_FILE_PATTERN = Pattern.compile(".*\\.str");
private final static Path FAILED_COMPILATION_PATH;
static {
try {
FAILED_COMPILATION_PATH = FileCommands.newTempFile("ctree");
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
private SGLR strParser;
private ModuleKeyCache<Path> strCache;
private Environment environment;
private AbstractBaseProcessor baseProcessor;
public STRCommands(SGLR strParser, ModuleKeyCache<Path> strCache, Environment environment, AbstractBaseProcessor baseProcessor) {
this.strParser = strParser;
this.strCache = strCache;
this.environment = environment;
this.baseProcessor = baseProcessor;
}
/**
* Compiles a {@code *.str} file to a single {@code *.java} file.
*/
private static void strj(boolean normalize, Path str, Path out, String main, Collection<Path> paths, AbstractBaseProcessor baseProcessor) throws IOException {
/*
* We can include as many paths as we want here, checking the
* adequacy of the occurring imports is done elsewhere.
*/
List<String> cmd = new ArrayList<String>(Arrays.asList(new String[] {
"-i", toWindowsPath(str.getAbsolutePath()),
"-o", toWindowsPath(out.getAbsolutePath()),
"-m", main,
"-p", "sugarj",
"--library",
"-O", "0",
}));
if (normalize)
cmd.add("-F");
cmd.add("-I");
cmd.add(StdLib.stdLibDir.getAbsolutePath());
cmd.add("-I");
cmd.add(baseProcessor.getLanguage().getPluginDirectory().getAbsolutePath());
for (Path path : paths)
if (path.getFile().isDirectory()){
cmd.add("-I");
cmd.add(path.getAbsolutePath());
}
final ByteArrayOutputStream log = new ByteArrayOutputStream();
// Strj requires a fresh context each time.
Context ctx = org.strategoxt.strj.strj.init();
try {
ctx.setIOAgent(strjIOAgent);
ctx.invokeStrategyCLI(main_strj_0_0.instance, "strj", cmd.toArray(new String[cmd.size()]));
}
catch (StrategoExit e) {
if (e.getValue() != 0)
throw new StrategoException("STRJ failed", e);
} finally {
if (log.size() > 0 && !log.toString().contains("Abstract syntax in"))
throw new StrategoException(log.toString());
}
}
public Path compile(Path str, String main, Map<Path, Integer> dependentFiles) throws TokenExpectedException, BadTokenException, IOException, InvalidParseTableException, SGLRException {
return STRCommands.compile(str, main, dependentFiles, strParser, strCache, environment, baseProcessor);
}
public static Path compile(Path str,
String main,
Map<Path, Integer> dependentFiles,
SGLR strParser,
ModuleKeyCache<Path> strCache,
Environment environment,
AbstractBaseProcessor baseProcessor) throws IOException,
InvalidParseTableException,
TokenExpectedException,
BadTokenException,
SGLRException {
ModuleKey key = getModuleKeyForAssimilation(str, main, dependentFiles, strParser);
Path prog = lookupAssimilationInCache(strCache, key);
StrategoException error = null;
if (prog == null) {
try {
prog = generateAssimilator(key, str, main, environment.getIncludePath(), baseProcessor);
} catch (StrategoException e) {
prog = FAILED_COMPILATION_PATH;
error = e;
} finally {
if (prog != null && FileCommands.exists(prog) && !FileCommands.isEmptyFile(prog))
prog = cacheAssimilator(strCache, key, prog, environment);
}
if (error != null)
throw error;
}
return prog;
}
private static Path generateAssimilator(ModuleKey key,
Path str,
String main,
List<Path> paths,
AbstractBaseProcessor baseProcessor) throws IOException {
boolean success = false;
log.beginTask("Generating", "Generate the assimilator", Log.TRANSFORM);
try {
Path prog = FileCommands.newTempFile("ctree");
log.log("calling STRJ", Log.TRANSFORM);
strj(true, str, prog, main, paths, baseProcessor);
success = FileCommands.exists(prog);
return prog;
} finally {
log.endTask(success);
}
}
private static Path cacheAssimilator(ModuleKeyCache<Path> strCache, ModuleKey key, Path prog, Environment environment) throws IOException {
if (strCache == null)
return prog;
log.beginTask("Caching", "Cache assimilator", Log.CACHING);
try {
Path cacheProg = environment.createCachePath(prog.getFile().getName());
if (FileCommands.exists(prog))
FileCommands.copyFile(prog, cacheProg);
else
cacheProg = prog;
Path oldProg = strCache.putGet(key, cacheProg);
// FileCommands.delete(oldProg);
log.log("Cache Location: " + cacheProg, Log.CACHING);
return cacheProg;
} finally {
log.endTask();
}
}
private static Path lookupAssimilationInCache(ModuleKeyCache<Path> strCache, ModuleKey key) {
if (strCache == null)
return null;
Path result = null;
log.beginTask("Searching", "Search assimilator in cache", Log.CACHING);
try {
result = strCache.get(key);
if (result == null || !result.getFile().exists())
return null;
log.log("Cache location: '" + result + "'", Log.CACHING);
return result;
} finally {
log.endTask(result != null);
}
}
private static ModuleKey getModuleKeyForAssimilation(Path str, String main, Map<Path, Integer> dependentFiles, SGLR strParser) throws IOException, InvalidParseTableException, TokenExpectedException, BadTokenException, SGLRException {
log.beginTask("Generating", "Generate module key for current assimilation", Log.CACHING);
try {
IStrategoTerm aterm = (IStrategoTerm) strParser.parse(FileCommands.readFileAsString(str), str.getAbsolutePath(), "StrategoModule");
aterm = ATermCommands.getApplicationSubterm(aterm, "Module", 1);
return new ModuleKey(dependentFiles, STR_FILE_PATTERN, aterm);
} catch (Exception e) {
throw new SGLRException(strParser, "could not parse STR file " + str, e);
} finally {
log.endTask();
}
}
public IStrategoTerm assimilate(String strategy, Path ctree, IStrategoTerm in) throws IOException {
return STRCommands.assimilate(strategy, ctree, in, baseProcessor.getInterpreter());
}
public static IStrategoTerm assimilate(Path ctree, IStrategoTerm in, HybridInterpreter interp) throws IOException {
return assimilate("internal-main", ctree, in, interp);
}
public static IStrategoTerm assimilate(String strategy, Path ctree, IStrategoTerm in, HybridInterpreter interp) throws IOException {
try {
interp.load(ctree.getAbsolutePath());
interp.setCurrent(in);
if (interp.invoke(strategy)) {
IStrategoTerm term = interp.current();
//XXX performance improvement?
// interp.reset();
// IToken left = ImploderAttachment.getLeftToken(in);
// IToken right = ImploderAttachment.getRightToken(in);
// String sort = ImploderAttachment.getSort(in);
//
// try {
// term = ATermCommands.makeMutable(term);
// ImploderAttachment.putImploderAttachment(term, false, sort, left, right);
// } catch (Exception e) {
// log.log("origin annotation failed");
// }
return term;
}
else
throw new RuntimeException("hybrid interpreter failed");
}
catch (InterpreterException e) {
throw new StrategoException("desugaring failed: " + (e.getCause() == null ? e : e.getCause()).getMessage(), e);
}
catch (Exception e) {
throw new StrategoException("desugaring failed", e);
}
}
/**
* Filters Stratego statements from the given term
* and compiles desugaring statements to Stratego.
*
* @param term a file containing a list of SDF
* and Stratego statements.
* @param str result file
* @throws InvalidParseTableException
*/
public static IStrategoTerm extractSTR(IStrategoTerm term) throws IOException {
IStrategoTerm result = null;
Context extractionContext = SugarJContexts.extractionContext();
try {
result = extract_str_0_0.instance.invoke(extractionContext, term);
}
catch (StrategoExit e) {
if (e.getValue() != 0 || result == null)
throw new RuntimeException("Stratego extraction failed", e);
} finally {
SugarJContexts.releaseContext(extractionContext);
}
return result;
}
/**
* Filters Spoofax editor-service declarations.
*
* @param term a SugarJ extension declaration.
*/
public static IStrategoTerm extractEditor(IStrategoTerm term) throws IOException {
IStrategoTerm result = null;
Context extractionContext = SugarJContexts.extractionContext();
try {
result = extract_editor_0_0.instance.invoke(extractionContext, term);
}
catch (StrategoExit e) {
if (e.getValue() != 0 || result == null)
throw new RuntimeException("Editor service extraction failed", e);
} finally {
SugarJContexts.releaseContext(extractionContext);
}
return result;
}
public static IStrategoTerm renameRules(IStrategoTerm term, String oldName, String newName) throws IOException {
IStrategoTerm result = null;
Context renameRulesContext = SugarJContexts.renameRulesContext();
try {
IStrategoTerm toldName = renameRulesContext.getFactory().makeString(oldName);
IStrategoTerm tnewName = renameRulesContext.getFactory().makeString(newName);
result = rename_rules_0_2.instance.invoke(renameRulesContext, term, toldName, tnewName);
}
catch (StrategoExit e) {
if (e.getValue() != 0 || result == null)
throw new RuntimeException("Stratego extraction failed", e);
} finally {
SugarJContexts.releaseContext(renameRulesContext);
}
return result;
}
}