/*******************************************************************************
* PSHDL is a library and (trans-)compiler for PSHDL input. It generates
* output suitable for implementation or simulation of it.
*
* Copyright (C) 2014 Karsten Becker (feedback (at) pshdl (dot) org)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This License does not grant permission to use the trade names, trademarks,
* service marks, or product names of the Licensor, except as required for
* reasonable and customary use in describing the origin of the Work.
*
* Contributors:
* Karsten Becker - initial API and implementation
******************************************************************************/
package org.pshdl.model.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.pshdl.model.HDLAssignment;
import org.pshdl.model.HDLClass;
import org.pshdl.model.HDLCompound;
import org.pshdl.model.HDLDeclaration;
import org.pshdl.model.HDLEnum;
import org.pshdl.model.HDLEnumDeclaration;
import org.pshdl.model.HDLEnumRef;
import org.pshdl.model.HDLForLoop;
import org.pshdl.model.HDLIfStatement;
import org.pshdl.model.HDLInterface;
import org.pshdl.model.HDLInterfaceDeclaration;
import org.pshdl.model.HDLInterfaceInstantiation;
import org.pshdl.model.HDLInterfaceRef;
import org.pshdl.model.HDLObject;
import org.pshdl.model.HDLObject.GenericMeta;
import org.pshdl.model.HDLPackage;
import org.pshdl.model.HDLStatement;
import org.pshdl.model.HDLSwitchCaseStatement;
import org.pshdl.model.HDLSwitchStatement;
import org.pshdl.model.HDLType;
import org.pshdl.model.HDLUnit;
import org.pshdl.model.HDLVariable;
import org.pshdl.model.HDLVariableDeclaration;
import org.pshdl.model.HDLVariableDeclaration.HDLDirection;
import org.pshdl.model.HDLVariableRef;
import org.pshdl.model.IHDLObject;
import org.pshdl.model.extensions.FullNameExtension;
import org.pshdl.model.simulation.PStoEXCompiler;
import org.pshdl.model.utils.HDLQuery.HDLFieldAccess;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
public class TestcaseReducer {
public static interface Validator {
/**
*
* @param pkg
* @return <code>true</code> when the code exhibits the unwanted
* behaviour
*/
boolean inspect(HDLPackage pkg);
}
public static class CrashValidator implements Validator {
public static interface CrashValidatorRunnable {
public void run(String src, HDLPackage pkg) throws Throwable;
}
public static class SimulationCrasher implements CrashValidatorRunnable {
private final HDLQualifiedName moduleName;
public SimulationCrasher(String moduleName) {
this.moduleName = new HDLQualifiedName(moduleName);
}
@Override
public void run(String src, HDLPackage pkg) throws Throwable {
Insulin.resetID();
final HDLUnit unit = HDLQuery.select(HDLUnit.class).from(pkg).whereObj().fullNameIs(moduleName).getFirst();
if (unit != null) {
PStoEXCompiler.createExecutable(unit, src, false, true);
}
}
}
public static class InsulinCrasher implements CrashValidatorRunnable {
@Override
public void run(String src, HDLPackage pkg) throws Throwable {
Insulin.resetID();
Insulin.transform(pkg, src);
}
}
private String expectedStacktrace;
private final CrashValidatorRunnable runnable;
private final String src;
public CrashValidator(String src, HDLPackage pkg, CrashValidatorRunnable runnable) {
boolean crashed = true;
this.runnable = runnable;
this.src = src;
try {
runnable.run(src, pkg);
crashed = false;
} catch (final Throwable e) {
expectedStacktrace = filter(Throwables.getStackTraceAsString(e));
System.out.println("Looking for " + expectedStacktrace);
}
if (!crashed)
throw new IllegalArgumentException("The runnable did not crash");
}
@Override
public boolean inspect(HDLPackage pkg) {
try {
runnable.run(src, pkg);
} catch (final Throwable e) {
final String traceAsString = filter(Throwables.getStackTraceAsString(e));
return traceAsString.equals(expectedStacktrace);
}
return false;
}
private String filter(String stackTraceAsString) {
final Iterable<String> split = Splitter.on('\n').split(stackTraceAsString);
final StringBuffer sb = new StringBuffer();
final Iterator<String> iter = split.iterator();
final String firstLine = iter.next();
sb.append(Splitter.on(':').split(firstLine).iterator().next()).append('\n');
while (iter.hasNext()) {
final String string = iter.next();
if (string.startsWith("\tat " + CrashValidator.class.getName()))
return sb.toString();
sb.append(string).append('\n');
}
return sb.toString();
}
}
public static interface Reducer {
/**
* Attempt to reduce the provided pkg
*
* @param pkg
* @return <code>null</code> when no reduction was possible
*/
public HDLPackage reduce(HDLPackage pkg);
}
public static class VariableRemover implements Reducer {
private static GenericMeta<Boolean> ATTEMPTEDREMOVE = new GenericMeta<>("ATTEMPTED_REMOVE_VARIABLE", true);
@Override
public HDLPackage reduce(HDLPackage pkg) {
final HDLVariable[] variables = pkg.getAllObjectsOf(HDLVariable.class, true);
for (final HDLVariable hdlVariable : variables) {
if (!hdlVariable.hasMeta(ATTEMPTEDREMOVE)) {
// System.out.println("TestcaseReducer.VariableRemover.reduce()"
// + hdlVariable.getName());
hdlVariable.setMeta(ATTEMPTEDREMOVE);
final HDLUnit unit = hdlVariable.getContainer(HDLUnit.class);
if (unit != null) {
final HDLUnit removed = Refactoring.removeVariable(unit, hdlVariable, Refactoring.SUBSTITUTE_RESOLVER);
final ModificationSet ms = new ModificationSet();
final HDLDirection direction = hdlVariable.getDirection();
if (direction != null) {
switch (direction) {
case IN:
case INOUT:
case OUT:
findAndRemoveReferences(pkg, hdlVariable, unit, ms);
break;
case PARAMETER:
default:
}
}
ms.replace(unit, removed);
return ms.apply(pkg);
}
}
}
return null;
}
private void findAndRemoveReferences(HDLPackage pkg, final HDLVariable hdlVariable, final HDLUnit unit, final ModificationSet ms) {
final HDLQualifiedName fqn = FullNameExtension.fullNameOf(unit);
final HDLInterfaceInstantiation[] allII = pkg.getAllObjectsOf(HDLInterfaceInstantiation.class, true);
for (final HDLInterfaceInstantiation hdi : allII) {
final Optional<HDLInterface> resolveHIf = hdi.resolveHIf();
if (resolveHIf.isPresent()) {
final HDLQualifiedName resolved = resolveHIf.get().asRef();
if (resolved.equals(fqn)) {
final Collection<HDLInterfaceRef> iRefs = HDLQuery.getInterfaceRefs(pkg, hdi.getVar());
for (final HDLInterfaceRef hiRef : iRefs) {
final Optional<HDLVariable> resolveVar = hiRef.resolveVar();
if (resolveVar.isPresent()) {
final HDLVariable hiVar = resolveVar.get();
if (hiVar.getName().equals(hdlVariable.getName())) {
if (hiRef.getContainer() instanceof HDLAssignment) {
ms.remove(hiRef.getContainer());
} else {
Refactoring.SubstituteExpressionResolver.replaceWithZeroOrConstant(hiRef, ms);
}
}
}
}
}
}
}
}
}
public static class StatementRemover implements Reducer {
private static GenericMeta<Boolean> ATTEMPTEDREMOVE = new GenericMeta<>("ATTEMPTED_REMOVE_STMNT", false);
@Override
public HDLPackage reduce(HDLPackage pkg) {
final HDLStatement[] stmnts = pkg.getAllObjectsOf(HDLStatement.class, true);
for (final HDLStatement statement : stmnts) {
if (!statement.hasMeta(ATTEMPTEDREMOVE)) {
final HDLFieldAccess<?, ?> feature = statement.getContainingFeature();
if ((feature != null) && !HDLStatement.class.isAssignableFrom(feature.type)) {
continue;
}
statement.setMeta(ATTEMPTEDREMOVE);
final ModificationSet ms = new ModificationSet();
ms.remove(statement);
return ms.apply(pkg);
}
}
return null;
}
}
public static class EmptyCompoundRemover implements Reducer {
private static GenericMeta<Boolean> ATTEMPTEDREMOVE = new GenericMeta<>("ATTEMPTED_REMOVE_COMPOUND", false);
@Override
public HDLPackage reduce(HDLPackage pkg) {
// final HDLUnit[] units = pkg.getAllObjectsOf(HDLUnit.class, true);
// for (final HDLUnit unit : units) {
// if (unit.hasMeta(ATTEMPTEDREMOVE)) {
// continue;
// }
// if (unit.getInits().isEmpty() && unit.getStatements().isEmpty())
// return remove(unit, pkg);
// }
final HDLCompound[] compounds = pkg.getAllObjectsOf(HDLCompound.class, true);
for (final HDLCompound hdlCompound : compounds) {
if (hdlCompound.hasMeta(ATTEMPTEDREMOVE)) {
continue;
}
switch (hdlCompound.getClassType()) {
case HDLIfStatement:
final HDLIfStatement hif = (HDLIfStatement) hdlCompound;
if (hif.getThenDo().isEmpty() && hif.getElseDo().isEmpty())
return remove(hif, pkg);
break;
case HDLForLoop:
final HDLForLoop forLoop = (HDLForLoop) hdlCompound;
if (forLoop.getDos().isEmpty())
return remove(forLoop, pkg);
break;
case HDLSwitchCaseStatement:
final HDLSwitchCaseStatement caseStatement = (HDLSwitchCaseStatement) hdlCompound;
if ((caseStatement.getLabel() != null) && caseStatement.getDos().isEmpty())
return remove(caseStatement, pkg);
break;
case HDLSwitchStatement:
final HDLSwitchStatement switchStatement = (HDLSwitchStatement) hdlCompound;
final ArrayList<HDLSwitchCaseStatement> cases = switchStatement.getCases();
if (cases.size() == 1) {
final HDLSwitchCaseStatement defaultCase = cases.get(0);
if (defaultCase.getDos().isEmpty())
return remove(switchStatement, pkg);
}
break;
default:
}
}
return null;
}
private HDLPackage remove(IHDLObject hif, HDLPackage pkg) {
hif.setMeta(ATTEMPTEDREMOVE);
final ModificationSet ms = new ModificationSet();
ms.remove(hif);
return ms.apply(pkg);
}
}
public static Reducer[] REDUCERS = new Reducer[] { new VariableRemover(), new EmptyCompoundRemover(), new StatementRemover() };
public HDLPackage reduce(HDLPackage pkg, Validator validator, String src) {
return reduce(pkg, src, validator, REDUCERS);
}
private static int libURICounter = 0;
private HDLPackage reduce(HDLPackage pkg, String src, Validator validator, Reducer[] reductions) {
final boolean inspect = validator.inspect(pkg);
if (!inspect)
throw new IllegalArgumentException("The input does not exhibit the unwanted effect");
final HDLPackage asPackage = importInterfaces(pkg);
pkg.getLibrary().addPkg(asPackage, src);
if (validator.inspect(asPackage)) {
pkg = asPackage;
} else {
System.out.println("Failed to use full library replacement");
}
System.out.println(pkg);
boolean reduction = false;
int reductionCount = 0;
final List<String> regLibs = Lists.newArrayList();
do {
reduction = false;
for (final Reducer reducer : reductions) {
HDLPackage reduced = null;
do {
reduced = reducer.reduce(pkg);
if (reduced != null) {
try {
reduced.validateAllFields(reduced.getContainer(), true);
final HDLLibrary lib = new HDLLibrary();
final String libURI = "reduceLib" + libURICounter++;
HDLLibrary.registerLibrary(libURI, lib);
regLibs.add(libURI);
final HDLPackage setLibURI = reduced.setLibURI(libURI).copyDeepFrozen(null);
lib.addPkg(setLibURI, src);
if (validator.inspect(reduced)) {
reduction = true;
reductionCount++;
pkg = reduced;
}
} catch (final Exception e) {
}
}
} while (reduced != null);
}
} while (reduction);
for (final String string : regLibs) {
if (!string.equals(pkg.getLibURI())) {
HDLLibrary.unregister(string);
}
}
System.out.println("Applied " + reductionCount + " reductions");
return pkg;
}
private HDLPackage importInterfaces(HDLPackage imp) {
HDLPackage pkg = imp;
final HDLDeclaration[] decls = pkg.getAllObjectsOf(HDLDeclaration.class, true);
final Set<HDLQualifiedName> declared = Sets.newHashSet();
for (final HDLDeclaration decl : decls) {
HDLQualifiedName fqn = null;
switch (decl.getClassType()) {
case HDLFunction:
fqn = FullNameExtension.fullNameOf(decl);
break;
case HDLEnumDeclaration:
fqn = FullNameExtension.fullNameOf(((HDLEnumDeclaration) decl).getHEnum());
break;
case HDLInterfaceDeclaration:
fqn = FullNameExtension.fullNameOf(((HDLInterfaceDeclaration) decl).getHIf());
break;
default:
}
if (fqn != null) {
declared.add(fqn);
}
}
for (final HDLUnit unit : pkg.getUnits()) {
final HDLQualifiedName fqn = FullNameExtension.fullNameOf(unit);
declared.add(fqn);
}
final HDLInterfaceInstantiation[] hii = pkg.getAllObjectsOf(HDLInterfaceInstantiation.class, true);
for (final HDLInterfaceInstantiation hi : hii) {
final Optional<HDLInterface> resolveHIf = hi.resolveHIf();
if (resolveHIf.isPresent()) {
final HDLInterface hif = resolveHIf.get();
final HDLQualifiedName fqn = FullNameExtension.fullNameOf(hif);
if (!declared.contains(fqn)) {
pkg = pkg.addDeclarations(new HDLInterfaceDeclaration().setHIf(hif));
declared.add(fqn);
}
}
}
final HDLEnumRef[] her = pkg.getAllObjectsOf(HDLEnumRef.class, true);
for (final HDLEnumRef henumRef : her) {
final Optional<HDLEnum> resolveHEnum = henumRef.resolveHEnum();
if (resolveHEnum.isPresent()) {
final HDLEnum hdlEnum = resolveHEnum.get();
final HDLQualifiedName fqn = FullNameExtension.fullNameOf(hdlEnum);
if (!declared.contains(fqn)) {
pkg = pkg.addDeclarations(new HDLEnumDeclaration().setHEnum(hdlEnum.setName(fqn.toString())));
declared.add(fqn);
}
}
}
final HDLVariableDeclaration[] hvds = pkg.getAllObjectsOf(HDLVariableDeclaration.class, true);
for (final HDLVariableDeclaration hvd : hvds) {
final Optional<? extends HDLType> resolveType = hvd.resolveType();
if (resolveType.isPresent()) {
final HDLType type = resolveType.get();
if (type.getClassType() == HDLClass.HDLEnum) {
final HDLEnum hdlEnum = (HDLEnum) type;
final HDLQualifiedName fqn = FullNameExtension.fullNameOf(hdlEnum);
if (!declared.contains(fqn)) {
pkg = pkg.addDeclarations(new HDLEnumDeclaration().setHEnum(hdlEnum.setName(fqn.toString())));
declared.add(fqn);
}
}
}
}
final HDLVariableRef[] refs = pkg.getAllObjectsOf(HDLVariableRef.class, true);
for (final HDLVariableRef varRef : refs) {
final Optional<HDLVariable> resolveVar = varRef.resolveVar();
if (resolveVar.isPresent()) {
final HDLVariable variable = resolveVar.get();
if (variable.getContainer(HDLPackage.class) != imp) {
final HDLVariableDeclaration hvd = variable.getContainer(HDLVariableDeclaration.class);
if (hvd != null) {
pkg = pkg.addDeclarations(hvd.setVariables(HDLObject.asList(variable)));
}
}
}
}
return pkg.copyDeepFrozen(null);
}
}