// Copyright (c) 2011, David J. Pearce (djp@ecs.vuw.ac.nz) // All rights reserved. // // This software may be modified and distributed under the terms // of the BSD license. See the LICENSE file for details. package wyautl_old.lang; import java.util.*; /** * <p> * A finite-state automaton for representing Whiley types. This is a machine for * accepting matching inputs of a given language. An automaton is a directed * graph whose nodes and edges are referred to as <i>states</i> and * <i>transitions</i>. Each state has a "kind" which determines how the state * behaves on given inputs. For example, a state with "OR" kind might accept an * input if either of its children does; in contrast, and state of "AND" kind * might accept an input only if all its children does. * </p> * * <p> * The organisation of children is done according to two approaches: * <i>deterministic</i> and <i>non-deterministic</i>. In the deterministic * approach, the ordering of children is important; in the non-deterministic * approach, the ordering of children is not important. A flag is used to * indicate whether a state is deterministic or not. * </p> * * <p> * Aside from having a particular kind, each state may also have supplementary * material. This can be used, for example, to effectively provide labelled * transitions. Another use of this might be to store a given string which must * be matched. * </p> * * <p> * <b>NOTE:</b> In the internal representation of automata, leaf states may be * not be represented as actual nodes. This will occur if the leaf node does not * include any supplementary data, and is primarily for space and performance * optimisation. In such case, the node is represented as a child node using a * negative index. * </p> * * @author David J. Pearce * */ public final class Automaton { public State[] states; // should not be public! public Automaton(State... states) { this.states = states; } public Automaton(List<State> states) { int statesSize = states.size(); this.states = new State[statesSize]; for(int i=0;i!=statesSize;++i) { this.states[i] = states.get(i); } } public Automaton(Automaton automaton) { this.states = new State[automaton.states.length]; for(int i=0;i!=states.length;++i) { states[i] = new State(automaton.states[i]); } } public int size() { return states.length; } /** * Determine the hashCode of a type. */ @Override public int hashCode() { int r = 0; for(State c : states) { r = r + c.hashCode(); } return r; } /** * This method compares two compound types to test whether they are * <i>identical</i>. Observe that it does not perform an * <i>isomorphism</i> test. Thus, two distinct types which are * structurally isomorphic will <b>not</b> be considered equal under * this method. <b>NOTE:</b> to test whether two types are structurally * isomorphic, using the <code>isomorphic(t1,t2)</code> method. */ @Override public boolean equals(Object o) { if(o instanceof Automaton) { State[] cs = ((Automaton) o).states; if(cs.length != states.length) { return false; } for(int i=0;i!=cs.length;++i) { if(!states[i].equals(cs[i])) { return false; } } return true; } return false; } @Override public String toString() { String r = ""; for(int i=0;i!=states.length;++i) { if(i != 0) { r = r + ", "; } Automaton.State state = states[i]; int kind = state.kind; r = r + "#"; r = r + i; r = r + "("; r = r + kind; if(state.data != null) { r = r + "," + state.data; } r = r + ")"; if(state.deterministic) { r = r + "["; } else { r = r + "{"; } boolean firstTime=true; for(int c : state.children) { if(!firstTime) { r = r + ","; } firstTime=false; r = r + c; } if(state.deterministic) { r = r + "]"; } else { r = r + "}"; } } return r; } /** * Represents a state in an automaton. Each state has a kind, along with zero * or more children and an (optional) supplementary data item. * * @author David J. Pearce * */ public static final class State { public int kind; public int[] children; public boolean deterministic; public Object data; /** * Construct a deterministic state with no children and no supplementary data. * * @param kind * --- State kind (must be positive integer). */ public State(int kind) { this(kind,null,true,NOCHILDREN); } /** * Construct a deterministic state with no supplementary data. * * @param kind * --- State kind (must be positive integer). * @param children * --- Array of child indices. */ public State(int kind, int... children) { this(kind,null,true,children); } /** * Construct a state with no supplementary data. * * @param kind * --- State kind (must be positive integer). * @param deterministic * --- Indicates whether node should be treated as * deterministic or not. * @param children * --- Array of child indices. */ public State(int kind, boolean deterministic, int... children) { this(kind,null,deterministic,children); } /** * Construct a state with children and supplementary data. * * @param kind * --- State kind (must be positive integer). * @param data * --- Supplementary data store with state. * @param deterministic * --- Indicates whether node should be treated as * deterministic or not. * @param children * --- Array of child indices. */ public State(int kind, Object data, boolean deterministic, int... children) { this.kind = kind; this.children = children; this.data = data; this.deterministic = deterministic; } public State(State state) { kind = state.kind; children = Arrays.copyOf(state.children, state.children.length); data = state.data; deterministic = state.deterministic; } @Override public boolean equals(final Object o) { if (o instanceof State) { State c = (State) o; // in the following, we only need to check data != null for this // node as both nodes have the same kind and, hence, this.data // != null implies c.data != null. return kind == c.kind && deterministic == c.deterministic && Arrays.equals(children, c.children) && (data == null || data.equals(c.data)); } return false; } @Override public int hashCode() { int r = Arrays.hashCode(children) + kind; if (data != null) { return r + data.hashCode(); } else { return r; } } } /** * The following constant is used simply to prevent unnecessary memory * allocations. */ public static final int[] NOCHILDREN = new int[0]; }