org.qcert.camp.translator.SemRule2CAMP.java Source code

Java tutorial

Introduction

Here is the source code for org.qcert.camp.translator.SemRule2CAMP.java

Source

/**
 * (c) Copyright IBM Corp. 2015-2017
 * Copyright (C) 2017 Joshua Auerbach 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.qcert.camp.translator;

import java.lang.reflect.Field;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.qcert.camp.CampMacros;
import org.qcert.camp.pattern.AssertPattern;
import org.qcert.camp.pattern.BinaryOperator;
import org.qcert.camp.pattern.BinaryPattern;
import org.qcert.camp.pattern.CampPattern;
import org.qcert.camp.pattern.CompoundPattern;
import org.qcert.camp.pattern.ConstPattern;
import org.qcert.camp.pattern.LetItPattern;
import org.qcert.camp.pattern.MapPattern;
import org.qcert.camp.pattern.UnaryOperator;
import org.qcert.camp.pattern.UnaryPattern;
import org.qcert.camp.rule.CampRule;
import org.qcert.camp.rule.CompoundRule;
import org.qcert.camp.rule.GlobalRule;
import org.qcert.camp.rule.NotRule;
import org.qcert.camp.rule.PatternRule;
import org.qcert.camp.rule.ReturnRule;
import org.qcert.camp.rule.WhenRule;

import com.ibm.rules.engine.lang.semantics.SemAbstractAnnotatedElement;
import com.ibm.rules.engine.lang.semantics.SemAggregate;
import com.ibm.rules.engine.lang.semantics.SemAggregateFunctionMetadata;
import com.ibm.rules.engine.lang.semantics.SemAnnotatedElement;
import com.ibm.rules.engine.lang.semantics.SemArrayClass;
import com.ibm.rules.engine.lang.semantics.SemAttribute;
import com.ibm.rules.engine.lang.semantics.SemAttribute.Implementation;
import com.ibm.rules.engine.lang.semantics.SemAttribute.NativeImplementation;
import com.ibm.rules.engine.lang.semantics.SemAttributeAssignment;
import com.ibm.rules.engine.lang.semantics.SemAttributeValue;
import com.ibm.rules.engine.lang.semantics.SemBagClass;
import com.ibm.rules.engine.lang.semantics.SemBlock;
import com.ibm.rules.engine.lang.semantics.SemCast;
import com.ibm.rules.engine.lang.semantics.SemClass;
import com.ibm.rules.engine.lang.semantics.SemCollectionDomain;
import com.ibm.rules.engine.lang.semantics.SemConditionalOperator;
import com.ibm.rules.engine.lang.semantics.SemConstant;
import com.ibm.rules.engine.lang.semantics.SemDomain;
import com.ibm.rules.engine.lang.semantics.SemDomainKind;
import com.ibm.rules.engine.lang.semantics.SemExtension;
import com.ibm.rules.engine.lang.semantics.SemFunctionalIf;
import com.ibm.rules.engine.lang.semantics.SemFunctionalSwitch;
import com.ibm.rules.engine.lang.semantics.SemGenericClass;
import com.ibm.rules.engine.lang.semantics.SemGenericInfo;
import com.ibm.rules.engine.lang.semantics.SemIndexerAssignment;
import com.ibm.rules.engine.lang.semantics.SemIndexerValue;
import com.ibm.rules.engine.lang.semantics.SemInterval;
import com.ibm.rules.engine.lang.semantics.SemLambdaBlock;
import com.ibm.rules.engine.lang.semantics.SemLambdaValue;
import com.ibm.rules.engine.lang.semantics.SemLocalVariableDeclaration;
import com.ibm.rules.engine.lang.semantics.SemMetadata;
import com.ibm.rules.engine.lang.semantics.SemMethod;
import com.ibm.rules.engine.lang.semantics.SemMethodInvocation;
import com.ibm.rules.engine.lang.semantics.SemMethodReference;
import com.ibm.rules.engine.lang.semantics.SemNamedVariableDeclaration;
import com.ibm.rules.engine.lang.semantics.SemNewObject;
import com.ibm.rules.engine.lang.semantics.SemStatement;
import com.ibm.rules.engine.lang.semantics.SemThis;
import com.ibm.rules.engine.lang.semantics.SemType;
import com.ibm.rules.engine.lang.semantics.SemTypeKind;
import com.ibm.rules.engine.lang.semantics.SemTypeRestriction;
import com.ibm.rules.engine.lang.semantics.SemValue;
import com.ibm.rules.engine.lang.semantics.SemValueSet;
import com.ibm.rules.engine.lang.semantics.SemValueVisitor;
import com.ibm.rules.engine.lang.semantics.SemVariableAssignment;
import com.ibm.rules.engine.lang.semantics.SemVariableDeclaration;
import com.ibm.rules.engine.lang.semantics.SemVariableValue;
import com.ibm.rules.engine.lang.semantics.metadata.SemTextLocationMetadata;
import com.ibm.rules.engine.ruledef.semantics.SemActionContent;
import com.ibm.rules.engine.ruledef.semantics.SemAggregateCondition;
import com.ibm.rules.engine.ruledef.semantics.SemClassCondition;
import com.ibm.rules.engine.ruledef.semantics.SemCondition;
import com.ibm.rules.engine.ruledef.semantics.SemConditionGenerator;
import com.ibm.rules.engine.ruledef.semantics.SemConditionVisitor;
import com.ibm.rules.engine.ruledef.semantics.SemEvaluateCondition;
import com.ibm.rules.engine.ruledef.semantics.SemExistsCondition;
import com.ibm.rules.engine.ruledef.semantics.SemInstanciatedCondition;
import com.ibm.rules.engine.ruledef.semantics.SemModalCondition;
import com.ibm.rules.engine.ruledef.semantics.SemNotCondition;
import com.ibm.rules.engine.ruledef.semantics.SemOrCondition;
import com.ibm.rules.engine.ruledef.semantics.SemProductCondition;
import com.ibm.rules.engine.ruledef.semantics.SemProductionRule;
import com.ibm.rules.engine.ruledef.semantics.SemQuery;
import com.ibm.rules.engine.ruledef.semantics.SemRule;
import com.ibm.rules.engine.ruledef.semantics.SemRuleContent;
import com.ibm.rules.engine.ruledef.semantics.SemRuleLanguageFactory;
import com.ibm.rules.engine.ruledef.semantics.SemValueConditionGenerator;
import com.ibm.rules.engine.ruledef.semantics.SemValueConditionGenerator.Kind;
import com.ibm.rules.engine.ruledef.semantics.SemVariableCondition;

/**
 * A translator from SemRule nodes to CampAST nodes.
 */
public class SemRule2CAMP implements SemValueVisitor<CampPattern>, SemConditionVisitor<Void, CampRule> {
    /** Specific time type used in certain time constants */
    private static final String TIME_TYPE = "ilog.rules.brl.Time";

    /** Specific date type used in certain date constants */
    private static final String DATE_TYPE = "ilog.rules.brl.Date";

    /** Method name for the runtime utility methods that get the size of a collection */
    private static final String GET_SIZE = "getSize";

    /** Method name for the standard size() method on Collection */
    private static final String SIZE = "size";

    /** Class name for the runtime utility to manage collections */
    private static final String COLLECTION_UTIL = "ilog.rules.brl.IlrCollectionUtil";

    /** Class name for the apache utility to manage collections  */
    private static final String APACHE_COLLECTION_UTIL = "org.apache.commons.collections.CollectionUtils";

    /** The set of known temporal types */
    private static final Set<String> temporalTypes = new HashSet<>(
            Arrays.asList(DATE_TYPE, "ilog.rules.brl.SimpleDate", TIME_TYPE, LocalTime.class.getName(),
                    ZonedDateTime.class.getName(), "com.ibm.ia.AbsoluteDuration", "com.ibm.ia.TimePeriod",
                    "com.ibm.ia.CalendarDuration", Duration.class.getName()));

    /** A SemRuleLanguageFactory to use as needed in minor local rewrites */
    private final SemRuleLanguageFactory factory;

    /** Ensure a single passert in front of arbitrarily nested pbinop and punop */
    private boolean passertSeen;

    /** A place to store the known prefix of the access path when converting a SemAggregateCondition and/or 
     * SemAttributeValue */
    private List<SemAttributeValue> linkChain;

    /** Place to cache a fetchRef link when iterating over collections of references */
    private CampPattern savedFetchRef;

    /** Count of pmap levels inserted during processing of processLinkChain */
    private int pmapCount;

    /**
     * Make a new SemRule2CAMP
     * @param factory the language factory to use
     */
    public SemRule2CAMP(SemRuleLanguageFactory factory /* , UnaryOperator operator, SemClass engineData*/) {
        this.factory = factory;
    }

