package org.simantics.db.layer0.scl; import java.util.ArrayList; import java.util.List; import org.simantics.databoard.Bindings; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.RuntimeDatabaseException; import org.simantics.db.request.Read; import org.simantics.layer0.Layer0; 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.EConstant; import org.simantics.scl.compiler.elaboration.expressions.ELiteral; import org.simantics.scl.compiler.elaboration.expressions.ESimpleLet; 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.environment.AbstractLocalEnvironment; import org.simantics.scl.compiler.environment.Environment; import org.simantics.scl.compiler.environment.LocalEnvironment; import org.simantics.scl.compiler.errors.CompilationError; import org.simantics.scl.compiler.runtime.RuntimeEnvironment; import org.simantics.scl.compiler.top.ExpressionEvaluator; 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.compiler.types.exceptions.MatchException; import org.simantics.scl.compiler.types.kinds.Kinds; import org.simantics.scl.compiler.types.util.MultiFunction; import org.simantics.scl.runtime.SCLContext; import org.simantics.scl.runtime.function.Function1; import org.simantics.utils.datastructures.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import gnu.trove.map.hash.THashMap; /** *

This is a base implementation for compiling expressions stored into * graph. It provides a skeleton and a set of methods that must be * implemented to get a concrete compilation request. * *

The request returns an SCL function with type {@code EvaluationContext -> Result} * where {@code EvaluationContext} is the type given by {@link #getContextVariableType()} * and {@code Result} is the type of the expression that can be optionally restricted * by {@link #getExpectedType(ReadGraph, AbstractExpressionCompilationContext)}. * *

Compilation calls {@link #getCompilationContext(ReadGraph)} to read all information * that is needed about the context of the resource during compilation. The result must * extend the type {@link AbstractExpressionCompilationContext} including at least information about * {@link RuntimeEnvironment}. * *

Compilation calls {@link #getVariableAccessExpression(ReadGraph, AbstractExpressionCompilationContext, Variable, String)} * to get access to local environment. The method may return null if the variable is not defined. * * @author Hannu Niemistö * * @param */ public abstract class AbstractExpressionCompilationRequest implements Read> { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExpressionCompilationRequest.class); protected static final Type RESOURCE = Types.con("Simantics/DB", "Resource"); protected static final Type VARIABLE = Types.con("Simantics/Variables", "Variable"); protected static Name PROPERTY_VALUE = Name.create("Simantics/Variables", "propertyValue"); protected static Name VARIABLE_PARENT = Name.create("Simantics/Variables", "variableParent"); protected static Name FROM_DOUBLE = Name.create("Prelude", "fromDouble"); protected static Name TO_DOUBLE = Name.create("Prelude", "toDouble"); protected static Name FROM_DYNAMIC = Name.create("Prelude", "fromDynamic"); private static final Type DEFAULT_EXPECTED_EFFECT = Types.union(new Type[] {Types.PROC, Types.READ_GRAPH}); /** * Returns the expression that will be compiled in textual form. */ protected abstract String getExpressionText(ReadGraph graph) throws DatabaseException; /** * Returns the context that is used for the compilation of the expression. The context * contains information about available constants and variables. */ protected abstract CompilationContext getCompilationContext(ReadGraph graph) throws DatabaseException; /** * This should return the SCL type corresponding to generic type parameter {@code EvaluationContext}. */ protected abstract Type getContextVariableType(); /** * Returns null, if variable {@code name} is not defined. */ protected abstract Expression getVariableAccessExpression(ReadGraph graph, CompilationContext context, Variable contextVariable, String name) throws DatabaseException; protected Type getExpectedType(ReadGraph graph, CompilationContext context) throws DatabaseException { return Types.metaVar(Kinds.STAR); } protected Type getExpectedEffect(ReadGraph graph, CompilationContext context) throws DatabaseException { return DEFAULT_EXPECTED_EFFECT; } protected ExpressionEvaluator prepareEvaluator(final ReadGraph graph, final CompilationContext context, Type expectedType) throws DatabaseException { final Variable contextVariable = new Variable("context", getContextVariableType()); LocalEnvironment localEnvironment = new AbstractLocalEnvironment() { THashMap> precalculatedVariables = new THashMap>(); @Override public Expression resolve(Environment environment, String localName) { Pair precalculatedVariable = precalculatedVariables.get(localName); if(precalculatedVariable == null) { try { Expression value = getVariableAccessExpression(graph, context, contextVariable, localName); if(value == null) return null; Variable variable = new Variable(localName); precalculatedVariable = Pair.make(variable, value); precalculatedVariables.put(localName, precalculatedVariable); } catch (DatabaseException e) { throw new RuntimeDatabaseException(e); } } return new EVariable(precalculatedVariable.first); } @Override public Expression preDecorateExpression(Expression expression) { for(Pair precalculatedVariable : precalculatedVariables.values()) expression = new ESimpleLet(precalculatedVariable.first, precalculatedVariable.second, expression); return expression; } @Override protected Variable[] getContextVariables() { return new Variable[] { contextVariable }; } }; String expressionText = getExpressionText(graph); return new ExpressionEvaluator(context.runtimeEnvironment, expressionText) .localEnvironment(localEnvironment) .expectedType(expectedType) .parseAsBlock(parseAsBlock()); } protected boolean parseAsBlock() { return false; } /* * Override this to provide location information in compilation error situations. */ protected String getContextDescription(ReadGraph graph) throws DatabaseException { return toString(); } @SuppressWarnings("unchecked") protected Function1 eval(ExpressionEvaluator evaluator, ReadGraph graph) throws DatabaseException { Object oldGraph = SCLContext.getCurrent().put("graph", graph); try { return (Function1)evaluator.eval(); } catch(RuntimeDatabaseException e) { LOGGER.error("Failed to evaluate SCL expression", e); if(e.getCause() instanceof DatabaseException) throw (DatabaseException)e.getCause(); else throw e; } catch (SCLExpressionCompilationException e) { StringBuilder b = new StringBuilder(); b.append("Couldn't compile '"); b.append(evaluator.getExpressionText()); b.append("' in "); b.append(getContextDescription(graph)); b.append("':\n"); StringBuilder b2 = new StringBuilder(); for(CompilationError error : e.getErrors()) { b2.append(error.description); b2.append('\n'); } SCLDatabaseException exception = new SCLDatabaseException(b.toString()+b2.toString(), b2.toString(), e.getErrors()); throw exception; } catch(Throwable e) { // Should not happen! LOGGER.error("This error should never happen", e); throw new RuntimeException(e); } finally { SCLContext.getCurrent().put("graph", oldGraph); } } public List getExpressionEffects(final ReadGraph graph) throws DatabaseException { CompilationContext context = getCompilationContext(graph); Type type = Types.metaVar(Kinds.STAR); eval(prepareEvaluator(graph, context, type), graph); try { MultiFunction mfun = Types.matchFunction(type, 1); ArrayList concreteEffects = new ArrayList(); mfun.effect.collectConcreteEffects(concreteEffects); return concreteEffects; } catch(MatchException e) { // Should not happen! LOGGER.error("Failed to get expression effects", e); throw new RuntimeException(e); } } @Override public Function1 perform(final ReadGraph graph) throws DatabaseException { CompilationContext context = getCompilationContext(graph); return eval(prepareEvaluator(graph, context, getExpectedType(graph, context)), graph); } protected static Expression getProperty(Environment environment, Expression variable, String propertyName, Type type) { return new EApply( new EConstant(environment.getValue(FROM_DYNAMIC), type), new EApply( new EConstant(environment.getValue(PROPERTY_VALUE), Types.DYNAMIC), variable, new ELiteral(new StringConstant(propertyName)))); } protected static Expression getPropertyFlexible(Environment environment, Expression variable, String propertyName, Type type) { return makeTypeFlexible(environment, getProperty(environment, variable, propertyName, type), type); } protected static Expression makeTypeFlexible(Environment environment, Expression base, Type originalType) { if(originalType.equals(Types.DOUBLE)) return new EApply( new EConstant(environment.getValue(FROM_DOUBLE)), base); else if(originalType.equals(Types.FLOAT)) return new EApply( new EConstant(environment.getValue(FROM_DOUBLE)), new EApply( new EConstant(environment.getValue(TO_DOUBLE), Types.FLOAT), base)); else return base; } protected static String resolveExpectedValueType(ReadGraph graph, Resource predicate) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); return graph.getPossibleRelatedValue(predicate, L0.RequiresValueType, Bindings.STRING); } @Override public abstract int hashCode(); @Override public abstract boolean equals(Object obj); }