/** TernaryLogic.java -- classes that describe a ternary logic system * @author Douglas Jones * @version mp4, 2017-04-06 * * This code borrows heavily from TernaryLogic.java version mp3, 2017-03-12. * ScanSupport and Simulation are from RoadNetwork.java version 2017-03-31. * Changes to ScanSupport are from the posted solution to Homework 7. * The gate simulation mechanism comes from the posted solution to Homework 8. */ import java.util.LinkedList; import java.io.File; import java.io.FileNotFoundException; import java.util.Scanner; import java.util.regex.Pattern; import java.util.PriorityQueue; /** Utility package for error handling */ class Errors { private Errors(){}; // you may never instantiate this class private static int count = 0; // warning count, really public read only /** Provide public read only access to the count of warnings. */ public static int count() { return count; } /** Call this to warn of non fatal errors */ public static void warn( String message ) { System.err.println( "Warning: " + message ); count = count + 1; } /** Call this to report fatal errors */ public static void fatal( String message ) { System.err.println( "Fatal error: " + message ); System.exit( -1 ); } } /** Support methods for scanning * @author Douglas Jones * @version 2017-04-05 * This is ripped from RoadNetwork.java version 2017-03-31 with a change: * nextFloat method is added based on the posted solution to Homework 7. * @see Errors */ class ScanSupport { /** Pattern for identifers */ public static final Pattern name // letter followed by alphanumeric = Pattern.compile( "[a-zA-Z][a-zA-Z0-9_]*|" ); /** Pattern for floating point numbers */ public static final Pattern numb // Digits.Digits or .Digits or nothing = Pattern.compile( "[0-9]+\\.?[0-9]*|\\.[0-9]+|" ); /** Pattern for whitespace excluding things like newline */ public static final Pattern whitespace = Pattern.compile( "[ \t]*" ); /** Get next name without skipping to next line (unlike sc.Next()) * @param sc the scanner from which end of line is scanned * @return the name, if there was one, or an empty string */ public static String nextName( Scanner sc ) { sc.skip( whitespace ); // the following is weird code, it skips the name // and then returns the string that matched what was skipped sc.skip( name ); return sc.match().group(); } /** Get next float without skipping lines (unlike sc.nextFloat()) * @param sc the scanner from which end of line is scanned * @return the name, if there was one, or NaN if not */ public static Float nextFloat( Scanner sc ) { sc.skip( whitespace ); // the following is weird code, it skips the name // and then returns the string that matched what was skipped sc.skip( numb ); String f = sc.match().group(); // now convert what we can or return NaN if (!"".equals( f )) { return Float.parseFloat( f ); } else { return Float.NaN; } } /** Class used only for deferred parameter passing to lineEnd */ public interface EndMessage { public abstract String myString(); } /** Advance to next line and complain if is junk at the line end; * call this when all useful content has been consumed from the line * then call this to skip optional line-end comments to the next line * @see Errors * @param sc the scanner from which end of line is scanned * @param message will be evaluated only when there is an error; * it is typically passed as a lambda expression, for example, * ScanSupport.lineEnd( sc, () -> "this " + x + " that" ); */ public static void lineEnd( Scanner sc, EndMessage message ) { sc.skip( whitespace ); String lineEnd = sc.nextLine(); if ( (!lineEnd.equals( "" )) && (!lineEnd.startsWith( "--" )) ) { Errors.warn( "" + message.myString() + " followed unexpected by '" + lineEnd + "'" ); } } } /** Simulation support framework * This is ripped from RoadNetwork.java version 2017-03-31. */ class Simulation { /** Interface allowing actions to be passed as lambda expressions */ public interface Action { void trigger( float time ); } /** Events are queued for Simulation.run to find */ private static class Event { float time; Action act; // constructor Event( float t, Action a ) { time = t; act = a; }; void trigger() { act.trigger( time ); } } private static PriorityQueue eventSet = new PriorityQueue ( (Event e1, Event e2)->Float.compare( e1.time, e2.time ) ); /** Users call schedule to schedule one action at some time. * @param time specifies when the event should occur. * @param act specifies what to do at that time. * Typically, act is a lambda expression, so a call to schedule could * look like this: *
	 *  Simulation.schedule( t, (float t)->object.method( params, t ) )
	 *  
*/ public static void schedule( float time, Action act ) { eventSet.add( new Event( time, act ) ); } /** the main program should build the model, * this inolves scheduling some initial events * and then, just once, it should call run. */ public static void run() { while (!eventSet.isEmpty()) { Event e = eventSet.remove(); e.trigger(); } } } /** Wires link gates * @see Gate * @see Errors * @see TernaryLogic#findGate(String) */ class Wire { private final float delay; // time delay of this wire private final Gate destination; // where wire goes, or null private final Gate source; // source of wire, or null // Wire name is the source-destination names /** initializer scans and processes one wire definition */ public Wire( Scanner sc ) { // textual names of source and dest String srcName = ScanSupport.nextName( sc ); String dstName = ScanSupport.nextName( sc ); // if there are no next names on this line, these are "" // therefore, the findGate calls below will fail // lookup names of source and dest source = TernaryLogic.findGate( srcName ); if (source == null) { Errors.warn( "Wire '" + srcName + "' '" + dstName + "' source undefined." ); } destination = TernaryLogic.findGate( dstName ); if (destination == null) { Errors.warn( "Wire '" + srcName + "' '" + dstName + "' destination undefined." ); } delay = ScanSupport.nextFloat( sc ); if (delay != delay) { // really asks if delay == NaN Errors.warn( "Wire '" + srcName + "' '" + dstName + "' has no delay." ); } else if (delay < 0.0f) { Errors.warn( "Wire '" + srcName + "' '" + dstName + "' '" + delay + "' has negative delay." ); } ScanSupport.lineEnd( sc, () -> this.toString() ); // Now, tell the gates that they've been wired together if (destination != null) destination.addIncoming( this ); if (source != null) source.addOutgoing( this ); } /** output this wire in a format like that used for input */ public String toString() { String srcName; String dstName; if (source == null) { srcName = "???"; } else { srcName = source.name; } if (destination == null) { dstName = "???"; } else { dstName = destination.name; } return( "wire " + srcName + " " + dstName + " " + delay ); } // ***** Logic Simulation ***** /** Event service routine called when the input to a wire changes * @param time the time at which the input changes * @param old the previous logic value carried over this wire * @param new the new logic value carried over this wire */ public void inputChangeEvent( float time, int oldv, int newv ) { Simulation.schedule( time + delay, (float t) -> outputChangeEvent( t, oldv, newv ) ); }; /** Event service routine called when the output of a wire changes * @param time the time at which the output changes * @param old the previous logic value carried over this wire * @param new the new logic value carried over this wire */ public void outputChangeEvent( float time, int oldv, int newv ) { // this version is optimized, we could have scheduled an event destination.inputChangeEvent( time, oldv, newv ); }; } /** Gates are linked by wires * @see Wire * @see MinGate * @see MaxGate * @see NotGate * @see IsTGate * @see IsFGate * @see IsUGate * @see TernaryLogic#findGate(String) * * The logic used to simulate a gate is largely lifted from the * posted solution to Homework 8. */ abstract class Gate { private final LinkedList outgoing = new LinkedList (); /** setter method to add outgoing wires to this gate * @param w the wire that connects from this gate */ public void addOutgoing( Wire w ) { outgoing.add( w ); } private int incount = 0; // how many inputs are connected? /** setter method to add incoming wires to this gate * @param w the wire that connects to this gate */ public void addIncoming( Wire w ) { // actually, we don't need w, but the public doesn't know that incount = incount + 1; } public final String name; // the name of the gate // Gripe: We'd like to declare the following as final, but can't // because they're set by the subclass constructor public int inputs; // the type of the gate public float delay; // the type of the gate /** constructor needed by subclasses to set the final fields of gate */ protected Gate( String n ) { name = n; } /** factory method scans and processes one gate definition */ public static Gate newGate( Scanner sc ) { String myName = ScanSupport.nextName( sc ); if ("".equals( myName )) { Errors.warn( "gate has no name" ); sc.nextLine(); return null; } if (TernaryLogic.findGate( myName ) != null) { Errors.warn( "Gate '" + myName + "' redefined." ); sc.nextLine(); return null; } String myType = ScanSupport.nextName( sc ); if ("min".equals( myType )) { return new MinGate( sc, myName ); } else if ("max".equals( myType )) { return new MaxGate( sc, myName ); } else if ("neg".equals( myType )) { return new NegGate( sc, myName ); } else if ("isfalse".equals( myType )) { return new IsFGate( sc, myName ); } else if ("istrue".equals( myType )) { return new IsTGate( sc, myName ); } else if ("isunknown".equals( myType )) { return new IsUGate( sc, myName ); } else { Errors.warn( "Gate '" + myName + "' '" + myType + "' has an illegal type." ); sc.nextLine(); return null; } } /** Scan gate's delay and line end to finish initialization; * this is always called at the end of the subclass constructor */ protected final void finishGate( Scanner sc ) { delay = sc.nextFloat(); if (delay != delay) { // really asks if delay == NaN Errors.warn( this.myString() + " -- has no delay" ); } else if (delay < 0.0f) { Errors.warn( this.myString() + " -- has negative delay." ); } ScanSupport.lineEnd( sc, () -> this.myString() ); } /** output this Gate in a format like that used for input * it would have been nice to use toString here, but can't protect it */ protected String myString() { return( "gate " + name ); } // ***** Logic Simulation ***** // for logic values, inputCount[v] shows how many inputs have that value int inputCounts[] = new int[3]; // this gate's most recently computed output value int output; /** Sanity check for gates */ public void check() { if (incount < inputs) { Errors.warn( this.myString() + " -- has missing inputs." ); } else if (incount > inputs) { Errors.warn( this.myString() + " -- has too many inputs." ); } // initially, all the inputs are unknown inputCounts[0] = 0; inputCounts[1] = inputs; inputCounts[2] = 0; // and initially, the output is unknown output = 1; // some subclasses will add to this behavior } /** Every subclass must define this function; * @return the new logic value, a function of inputCounts; */ protected abstract int logicValue(); /** Event service routine called when the input to a gate changes * @param time the time at which the input changes * @param old the previous logic value carried over this input * @param new the new logic value carried over this input */ public void inputChangeEvent( float time, int oldv, int newv ) { inputCounts[oldv]--; inputCounts[newv]++; final int newOut = logicValue(); if (output != newOut) { final int old = output; Simulation.schedule( time + delay, (float t) -> outputChangeEvent( t, old, newOut ) ); output = newOut; } }; /** Event service routine called when the output of a gate changes * @param time the time at which the output changes * @param old the previous logic value of this gate's output * @param new the new logic value of this gate's output */ public void outputChangeEvent( float time, int oldv, int newv ) { // produce the output required by MP4 System.out.println( "At " + time + " gate " + name + " output changed from " + oldv + " to " + newv ); // send the new value out to all the outgoing wires for ( Wire w: outgoing ) { // this is optimized, we could have scheduled an event w.inputChangeEvent( time, oldv, newv ); } }; } /** MinGate a kind of Gate * @see Gate */ class MinGate extends Gate { /** initializer scans and processes one min gate * @parame sc Scanner from which gate description is read * @param myName the value to be put in the name field */ MinGate( Scanner sc, String myName ) { // the text "gate myName min" has already been scanned super( myName ); // get inputs if (sc.hasNextInt()) { inputs = sc.nextInt(); } else { Errors.warn( this.myString() + " min -- has no input count" ); } this.finishGate( sc ); } /** output this Gate in a format like that used for input */ public String toString() { return( this.myString() + " min " + inputs + " " + delay ); } // ***** Logic Simulation for MinGate ***** /** Every subclass of Gate must define this function; * @return the new logic value, a function of inputCounts; */ protected int logicValue() { // find the minimum of all the inputs int newOutput = 0; while (inputCounts[newOutput] == 0) newOutput++; return newOutput; } } /** MaxGate a kind of Gate * @see Gate */ class MaxGate extends Gate { /** initializer scans and processes one max gate * @parame sc Scanner from which gate description is read * @param myName the value to be put in the name field */ MaxGate( Scanner sc, String myName ) { // the text "gate myName min" has already been scanned super( myName ); // get inputs if (sc.hasNextInt()) { inputs = sc.nextInt(); } else { Errors.warn( this.myString() + " max -- has no input count" ); } this.finishGate( sc ); } /** output this Gate in a format like that used for input */ public String toString() { return( this.myString() + " max " + inputs + " " + delay ); } // ***** Logic Simulation for MaxGate ***** /** Every subclass of Gate must define this function; * @return the new logic value, a function of inputCounts; */ protected int logicValue() { // find the maximum of all the inputs int newOutput = 2; while (inputCounts[newOutput] == 0) newOutput--; return newOutput; } } /** NegGate a kind of Gate * @see Gate */ class NegGate extends Gate { /** initializer scans and processes one neg gate * @parame sc Scanner from which gate description is read * @param myName the value to be put in the name field */ NegGate( Scanner sc, String myName ) { // the text "gate myName min" has already been scanned super( myName ); inputs = 1; // it is a one-input gate this.finishGate( sc ); } /** output this Gate in a format like that used for input */ public String toString() { return( this.myString() + " neg " + " " + delay ); } // ***** Logic Simulation for NegGate ***** /** Every subclass of Gate must define this function; * @return the new logic value, a function of inputCounts; */ protected int logicValue() { // Warning this is mildly tricky code int newOutput = 2; while (inputCounts[2 - newOutput] == 0) newOutput--; return newOutput; } } /** IsTGate a kind of Gate * @see Gate */ class IsTGate extends Gate { /** initializer scans and processes one neg gate * @parame sc Scanner from which gate description is read * @param myName the value to be put in the name field */ IsTGate( Scanner sc, String myName ) { // the text "gate myName min" has already been scanned super( myName ); inputs = 1; // it is a one-input gate this.finishGate( sc ); } /** output this Gate in a format like that used for input */ public String toString() { return( this.myString() + " istrue " + delay ); } // ***** Logic Simulation for IsTGate ***** /** Sanity check for IsTGate */ public void check() { super.check(); // now change the output from unknown to false Simulation.schedule( delay, (float t) -> this.outputChangeEvent( t, 1, 0 ) ); output = 0; } /** Every subclass of Gate must define this function; * @return the new logic value, a function of inputCounts; */ protected int logicValue() { int newOutput = 0; if (inputCounts[2] != 0) newOutput = 2; return newOutput; } } /** IsFGate a kind of Gate * @see Gate */ class IsFGate extends Gate { /** initializer scans and processes one neg gate * @parame sc Scanner from which gate description is read * @param myName the value to be put in the name field */ IsFGate( Scanner sc, String myName ) { // the text "gate myName min" has already been scanned super( myName ); inputs = 1; // it is a one-input gate this.finishGate( sc ); } /** output this Gate in a format like that used for input */ public String toString() { return( this.myString() + " isfalse " + delay ); } // ***** Logic Simulation for IsFGate ***** /** Sanity check for IsFGate */ public void check() { super.check(); // now change the output from unknown to false Simulation.schedule( delay, (float t) -> this.outputChangeEvent( t, 1, 0 ) ); output = 0; } /** Every subclass of Gate must define this function; * @return the new logic value, a function of inputCounts; */ protected int logicValue() { int newOutput = 0; if (inputCounts[0] != 0) newOutput = 2; return newOutput; } } /** IsUGate a kind of Gate * @see Gate */ class IsUGate extends Gate { /** initializer scans and processes one neg gate * @parame sc Scanner from which gate description is read * @param myName the value to be put in the name field */ IsUGate( Scanner sc, String myName ) { // the text "gate myName min" has already been scanned super( myName ); inputs = 1; // it is a one-input gate this.finishGate( sc ); } /** output this Gate in a format like that used for input */ public String toString() { return( this.myString() + " isundefined " + delay ); } // ***** Logic Simulation for IsUGate ***** /** Sanity check for IsUGate */ public void check() { super.check(); // now change the output from unknown to true Simulation.schedule( delay, (float t) -> this.outputChangeEvent( t, 1, 2 ) ); output = 2; } /** Every subclass of Gate must define this function; * @return the new logic value, a function of inputCounts; */ protected int logicValue() { int newOutput = 0; if (inputCounts[1] != 0) newOutput = 2; return newOutput; } } /** TernaryLogic -- main program that reads and writes a ternary logic system * @see Wire * @see Gate * @see Errors * @see #main */ public class TernaryLogic { // lists of roads and intersectins static LinkedList wires = new LinkedList (); static LinkedList gates = new LinkedList (); /** utility method to look up an gate by name * @param s is the name of the gate, a string * @return is the Gate object with that name */ public static Gate findGate( String s ) { for ( Gate g: gates ) { if (g.name.equals( s )) return g; } return null; } /** read a ternary logic system */ public static void initializeTernary( Scanner sc ) { while (sc.hasNext()) { // until we hit the end of the file String command = ScanSupport.nextName( sc ); if ("gate".equals( command )) { Gate g = Gate.newGate( sc ); if (g != null) gates.add( g ); } else if ("wire".equals( command )) { wires.add( new Wire( sc ) ); } else if ("".equals( command )) { // blank or comment // line holding -- ends up here! ScanSupport.lineEnd( sc, () -> "Line" ); } else { Errors.warn( "Command '" + command + "' is not gate or wire" ); sc.nextLine(); // skip the rest of the error } } } /** check the sanity of the network */ public static void checkNetwork() { for ( Gate g: gates ) { g.check(); } // we could also go through the wires, // but there's nothing to check there. } /** write out a ternary logic system */ public static void writeTernary() { for ( Gate g: gates ) { System.out.println( g.toString() ); } for ( Wire w: wires ) { System.out.println( w.toString() ); } } /** main program that reads and writes a road network * @param args the command line arguments must hold one file name */ public static void main( String[] args ) { // verify that the argument exists. if (args.length < 1) { Errors.fatal( "Missing file name on command line" ); } else if (args.length > 1) { Errors.fatal( "Unexpected command line args" ); } else try { initializeTernary( new Scanner( new File( args[0] ) ) ); checkNetwork(); if (Errors.count() > 0) { writeTernary(); } else { Simulation.run(); } } catch (FileNotFoundException e) { Errors.fatal( "Could not read '" + args[0] + "'" ); } } }