    /**
     * Translate a Rule
     * @param rule the Rule to translate
     * @return the result as a CAMP AST
     */
    public CampRule translate(SemRule rule) {
        SemCondition condition = null;
        CampPattern result = null;
        if (rule instanceof SemProductionRule) {
            SemRuleContent content = ((SemProductionRule) rule).getContent();
            if (content instanceof SemActionContent) {
                condition = content.getCondition();
                SemBlock statements = ((SemActionContent) content).getStatements();
                if (isQueryAsRule(statements)
                        && getEffectiveCondition(condition) instanceof SemAggregateCondition) {
                    condition = getEffectiveCondition(condition);
                    result = condition.getBindings().get(0).asValue().accept(this);
                } else
                    result = translateBlock(statements, condition);
            }
        } else if (rule instanceof SemQuery) {
            condition = ((SemQuery) rule).getCondition();
            result = condition.getBindings().get(0).asValue().accept(this);
        }
        if (result == null)
            notImplemented("translation for rule of type " + rule.getClass().getName());
        ReturnRule ans = new ReturnRule(result);
        return condition == null ? ans : condition.accept(this, null).applyTo(ans);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueVisitor#visit(com.ibm.rules.engine.lang.semantics.SemAggregate)
     */
    @Override
    public CampPattern visit(SemAggregate aggregate) {
        return notImplemented(aggregate);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.ruledef.semantics.SemConditionVisitor#visit(com.ibm.rules.engine.ruledef.semantics.SemAggregateCondition, java.lang.Object)
     */
    @Override
    public CampRule visit(SemAggregateCondition ast, Void parameter) {
        /* Rule out things that keep us from succeeding */
        if (ast.hasGroupbyDefinition() || ast.hasGroupbyVariable())
            notImplemented("GroupBy");
        if (0 != ast.getTests().size())
            // TODO we should fix this.  Need some examples and then it should be obvious.
            notImplemented("SemAggregateCondition with tests");
        SemValue cls = ast.getAggregateApplication().getInstanceOfAggregateClass();
        if (!(cls instanceof SemNewObject))
            notImplemented("SemAggregateCondition whose application isn't an object creation");

        /* Get the condition and fork off the case of a per-entity aggregation */
        SemCondition cond = ast.getGeneratorCondition();
        if (cond instanceof SemClassCondition && ((SemClassCondition) cond).hasGenerator()
                && isPerEntityGenerator(((SemClassCondition) cond).getGenerator()))
            return translatePerEntityAggregate(ast);

        /* Consider product conditions and add to the link chain if appropriate; otherwise just process the condition  */
        List<SemAttributeValue> linkChain = new ArrayList<>();
        CampRule rule = null;
        if (cond instanceof SemProductCondition) {
            for (SemCondition c : ((SemProductCondition) cond).getConditions()) {
                if (getAttributeValueLink(c, linkChain))
                    continue;
                CampRule newRule = c.accept(this, null);
                rule = rule == null ? newRule : new CompoundRule(rule, newRule);
            }
        } else {
            rule = cond.accept(this, null);
        }

        /* Get the 'over' expression and the operator */
        Object[] overAndOp = getOverAndOp((SemNewObject) cls, ast, linkChain.isEmpty() ? null : linkChain);
        SemValue over = (SemValue) overAndOp[0];
        UnaryOperator op = (UnaryOperator) overAndOp[1];

        /* Process the 'over' expression along with the linkChain if any. */
        this.linkChain = linkChain.isEmpty() ? null : linkChain;
        CampPattern arg = null;
        if (over == null)
            arg = processLinkChain(true);
        else if (!linkChain.isEmpty()) {
            if (over instanceof SemVariableValue
                    && ((SemVariableValue) over).getVariableDeclaration() instanceof SemLocalVariableDeclaration)
                // TODO this is dubious since we do not specifically check that the variable reference is to the end of the linkChain
                arg = processLinkChain(true);
            else if (over instanceof SemAttributeValue) {
                this.linkChain.add((SemAttributeValue) over);
                arg = processLinkChain(true);
            } else
                notImplemented("Aggregate condition too complex to analyze");
        } else
            arg = over.accept(this);

        /* Build the answer */
        CampPattern ans = CampMacros.aggregate(rule, op, arg, pmapCount);
        pmapCount = 0;
        return makeAggregateAnswer(ast, ans);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueAndStatementVisitor#visit(com.ibm.rules.engine.lang.semantics.SemAttributeAssignment)
     */
    @Override
    public CampPattern visit(SemAttributeAssignment value) {
        return notImplemented(value);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueVisitor#visit(com.ibm.rules.engine.lang.semantics.SemAttributeValue)
     */
    @Override
    public CampPattern visit(SemAttributeValue ast) {
        SemAttribute attr = ast.getAttribute();
        if (attr.isStatic()) {
            if (isEnumeration(attr.getAttributeType())) {
                return new ConstPattern(attr.getName());
            }
            Implementation impl = attr.getGetterImplementation();
            if (impl instanceof NativeImplementation) {
                Field field = ((NativeImplementation) impl).getField();
                try {
                    return factory.getConstant(field.get(null), ast.getType()).accept(this);
                } catch (IllegalArgumentException | IllegalAccessException e) {
                }
            }
            notImplemented("Non-constant or non-native static attribute");
        }
        if (linkChain == null)
            linkChain = new ArrayList<>();
        linkChain.add(ast);
        return processLinkChain(false);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueVisitor#visit(com.ibm.rules.engine.lang.semantics.SemCast)
     */
    @Override
    public CampPattern visit(SemCast cast) {
        SemType toType = cast.getType();
        SemValue value = cast.getValue();
        SemType fromType = value.getType();
        /* Vacuous casts are always accepted (and elided) */
        if (toType.equals(fromType))
            return value.accept(this);
        /* If we are doing floating point and the cast is between numeric types, we translate the cast to the
         * appropriate conversion operation in CAMP.  If we are not doing floating point, then all casts between
         * numeric types are vacuous since we then have only one numeric type in CAMP.  Even when doing floating
         * point, casts are vacuous unless they actively change between integer and floating point (in either direction).
         */
        if (isNumeric(toType) && isNumeric(fromType)) {
            CampPattern convertedVal = value.accept(this);
            if (isFloatingPoint(toType))
                if (isFloatingPoint(fromType))
                    return convertedVal;
                else
                    return new UnaryPattern(UnaryOperator.AFloatOfInt, convertedVal);
            else if (isFloatingPoint(fromType))
                return new UnaryPattern(UnaryOperator.AFloatTruncate, convertedVal);
            else
                return convertedVal;
        }
        if (isTemporalType(toType) && isTemporalType(fromType))
            // Accept various casts that ARL normally applies for temporal operations on timestamps
            return value.accept(this);
        /* We aren't yet prepared to deal with the more complex cases */
        return notImplemented("A cast expression that actually changes the value");
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.ruledef.semantics.SemConditionVisitor#visit(com.ibm.rules.engine.ruledef.semantics.SemClassCondition, java.lang.Object)
     */
    @Override
    public CampRule visit(SemClassCondition ast, Void parameter) {
        String varName = getConditionVariableName(ast);
        String typeName = ast.getConditionType().getDisplayName();
        CampPattern tests = translateTests(ast.getTests());
        CampPattern initial = (varName != null) ? CampMacros.instanceOf(varName, typeName, tests)
                : CampMacros.matches(typeName, tests);
        return new WhenRule(initial);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueVisitor#visit(com.ibm.rules.engine.lang.semantics.SemConditionalOperator)
     */
    @Override
    public CampPattern visit(SemConditionalOperator operator) {
        SemValue left = operator.getLeftValue();
        SemValue right = operator.getRightValue();
        switch (operator.getKind()) {
        case AND:
            return translateBinaryOp(BinaryOperator.AAnd, left, right);
        case OR:
            return translateBinaryOp(BinaryOperator.AOr, left, right);
        default:
            return notImplemented(operator);
        }
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueVisitor#visit(com.ibm.rules.engine.lang.semantics.SemConstant)
     */
    @Override
    public CampPattern visit(SemConstant constant) {
        return new ConstPattern(constant.getValue());
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.ruledef.semantics.SemConditionVisitor#visit(com.ibm.rules.engine.ruledef.semantics.SemEvaluateCondition, java.lang.Object)
     */
    @Override
    public CampRule visit(SemEvaluateCondition ast, Void parameter) {
        List<SemLocalVariableDeclaration> bindings = ast.getBindings();
        List<SemValue> tests = ast.getTests();
        CampPattern pattern = null;
        if (1 == bindings.size() && 0 == tests.size()) {
            /* Using SemEvaluateCondition to define a single binding */
            SemLocalVariableDeclaration binding = bindings.get(0);
            pattern = CampMacros.varWith(binding.getVariableName(), binding.getInitialValue().accept(this));
        } else if (bindings.size() != 0) {
            // TODO we can almost certainly be more general here
            notImplemented(bindings.size() + " bindings and " + tests.size() + " tests in an evaluate condition");
            return null;
        } else
            pattern = translateTests(tests);
        return new GlobalRule(pattern);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.ruledef.semantics.SemConditionVisitor#visit(com.ibm.rules.engine.ruledef.semantics.SemExistsCondition, java.lang.Object)
     */
    @Override
    public CampRule visit(SemExistsCondition cond, Void parameter) {
        notImplemented(cond);
        return null;
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueVisitor#visit(com.ibm.rules.engine.lang.semantics.SemExtension)
     */
    @Override
    public CampPattern visit(SemExtension extension) {
        return notImplemented(extension);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueVisitor#visit(com.ibm.rules.engine.lang.semantics.SemFunctionalIf)
     */
    @Override
    public CampPattern visit(SemFunctionalIf functionalIf) {
        return notImplemented(functionalIf);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueVisitor#visit(com.ibm.rules.engine.lang.semantics.SemFunctionalSwitch)
     */
    @Override
    public CampPattern visit(SemFunctionalSwitch functionalSwitch) {
        return notImplemented(functionalSwitch);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueAndStatementVisitor#visit(com.ibm.rules.engine.lang.semantics.SemIndexerAssignment)
     */
    @Override
    public CampPattern visit(SemIndexerAssignment value) {
        return notImplemented(value);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueVisitor#visit(com.ibm.rules.engine.lang.semantics.SemIndexerValue)
     */
    @Override
    public CampPattern visit(SemIndexerValue value) {
        return notImplemented(value);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.ruledef.semantics.SemConditionVisitor#visit(com.ibm.rules.engine.ruledef.semantics.SemInstanciatedCondition, java.lang.Object)
     */
    @Override
    public CampRule visit(SemInstanciatedCondition cond, Void parameter) {
        notImplemented(cond);
        return null;
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueVisitor#visit(com.ibm.rules.engine.lang.semantics.SemInterval)
     */
    @Override
    public CampPattern visit(SemInterval interval) {
        return notImplemented(interval);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueVisitor#visit(com.ibm.rules.engine.lang.semantics.SemLambdaBlock)
     */
    @Override
    public CampPattern visit(SemLambdaBlock lambdaBlock) {
        return notImplemented(lambdaBlock);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueVisitor#visit(com.ibm.rules.engine.lang.semantics.SemLambdaValue)
     */
    @Override
    public CampPattern visit(SemLambdaValue lambdaValue) {
        return notImplemented(lambdaValue);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueAndStatementVisitor#visit(com.ibm.rules.engine.lang.semantics.SemMethodInvocation)
     */
    @Override
    public CampPattern visit(SemMethodInvocation ast) {
        SemMethod method = ast.getMethod();
        List<SemValue> args = ast.getArguments();
        switch (method.getOperatorKind()) {
        case NOT_AN_OPERATOR: {
            if (isIntegerToString(ast) || isStringListToString(ast)) {
                return CampMacros.stringify(args.get(0).accept(this));
            } else if (isSystemOutPrintln(ast)) {
                // TODO this only works when the argument is a string concat already
                return args.get(0).accept(this);
            } else if (isToString(ast)) {
                return CampMacros.stringify(ast.getCurrentObject().accept(this));
            } else if (isTranslatableEquals(ast)) {
                return translateBinaryOp(BinaryOperator.AEq, ast.getCurrentObject(), args.get(0));
            } else if (isValueOf(ast)) {
                return args.get(0).accept(this);
            } else if (isXValue(ast)) {
                return ast.getCurrentObject().accept(this);
            } else if (isContains(ast)) {
                SemValue receiver = ast.getCurrentObject();
                if (receiver instanceof SemInterval)
                    return translateIntervalContains((SemInterval) receiver, args.get(0));
                return translateBinaryOp(BinaryOperator.AContains, args.get(0), ast.getCurrentObject());
            } else if (isFormatEntities(ast)) {
                return CampMacros.variables(getVariableNames(args));
            } else if (isTimeStringToEpochMillis(ast)) {
                return translateTimeStringToEpochMillis(ast);
            } else if (isContainsAny(ast)) {
                return translateContainsAny(args.get(0), args.get(1));
            } else if (isSizeMethod(ast)) {
                return translateSizeMethod(ast);
            } else {
                return notImplemented(ast);
            }
        }
        case ADD: {
            return translateAdd(ast);
        }
        case EQUALS: {
            return translateBinaryOp(BinaryOperator.AEq, args.get(0), args.get(1));
        }
        case NOT_EQUALS: {
            return translateNotEquals(ast);
        }
        case LESS_OR_EQUALS_THAN: {
            return translateBinaryOp(BinaryOperator.ALe, args.get(0), args.get(1));
        }
        case LESS_THAN: {
            return translateBinaryOp(BinaryOperator.ALt, args.get(0), args.get(1));
        }
        case GREATER_OR_EQUALS_THAN: {
            return translateBinaryOp(BinaryOperator.ALe, args.get(1), args.get(0));
        }
        case GREATER_THAN: {
            return translateBinaryOp(BinaryOperator.ALt, args.get(1), args.get(0));
        }
        case AND: {
            return translateBinaryOp(BinaryOperator.AAnd, args.get(0), args.get(1));
        }
        case SUB: {
            return translateBinaryOp(BinaryOperator.ArithMinus, args.get(0), args.get(1));
        }
        case DIV: {
            return translateBinaryOp(BinaryOperator.ArithDivide, args.get(0), args.get(1));
        }
        case MUL: {
            return translateBinaryOp(BinaryOperator.ArithMult, args.get(0), args.get(1));
        }
        // TODO appropriate subset of the following
        case BIT_NOT:
        case COUNT:
        case EXPLICIT_CAST:
        case IMPLICIT_CAST:
        case INSTANCEOF:
        case LSH:
        case MINUS:
        case NOT:
        case OR:
        case PLUS:
        case REM:
        case RSH:
        case URSH:
        case XOR:
        default:
            return notImplemented(method.getOperatorKind());
        }
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueVisitor#visit(com.ibm.rules.engine.lang.semantics.SemMethodReference)
     */
    @Override
    public CampPattern visit(SemMethodReference methodReference) {
        return notImplemented(methodReference);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.ruledef.semantics.SemConditionVisitor#visit(com.ibm.rules.engine.ruledef.semantics.SemModalCondition, java.lang.Object)
     */
    @Override
    public CampRule visit(SemModalCondition cond, Void parameter) {
        notImplemented(cond);
        return null;
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueAndStatementVisitor#visit(com.ibm.rules.engine.lang.semantics.SemNewObject)
     */
    @Override
    public CampPattern visit(SemNewObject newObject) {
        if (isTemporalValue(newObject))
            return translateTemporalAllocation(newObject);
        else
            return notImplemented(newObject);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.ruledef.semantics.SemConditionVisitor#visit(com.ibm.rules.engine.ruledef.semantics.SemNotCondition, java.lang.Object)
     */
    @Override
    public CampRule visit(SemNotCondition cond, Void parameter) {
        PatternRule inner = (PatternRule) cond.getCondition().accept(this, null);
        return new NotRule(inner.getPattern());
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.ruledef.semantics.SemConditionVisitor#visit(com.ibm.rules.engine.ruledef.semantics.SemOrCondition, java.lang.Object)
     */
    @Override
    public CampRule visit(SemOrCondition cond, Void parameter) {
        notImplemented(cond);
        return null;
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.ruledef.semantics.SemConditionVisitor#visit(com.ibm.rules.engine.ruledef.semantics.SemProductCondition, java.lang.Object)
     */
    @Override
    public CampRule visit(SemProductCondition ast, Void parameter) {
        while (ProductReducer.isReducible(ast)) {
            ProductReducer reducer = new ProductReducer(factory, ast);
            ast = (SemProductCondition) reducer.transformCondition(ast);
        }
        CampRule ans = null;
        for (SemCondition c : ast.getConditions()) {
            CampRule nextRule = c.accept(this, null);
            ans = ans == null ? nextRule : new CompoundRule(ans, nextRule);
        }
        return ans;
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueVisitor#visit(com.ibm.rules.engine.lang.semantics.SemThis)
     */
    @Override
    public CampPattern visit(SemThis thisValue) {
        // TODO it isn't clear this is always the right thing to do
        return CampPattern.IT;
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueVisitor#visit(com.ibm.rules.engine.lang.semantics.SemValueSet)
     */
    @Override
    public CampPattern visit(SemValueSet valueSet) {
        return notImplemented(valueSet);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueAndStatementVisitor#visit(com.ibm.rules.engine.lang.semantics.SemVariableAssignment)
     */
    @Override
    public CampPattern visit(SemVariableAssignment value) {
        return notImplemented(value);
    }

    /* (non-Javadoc)
     * @see com.ibm.rules.engine.lang.semantics.SemValueVisitor#visit(com.ibm.rules.engine.lang.semantics.SemVariableValue)
     */
    @Override
    public CampPattern visit(SemVariableValue value) {
        if (value.getVariableDeclaration() instanceof SemClassCondition)
            return CampPattern.IT;
        String var = getVariableName(value);
        if (var != null)
            return CampMacros.lookup(var);
        else
            return CampPattern.IT;
    }

    /**
     * Translate the aggregate operator types from int to float as appropriate
     * @param op the op
     * @return the translation of the op (may be the same op)
     */
    private UnaryOperator aggregateOpToFloat(UnaryOperator op) {
        switch (op) {
        case ANumMax:
            return UnaryOperator.AFloatListMax;
        case AArithMean:
            return UnaryOperator.AFloatArithMean;
        case ANumMin:
            return UnaryOperator.AFloatListMin;
        case ASum:
            return UnaryOperator.AFloatSum;
        default:
            return op;
        }
    }

    /**
     * Indicates whether a SemTypeRestriction is one that we know how to translate
     */
    private boolean canTranslateRestriction(SemTypeRestriction type) {
        SemDomain domain = type.getDomain();
        if (domain.getKind() == SemDomainKind.COLLECTION) {
            return canTranslateType(((SemCollectionDomain) domain).getElementType());
        }
        return false;
    }

    /**
     * Indicates whether a SemType is one that we know how to translate
     */
    private boolean canTranslateType(SemType type) {
        switch (type.getKind()) {
        case BOOLEAN:
        case INT:
        case BYTE:
        case LONG:
        case SHORT:
        case FLOAT:
        case DOUBLE:
        case CHAR:
        case STRING:
        case RESTRICTION:
            return canTranslateRestriction((SemTypeRestriction) type);
        case ARRAY:
            return canTranslateType(((SemArrayClass) type).getComponentType());
        default:
            break;
        }
        if (isEnumeration(type))
            return true;
        if (type instanceof SemClass)
            return ((SemClass) type).getNativeClass() != null;
        return false;
    }

    /** Issue the closing bracket of a passert iff the calling context was what started it */
    private CampPattern checkPassertEnd(CampPattern result, boolean passertStarted) {
        if (passertStarted) {
            passertSeen = false;
            return new AssertPattern(result);
        }
        return result;
    }

    /** Manipulate the initial passert on a series of patterns composed using pbinop and punop */
    private boolean checkPassertStart() {
        if (passertSeen)
            return false;
        return passertSeen = true;
    }

    /**
     * Check whether a SemTypeKind has an "easy" (for our Coq code) toString semantics.  This is true of primitive
     *   types (assuming we can handle them) but not of arbitrary Objects.  The Coq code will format anything,
     *   but, in the case of Objects, it will unbrand then and format their contents, which does not match the
     *   JRules semantics.
     * @param kind the type kind
     * @return whether it has an "easy" toString
     */
    private boolean easyToString(SemTypeKind kind) {
        switch (kind) {
        case BOOLEAN:
        case BYTE:
        case CHAR:
        case DOUBLE:
        case FLOAT:
        case INT:
        case LONG:
        case SHORT:
        case STRING:
            return true;
        default:
            return false;
        }
    }

    /**
     * Expand an attribute value into a canonical link chain before processing all the "dots"
     * @param val the attribute value to expand
     * @param canonical the canonical link chain
     */
    private void expandAttributeValue(SemAttributeValue val, List<SemAttributeValue> canonical) {
        SemValue previous = val.getCurrentObject();
        if (previous instanceof SemAttributeValue) {
            expandAttributeValue((SemAttributeValue) previous, canonical);
        }
        canonical.add(val);
    }

    /** Fill in a chain of SemAttributeValue links from class conditions in a product condition */
    private boolean getAttributeValueLink(SemCondition ast, List<SemAttributeValue> results) {
        if (ast instanceof SemClassCondition) {
            SemClassCondition clsCond = (SemClassCondition) ast;
            if (clsCond.hasGenerator()) {
                SemValueConditionGenerator generator = clsCond.getGenerator().getValueConditionGenerator();
                SemValue value = generator.getValue();
                if (value instanceof SemAttributeValue) {
                    results.add((SemAttributeValue) value);
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Given a type, return its component type iff it is an array or collection, otherwise null
     * @param type the type to examine
     * @return the component type or null
     */
    private SemType getComponentType(SemType type) {
        switch (type.getKind()) {
        case RESTRICTION: {
            SemDomain domain = ((SemTypeRestriction) type).getDomain();
            if (domain.getKind() == SemDomainKind.COLLECTION)
                return ((SemCollectionDomain) domain).getElementType();
            break;
        }
        case ARRAY:
            return ((SemArrayClass) type).getComponentType();
        default:
            break;
        }
        if (type instanceof SemClass) {
            SemClass cls = (SemClass) type;
            Class<?> nativeCls = cls.getNativeClass();
            if (nativeCls != null) {
                if (Collection.class.isAssignableFrom(nativeCls)) {
                    SemGenericInfo<SemGenericClass> genericInfo = cls.getGenericInfo();
                    if (genericInfo != null) {
                        return genericInfo.getGenericParameters().getTypeParameters().get(0);
                    }
                }
            }
        }
        return null;
    }

    /** Gets the variable name from a SemVariableCondition or returns null if none is found */
    private String getConditionVariableName(SemVariableCondition ast) {
        for (SemLocalVariableDeclaration decl : ast.getBindings()) {
            SemValue initValue = decl.getInitialValue();
            if (null != initValue && initValue instanceof SemVariableValue) {
                SemVariableValue var = (SemVariableValue) initValue;
                if (var.getVariableDeclaration() == ast)
                    return decl.getVariableName();
            }
        }
        return null;
    }

    /**
     * Strip off one layer of product condition when vacuous
     */
    private SemCondition getEffectiveCondition(SemCondition condition) {
        if (condition instanceof SemProductCondition) {
            List<SemCondition> conditions = ((SemProductCondition) condition).getConditions();
            if (conditions.size() == 1)
                return conditions.get(0);
        }
        return condition;
    }

    /**
     * Return the id attribute of a type; if the type is not a class, there can be no id attribute.  Otherwise,
     *  the attributes of the class are searched
     * @param type the type to analyze
     * @return the name of the id attribute or null if the type is not a class or there is no id attribute
     */
    private String getIdAttribute(SemType type) {
        // This is meaningful only in the insights version of ODM but we have revised this code to be dependent only on the
        // classic version.   There is never any id attribute.  We may consider revisiting this.
        return null;
    }

    /**
     * Get the native class from a type or null if it doesn't denote a native class
     * @param theType
     * @return the native class or null
     */
    private Class<?> getNativeClass(SemType theType) {
        return theType instanceof SemClass ? ((SemClass) theType).getNativeClass() : null;
    }

    /**
     * Common subroutine for processing both per-entity and global aggregates.  Gets the "over" expression and the
     *   operator
     * @param newObject the SemNewObject portion of the original aggregate
     * @param ast the original aggregate condition
     * @param linkChain the active link chain if there is one or null otherwise
     * @return the "over" and the "op as a pair of objects in an array
     */
    private Object[] getOverAndOp(SemNewObject newObject, SemAggregateCondition ast,
            List<SemAttributeValue> linkChain) {
        SemType type = newObject.getConstructor().getDeclaringType();
        SemAggregateFunctionMetadata meta = (SemAggregateFunctionMetadata) type
                .getMetadata(SemAggregateFunctionMetadata.class);
        String tName = (null == meta) ? type.getDisplayName() : meta.getName();
        List<SemValue> args = ast.getAggregateApplication().getArguments();
        SemValue over = null;
        if (1 == args.size())
            over = args.get(0);
        if (over instanceof SemConstant) // special convention for agg rules using PartialMoment
            over = null;
        else if (over != null)
            over = simplify(over);
        UnaryOperator op = null;
        if (tName.startsWith("java.util.ArrayList"))
            op = UnaryOperator.AIdOp;
        else if ("count".equals(tName))
            op = UnaryOperator.ACount;
        else if ("min".equals(tName))
            op = UnaryOperator.ANumMin;
        else if ("max".equals(tName))
            op = UnaryOperator.ANumMax;
        else if ("sum".equals(tName))
            op = UnaryOperator.ASum;
        else if ("mean".equals(tName))
            op = UnaryOperator.AArithMean;
        else
            notImplemented("Aggregation operator " + tName);
        boolean isFloat;
        if (over != null)
            isFloat = isFloatingPoint(over.getType());
        else if (linkChain != null)
            isFloat = isFloatingPoint(getValueType(linkChain));
        else {
            notImplemented("Aggregates whose collection element type cannot be detemined");
            return null; // not reached
        }
        if (isFloat)
            op = aggregateOpToFloat(op);
        else if (op == UnaryOperator.AArithMean) {
            op = UnaryOperator.AFloatArithMean;
            over = factory.cast(SemCast.Kind.HARD, factory.getObjectModel().getType(SemTypeKind.DOUBLE), over);
        }
        return new Object[] { over, op };
    }

    /**
     * Given a "link chain" (ordered list of SemAttributeValue representing an access path) return the type of the
     *   overall expression (ie the last link in the chain)
     * @param linkChain the link chain
     * @return the type
     */
    private SemType getValueType(List<SemAttributeValue> linkChain) {
        return linkChain.isEmpty() ? null : linkChain.get(linkChain.size() - 1).getType();
    }

    /** Gets the variable name from a SemVariableValue or null if the SemVariableValue does not have
     *   a variable name (e.g. when its SemVariableDeclaration is a SemCondition without bindings). */
    private String getVariableName(SemVariableValue ast) {
        SemVariableDeclaration decl = ast.getVariableDeclaration();
        SemLocalVariableDeclaration realDecl = null;
        if (decl instanceof SemLocalVariableDeclaration)
            realDecl = (SemLocalVariableDeclaration) decl;
        else
            return null;
        return realDecl.getVariableName();
    }

    /** Gets an array of names from the argument list of a formatEntities call.  The list is in the middle argument */
    private List<String> getVariableNames(List<SemValue> vals) {
        if (vals.size() > 3) {
            SemValue listArg = vals.get(2);
            if (listArg instanceof SemExtension) {
                List<SemValue> args = ((SemExtension) listArg).getValues();
                List<String> ans = new ArrayList<>();
                boolean allStrings = true;
                for (SemValue arg : args) {
                    if (arg instanceof SemConstant) {
                        Object val = ((SemConstant) arg).getValue();
                        if (val instanceof String) {
                            ans.add((String) val);
                            continue;
                        }
                    }
                    allStrings = false;
                    break;
                }
                if (allStrings)
                    return ans;
            }
        }
        throw new IllegalStateException("formatEntities arguments did not have the expected form");
    }

    /**
     * Get the variable names of objects used to match a SemCondition.
     * @param condition the condition
     * @return the list of variable names
     */
    private List<String> getVariableNames(SemCondition condition) {
        if (condition instanceof SemProductCondition) {
            List<SemCondition> subconditions = ((SemProductCondition) condition).getConditions();
            List<String> ans = new ArrayList<>();
            for (SemCondition cond : subconditions) {
                ans.addAll(getVariableNames(cond));
            }
            return ans; // May be empty if no subcondition produced anything
        } else if (condition instanceof SemVariableCondition) {
            SemVariableDeclaration decl = ((SemVariableCondition) condition).asValue().getVariableDeclaration();
            if (decl instanceof SemNamedVariableDeclaration)
                return Collections.singletonList(((SemNamedVariableDeclaration) decl).getVariableName());
            if (condition instanceof SemAggregateCondition)
                condition = ((SemAggregateCondition) condition).getGeneratorCondition();
            if (condition instanceof SemClassCondition) {
                // either originally or after chasing through an aggregate condition
                SemValue generatorValue = ((SemClassCondition) condition).getValueConditionGenerator().getValue();
                if (generatorValue instanceof SemAttributeValue)
                    return Collections.singletonList(((SemAttributeValue) generatorValue).getAttribute().getName());
            }
        }
        // If we arrive here we did not produce a compound answer from a product condition nor a simple answer from a variable condition
        // Look at the bindings.
        List<SemLocalVariableDeclaration> bindings = condition.getBindings();
        List<String> ans = new ArrayList<>();
        for (SemLocalVariableDeclaration binding : bindings)
            ans.add(binding.getVariableName());
        return ans; // May be empty if there are no bindings
    }

    /**
     * When we don't have an original object model, this heuristically decides that a type is an enum.
     * For the heuristic to fire, all members must be static and public and of the type's type.
     * There must be no methods other than an equals method.
     * @return true if type looks like an enumeration enough to take a chance on it.
     */
    private boolean heuristicEnum(SemClass type) {
        Collection<SemMethod> methods = type.getMethods();
        if (methods.size() > 1)
            return false;
        if (methods.size() == 1 && !methods.iterator().next().getName().equals("instanceof"))
            return false;
        Collection<SemAttribute> attrs = type.getExtra().getAllAttributes();
        for (SemAttribute attr : attrs) {
            if (attr.getName().equals("class"))
                continue;
            if (!attr.isStatic())
                return false;
            if (!attr.getAttributeType().equals(type))
                return false;
        }
        return true;
    }

    /**
     * Get an int from a SemValue assuming it is a constant (notImplemented exception if not)
     * @param value the value to turn into an int
     * @return the int
     */
    private int intFrom(SemValue value) {
        if (value.isConstant()) {
            return ((Number) ((SemConstant) value).getValue()).intValue();
        }
        notImplemented("Non-constant value as argument to temporal construction: " + value);
        return -1; // not reached
    }

    /** Checks whether a method invocation is the contains method of a collection */
    private boolean isContains(SemMethodInvocation ast) {
        SemMethod method = ast.getMethod();
        if (!method.getName().equals("contains"))
            return false; // must have the expected name
        SemType declaringType = method.getDeclaringType();
        if (declaringType instanceof SemBagClass)
            return true;
        Class<?> nativeClass = getNativeClass(declaringType);
        return nativeClass != null && Collection.class.isAssignableFrom(nativeClass);
    }

    /**
     * Test whether a method invocation ast is a call to CollectionUtils.containsAny
     * @param ast the SemMethodInvocation ast
     * @return true if it's a containsAny call
     */
    private boolean isContainsAny(SemMethodInvocation ast) {
        SemMethod method = ast.getMethod();
        return method.getName().equals("containsAny")
                && method.getDeclaringType().getDisplayName().equals(APACHE_COLLECTION_UTIL)
                && ast.getCurrentObject() == null;
    }

    /**
     * Test whether a type is an enumeration type; if so, its translation is integer (at least for now).
     * @param type the type to test
     * @return true if it is an an enumeration
     */
    private boolean isEnumeration(SemType type) {
        if (type instanceof SemClass) {
            Class<?> nativeClass = ((SemClass) type).getNativeClass();
            if (nativeClass != null && nativeClass.isEnum())
                return true;
            return heuristicEnum((SemClass) type);
        }
        return false;
    }

    /**
     * Return true iff type denotes a primitive numeric type
     * @param type the type to check
     * @return true iff it is numeric
     */
    private boolean isFloatingPoint(SemType type) {
        Class<?> nativeClass = type instanceof SemClass ? ((SemClass) type).getNativeClass() : null;
        if (nativeClass == null)
            return false;
        if (nativeClass.isPrimitive())
            switch (nativeClass.getName()) {
            case "double":
            case "float":
                return true;
            default:
                return false;
            }
        if (Collection.class.isAssignableFrom(nativeClass)) {
            new Object();
        }
        return Double.class.isAssignableFrom(nativeClass) || Float.class.isAssignableFrom(nativeClass);
    }

    /** Test whether a method invocation ast is a call to Runtime.formatEntities */
    private boolean isFormatEntities(SemMethodInvocation ast) {
        SemMethod method = ast.getMethod();
        return method.getName().equals("formatEntities")
                && method.getDeclaringType().getDisplayName().equals(RuntimeUtils.class.getName())
                && ast.getCurrentObject() == null;
    }

    /** Tests whether a method invocation is for the toString method of Integer */
    private boolean isIntegerToString(SemMethodInvocation ast) {
        SemValue receiver = ast.getCurrentObject();
        SemMethod method = ast.getMethod();
        String type = method.getDeclaringType().getDisplayName();
        List<SemValue> args = ast.getArguments();
        return null == receiver && Integer.class.getName().equals(type) && "toString".equals(method.getName())
                && 1 == args.size();
    }

    /**
     * Return true iff type denotes a primitive numeric type
     * @param type the type to check
     * @return true iff it is numeric
     */
    private boolean isNumeric(SemType type) {
        Class<?> nativeClass = type instanceof SemClass ? ((SemClass) type).getNativeClass() : null;
        if (nativeClass == null)
            return false;
        if (nativeClass.isPrimitive())
            switch (nativeClass.getName()) {
            case "int":
            case "double":
            case "long":
            case "short":
            case "byte":
            case "float":
                return true;
            default: // char or boolean
                return false;
            }
        return Number.class.isAssignableFrom(nativeClass);
    }

    /** Test whether a type is something we intend to treat as an object type.  */
    private boolean isObjectType(SemType type) {
        if (isEnumeration(type))
            return false;
        if (type instanceof SemClass)
            switch (type.getKind()) {
            case BOOLEAN:
            case BYTE:
            case CHAR:
            case DOUBLE:
            case FLOAT:
            case INT:
            case LONG:
            case SHORT:
            case VOID:
            case STRING:
            case ARRAY:
                return false;
            default:
                Class<?> nativeCls = ((SemClass) type).getNativeClass();
                if (nativeCls != null) {
                    if (Collection.class.isAssignableFrom(nativeCls)) {
                        return false;
                    } else if (Boolean.class.isAssignableFrom(nativeCls)) {
                        return false;
                    } else if (Number.class.isAssignableFrom(nativeCls)) {
                        return false;
                    }
                }
                return true;
            }
        else
            return false;
    }

    /**
     * Determine if a generator found in an aggregate condition means that it's "per entity" 
     */
    private boolean isPerEntityGenerator(SemConditionGenerator generator) {
        SemValue collectionValue = generator.getValueConditionGenerator().getValue();
        if (collectionValue instanceof SemAttributeValue
                && ((SemAttributeValue) collectionValue).getCurrentObject() instanceof SemThis)
            return false;
        return true;
    }

    /**
     * Determine if a production rule is actually a SemQuery in spirit (in the solution archive, the ARL representation
     *  of the local query for the Q1 engine takes the form of a production rule)
     * @param block the statement block from the SemActionContent of the rule
     * @return true if the form of the statements indicates a query
     */
    private boolean isQueryAsRule(SemBlock block) {
        List<SemStatement> statements = block.getStatements();
        if (statements.size() != 1)
            return false;
        SemStatement toCheck = statements.get(0);
        return toCheck instanceof SemAttributeAssignment
                && "localQueryResult".equals(((SemAttributeAssignment) toCheck).getAttribute().getName());
    }

    /**
     * Determine if a method invocation ast is obtaining the size of a collection
     * @param ast the node to examine
     * @return true if it is a size method
     */
    private boolean isSizeMethod(SemMethodInvocation method) {
        SemMethod callee = method.getMethod();
        List<SemValue> args = method.getArguments();
        if (args.size() == 1 && callee.getName().equals(GET_SIZE)
                && callee.getDeclaringType().getDisplayName().equals(COLLECTION_UTIL)) {
            return true;
        }
        if (args.size() == 0 && callee.getName().equals(SIZE)) {
            SemType declaringType = callee.getDeclaringType();
            if (declaringType instanceof SemClass) {
                if (Collection.class.isAssignableFrom(((SemClass) declaringType).getNativeClass())) {
                    return true;
                }
            }
        }
        return false;
    }

    /** Tests whether a method invocation is for the special stringListToString method defined in Runtime */
    private boolean isStringListToString(SemMethodInvocation ast) {
        SemValue receiver = ast.getCurrentObject();
        SemMethod method = ast.getMethod();
        String type = method.getDeclaringType().getDisplayName();
        List<SemValue> args = ast.getArguments();
        return null == receiver && RuntimeUtils.class.getName().equals(type)
                && "stringListToString".equals(method.getName()) && 1 == args.size();
    }

    /** Test whether a method invocation is a println or the ODM equivalent thereof */
    private boolean isSystemOutPrintln(SemMethodInvocation ast) {
        SemValue receiver = ast.getCurrentObject();
        if (null != receiver && receiver instanceof SemAttributeValue) {
            SemAttributeValue attr = (SemAttributeValue) receiver;
            if (null == attr.getCurrentObject()) {
                String type = attr.getAttribute().getDeclaringType().getDisplayName();
                String field = attr.getAttribute().getName();
                String method = ast.getMethod().getName();
                if (System.class.getName().equals(type) && "out".equals(field) && "println".equals(method)
                        && 1 == ast.getArguments().size())
                    return true;
            }
        }
        SemMethod method = ast.getMethod();
        return method.getDeclaringType().getDisplayName().equals("ilog.rules.brl.System")
                && method.getName().equals("printMessage");
    }

    /**
     * Determine if a SemType is one of several possible temporal types.  For various reasons, these have
     *   both a semi-fictional representation and an actual one the latter drawn from java.time.*).
     * TODO the current recognition doesn't distinction between dates, times, durations, etc. so it is 
     * possible for weird cases to be missed, but I think it works for correct code at least.
     * @param type the type to test
     * @return true iff the type is a temporal type
     */
    private boolean isTemporalType(SemType type) {
        return temporalTypes.contains(type.getDisplayName());
    }

    /**
     * Test whether a SemValue has a temporal type
     * @param toTest the SemValue to test
     * @return true if it has a temporal type
     */
    private boolean isTemporalValue(SemValue toTest) {
        if (toTest == null)
            return false;
        return isTemporalType(toTest.getType());
    }

    /**
     * Check whether a method invocation represents a conversion from a string to epoch milliseconds on
     *   the assumption that the string represents a dateTime.  This is a somewhat specialized use case
     *   but one that can arise in the general case and has practical import for us right now.
     * @param ast the SemMethodInvocation to analyze
     * @return true if it is of the form ZonedDateTime.parse(string).toInstant().toEpochMilli();
     */
    private boolean isTimeStringToEpochMillis(SemMethodInvocation ast) {
        if ("toEpochMilli".equals(ast.getMethod().getName())) {
            SemValue recvr = ast.getCurrentObject();
            if (recvr instanceof SemMethodInvocation) {
                ast = (SemMethodInvocation) recvr;
                if ("toInstant".equals(ast.getMethod().getName())) {
                    recvr = ast.getCurrentObject();
                    if (recvr instanceof SemMethodInvocation) {
                        SemMethod toTest = ((SemMethodInvocation) recvr).getMethod();
                        return "parse".equals(toTest.getName())
                                && ZonedDateTime.class.getName().equals(toTest.getReturnType().getDisplayName());
                    }
                }
            }
        }
        return false;
    }

    /** Tests whether a method invocation is for the general toString() method of any object */
    private boolean isToString(SemMethodInvocation ast) {
        SemValue receiver = ast.getCurrentObject();
        String method = ast.getMethod().getName();
        List<SemValue> args = ast.getArguments();
        return null != receiver && "toString".equals(method) && 0 == args.size();
    }

    /** Tests whether a method invocation is for an equals between compatible types that we know how to translate */
    private boolean isTranslatableEquals(SemMethodInvocation ast) {
        SemMethod method = ast.getMethod();
        if ("equals".equals(method.getName())) {
            List<SemValue> args = ast.getArguments();
            if (args.size() == 1) {
                SemType left = method.getDeclaringType();
                SemType right = args.get(0).getType();
                if (canTranslateType(left) && canTranslateType(right))
                    return left.getExtra().isApplicable(right) || right.getExtra().isApplicable(left);
            }
        }
        return false;
    }

    /** Tests whether a method invocation is the valueOf method of a primitive type */
    private boolean isValueOf(SemMethodInvocation ast) {
        if (ast.getCurrentObject() != null)
            return false; // must be static
        SemMethod method = ast.getMethod();
        if (!method.getName().equals("valueOf"))
            return false; // must have the expected name
        Class<?> cls = ((SemClass) method.getDeclaringType()).getNativeClass();
        return Number.class.isAssignableFrom(cls) || cls == Character.class || cls == Boolean.class;
    }

    /** Tests whether a method invocation is an xxxValue method of a primitive type */
    private boolean isXValue(SemMethodInvocation ast) {
        SemValue obj = ast.getCurrentObject();
        if (obj == null)
            return false; // must not be static
        SemMethod method = ast.getMethod();
        if (!method.getName().endsWith("Value"))
            return false; // must have the expected name form
        if (!ast.getArguments().isEmpty())
            return false; // must not have arguments
        Class<?> cls = ((SemClass) obj.getType()).getNativeClass();
        return Number.class.isAssignableFrom(cls) || cls == Character.class || cls == Boolean.class;
    }

    /**
     * Get a long from a SemValue assuming it is a constant (notImplemented exception if not)
     * @param value the value to turn into an long
     * @return the long
     */
    private long longFrom(SemValue value) {
        if (value.isConstant()) {
            return ((Number) ((SemConstant) value).getValue()).longValue();
        }
        notImplemented("Non-constant value as argument to temporal construction: " + value);
        return -1; // not reached
    }

    /**
     * Common finishing for all aggregate cases (global and per-entity)
     * @param ast the ast being processed
     * @param pattern the pattern that constitutes the core of the answer
     * @return the Rule form of the answer
     */
    private CampRule makeAggregateAnswer(SemAggregateCondition ast, CampPattern pattern) {
        String varName = getConditionVariableName(ast);
        if (varName != null)
            pattern = CampMacros.is(varName, pattern);
        return new GlobalRule(pattern);
    }

    /** We arrive here when there's a hole in our implementation that arguably should be filled some day.
     * This method does not actually return */
    private CampPattern notImplemented(Object ast) {
        String at = "";
        String what = ast.toString();
        if (ast instanceof SemAnnotatedElement) {
            SemMetadata loc = ((SemAbstractAnnotatedElement) ast).getMetadata(SemTextLocationMetadata.class);
            if (null != loc)
                at = "At" + loc.toString() + ": ";
        }
        String name = ast.getClass().getName();
        if (what.contains(name))
            what = name;
        throw new UnsupportedOperationException(at + "not implemented: " + what);
    }

    /**
     * Replace an integer op with its floating point counterpart if the types are floating point.
     * This method is only called when the op is arithmetic and also floating point is enabled.
     * We assume that SemRule always casts to a common type.  If we were completely sure of this
     *   we'd only need one type argument but we use the opportunity to check that the assumption
     *   holds.
     * @param op the op
     * @param type1 the first type
     * @param type2 the second type
     * @return the BinaryOperator to use
     */
    private BinaryOperator opByType(BinaryOperator op, SemType type1, SemType type2) {
        if (isFloatingPoint(type1) && isFloatingPoint(type2)) {
            switch (op) {
            case ArithDivide:
                return BinaryOperator.AFloatDiv;
            case ArithMax:
                return BinaryOperator.AFloatMax;
            case ArithMin:
                return BinaryOperator.AFloatMin;
            case ArithMinus:
                return BinaryOperator.AFloatMinus;
            case ArithMult:
                return BinaryOperator.AFloatMult;
            case ArithPlus:
                return BinaryOperator.AFloatPlus;
            case ALe:
                return BinaryOperator.AFloatLe;
            case ALt:
                return BinaryOperator.AFloatLt;
            case ArithRem:
                // Punting on this.  In theory we should translate it but Java's semantics are
                // different than IEEE and possibly / probably some other languages.
            default:
                notImplemented("Floating point equivalent to operation " + op);
                return null; // not reached
            }
        } else if (!isFloatingPoint(type1) && !isFloatingPoint(type2))
            return op;
        notImplemented(
                String.format("Operator %s between %s and %s", op, type1.getDisplayName(), type2.getDisplayName()));
        return null; // not reached
    }

    /**
     * Process the accumulated chain of SemAttributeValue representing an access path
     * @param terminalMap if true, a map operation is added when the type of the final link is a collection
     * @return the CampPattern representing the evaluation of that access path in CAMP
     */
    private CampPattern processLinkChain(boolean terminalMap) {
        /* First canonicalize the link chain to unfold attribute values that have attribute values as their
         *   current object.
         */
        List<SemAttributeValue> canonical = new ArrayList<>();
        for (SemAttributeValue val : linkChain) {
            expandAttributeValue(val, canonical);
        }
        linkChain = null; // reset for next time
        pmapCount = 0; // and this too

        /* Prime the iteration */
        Iterator<SemAttributeValue> attributes = canonical.iterator();
        SemAttributeValue ast = attributes.next();

        /* Consider whether to start with a saved fetchRef.  There will be one if the
         *  link chain arises inside the translation of a nested aggregation and the
         *  collection is a colllection of references. */
        CampPattern current = savedFetchRef;

        /* If no saved fetchRef, take the currentObject of the first attribute and visit it to get started.  If it
         *  represents anything other than 'pit' issue a LetIt to start the chain. */
        if (current == null) {
            CampPattern start = ast.getCurrentObject().accept(this);
            current = start == CampPattern.IT ? null : new LetItPattern(start);
        }

        /* Loop through the attributes completing the expression */
        String idAttribute = null;
        SemType componentType = null;
        for (;;) {
            CampPattern access = CampMacros.unbrandDot(ast.getAttribute().getName());
            if (isObjectType(ast.getType())) {
                access = new CompoundPattern(access, new LetItPattern(CampPattern.LEFT));
            }
            if (idAttribute != null)
                access = new CompoundPattern(new CompoundPattern(new LetItPattern(CampPattern.LEFT),
                        CampMacros.fetchRef(idAttribute, componentType.getDisplayName())), access);
            else
                access = tryRelationship(ast, access);
            current = current == null ? access : new CompoundPattern(current, access);
            if (attributes.hasNext()) {
                SemAttributeValue next = attributes.next();
                /* Check for iterated case */
                componentType = getComponentType(ast.getType());
                if (componentType != null) {
                    SemType toType = next.getCurrentObject().getType();
                    if (toType.getExtra().isAssignableFrom(componentType)) {
                        /* Iterated case: record whether it is a reference and make it into
                         * a map
                         */
                        idAttribute = getIdAttribute(componentType);
                        current = new CompoundPattern(new LetItPattern(current.applyTo(CampPattern.IT)),
                                new MapPattern());
                        if (idAttribute == null && isObjectType(componentType))
                            current = new CompoundPattern(current, new LetItPattern(CampPattern.LEFT));
                        pmapCount++;
                    } else
                        /* Not iterated case, so don't record */
                        componentType = null;
                }
                ast = next;
            } else
                break;
        }
        if (terminalMap && getComponentType(ast.getType()) != null) {
            current = new CompoundPattern(new LetItPattern(current.applyTo(CampPattern.IT)), new MapPattern());
            // TODO perhaps more logic needed here (see above)
            pmapCount++;
        }
        return current.applyTo(CampPattern.IT);
    }

    /**
     * Simplify a SemValue based on the idea that we translate some things as identities; sometimes, even without translating,
     *   we want to find the simplest possible SemValue that gives the same result
     * @param value the SemValue to simplify
     * @return the simplified value, which might be the same
     */
    private SemValue simplify(SemValue value) {
        if (value instanceof SemMethodInvocation) {
            SemMethodInvocation smi = (SemMethodInvocation) value;
            if (isXValue(smi)) {
                return simplify(smi.getCurrentObject());
            } else if (isValueOf(smi)) {
                return simplify(smi.getArguments().get(0));
            } // else cannot simplify
        }
        if (value instanceof SemCast) {
            SemCast cast = (SemCast) value;
            SemType toType = cast.getType();
            SemValue subValue = cast.getValue();
            if (toType.equals(subValue.getType()) || isNumeric(toType) && isNumeric(subValue.getType()))
                // See similar logic in visit(SemCast)
                return simplify(subValue);
            // else cannot simplify
        }
        return value;
    }

    /**
     * Translate a + operator, which might be arithmetic or string concatenation
     * @param ast the ast to examine and translate
     * @return the translation
     */
    private CampPattern translateAdd(SemMethodInvocation ast) {
        SemValue arg1 = ast.getArguments().get(0);
        SemValue arg2 = ast.getArguments().get(1);
        if (arg1.getType().getKind() == SemTypeKind.STRING && easyToString(arg2.getType().getKind())
                || arg2.getType().getKind() == SemTypeKind.STRING && easyToString(arg1.getType().getKind())) {
            CampPattern stringArg1 = CampMacros.stringify(arg1.accept(this));
            CampPattern stringArg2 = CampMacros.stringify(arg2.accept(this));
            return new BinaryPattern(BinaryOperator.ASConcat, stringArg1, stringArg2);
        } else if (arg1.getType().getKind() == SemTypeKind.INT && arg2.getType().getKind() == SemTypeKind.INT)
            return translateBinaryOp(BinaryOperator.ArithPlus, arg1, arg2);
        return notImplemented(ast);
    }

    /** 
     * Provide a binary operator translation
     * @param op the operator
     * @param arg1 the first operand
     * @param arg2 the second operand
     * @return a CampPattern which is the translation
     */
    private CampPattern translateBinaryOp(BinaryOperator op, SemValue arg1, SemValue arg2) {
        boolean started = op.isBoolean() ? checkPassertStart() : false;
        if (op.isArithmetic())
            op = opByType(op, arg1.getType(), arg2.getType());
        return checkPassertEnd(new BinaryPattern(op, arg1.accept(this), arg2.accept(this)), started);
    }

    /**
     * Translate a block containing the statements of the action clause of a production rule.
     * If the block contains a single statement which is a SemValue (e.g. a method call), it is simply
     * translated according to the usual rules.  This allows for production rules that take charge of their
     * own results display.  In all other cases (multiple statements in the block, statement that is not a SemValue)
     * we gather up the variables that name objects in the condition and turn them into a VARIABLES macro.
     * @param block the block to examine
     * @param condition the condition from the original action rule, in case a search for variables that name objects is
     *   required
     * @return a translation (never returns null)
     */
    private CampPattern translateBlock(SemBlock block, SemCondition condition) {
        List<SemStatement> statements = block.getStatements();
        if (1 == statements.size()) {
            SemStatement stmt = statements.get(0);
            if (stmt instanceof SemValue) {
                try {
                    return ((SemValue) stmt).accept(this);
                } catch (UnsupportedOperationException e) {
                }
            }
        }
        List<String> variableNames = getVariableNames(condition);
        if (variableNames.isEmpty())
            return notImplemented("Action clause too difficult to analyze");
        return CampMacros.variables(variableNames);
    }

    /**
     * Translate a "containsAny" method call to the bag intersection equivalent expression for CAMP
     * @param x the SemValue for one collection 
     * @param y the SemValue for the other collection
     * @return the CAMP pattern required
     */
    private CampPattern translateContainsAny(SemValue x, SemValue y) {
        boolean started = checkPassertStart();
        CampPattern countIntersect = new UnaryPattern(UnaryOperator.ACount,
                new BinaryPattern(BinaryOperator.AMin, x.accept(this), y.accept(this)));
        CampPattern eq0 = new BinaryPattern(BinaryOperator.AEq, new ConstPattern(0), countIntersect);
        CampPattern negation = new UnaryPattern(UnaryOperator.ANeg, eq0);
        return checkPassertEnd(negation, started);
    }

    /**
     * Translate a contains method call where the receiver is an interval (denoting a bag containing all the values
     *  in the interval)
     * @param interval the interval
     * @param value the value being tested for containment in the interval
     * @return the CampPattern that performs that test
     */
    private CampPattern translateIntervalContains(SemInterval interval, SemValue value) {
        SemValue lower = interval.getLowerBound();
        SemValue higher = interval.getHigherBound();
        CampPattern geLower = translateBinaryOp(BinaryOperator.ALe, lower, value);
        CampPattern leHigher = translateBinaryOp(BinaryOperator.ALe, value, higher);
        return new BinaryPattern(BinaryOperator.AAnd, geLower, leHigher);
    }

    /**
     * Translate a not-equals expression
     * @param ast the ast for the expression
     * @return the translation
     */
    private CampPattern translateNotEquals(SemMethodInvocation ast) {
        boolean started = checkPassertStart();
        List<SemValue> args = ast.getArguments();
        CampPattern ans = new UnaryPattern(UnaryOperator.ANeg,
                translateBinaryOp(BinaryOperator.AEq, args.get(0), args.get(1)));
        return checkPassertEnd(ans, started);
    }

    /**
     * Translate an aggregate condition that does not aggregate on working memory but rather
     *    on a per-entity collection
     * @param ast the SemAggregateCondition found to meet the criteria for this method
     * @return the translation
     */
    private CampRule translatePerEntityAggregate(SemAggregateCondition ast) {
        /* Get the collection as a SemValue */
        SemClassCondition cond = (SemClassCondition) ast.getGeneratorCondition();
        SemValue collectionValue = cond.getGenerator().getValueConditionGenerator().getValue();
        SemType componentType = getComponentType(collectionValue.getType());

        /* Detect collection of references and do special processing */
        String idAttribute = getIdAttribute(componentType);
        if (idAttribute != null) {
            /* Collection of references */
            savedFetchRef = new CompoundPattern(new LetItPattern(CampPattern.LEFT),
                    CampMacros.fetchRef(idAttribute, componentType.getDisplayName()));
        }

        /* Translate the tests, with savedFetchRef in force if appropriate; then clear the saved state */
        CampPattern filter = translateTests(cond.getTests());
        savedFetchRef = null;

        /* With 'it' set to the collection, map the filter to obtain the filtered collection */
        CampPattern filteredCollection = new LetItPattern(collectionValue.accept(this), new MapPattern(filter));

        /* Do the actual aggregation */
        SemNewObject newObject = (SemNewObject) ast.getAggregateApplication().getInstanceOfAggregateClass();
        UnaryOperator op = (UnaryOperator) getOverAndOp(newObject, ast, null)[1];
        return makeAggregateAnswer(ast, new UnaryPattern(op, filteredCollection));
    }

    /**
     * Translate a method invocation known to be a size method invocation
     * @param ast the method invocation node
     * @return the translation
     */
    private CampPattern translateSizeMethod(SemMethodInvocation ast) {
        /* First distinguish the static from the instance form and translate the collection */
        List<SemValue> args = ast.getArguments();
        CampPattern collection = (args.size() > 0 ? args.get(0) : ast.getCurrentObject()).accept(this);
        /* Produce a bag count operation on the collection */
        return new UnaryPattern(UnaryOperator.ACount, collection);
    }

    /**
     * Incomplete but evolving translation for object allocations involving temporal constructs
     * @param ast the object allocation (SemNewObject)
     * @return the translation
     */
    private CampPattern translateTemporalAllocation(SemNewObject ast) {
        List<SemValue> args = ast.getArguments();
        if (DATE_TYPE.equals(ast.getType().getDisplayName())) {
            Iterator<SemValue> argIter = args.iterator();
            ZonedDateTime translation = ZonedDateTime.ofInstant(Instant.EPOCH, ZoneId.systemDefault())
                    .withYear(intFrom(argIter.next())).withMonth(intFrom(argIter.next()))
                    .withDayOfMonth(intFrom(argIter.next()));
            if (argIter.hasNext())
                translation = translation.withHour(intFrom(argIter.next()));
            if (argIter.hasNext())
                translation = translation.withMinute(intFrom(argIter.next()));
            if (argIter.hasNext())
                translation = translation.withSecond(intFrom(argIter.next()));
            ConstPattern constant = new ConstPattern(translation.toString());
            return new UnaryPattern(UnaryOperator.ATimeFromString, constant);
        }
        if (TIME_TYPE.equals(ast.getType().getDisplayName()) && args.size() == 1) {
            long epochMilli = longFrom(args.get(0));
            LocalTime translation = ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), ZoneOffset.UTC)
                    .toLocalTime();
            // TODO this should really be a unique CAMP type corresponding to the TTRL type for LocalTimeComponentValue
            return new ConstPattern(translation.toString());
        }
        return notImplemented("Translation of temporal allocation: " + ast);
    }

    /**
     * Translate a list of boolean tests, turning them into an asserted conjunction
     * @param tests the tests to process
     * @return a CampPattern for the translation
     */
    private CampPattern translateTests(List<SemValue> tests) {
        if (0 == tests.size())
            return CampMacros.accept();
        CampPattern pattern = null;
        boolean started = checkPassertStart();
        for (SemValue test : tests) {
            CampPattern pat = test.accept(this);
            if (pattern == null)
                pattern = pat;
            else {
                pattern = new BinaryPattern(BinaryOperator.AAnd, pattern, pat);
            }
        }
        return checkPassertEnd(pattern, started);
    }

    /**
     * Generate the appropriate output for converting a String to a long.  At present, we only do this when the string is a constant
     *   (in which case we can evaluate it at compile time and simply produce a long SemConstant).  Generalization to expressions
     *   would be possible but would require timeDate manipulation functions to be present in coq.
     * @param ast the SemMethodInvocation to convert, known to be of the proper form because it was examined by isTimeStringToEpochMillis
     * @return
     */
    private CampPattern translateTimeStringToEpochMillis(SemMethodInvocation ast) {
        ast = ((SemMethodInvocation) ast.getCurrentObject()); // the toInstant() call
        ast = ((SemMethodInvocation) ast.getCurrentObject()); // the parse() call
        SemValue val = ast.getArguments().get(0); // the string
        if (val instanceof SemConstant) {
            String string = (String) ((SemConstant) val).getValue();
            if (!string.contains("Z"))
                string += "Z";
            long millis = ZonedDateTime.parse(string).toInstant().toEpochMilli();
            return new ConstPattern(millis);
        }
        return notImplemented("String to date conversion for non-constants");
    }

    /**
     * If an attribute value is a relationship reference, apply the appropriate macro form to the 
     *   the access path (actually composes the functions to be applied later)
     * @param value the attribute value in question
     * @param access the access path so far, to be modified or not
     * @return the appropriate CampPattern, either the original access or a modified
     *   one for a relationship reference
     */
    private CampPattern tryRelationship(SemAttributeValue value, CampPattern access) {
        SemValue source = value.getCurrentObject();
        SemClassCondition classSource = null;
        while (source instanceof SemVariableValue) {
            SemVariableDeclaration decl = ((SemVariableValue) source).getVariableDeclaration();
            if (decl instanceof SemClassCondition) {
                classSource = (SemClassCondition) decl;
                break;
            } else if (decl instanceof SemLocalVariableDeclaration) {
                source = ((SemLocalVariableDeclaration) decl).getInitialValue();
            }
        }
        SemType type = null;
        if (classSource != null && classSource.hasGenerator()) {
            SemConditionGenerator gen = classSource.getGenerator();
            if (gen.getValueConditionGenerator().getKind() == Kind.FROM)
                type = classSource.getVariableType();
        } else if (source instanceof SemAttributeValue)
            type = source.getType();
        String idAttribute = type == null ? null : getIdAttribute(type);
        if (idAttribute != null) {
            //           System.out.println(value + " is an entity reference");
            return new CompoundPattern(CampMacros.fetchRef(idAttribute, type.getDisplayName()), access);
        }
        //       System.out.println(value + " is not an entity reference");
        return access;
    }
}