// MP4.java // Author: Douglas W. Jones // Version: 2019-04-03 import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.Iterator; import java.util.List; import java.util.LinkedList; import java.util.regex.Pattern; import java.util.Scanner; import java.util.PriorityQueue; /** Error reporting package * Provide a standard prefix and behavior for error reporting * @author Douglas W. Jones * @version 2019-03-03 * Lifted from RoadNetwork.java version 2019-03-01 * augmented to count errors */ class Errors { // prefix string for all error messages private static String prefix = "??: "; // how many warnings have been issued (see warnings() method) private static int warnCount = 0; /** Set prefix on error reports, should be done before any error reports * @arg p the prefix on any error messages */ public static void setPrefix( String p ) { prefix = p; } /** Report nonfatal errors, output a message and return * @arg m the message to output */ public static void warn( String m ) { System.err.println( prefix + ": " + m ); warnCount = warnCount + 1; } /** Report the number of warnings that have been issued * @returns the count */ public static int warnings() { return warnCount; } /** Report fatal errors, output a message and die * @arg m the message to output */ public static void fatal( String m ) { warn( m ); System.exit( 1 ); } } /** Support package for application dependent extensions to class Scanner * @author Douglas Jones * @version 2019-03-04 * Based on RoadNetwork.java version 2019-03-01 * augmented to use lambda expressions for error messages * and to scan floating point numbers * @see Errors * If class Scanner wasn't final, this could be a subclass */ class ScanSupport { // patterns used in methods below private static final Pattern name = // match one legal name Pattern.compile( "[A-Za-z]*[A-Za-z0-9]" ); private static final Pattern blanks = // match any number of blanks Pattern.compile( "[ \t]*" ); private static final Pattern theRest = // match text up to newline Pattern.compile( "[^\n]*" ); /** Support interface for passing deferred computation of message text * set up so that lambda notation can be used */ public static interface MessageCarrier { String content(); } /** Scan and return one name, if available * @arg sc the scanner to read from * @arg msg the message to output if there is no name * @returns the name, or null if there is no name * Typically called as ScanSupport.scanName( sc, () -> string + expr ) */ public static String scanName( Scanner sc, MessageCarrier msg ) { if (sc.hasNext( name )) return sc.next(name); Errors.warn( msg.content() ); return null; } /** Scan and return one nonnegative int, if available * @arg sc the scanner to read from * @arg msg the message prefix to output if there is no int * @returns the value, or 0 if there is no number * Typically called as ...scanPositiveInt( sc, () -> string + expr ) */ public static int scanPositiveInt( Scanner sc, MessageCarrier msg ) { if (sc.hasNextInt()) { int value = sc.nextInt(); if (value >= 0) return value; Errors.warn( msg.content() + " " + value + ": must be positive" ); } Errors.warn( msg.content() + ": integer value expected" ); return 0; } /** Scan and return one nonnegative float, if available * @arg sc the scanner to read from * @arg msg the message prefix to output if there is no float * @returns the value, or NaN if there is no number * Typically called as ...scanPositiveFloat( sc, () -> string + expr ) */ public static float scanPositiveFloat( Scanner sc, MessageCarrier msg ) { if (sc.hasNextFloat()) { float value = sc.nextFloat(); if (value >= 0.0F) return value; Errors.warn( msg.content() + " " + value + ": must be positive" ); } Errors.warn( msg.content() + ": float value expected" ); return Float.NaN; } /** Skip the rest of this line, and complain if it's nonblank noncomment * @arg sc the scanner to read from * @arg msg the object that computes the message to output if needed * Typically called as ScanSupport.finishLine( sc, () -> string + expr ) * This code improves on the posted solution for homework 6, problem 2 */ public static void finishLine( Scanner sc, MessageCarrier msg ) { // first skip trailing blanks sc.skip( blanks ); // then get everything that remains on this line sc.skip( theRest ); final String remainder = sc.match().group( 0 ); // then see if it all makes sense if ("".equals( remainder )) return; if (remainder.startsWith ("--")) return; Errors.warn( msg.content() + ": " + remainder ); } } /** Simulation framework * @author Douglas W. Jones * @version 2019-03-08 */ class Simulation { // this interface is so we can schedule events with lambda expressions public interface Action { void trigger( float time ); } // Events are scheduled on the event set private static class Event { final float time; // the simulated time of the event final Action act; // the action to trigger at that time Event( float t, Action a ) { time = t; act = a; } } // the central organizing data structure of the simulation private static final PriorityQueue eventSet = new PriorityQueue ( ( Event e1, Event e2 ) -> Float.compare( e1.time, e2.time ) ); /** schedule a new event * @param t the time at which the event should occur * @param a the Action that should occur at that time * A typical call will look like this * Simulation.schedule( someTime, (float t)->codeToRunAt( t ) ); * That is, the Action will be constructed by a lambda expression */ public static void schedule( float t, Action a ) { eventSet.add( new Event( t, a ) ); } /** run a simulation * Call this after scheduling at least one event. * The simulation will run until either there are no more events or * some event terminates the program. * From this point on, a typical simulation program will be event driven * with the ordering of computations determined by chronological ordering * of scheduled events. */ public static void run() { while (!eventSet.isEmpty()) { Event e = eventSet.remove(); e.act.trigger( e.time ); } } } /** Subclasses of Gates are joined by Wires * @author Douglas Jones * @version 2019-04-03 * Lightly edited from RoadNetwork.java class Intersection version 2019-02-15 * then altered to become an abstract class with subclasses and a factory * @see Wire * @see InputCountGate * @see InputGate * @see OutputGate */ abstract class Gate { // a list of all of the gates in the universe private static final List allGates = new LinkedList (); /** Allow outsiders to iterate over all the gates * @returns an Iterator allowing access to gates */ public static Iterator iterator() { return allGates.iterator(); } /** look up a gate by name * @param n the name of the gate, possibly null (matches nothing) * @returns the Gate with that name, or null if no match */ public static Gate findGate( String n ) { // stupid code, a linear search, but does it matter? for (Gate g: allGates) { if (g.name.equals( n )) return g; } return null; } // attributes of individual gates /** The name of this gate */ public final String name; // the gate's name or null if broken // where the outputs from this gate go protected final LinkedList outgoing = new LinkedList (); // where the inputs to this gate come from protected int inCount = 0; // we only seem to need a count of inputs /** constructor, used only from within subclasses * @param n the name of the new gate * this is needed in order to make the name final */ protected Gate( String n ) { name = n; } /** build a new Gate and add it to the list of gates * @param sc the scanner used to get the attributes of this gate * @returns a new gate or null if it cannot be constructed * When called, the keyword "gate" has already been scanned, * so we are ready to scan the additional attributes, if any. */ public static void make( Scanner sc ) { // first worry about the gate name final String name = ScanSupport.scanName( sc, () -> "Gate has missing name" ); if (name == null) { ScanSupport.finishLine( sc, () -> "gate: followed by" ); return; // no gate added to allGates } if (findGate( name ) != null) { Errors.warn( "gate " + name + ": name reused" ); ScanSupport.finishLine( sc, () -> "gate " + name + ": followed by" ); return; // no gate added to allGates } // get the gate kind final String kind = ScanSupport.scanName( sc, () -> "Gate " + name + ": kind missing" ); if (kind == null) { ScanSupport.finishLine( sc, () -> "gate " + name + ": followed by" ); return; // no gate added to allGates } // construct the right kind of gate if ("xor".equals( kind )) { allGates.add( new XorGate( sc, name ) ); } else if ("threshold".equals( kind )) { allGates.add( new ThresholdGate( sc, name ) ); } else if ("input".equals( kind )) { allGates.add( new InputGate( sc, name ) ); } else if ("output".equals( kind )) { allGates.add( new OutputGate( sc, name ) ); } else { Errors.warn( "gate " + name + " " + kind + ": kind unknown" ); ScanSupport.finishLine( sc, () -> "gate " + name + " " + kind + ": followed by" ); } } /** Every subclass of gate offers a sanity check * It should call Errors.warn() for each failure it detects */ public abstract void sanityCheck(); /** connect a wire as an input to this gate * @arg w the wire */ public void connectTo( Wire w ) { inCount = inCount + 1; // it seems that all we need to do is count them } /** connect a wire as an output from this gate * @arg w the wire */ public void connectFrom( Wire w ) { outgoing.add( w ); } public String toString() { // note, we have a guarantee that name is not null, so return "Gate " + name; } // Simulation methods /** tell the gate that one of its inputs has changed * @param now the time at which the change occurs * @param value the new value of that input * each subclass must implement this method */ public abstract void inputChange( float now, int value ); // actually change the output protected void outputChange( float now, int value ) { final int comp = 1 - value; // logical complement of the value System.out.println( "time "+ now +" "+ comp +"->"+ value +" "+ this ); for (Wire w: outgoing) w.inputChange( now, value ); } } /** Any kind of gate that needs to track its input count * @author Douglas Jones * @version 2019-04-03 * @see Gate * @see XorGate * @see ThresholdGate */ abstract class InputCountGate extends Gate { private int inputCount = 0; // number of inputs that are currently one private int oldOutput = 0; // the previous output of this gate protected float delay = Float.NaN; // default value allows this in err msgs /** constructor, used only from within subclasses * @param n the name of the new gate * this is a pass-through to Gate */ protected InputCountGate( String n ) { super( n ); } /** tell the gate that one of its inputs has changed * @param now the time at which the change occurs * @param value the new value of that input * each subclass must implement this method */ public void inputChange( float now, int value ) { if (value == 1) { // it change to 1 inputCount = inputCount + 1; } else { // it changed to 0 inputCount = inputCount - 1; } int newOutput = logicRule( inputCount ); if (newOutput != oldOutput) { // the output changes Simulation.schedule( now + delay, (float t) -> this.outputChange( t, newOutput ) ); oldOutput = newOutput; } } /** compute the gate's logical value * @param count the number of ones on the input * each subclass must implement this method */ public abstract int logicRule( int count ); } /** Exclusive Or gates * @author Douglas Jones * @version 2019-04-03 * @see Gate */ class XorGate extends InputCountGate { public XorGate( Scanner sc, String name ){ super( name ); delay = ScanSupport.scanPositiveFloat( sc, () -> this.toString() ); ScanSupport.finishLine( sc, () -> this + " is followed by" ); } /** Every subclass of gate offers a sanity check * Calls Errors.warn() for any gate that has not got two inputs */ public void sanityCheck() { if (inCount != 2) { Errors.warn( this.toString() + ": input wire count must be two" ); } } public String toString() { return super.toString() + " xor " + delay; } /** compute the gate's logical value * @param count the number of ones on the input * each subclass must implement this method */ public int logicRule( int count ) { return count % 2; } } /** threshold logic gates * @author Douglas Jones * @version 2019-04-03 * @see Gate */ class ThresholdGate extends InputCountGate { private int threshold = 0; public ThresholdGate( Scanner sc, String name ){ super( name ); threshold = ScanSupport.scanPositiveInt( sc, () -> this.toString() ); delay = ScanSupport.scanPositiveFloat( sc, () -> this.toString() ); ScanSupport.finishLine( sc, () -> this + " is followed by" ); } /** Every subclass of gate offers a sanity check * Calls Errors.warn() for any gate where threshold > input count */ public void sanityCheck() { if (threshold > inCount) { Errors.warn( this.toString() + ": has threshold > input wires" ); } } public String toString() { return super.toString() + " threshold " + threshold + " " + delay; } /** compute the gate's logical value * @param count the number of ones on the input * each subclass must implement this method */ public int logicRule( int count ) { if (count >= threshold) return 1; return 0; } } /** input gates will provide input to the simulation * @author Douglas Jones * @version 2019-04-03 * @see Gate */ class InputGate extends Gate { private float delay = Float.NaN; // default values allow this in err msgs private int initial = 0; private int changeCount = 0; public InputGate( Scanner sc, String name ){ super( name ); initial = ScanSupport.scanPositiveInt( sc, () -> this.toString() ); delay = ScanSupport.scanPositiveFloat( sc, () -> this.toString() ); changeCount = ScanSupport.scanPositiveInt( sc, () -> this.toString() ); if (initial > 1) Errors.warn( this + ": initial value > 1" ); ScanSupport.finishLine( sc, () -> this + " is followed by" ); } /** Every subclass of gate offers a sanity check * Calls Errors.warn() for any gates where input count > 0 */ public void sanityCheck() { if (inCount != 0) { Errors.warn( this.toString() + ": has unexpected input wires" ); } // launch the simulation! if (initial == 1) this.outputChange( 0.0F, 1 ); if (changeCount > 0) Simulation.schedule( delay, (float t) -> this.nextChange( t, 1 - initial ) ); } public String toString() { return super.toString() + " input " + initial + " " + delay + " " + changeCount; } // Simulation methods /** tell this input gate that one of its inputs has changed * @param now the time at which the change occurs * @param value the new value of that input * The sanity check guarantees that this will never be called */ public void inputChange( float now, int value ) { Errors.warn( this.toString() + ": Impossible input change" ); } // Output change process for sequence of inputs private void nextChange( float now, int value ) { // first change the output this.outputChange( now, value ); // then schedule the next change, if any changeCount = changeCount - 1; if (changeCount > 0) Simulation.schedule( now + delay, (float t) -> this.nextChange( t, 1 - value ) ); } } /** output gates will provide output from the simulation * @author Douglas Jones * @version 2019-04-03 * @see Gate */ class OutputGate extends Gate { public OutputGate( Scanner sc, String name ){ super( name ); ScanSupport.finishLine( sc, () -> this + " is followed by" ); } /** Every subclass of gate offers a sanity check * Calls Errors.warn() for any gates where input count > 0 */ public void sanityCheck() { if (this.outgoing.peek() != null) { // something in the outgoing list Errors.warn( this.toString() + ": has outgoing wires" ); } } public String toString() { return super.toString() + " output"; } // Simulation methods /** tell this output gate that one of its inputs has changed * @param now the time at which the change occurs * @param value the new value of that input */ public void inputChange( float now, int value ) { // due to the specs for MP4, nothing needs to be done here // later spec changes might put all the output here } } /** Wires connect Gates * @author Douglas Jones * @version 2019-03-04 * Lightly edited from RoadNetwork.java class Road version 2019-02-15, * except for the constructor, which is heavily edited * @see Gate */ class Wire { // a list of all of the wires in the universe private static final List allWires = new LinkedList (); /** Allow outsiders to iterate over all the wires * @returns an Iterator allowing access to wires */ public static Iterator iterator() { return allWires.iterator(); } // attributes of each Wire private final Gate source; // where does this wire come from private final Gate destination; // where does it go private final float delay; // how long a signal takes to far end // construct a new Wire private Wire( Gate src, Gate dst, Float del ) { source = src; destination = dst; delay = del; } /** build a new Gate and add it to the list of gates * @param sc the scanner used to get the attributes of this wire * @param src the source gate, guaranteed non null * When called, the keyword "wire" and the name of the source gate * have already been scanned, so we are ready to scan the delay and * destination. */ public static void make( Scanner sc, Gate src ) { // wire's delay and destination, until constructor call final Float delay = ScanSupport.scanPositiveFloat( sc, () -> "wire " + src.name ); final String dstName = ScanSupport.scanName( sc, () -> "Wire " + src.name + " " + delay + ": destination missing" ); // all scanning is done now, may return at any time to avoid making wire if (dstName == null) return; Gate dst = Gate.findGate( dstName ); if (dst == null) { Errors.warn( "Wire " + src.name + " " + delay + " " + dstName + ": undefined destination" ); return; } // make the wire, after which w.toString is legal Wire w = new Wire( src, dst, delay ); // now, we can actually wire the source and destination gates src.connectFrom( w ); dst.connectTo( w ); // finally, add wire to allWires allWires.add( w ); } public String toString() { // note that source and destination are never null return "Wire " + source.name + ' ' + delay + ' ' + destination.name; } // Simulation methods /** tell the wire that its input has changed * @param now the time at which the change occurs * @param value the new value of that input */ public void inputChange( float now, int value ) { Simulation.schedule( now + delay, (float t) -> this.outputChange( t, value ) ); } private void outputChange( float now, int value ) { final int comp = 1 - value; // logical complement of the value System.out.println( "time "+ now +" "+ comp +"->"+ value +" "+ this ); destination.inputChange( now, value ); } } /** Main program * @author Douglas Jones * @version 2019-04-03 * Originated as RoadNetwork.java class RoadNetwork version 2019-02-13, * With buildLogic code heavily edited from buildNetwork, * and with the lists of gates and wires moved into those classes. * @see Gate * @see Wire */ public class MP4 { // build the logic circuit by scanning a source file private static void buildLogic( Scanner sc ) { while (sc.hasNext()) { // pick off the next part of the logic circuit description String command = sc.next(); if ("--".equals( command )) { sc.nextLine(); // discard remainder of comment line // BUG: bad way to skip comments, requires whitespace post -- } else if ("gate".equals( command )) { Gate.make( sc ); } else if ("wire".equals( command )) { // because of multiple destinations, must get the source here final String srcName = ScanSupport.scanName( sc, () -> "Wire: has no source" ); if (srcName != null) { final Gate source = Gate.findGate( srcName ); if (source != null) { // now build at least one wire and possibly more Wire.make( sc, source ); while (sc.hasNextFloat()) { Wire.make( sc, source ); } ScanSupport.finishLine( sc, () -> "Wire " + srcName + ": followed by" ); } else { Errors.warn( "Wire " + srcName + ": undefined source" ); ScanSupport.finishLine( sc, () -> "Wire " + srcName + ": followed by" ); } } else { // error message already done, but ScanSupport.finishLine( sc, () -> "Wire: followed by" ); } } else { Errors.warn( "invalid command " + command ); ScanSupport.finishLine( sc, () -> command + ": followed by" ); } } } // perform sanity checks on all gates private static void sanityChecks() { for (Iterator i = Gate.iterator(); i.hasNext();) { i.next().sanityCheck(); } } // print out the entire logic circuit private static void printLogic() { for (Iterator i = Gate.iterator(); i.hasNext();) { System.out.println( i.next() ); } for (Iterator i = Wire.iterator(); i.hasNext();) { System.out.println( i.next() ); } } public static void main( String[] args ) { Errors.setPrefix( "MP3" ); if (args.length < 1) { Errors.fatal( "missing argument" ); } if (args.length > 1) { Errors.warn( "extra arguments" ); } try { // args[0] is the text file holding the logic circuit description buildLogic( new Scanner( new FileInputStream( args[0] ) ) ); sanityChecks(); printLogic(); // something testable! if (Errors.warnings() == 0) Simulation.run(); } catch( FileNotFoundException e ) { Errors.fatal( "can't open file" ); } if (Errors.warnings() > 0) System.exit( 1 ); // exit failure } }