public abstract class BinaryOperation implements NodeWithValue {
	@Nonnull
	public static BinaryOperation fromOperator(@Nonnull BinaryOperator operator, @Nonnull NodeWithValue left, @Nonnull NodeWithValue right) {
		if (operator instanceof FloatMath.Operator) {
			return new FloatMath((FloatMath.Operator) operator, left, right);
		} else if (operator instanceof IntMath.Operator) {
			return new IntMath((IntMath.Operator) operator, left, right);
		} else if (operator instanceof Equality.Operator) {
			return new Equality((Equality.Operator) operator, left, right);
		} else if (operator instanceof Logic.Operator) {
			return new Logic((Logic.Operator) operator, left, right);
		} else if (operator instanceof RelationalComparison.Operator) {
			return new RelationalComparison((RelationalComparison.Operator) operator, left, right);
		} else {
			throw new RuntimeException("Not reached");
		}
	}

	@Nonnull
	public abstract NodeWithValue left();

	@Nonnull
	public abstract NodeWithValue right();
} public interface BinaryOperator {
} public class Equality extends BinaryOperation {
	@Nonnull
	public final Operator operator;
	@Nonnull
	public final NodeWithValue left;
	@Nonnull
	public final NodeWithValue right;


	public Equality(@Nonnull Operator operator, @Nonnull NodeWithValue left, @Nonnull NodeWithValue right) {
		this.operator = operator;
		this.left = left;
		this.right = right;
	}

	@Override
	@Nonnull
	public NodeWithValue left() {
		return this.left;
	}

	@Override
	@Nonnull
	public NodeWithValue right() {
		return this.right;
	}

	public enum Operator implements BinaryOperator { // TODO could get by with as few as one of these
		Eq("=="),
		Neq("!="),
		StrictEq("==="),
		StrictNeq("!==");

		@Nonnull
		public final String name;

		Operator(@Nonnull String name) {
			this.name = name;
		}
	}
} public class FloatMath extends BinaryOperation {
	@Nonnull
	public final Operator operator;
	@Nonnull
	public final NodeWithValue left;
	@Nonnull
	public final NodeWithValue right;


	public FloatMath(@Nonnull Operator operator, @Nonnull NodeWithValue left, @Nonnull NodeWithValue right) {
		this.operator = operator;
		this.left = left;
		this.right = right;
	}

	@Override
	@Nonnull
	public NodeWithValue left() {
		return this.left;
	}

	@Override
	@Nonnull
	public NodeWithValue right() {
		return this.right;
	}

	public enum Operator implements BinaryOperator {
		Plus("+"),
		Minus("-"),
		Mul("*"),
		Div("/"),
		Rem("%"),
		Exp("**");

		@Nonnull
		public final String name;

		Operator(@Nonnull String name) {
			this.name = name;
		}
	}
} public class In extends BinaryOperation {
	@Nonnull
	public final NodeWithValue left;


	@Nonnull

	public final NodeWithValue right;

	@Override
	@Nonnull
	public NodeWithValue left() {
		return this.left;
	}

	@Override
	@Nonnull
	public NodeWithValue right() {
		return this.right;
	}

	public In(@Nonnull NodeWithValue left, @Nonnull NodeWithValue right) {
		this.left = left;
		this.right = right;
	}
} public class InstanceOf extends BinaryOperation {
	@Nonnull
	public final NodeWithValue left;


	@Nonnull
	public final NodeWithValue right;

	@Override
	@Nonnull
	public NodeWithValue left() {
		return this.left;
	}

	@Override
	@Nonnull
	public NodeWithValue right() {
		return this.right;
	}

	public InstanceOf(@Nonnull NodeWithValue left, @Nonnull NodeWithValue right) {
		this.left = left;
		this.right = right;
	}
} public class IntMath extends BinaryOperation {
	@Nonnull
	public final Operator operator;
	@Nonnull
	public final NodeWithValue left;
	@Nonnull
	public final NodeWithValue right;


	public IntMath(@Nonnull Operator operator, @Nonnull NodeWithValue left, @Nonnull NodeWithValue right) {
		this.operator = operator;
		this.left = left;
		this.right = right;
	}

	@Override
	@Nonnull
	public NodeWithValue left() {
		return this.left;
	}

	@Override
	@Nonnull
	public NodeWithValue right() {
		return this.right;
	}

	public enum Operator implements BinaryOperator {
		BitwiseAnd("&"),
		BitwiseOr("|"),
		BitwiseXor("^"),
		LeftShift("<<"),
		RightShift(">>"),
		UnsignedRightShift(">>>");

		@Nonnull
		public final String name;

		Operator(@Nonnull String name) {
			this.name = name;
		}
	}
} public class RelationalComparison extends BinaryOperation {
	@Nonnull
	public final Operator operator;
	@Nonnull
	public final NodeWithValue left;


	@Nonnull

	public final NodeWithValue right;

	public RelationalComparison(@Nonnull Operator operator, @Nonnull NodeWithValue left, @Nonnull NodeWithValue right) {
		this.operator = operator;
		this.left = left;
		this.right = right;
	}

	@Override
	@Nonnull
	public NodeWithValue left() {
		return this.left;
	}

	@Override
	@Nonnull
	public NodeWithValue right() {
		return this.right;
	}

	public enum Operator implements BinaryOperator {
		LessThan("<"),
		LessThanEqual("<="),
		GreaterThan(">"),
		GreaterThanEqual(">=");

		@Nonnull
		public final String name;

		Operator(@Nonnull String name) {
			this.name = name;
		}
	}
} public class BlockWithValue implements NodeWithValue {
	@Nonnull
	public Block head;
	@Nonnull
	public NodeWithValue result;


	public BlockWithValue(@Nonnull Block head, @Nonnull NodeWithValue result) {
		this.head = head;
		this.result = result;
	}

	public BlockWithValue(@Nonnull ImmutableList<Node> children, @Nonnull NodeWithValue result) {
		this.head = new Block(children);
		this.result = result;
	}
} public class DeleteGlobalProperty implements NodeWithValue {


	@Nonnull
	public final String which;

	public DeleteGlobalProperty(@Nonnull String which) {
		this.which = which;
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof DeleteGlobalProperty && this.which.equals(((DeleteGlobalProperty) node).which);
	}
} public class GlobalReference implements NodeWithValue {


	@Nonnull
	public final String name;

	public GlobalReference(@Nonnull String name) {
		this.name = name;
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof GlobalReference && this.name.equals(((GlobalReference) node).name);
	}
} public class Halt implements NodeWithValue {
	@Nonnull
	public static final Halt INSTANCE = new Halt();


	private Halt() {
	}

} public class IfElse implements Node {
	@Nonnull
	public final NodeWithValue test; // TODO could be a type coercion node


	@Nonnull
	public final Block consequent;
	@Nonnull
	public final Block alternate;

	public IfElse(@Nonnull NodeWithValue test, @Nonnull Block consequent, @Nonnull Block alternate) {
		this.test = test;
		this.consequent = consequent;
		this.alternate = alternate;
	}
} public class Keys implements NodeWithValue {


	@Nonnull
	public final NodeWithValue _object;

	public Keys(@Nonnull NodeWithValue object) {
		_object = object;
	}
} public class LiteralBoolean implements Literal {


	public final boolean value;

	public LiteralBoolean(boolean value) {
		this.value = value;
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof LiteralBoolean && this.value == ((LiteralBoolean) node).value;
	}
} public class LiteralEmptyObject implements Literal {
	@Nonnull
	public static final LiteralEmptyObject INSTANCE = new LiteralEmptyObject();


	private LiteralEmptyObject() {
	}
} public class LiteralFunction implements Literal {
	@Nonnull
	public final Maybe<Variable> name;
	@Nonnull
	public final Maybe<Variable> arguments;
	@Nonnull
	public final ImmutableList<Variable> parameters;
	@Nonnull
	public final ImmutableList<Variable> locals;


	@Nonnull
	public final ImmutableList<Variable> captured;
	@Nonnull
	public final Block body;
	@Nonnull
	public final boolean isStrict;

	public LiteralFunction(
		@Nonnull Maybe<Variable> name, @Nonnull Maybe<Variable> arguments, @Nonnull ImmutableList<Variable> parameters, @Nonnull ImmutableList<Variable> locals,
		@Nonnull ImmutableList<Variable> captured, @Nonnull Block body, boolean isStrict
	) {
		this.name = name;
		this.arguments = arguments;
		this.parameters = parameters;
		this.locals = locals;
		this.captured = captured;
		this.body = body;
		this.isStrict = isStrict;
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof LiteralFunction &&
			this.name.equals(((LiteralFunction) node).name) &&
			this.arguments.equals(((LiteralFunction) node).arguments) &&
			this.parameters.equals(((LiteralFunction) node).parameters) &&
			this.locals.equals(((LiteralFunction) node).locals) &&
			this.captured.equals(((LiteralFunction) node).captured) &&
			this.isStrict == ((LiteralFunction) node).isStrict;
	}
} public class LiteralInfinity implements Literal {
	@Nonnull
	public static final LiteralInfinity INSTANCE = new LiteralInfinity();


	private LiteralInfinity() {
	}
} public class LiteralNull implements Literal {
	@Nonnull
	public static final LiteralNull INSTANCE = new LiteralNull();


	private LiteralNull() {
	}

} public class LiteralNumber implements Node, Literal {


	public final double value;

	public LiteralNumber(double value) {
		this.value = value;
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof LiteralNumber && this.value == ((LiteralNumber) node).value;
	}
} public class LiteralRegExp implements Literal {
	@Nonnull
	public final String pattern;
	public final boolean global;
	public final boolean ignoreCase;
	public final boolean multiLine;
	public final boolean sticky;
	public final boolean unicode;


	public LiteralRegExp(@Nonnull String pattern, boolean global, boolean ignoreCase, boolean multiLine, boolean sticky, boolean unicode) {
		this.pattern = pattern;
		this.global = global;
		this.ignoreCase = ignoreCase;
		this.multiLine = multiLine;
		this.sticky = sticky;
		this.unicode = unicode;
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof LiteralRegExp &&
			this.pattern.equals(((LiteralRegExp) node).pattern) &&
			this.global == ((LiteralRegExp) node).global &&
			this.ignoreCase == ((LiteralRegExp) node).ignoreCase &&
			this.multiLine == ((LiteralRegExp) node).ignoreCase &&
			this.sticky == ((LiteralRegExp) node).sticky &&
			this.unicode == ((LiteralRegExp) node).unicode;
	}
} public class LiteralString implements Literal {
	@Nonnull
	public final String value;


	public LiteralString(@Nonnull String value) {
		this.value = value;
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof LiteralString && this.value.equals(((LiteralString) node).value);
	}
} public class LocalReference implements NodeWithValue {
	@Nonnull
	public Variable variable;


	public LocalReference(@Nonnull Variable variable) {
		this.variable = variable;
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node.getClass() == LocalReference.class && this.variable.equals(((LocalReference) node).variable);
	}
} public class MemberAccess implements NodeWithValue {
	@Nonnull
	public final NodeWithValue object;


	@Nonnull
	public final NodeWithValue fieldExpression;

	public MemberAccess(@Nonnull NodeWithValue object, @Nonnull NodeWithValue fieldExpression) {
		this.object = object;
		this.fieldExpression = fieldExpression;
	}
} public class MemberAssignment implements NodeWithValue {
	@Nonnull
	public final NodeWithValue object;
	@Nonnull
	public final NodeWithValue fieldExpression;
	@Nonnull
	public final MemberAssignmentProperty.StaticValue property;
	public final boolean strict;


	public MemberAssignment(
		@Nonnull NodeWithValue object, @Nonnull NodeWithValue fieldExpression,
		@Nonnull MemberAssignmentProperty.StaticValue property,
		boolean strict
	) {
		this.object = object;
		this.fieldExpression = fieldExpression;
		this.property = property;
		this.strict = strict;
	}

	public MemberAssignment(
		@Nonnull NodeWithValue object, @Nonnull NodeWithValue fieldExpression, @Nonnull NodeWithValue staticValue, boolean strict
	) {
		this.object = object;
		this.fieldExpression = fieldExpression;
		this.property = new MemberAssignmentProperty.StaticValue(staticValue);
		this.strict = strict;
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof MemberAssignment && this.strict == ((MemberAssignment) node).strict;
	}
} public interface MemberAssignmentProperty extends Node {
	class StaticValue implements MemberAssignmentProperty {
		@Nonnull
		public final NodeWithValue value;

		public StaticValue(@Nonnull NodeWithValue value) {
			this.value = value;
		}
	}

	class Getter implements MemberAssignmentProperty {
		@Nonnull
		public final LiteralFunction value;

		public Getter(@Nonnull LiteralFunction value) {
			this.value = value;
		}
	}

	class Setter implements MemberAssignmentProperty {
		@Nonnull
		public final LiteralFunction value;

		public Setter(@Nonnull LiteralFunction value) {
			this.value = value;
		}
	}
} public class MemberCall implements NodeWithValue {
	@Nonnull
	public final NodeWithValue object;

	@Nonnull
	public final NodeWithValue fieldExpression;

	@Nonnull
	public final ImmutableList<NodeWithValue> arguments;

	public MemberCall(@Nonnull NodeWithValue object, @Nonnull NodeWithValue fieldExpression, @Nonnull ImmutableList<NodeWithValue> arguments) {
		this.object = object;
		this.fieldExpression = fieldExpression;
		this.arguments = arguments;
	}


} public class MemberDefinition implements Node {
	@Nonnull
	public final NodeWithValue object;
	@Nonnull
	public final NodeWithValue fieldExpression;


	@Nonnull
	public final MemberAssignmentProperty property;

	public MemberDefinition(
		@Nonnull NodeWithValue object, @Nonnull NodeWithValue fieldExpression, @Nonnull MemberAssignmentProperty property
	) {
		this.object = object;
		this.fieldExpression = fieldExpression;
		this.property = property;
	}

	public MemberDefinition(
		@Nonnull NodeWithValue object, @Nonnull NodeWithValue fieldExpression, @Nonnull NodeWithValue staticValue
	) {
		this.object = object;
		this.fieldExpression = fieldExpression;
		this.property = new MemberAssignmentProperty.StaticValue(staticValue);
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof MemberDefinition && this.property.getClass() == ((MemberDefinition) node).property.getClass();
	}
} public class New implements NodeWithValue {
	@Nonnull
	public final NodeWithValue callee;
	@Nonnull
	public final ImmutableList<NodeWithValue> arguments;


	public New(@Nonnull NodeWithValue callee, @Nonnull ImmutableList<NodeWithValue> arguments) {
		this.callee = callee;
		this.arguments = arguments;
	}
} public interface NodeWithValue extends Node {
} public class Return implements Node {
	@Nonnull
	public final Maybe<NodeWithValue> expression;


	public Return(@Nonnull Maybe<NodeWithValue> expression) {
		this.expression = expression;
	}

	public Return(@Nonnull NodeWithValue expression) {
		this.expression = Maybe.of(expression);
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof Return && this.expression.isJust() == ((Return) node).expression.isJust();
	}
} public class ReturnAfterFinallies implements Node {
	@Nonnull
	public final Maybe<LocalReference> savedValue;

	@Nonnull
	public final ImmutableList<BrokenThrough> broken;

	public ReturnAfterFinallies(@Nonnull Maybe<LocalReference> savedValue, @Nonnull ImmutableList<BrokenThrough> broken) {
		this.savedValue = savedValue;
		this.broken = broken;
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof ReturnAfterFinallies &&
			this.savedValue.isJust() == ((ReturnAfterFinallies) node).savedValue.isJust()
			&& this.broken.equals(((ReturnAfterFinallies) node).broken);
	}

} public class TemporaryReference extends LocalReference {
	public TemporaryReference() {
		super(new Variable(
			"LOCAL",
			ImmutableList.empty(),
			ImmutableList.empty()
		)); // TODO consider if this is what we actually want to do
	}

	// This is used in ReconstructingReducer
	public TemporaryReference(@Nonnull Variable variable) {
		super(variable);
	}
} public class This implements NodeWithValue {
	public final boolean strict;


	public This(boolean strict) {
		this.strict = strict;
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof This && this.strict == ((This) node).strict;
	}
} public class Throw implements Node {
	@Nonnull
	public final NodeWithValue expression;


	public Throw(@Nonnull NodeWithValue expression) {
		this.expression = expression;
	}
} public class TryCatch implements Node {
	@Nonnull
	public final Block tryBody;
	@Nonnull
	public final Pair<Variable, Block> catchBody;


	public TryCatch(@Nonnull Block tryBody, @Nonnull Pair<Variable, Block> catchBody) {
		this.tryBody = tryBody;
		this.catchBody = catchBody;
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof TryCatch && this.catchBody.left.equals(((TryCatch) node).catchBody.left);
	}
} public class TryFinally implements Node {
	@Nonnull
	public final Block tryBody;

	@Nonnull
	public final Block finallyBody;


	public TryFinally(@Nonnull Block tryBody, @Nonnull Block finallyBody) {
		this.tryBody = tryBody;
		this.finallyBody = finallyBody;
	}
} public class TypeCoercionNumber implements NodeWithValue {
	@Nonnull
	public final NodeWithValue expression;


	public TypeCoercionNumber(@Nonnull NodeWithValue expression) {
		this.expression = expression;
	}
} public class TypeCoercionObject implements NodeWithValue {
	@Nonnull
	public final NodeWithValue expression;


	public TypeCoercionObject(@Nonnull NodeWithValue expression) {
		this.expression = expression;
	}
} public class TypeCoercionString implements NodeWithValue {
	@Nonnull
	public final NodeWithValue expression;


	public TypeCoercionString(@Nonnull NodeWithValue expression) {
		this.expression = expression;
	}
} public class TypeofGlobal implements NodeWithValue {
	@Nonnull
	public final String which;


	public TypeofGlobal(@Nonnull String which) {
		this.which = which;
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof TypeofGlobal && this.which.equals(((TypeofGlobal) node).which);
	}
} public class Negation extends UnaryOperation {


	@Nonnull
	public final NodeWithValue expression;

	@Nonnull
	public NodeWithValue expression() {
		return this.expression;
	}

	public Negation(@Nonnull NodeWithValue expression) {
		this.expression = expression;
	}
} public class Not extends UnaryOperation {


	@Nonnull
	public final NodeWithValue expression;

	@Nonnull
	public NodeWithValue expression() {
		return this.expression;
	}

	public Not(@Nonnull NodeWithValue expression) {
		this.expression = expression;
	}
} public class Typeof extends UnaryOperation {
	@Nonnull
	public final NodeWithValue expression;

	@Nonnull
	public NodeWithValue expression() {
		return this.expression;
	}


	public Typeof(@Nonnull NodeWithValue expression) {
		this.expression = expression;
	}
} public class Void implements Node {
	@Nonnull
	public static final Void INSTANCE = new Void();


	private Void() {
	}
} public class Logic extends BinaryOperation {
	@Nonnull
	public final Operator operator;
	@Nonnull
	public final NodeWithValue left;


	@Nonnull

	public final NodeWithValue right;

	public Logic(@Nonnull Operator operator, @Nonnull NodeWithValue left, @Nonnull NodeWithValue right) {
		this.operator = operator;
		this.left = left;
		this.right = right;
	}

	@Override
	@Nonnull
	public NodeWithValue left() {
		return this.left;
	}

	@Override
	@Nonnull
	public NodeWithValue right() {
		return this.right;
	}

	public enum Operator implements BinaryOperator {
		And("&&"),
		Or("||");

		@Nonnull
		public final String name;

		Operator(@Nonnull String name) {
			this.name = name;
		}
	}

} public class Block implements Node {
	@Nonnull
	public final ImmutableList<Node> children;

	// Note: Directives are being added to blocks in the cases where we are transforming the AST with new blocks and want
	// to ensure the directives are being applied to the block (i.e ensuring directives in IIFE's are executed). This is
	// not meant for users inserting directives in their code for blocks, but for the explicator to populate in situations
	// needing this functionality.
	@Nonnull
	public final ImmutableList<Directive> directives;

	public Block(@Nonnull ImmutableList<Node> children) {
		this.children = children;
		this.directives = ImmutableList.empty();
	}

	public Block(@Nonnull ImmutableList<Node> children, @Nonnull ImmutableList<Directive> directives) {
		this.children = children;
		this.directives = directives;
	}

	public Block(@Nonnull Node child) {
		this.children = ImmutableList.of(child);
		this.directives = ImmutableList.empty();
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof Block && this.directives.equals(((Block) node).directives);
	}

	public boolean isNoOp() {
		return !children.exists(c -> { // is no-op <==> no non-trivial ops
			if (c instanceof Void || c instanceof Literal || c instanceof LocalReference) { // TODO it might probably be better for each node to have a isNoOp method, which this method could call. or have a no-op interface, maybe.
				return false;
			} else if (c instanceof Block) {
				return !((Block) c).isNoOp();
			} else {
				return true;
			}
		});
	}
} public class Break implements Node {
	@Nonnull
	public final BreakTarget target;

	@Nonnull
	public final ImmutableList<BrokenThrough> broken;


	public Break(@Nonnull BreakTarget target, @Nonnull ImmutableList<BrokenThrough> broken) {
		this.target = target;
		this.broken = broken;
	}


	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof Break && this.broken.equals(((Break) node).broken);
	}
} public class BreakTarget implements Node { // no-op. target of a break.

	public BreakTarget() {
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node == this;
	}
} public class Call implements NodeWithValue {
	@Nonnull
	public final NodeWithValue callee;


	@Nonnull
	public final ImmutableList<NodeWithValue> arguments;

	public Call(@Nonnull NodeWithValue callee, @Nonnull ImmutableList<NodeWithValue> arguments) {
		this.callee = callee;
		this.arguments = arguments;
	}
} public class DeleteProperty implements NodeWithValue {


	@Nonnull
	public final NodeWithValue object;
	@Nonnull
	public final NodeWithValue fieldExpression;
	public final boolean strict;

	public DeleteProperty(@Nonnull NodeWithValue object, @Nonnull NodeWithValue fieldExpression, boolean strict) {
		this.object = object;
		this.fieldExpression = fieldExpression;
		this.strict = strict;
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof DeleteProperty && this.strict == ((DeleteProperty) node).strict;
	}
} public class LiteralEmptyArray implements Literal {
	@Nonnull
	public static final LiteralEmptyArray INSTANCE = new LiteralEmptyArray();


	private LiteralEmptyArray() {
	}
} public class LiteralUndefined implements Literal {
	@Nonnull
	public static final LiteralUndefined INSTANCE = new LiteralUndefined();


	private LiteralUndefined() {
	}
} public class Loop implements Node {
	@Nonnull
	public final Block block;


	public Loop(@Nonnull Block block) {
		this.block = block;
	}
} public class RequireObjectCoercible implements NodeWithValue {
	@Nonnull
	public final NodeWithValue expression;


	public RequireObjectCoercible(@Nonnull NodeWithValue expression) {
		this.expression = expression;
	}
} public class BitwiseNot extends UnaryOperation {
	@Nonnull
	public final NodeWithValue expression;

	@Override
	@Nonnull
	public NodeWithValue expression() {
		return expression;
	}


	public BitwiseNot(@Nonnull NodeWithValue expression) {
		this.expression = expression;
	}
} public class VoidOp extends UnaryOperation {


	@Nonnull
	public final NodeWithValue expression;

	@Nonnull
	public NodeWithValue expression() {
		return this.expression;
	}

	public VoidOp(@Nonnull NodeWithValue expression) {
		this.expression = expression;
	}
} public class VariableAssignment implements NodeWithValue {


	@Nonnull
	public final Either<GlobalReference, LocalReference> ref;
	@Nonnull
	public final NodeWithValue value;
	public final boolean strict;

	public VariableAssignment(
		@Nonnull Either<GlobalReference, LocalReference> ref, @Nonnull NodeWithValue value, boolean strict
	) {
		this.ref = ref;
		this.value = value;
		this.strict = strict;
	}

	public VariableAssignment(@Nonnull GlobalReference ref, @Nonnull NodeWithValue value, boolean strict) {
		this.ref = Either.left(ref);
		this.value = value;
		this.strict = strict;
	}

	public VariableAssignment(@Nonnull LocalReference ref, @Nonnull NodeWithValue value, boolean strict) {
		this.ref = Either.right(ref);
		this.value = value;
		this.strict = strict;
	}

	@Override
	public boolean equalsIgnoringChildren(@Nonnull Node node) {
		return node instanceof VariableAssignment && this.strict == ((VariableAssignment) node).strict;
	}
} public class ThisOrArgumentsOrTryCatchFinallyChecker {
	public static boolean containsThisOrArgumentsOrTryCatchFinally(FunctionDeclaration declaration) {
		return declaration.body.statements.exists(statement -> Director.reduceStatement(Reducer.INSTANCE, statement)); // ignoring FormalParameters because ES5 formal parameters can't contain expressions
	}

	public static boolean containsThisOrArgumentsOrTryCatchFinally(FunctionExpression expr) {
		return expr.body.statements.exists(statement -> Director.reduceStatement(Reducer.INSTANCE, statement));
	}

	// Note: Does not support arguments shadowing
	private static class Reducer extends MonoidalReducer<Boolean> {
		static Reducer INSTANCE = new Reducer();
		private Reducer() {
			super(Monoid.BOOLEAN_OR);
		}

		@Nonnull
		@Override
		public Boolean reduceIdentifierExpression(@Nonnull IdentifierExpression node) {
			return node.name.equals("arguments");
		}

		@Nonnull
		@Override
		public Boolean reduceAssignmentTargetIdentifier(@Nonnull AssignmentTargetIdentifier node) {
			return node.name.equals("arguments");
		}

		@Nonnull
		@Override
		public Boolean reduceThisExpression(@Nonnull ThisExpression node) {
			return true;
		}

		@Nonnull
		public Boolean reduceFunctionBody(@Nonnull FunctionBody node, @Nonnull ImmutableList<Boolean> directives, @Nonnull ImmutableList<Boolean> statements) {
			return false;
		}

		@Nonnull
		@Override
		public Boolean reduceTryCatchStatement(
				@Nonnull TryCatchStatement node,
				@Nonnull Boolean body,
				@Nonnull Boolean catchClause) {
			return true;
		}

		@Nonnull
		@Override
		public Boolean reduceTryFinallyStatement(
				@Nonnull TryFinallyStatement node,
				@Nonnull Boolean body,
				@Nonnull Maybe<Boolean> catchClause,
				@Nonnull Boolean finalizer) {
			return true;
		}
	}
} public class FinallyJumpReducer extends MonoidalReducer<FinallyJumpReducer.State> {
	@Nonnull
	public static final FinallyJumpReducer INSTANCE = new FinallyJumpReducer();

	private FinallyJumpReducer() {
		super(new StateMonoid());
	}

	@Nonnull
	public static HashTable<Node, Pair<Node, ImmutableList<BrokenThrough>>> extract(@Nonnull FinallyJumpReducer.State state) {
		assert state.unlabelledBreaks.length == 0;
		assert state.labelledBreaks.length == 0;
		assert state.unlabelledContinues.length == 0;
		assert state.labelledContinues.length == 0;
		assert state.returns.length == 0;
		return state.knownJumps;
	}

	@Nonnull
	public static HashTable<Node, Pair<Node, ImmutableList<BrokenThrough>>> analyze(@Nonnull Script script) {
		return FinallyJumpReducer.extract(Director.reduceScript(INSTANCE, script));
	}

	@Nonnull
	public static HashTable<Node, Pair<Node, ImmutableList<BrokenThrough>>> analyze(@Nonnull Module module) {
		return FinallyJumpReducer.extract(Director.reduceModule(INSTANCE, module));
	}


	@Nonnull
	private static State loopHelper(@Nonnull Node loopNode, @Nonnull State body) { // no labels in loop bounds
		HashTable<Node, Pair<Node, ImmutableList<BrokenThrough>>> knownJumps = body.knownJumps;
		knownJumps = body.unlabelledBreaks.foldLeft((table, kv) -> table.put(kv.left, new Pair<>(loopNode, kv.right)), knownJumps);
		knownJumps = body.unlabelledContinues.foldLeft((table, kv) -> table.put(kv.left, new Pair<>(loopNode, kv.right)), knownJumps);
		return new State(
			HashTable.emptyUsingIdentity(),
			body.labelledBreaks,
			HashTable.emptyUsingIdentity(),
			body.labelledContinues,
			body.returns,
			knownJumps
		);
	}

	@Nonnull
	private State switchHelper(@Nonnull Node switchNode, @Nonnull State body) { // no labels in discriminant
		return new State(
			HashTable.emptyUsingIdentity(),
			body.labelledBreaks,
			body.unlabelledContinues,
			body.labelledContinues,
			body.returns,
			body.unlabelledBreaks.foldLeft((table, kv) -> table.put(kv.left, new Pair<>(switchNode, kv.right)), body.knownJumps)
		);
	}

	@Nonnull
	@Override
	public State reduceBreakStatement(@Nonnull BreakStatement node) {
		if (node.label.isJust()) {
			String label = node.label.fromJust();
			return new State(
				HashTable.emptyUsingIdentity(),
				HashTable.<String, ImmutableList<Pair<BreakStatement, ImmutableList<BrokenThrough>>>>emptyUsingEquality().put(
					label,
					ImmutableList.of(new Pair<>(node, ImmutableList.empty()))
				),
				HashTable.emptyUsingIdentity(),
				HashTable.emptyUsingEquality(),
				HashTable.emptyUsingIdentity(),
				HashTable.emptyUsingIdentity()
			);
		} else {
			return new State(
				HashTable.<BreakStatement, ImmutableList<BrokenThrough>>emptyUsingIdentity().put(node, ImmutableList.empty()),
				HashTable.emptyUsingEquality(),
				HashTable.emptyUsingIdentity(),
				HashTable.emptyUsingEquality(),
				HashTable.emptyUsingIdentity(),
				HashTable.emptyUsingIdentity()
			);
		}
	}

	@Nonnull
	@Override
	public State reduceContinueStatement(@Nonnull ContinueStatement node) {
		if (node.label.isJust()) {
			String label = node.label.fromJust();
			return new State(
				HashTable.emptyUsingIdentity(),
				HashTable.emptyUsingEquality(),
				HashTable.emptyUsingIdentity(),
				HashTable.<String, ImmutableList<Pair<ContinueStatement, ImmutableList<BrokenThrough>>>>emptyUsingEquality().put(
					label,
					ImmutableList.of(new Pair<>(node, ImmutableList.empty()))
				),
				HashTable.emptyUsingIdentity(),
				HashTable.emptyUsingIdentity()
			);
		} else {
			return new State(
				HashTable.emptyUsingIdentity(),
				HashTable.emptyUsingEquality(),
				HashTable.<ContinueStatement, ImmutableList<BrokenThrough>>emptyUsingIdentity().put(node, ImmutableList.empty()),
				HashTable.emptyUsingEquality(),
				HashTable.emptyUsingIdentity(),
				HashTable.emptyUsingIdentity()
			);
		}
	}

	@Nonnull
	@Override
	public State reduceDoWhileStatement(@Nonnull DoWhileStatement node, @Nonnull State body, @Nonnull State test) {
		return loopHelper(node, super.reduceDoWhileStatement(node, body, test));
	}

	@Nonnull
	@Override
	public State reduceForInStatement(
		@Nonnull ForInStatement node, @Nonnull State left, @Nonnull State right, @Nonnull State body
	) {
		return loopHelper(node, super.reduceForInStatement(node, left, right, body));
	}

	@Nonnull
	@Override
	public State reduceForOfStatement(
		@Nonnull ForOfStatement node, @Nonnull State left, @Nonnull State right, @Nonnull State body
	) {
		return loopHelper(node, super.reduceForOfStatement(node, left, right, body));
	}

	@Nonnull
	@Override
	public State reduceForStatement(
		@Nonnull ForStatement node, @Nonnull Maybe<State> init, @Nonnull Maybe<State> test, @Nonnull Maybe<State> update,
		@Nonnull State body
	) {
		return loopHelper(node, super.reduceForStatement(node, init, test, update, body));
	}

	@Nonnull
	@Override
	public State reduceLabeledStatement(@Nonnull LabeledStatement node, @Nonnull State body) {
		ImmutableList<Pair<BreakStatement, ImmutableList<BrokenThrough>>> newBreaks = body.labelledBreaks.get(node.label).orJust(ImmutableList.empty());
		ImmutableList<Pair<ContinueStatement, ImmutableList<BrokenThrough>>> newContinues =
			body.labelledContinues.get(node.label).orJust(ImmutableList.empty());
		HashTable<String, ImmutableList<Pair<BreakStatement, ImmutableList<BrokenThrough>>>> labelledBreaks = body.labelledBreaks.remove(node.label);
		HashTable<String, ImmutableList<Pair<ContinueStatement, ImmutableList<BrokenThrough>>>> labelledContinues =
			body.labelledContinues.remove(node.label);

		HashTable<Node, Pair<Node, ImmutableList<BrokenThrough>>> knownJumps = body.knownJumps;
		knownJumps = newBreaks.foldLeft((table, kv) -> table.put(kv.left, new Pair<>(node.body, kv.right)), knownJumps);
		knownJumps = newContinues.foldLeft((table, kv) -> table.put(kv.left, new Pair<>(node.body, kv.right)), knownJumps);

		return new State(
			body.unlabelledBreaks,
			labelledBreaks,
			body.unlabelledContinues,
			labelledContinues,
			body.returns,
			knownJumps
		);
	}


	@Nonnull
	@Override
	public State reduceSwitchStatement(
		@Nonnull SwitchStatement node, @Nonnull State discriminant, @Nonnull ImmutableList<State> cases
	) {
		return switchHelper(node, super.reduceSwitchStatement(node, discriminant, cases));
	}

	@Nonnull
	@Override
	public State reduceSwitchStatementWithDefault(
		@Nonnull SwitchStatementWithDefault node, @Nonnull State discriminant, @Nonnull ImmutableList<State> preDefaultCases,
		@Nonnull State defaultCase, @Nonnull ImmutableList<State> postDefaultCases
	) {
		return switchHelper(
			node,
			super.reduceSwitchStatementWithDefault(node, discriminant, preDefaultCases, defaultCase, postDefaultCases)
		);
	}

	@Nonnull
	public State reduceTryCatchStatement(@Nonnull TryCatchStatement node, @Nonnull State body, @Nonnull State catchClause) {
		return super.reduceTryCatchStatement(node, body.observeTryWithCatch(), catchClause);
	}

	@Nonnull
	@Override
	public State reduceTryFinallyStatement(
		@Nonnull TryFinallyStatement node, @Nonnull State block, @Nonnull Maybe<State> catchClause, @Nonnull State finalizer
	) {
		// We model try-catch-finally as a separate try-catch inside of the try of a try-finally
		if (catchClause.isJust()) {
			block = block.observeTryWithCatch();
		}
		block = block.observeTryWithFinally();
		catchClause = catchClause.map(State::observeTryWithFinally);
		finalizer = finalizer.observeFinally();
		return super.reduceTryFinallyStatement(node, block, catchClause, finalizer);
	}

	@Nonnull
	@Override
	public State reduceWhileStatement(@Nonnull WhileStatement node, @Nonnull State test, @Nonnull State body) {
		return loopHelper(node, super.reduceWhileStatement(node, test, body));
	}


	@Nonnull
	@Override
	public State reduceReturnStatement(@Nonnull ReturnStatement node, @Nonnull Maybe<State> expression) {
		return super.reduceReturnStatement(node, expression).observeReturn(node);
	}

	@Nonnull
	public State reduceFunctionBody(@Nonnull FunctionBody node, @Nonnull ImmutableList<State> directives, @Nonnull ImmutableList<State> statements) {
		State superState = super.reduceFunctionBody(node, directives, statements);
		assert superState.unlabelledBreaks.length == 0;
		assert superState.labelledBreaks.length == 0;
		assert superState.unlabelledContinues.length == 0;
		assert superState.labelledContinues.length == 0;

		return new State(
				HashTable.emptyUsingIdentity(),
				HashTable.emptyUsingEquality(),
				HashTable.emptyUsingIdentity(),
				HashTable.emptyUsingEquality(),
				HashTable.emptyUsingIdentity(),
				superState.returns.foldLeft((table, kv) -> table.put(kv.left, new Pair<>(node, kv.right)), superState.knownJumps)
		);
	}

	static final class State {
		@Nonnull
		private final HashTable<BreakStatement, ImmutableList<BrokenThrough>> unlabelledBreaks;
		@Nonnull
		private final HashTable<String, ImmutableList<Pair<BreakStatement, ImmutableList<BrokenThrough>>>> labelledBreaks;
		@Nonnull
		private final HashTable<ContinueStatement, ImmutableList<BrokenThrough>> unlabelledContinues;
		@Nonnull
		private final HashTable<String, ImmutableList<Pair<ContinueStatement, ImmutableList<BrokenThrough>>>> labelledContinues;
		@Nonnull
		private final HashTable<ReturnStatement, ImmutableList<BrokenThrough>> returns;
		@Nonnull
		private final HashTable<Node, Pair<Node, ImmutableList<BrokenThrough>>> knownJumps;

		State(
				@Nonnull HashTable<BreakStatement, ImmutableList<BrokenThrough>> unlabelledBreaks,
				@Nonnull HashTable<String, ImmutableList<Pair<BreakStatement, ImmutableList<BrokenThrough>>>> labelledBreaks,
				@Nonnull HashTable<ContinueStatement, ImmutableList<BrokenThrough>> unlabelledContinues,
				@Nonnull HashTable<String, ImmutableList<Pair<ContinueStatement, ImmutableList<BrokenThrough>>>> labelledContinues,
				@Nonnull HashTable<ReturnStatement, ImmutableList<BrokenThrough>> returns,
				@Nonnull HashTable<Node, Pair<Node, ImmutableList<BrokenThrough>>> knownJumps
		) {
			this.unlabelledBreaks = unlabelledBreaks;
			this.labelledBreaks = labelledBreaks;
			this.unlabelledContinues = unlabelledContinues;
			this.labelledContinues = labelledContinues;
			this.returns = returns;
			this.knownJumps = knownJumps;
		}

		State() {
			this.unlabelledBreaks = HashTable.emptyUsingIdentity();
			this.labelledBreaks = HashTable.emptyUsingEquality();
			this.unlabelledContinues = HashTable.emptyUsingIdentity();
			this.labelledContinues = HashTable.emptyUsingEquality();
			this.returns = HashTable.emptyUsingIdentity();
			this.knownJumps = HashTable.emptyUsingIdentity();
		}

		State(@Nonnull State a, @Nonnull State b) {
			this.unlabelledBreaks = a.unlabelledBreaks.merge(b.unlabelledBreaks);
			this.labelledBreaks = a.labelledBreaks.merge(b.labelledBreaks, ImmutableList::append);
			this.unlabelledContinues = a.unlabelledContinues.merge(b.unlabelledContinues);
			this.labelledContinues = a.labelledContinues.merge(b.labelledContinues, ImmutableList::append);
			this.returns = a.returns.merge(b.returns);
			this.knownJumps = a.knownJumps.merge(b.knownJumps);
		}


		@Nonnull
		State observeTryWithCatch() {
			return new State(
					this.unlabelledBreaks.map(x -> x.cons(BrokenThrough.TRY_WITHOUT_FINALLY)),
					this.labelledBreaks.map(l -> l.map(p -> new Pair<>(p.left, p.right.cons(BrokenThrough.TRY_WITHOUT_FINALLY)))),
					this.unlabelledContinues.map(x -> x.cons(BrokenThrough.TRY_WITHOUT_FINALLY)),
					this.labelledContinues.map(l -> l.map(p -> new Pair<>(p.left, p.right.cons(BrokenThrough.TRY_WITHOUT_FINALLY)))),
					this.returns.map(x -> x.cons(BrokenThrough.TRY_WITHOUT_FINALLY)),
					this.knownJumps
			);
		}

		@Nonnull
		State observeTryWithFinally() {
			return new State(
					this.unlabelledBreaks.map(x -> x.cons(BrokenThrough.TRY_WITH_FINALLY)),
					this.labelledBreaks.map(l -> l.map(p -> new Pair<>(p.left, p.right.cons(BrokenThrough.TRY_WITH_FINALLY)))),
					this.unlabelledContinues.map(x -> x.cons(BrokenThrough.TRY_WITH_FINALLY)),
					this.labelledContinues.map(l -> l.map(p -> new Pair<>(p.left, p.right.cons(BrokenThrough.TRY_WITH_FINALLY)))),
					this.returns.map(x -> x.cons(BrokenThrough.TRY_WITH_FINALLY)),
					this.knownJumps
			);
		}

		@Nonnull
		State observeFinally() {
			return new State(
					this.unlabelledBreaks.map(x -> x.cons(BrokenThrough.FINALLY)),
					this.labelledBreaks.map(l -> l.map(p -> new Pair<>(p.left, p.right.cons(BrokenThrough.FINALLY)))),
					this.unlabelledContinues.map(x -> x.cons(BrokenThrough.FINALLY)),
					this.labelledContinues.map(l -> l.map(p -> new Pair<>(p.left, p.right.cons(BrokenThrough.FINALLY)))),
					this.returns.map(x -> x.cons(BrokenThrough.FINALLY)),
					this.knownJumps
			);
		}

		@Nonnull
		State observeReturn(ReturnStatement node) {
			return new State(
					this.unlabelledBreaks,
					this.labelledBreaks,
					this.unlabelledContinues,
					this.labelledContinues,
					this.returns.put(node, ImmutableList.empty()),
					this.knownJumps
			);
		}
	}

	private static final class StateMonoid implements Monoid<State> {
		@Override
		@Nonnull
		public State identity() {
			return new State();
		}

		@Override
		@Nonnull
		public State append(@Nonnull State a, @Nonnull State b) {
			if (a == b) {
				return a;
			}
			return new State(a, b);
		}
	}
} public class FindWithsReducer extends MonoidalReducer<Pair<ConcatList<WithStatement>, MultiHashTable<FunctionBody, WithStatement>>> {
	// Gives a map from function bodies to the WithStatements they contain.
	// Would need to be changed slightly for ES6, where you can have with statements in parameter lists (ugh), but that doesn't come up here.
	private static class StateMonoid implements Monoid<Pair<ConcatList<WithStatement>, MultiHashTable<FunctionBody, WithStatement>>> {
		// TODO: it would be better to use the FreePairingMonoid, but shape-functional-java doesn't provide an implementation for MultiHashTableIdentityMergeMonoid
		static StateMonoid INSTANCE = new StateMonoid();

		@Nonnull
		@Override
		public Pair<ConcatList<WithStatement>, MultiHashTable<FunctionBody, WithStatement>> identity() {
			return Pair.of(ConcatList.empty(), MultiHashTable.emptyUsingIdentity());
		}

		@Nonnull
		@Override
		public Pair<ConcatList<WithStatement>, MultiHashTable<FunctionBody, WithStatement>> append(Pair<ConcatList<WithStatement>, MultiHashTable<FunctionBody, WithStatement>> left, Pair<ConcatList<WithStatement>, MultiHashTable<FunctionBody, WithStatement>> right) {
			return Pair.of(left.left.append(right.left), left.right.merge(right.right));
		}
	}

	private static FindWithsReducer INSTANCE = new FindWithsReducer();
	private FindWithsReducer() {
		super(StateMonoid.INSTANCE);
	}

	public static Pair<ConcatList<WithStatement>, MultiHashTable<FunctionBody, WithStatement>> reduce(@Nonnull Program program) {
		return Director.reduceProgram(INSTANCE, program);
	}

	@Nonnull
	@Override
	public Pair<ConcatList<WithStatement>, MultiHashTable<FunctionBody, WithStatement>> reduceWithStatement(@Nonnull WithStatement node, @Nonnull Pair<ConcatList<WithStatement>, MultiHashTable<FunctionBody, WithStatement>> object, @Nonnull Pair<ConcatList<WithStatement>, MultiHashTable<FunctionBody, WithStatement>> body) {
		Pair<ConcatList<WithStatement>, MultiHashTable<FunctionBody, WithStatement>> s = super.reduceWithStatement(node, object, body);
		return Pair.of(s.left.append1(node), s.right);
	}

	@Nonnull
	@Override
	public Pair<ConcatList<WithStatement>, MultiHashTable<FunctionBody, WithStatement>> reduceFunctionBody(@Nonnull FunctionBody node, @Nonnull ImmutableList<Pair<ConcatList<WithStatement>, MultiHashTable<FunctionBody, WithStatement>>> directives, @Nonnull ImmutableList<Pair<ConcatList<WithStatement>, MultiHashTable<FunctionBody, WithStatement>>> statements) {
		Pair<ConcatList<WithStatement>, MultiHashTable<FunctionBody, WithStatement>> s = super.reduceFunctionBody(node, directives, statements);
		return Pair.of(s.left, s.left.foldLeft((acc, statement) -> acc.put(node, statement), s.right));
	}
}
import com.shapesecurity.functional.F;
import com.shapesecurity.functional.Pair;
import com.shapesecurity.functional.data.*;
import com.shapesecurity.shift.es2017.ast.*;
import com.shapesecurity.shift.es2017.ast.operators.UpdateOperator;
import com.shapesecurity.shift.es2017.ast.Module;
import com.shapesecurity.shift.es2017.scope.*;
import com.shapesecurity.shift.es2017.semantics.asg.BinaryOperation.BinaryOperation;
import com.shapesecurity.shift.es2017.semantics.asg.BinaryOperation.BinaryOperator;
import com.shapesecurity.shift.es2017.semantics.asg.BinaryOperation.Equality;
import com.shapesecurity.shift.es2017.semantics.asg.BinaryOperation.FloatMath;
import com.shapesecurity.shift.es2017.semantics.asg.BinaryOperation.In;
import com.shapesecurity.shift.es2017.semantics.asg.BinaryOperation.InstanceOf;
import com.shapesecurity.shift.es2017.semantics.asg.BinaryOperation.IntMath;
import com.shapesecurity.shift.es2017.semantics.asg.BinaryOperation.RelationalComparison;
import com.shapesecurity.shift.es2017.semantics.asg.BlockWithValue;
import com.shapesecurity.shift.es2017.semantics.asg.DeleteGlobalProperty;
import com.shapesecurity.shift.es2017.semantics.asg.GlobalReference;
import com.shapesecurity.shift.es2017.semantics.asg.Halt;
import com.shapesecurity.shift.es2017.semantics.asg.IfElse;
import com.shapesecurity.shift.es2017.semantics.asg.Keys;
import com.shapesecurity.shift.es2017.semantics.asg.LiteralBoolean;
import com.shapesecurity.shift.es2017.semantics.asg.LiteralEmptyObject;
import com.shapesecurity.shift.es2017.semantics.asg.LiteralFunction;
import com.shapesecurity.shift.es2017.semantics.asg.LiteralInfinity;
import com.shapesecurity.shift.es2017.semantics.asg.LiteralNull;
import com.shapesecurity.shift.es2017.semantics.asg.LiteralNumber;
import com.shapesecurity.shift.es2017.semantics.asg.LiteralRegExp;
import com.shapesecurity.shift.es2017.semantics.asg.LiteralString;
import com.shapesecurity.shift.es2017.semantics.asg.LocalReference;
import com.shapesecurity.shift.es2017.semantics.asg.MemberAccess;
import com.shapesecurity.shift.es2017.semantics.asg.MemberAssignment;
import com.shapesecurity.shift.es2017.semantics.asg.MemberAssignmentProperty;
import com.shapesecurity.shift.es2017.semantics.asg.MemberCall;
import com.shapesecurity.shift.es2017.semantics.asg.MemberDefinition;
import com.shapesecurity.shift.es2017.semantics.asg.New;
import com.shapesecurity.shift.es2017.semantics.asg.NodeWithValue;
import com.shapesecurity.shift.es2017.semantics.asg.Return;
import com.shapesecurity.shift.es2017.semantics.asg.ReturnAfterFinallies;
import com.shapesecurity.shift.es2017.semantics.asg.TemporaryReference;
import com.shapesecurity.shift.es2017.semantics.asg.This;
import com.shapesecurity.shift.es2017.semantics.asg.Throw;
import com.shapesecurity.shift.es2017.semantics.asg.TryCatch;
import com.shapesecurity.shift.es2017.semantics.asg.TryFinally;
import com.shapesecurity.shift.es2017.semantics.asg.TypeCoercionNumber;
import com.shapesecurity.shift.es2017.semantics.asg.TypeCoercionObject;
import com.shapesecurity.shift.es2017.semantics.asg.TypeCoercionString;
import com.shapesecurity.shift.es2017.semantics.asg.TypeofGlobal;
import com.shapesecurity.shift.es2017.semantics.asg.UnaryOperation.Negation;
import com.shapesecurity.shift.es2017.semantics.asg.UnaryOperation.Not;
import com.shapesecurity.shift.es2017.semantics.asg.UnaryOperation.Typeof;
import com.shapesecurity.shift.es2017.semantics.asg.Void;
import com.shapesecurity.shift.es2017.semantics.asg.BinaryOperation.Logic;
import com.shapesecurity.shift.es2017.semantics.asg.Block;
import com.shapesecurity.shift.es2017.semantics.asg.Break;
import com.shapesecurity.shift.es2017.semantics.asg.BreakTarget;
import com.shapesecurity.shift.es2017.semantics.asg.Call;
import com.shapesecurity.shift.es2017.semantics.asg.DeleteProperty;
import com.shapesecurity.shift.es2017.semantics.asg.LiteralEmptyArray;
import com.shapesecurity.shift.es2017.semantics.asg.LiteralUndefined;
import com.shapesecurity.shift.es2017.semantics.asg.Loop;
import com.shapesecurity.shift.es2017.semantics.asg.RequireObjectCoercible;
import com.shapesecurity.shift.es2017.semantics.asg.UnaryOperation.BitwiseNot;
import com.shapesecurity.shift.es2017.semantics.asg.UnaryOperation.VoidOp;
import com.shapesecurity.shift.es2017.semantics.asg.VariableAssignment;
import com.shapesecurity.shift.es2017.semantics.ast.ThisOrArgumentsOrTryCatchFinallyChecker;
import com.shapesecurity.shift.es2017.semantics.visitor.FinallyJumpReducer;
import com.shapesecurity.shift.es2017.semantics.visitor.FindWithsReducer;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.function.Function;
import java.util.function.Supplier;
/*
 * Copyright 2016 Shape Security, Inc.
 *
 * 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 com.shapesecurity.shift.es2017.semantics;




public class Explicator {
	@Nonnull
	final GlobalScope scope;
	@Nonnull
	final Either<Script, Module> program;
	@Nonnull
	final HashTable<com.shapesecurity.shift.es2017.ast.Node, Pair<com.shapesecurity.shift.es2017.ast.Node, ImmutableList<BrokenThrough>>> jumpMap;
	@Nonnull
	final ScopeLookup scopeLookup;
	@Nonnull
	HashTable<com.shapesecurity.shift.es2017.ast.Node, Pair<BreakTarget, Maybe<BreakTarget>>> targets = HashTable.emptyUsingIdentity();
	// map from AST loops and labelled statement to their corresponding Target nodes. Loops have an outer and an inner, for break and continue respectively.

	@Nonnull
	private ImmutableList<PerFunctionState> oldStates = ImmutableList.empty();
	@Nonnull
	PerFunctionState currentState = new PerFunctionState();
	@Nonnull
	final IdentityHashMap<LiteralFunction, Scope> functionScopes;
	@Nonnull
	final MultiHashTable<Reference, WithStatement> withReferences;
	@Nonnull
	final IdentityHashMap<WithStatement, LocalReference> withObjects;