/* Logic.java * Program to process description of a logic circuit * author Douglas W. Jones * version 2017-10-11 (MP3 solution) * Adapted from Logic.java Version 2017-09-27 (the MP2 solution), * which was adapted from RoadNetwork.java Version 2017-09-14 * * Class ScanSupport added from lecture notes for Oct. 3 and 5, augmented * with ScanSupport.nextFloat from the homework 6 solution * * This solution involves lots of duplicate code between subclasses of Gate * because it makes no effort to combine, for example, and and or gates into * a common subclass containing all gates that have 2 inputs named in1 and in2. * A more elaborate class hierarchy could have reduced this duplication. * * Bug notices in the code indicate unsolved problems */ import java.util.regex.Pattern; import java.util.LinkedList; import java.io.File; import java.io.FileNotFoundException; import java.util.Scanner; /** Error reporting package * provides standard prefix and behavior for messages */ class Errors { // error messages are counted. private static int errorCount = 0; /** Allow public read-only access to the count of error messages * @return the count */ public static int count() { return errorCount; } /** Report nonfatal errors, output a message and return * @arg message the message to output */ public static void warn( String message ) { System.err.println( "Logic: " + message ); errorCount = errorCount + 1; } /** Report fatal errors, output a message and exit, never to return * @arg message the message to output */ public static void fatal( String message ) { warn( message ); System.exit( 1 ); } } /** Support methods for scanning * @see Errors */ class ScanSupport { // patterns needed for scanning private static final Pattern name = Pattern.compile( "[a-zA-Z0-9_]*" ); private static final Pattern number = Pattern.compile( "[0-9][0-9]*\\.?[0-9]*|\\.[0-9][0-9]*|" ); private static final Pattern whitespace = Pattern.compile( "[ \t]*" // no newlines in this pattern ); /** Interface used for error messages * Messages are typically formulated as string-valued lambda expressions * so that any concatenations they contain are only computed if the * message is needed. */ public static interface ErrorMessage { public String myString(); } /** Get next name without skipping to next line (unlike sc.next()) * @param sc the scanner from which end of line is scanned * @param message the message to output if there was no name * @return the name, if there was one, or an empty string */ public static String nextName( Scanner sc, ErrorMessage message ) { sc.skip( whitespace ); sc.skip( name ); String s = sc.match().group(); if ("".equals( s )) { Errors.warn( "Name expected: " + message.myString() ); sc.nextLine(); } return s; } /** Get next float without skipping to next line (unlike sc.nextFloat()) * @param sc the scanner from which end of line is scanned * @param message the message to output if there was no float * @return the value, if there was one, or NaN if not */ public static float nextFloat( Scanner sc, ErrorMessage message ) { sc.skip( whitespace ); sc.skip( number ); String s = sc.match().group(); if ("".equals( s )) { Errors.warn( "Float expected: " + message.myString() ); sc.nextLine(); return Float.NaN; } // now, s is guaranteed to hold a legal float return Float.parseFloat( s ); } /** Advance to next line and complain if is junk at the line end * @see Errors * @param sc the scanner from which end of line is scanned * @param message gives a prefix to give context to error messages * This version supports comments starting with -- */ public static void lineEnd( Scanner sc, ErrorMessage message ) { sc.skip( whitespace ); String lineEnd = sc.nextLine(); if ( (!lineEnd.equals( "" )) && (!lineEnd.startsWith( "--" )) ) { Errors.warn( message.myString() + " followed unexpected by '" + lineEnd + "'" ); } } } /** Wires join Gates * @see Gate */ class Wire { // constructors may throw this when an error prevents construction public static class ConstructorFailure extends Exception {} // fields of a gate private final float delay; // measured in seconds private final Gate source; // where this wire comes from, never null private final String srcPin; // what pin of source, never null private final Gate destination; // where this wire goes, never null private final String dstPin; // what pin of the destination, never null // name of a wire is source-srcpin-destination-dstpin /** construct a new wire by scanning its description from the source file */ public Wire( Scanner sc ) throws ConstructorFailure { String sourceName = ScanSupport.nextName( sc, ()-> "wire ???" ); if ("".equals( sourceName )) throw new ConstructorFailure(); srcPin = ScanSupport.nextName( sc, ()->"wire " + sourceName + " ???" ); if ("".equals( srcPin )) throw new ConstructorFailure(); String dstName = ScanSupport.nextName( sc, ()->"wire " + " " + srcPin + " ???" ); if ("".equals( dstName )) throw new ConstructorFailure(); dstPin = ScanSupport.nextName( sc, ()->"wire " + " " + srcPin + " " + dstName + " ???" ); if ("".equals( dstPin )) throw new ConstructorFailure(); source = Logic.findGate( sourceName ); destination = Logic.findGate( dstName ); if (source == null) { Errors.warn( "No such source gate: wire " + sourceName + " " + srcPin + " " + dstName + " " + dstPin ); sc.nextLine(); throw new ConstructorFailure(); } if (destination == null) { Errors.warn( "No such destination gate: wire " + sourceName + " " + srcPin + " " + dstName + " " + dstPin ); sc.nextLine(); throw new ConstructorFailure(); } // take care of source and destination pins // Bug: This is a start, but in the long run, it might not be right source.registerOutput( srcPin ); destination.registerInput( dstPin ); delay = ScanSupport.nextFloat( sc, ()->"wire " + sourceName + " " + srcPin + " " + dstName + " " + dstPin + " ???" ); if (Float.isNaN( delay )) throw new ConstructorFailure(); if (delay < 0.0F) Errors.warn( "Negative delay: " + this.toString() ); ScanSupport.lineEnd( sc, ()->this.toString() ); } /** output the wire in a form like that used for input * @return the textual form */ public String toString() { return "wire " + source.name + " " + srcPin + " " + destination.name + " " + dstPin + " " + delay; } } /** Gates process inputs from Wires and deliver outputs to Wires * @see Wire */ abstract class Gate { // constructors may throw this when an error prevents construction public static class ConstructorFailure extends Exception {} // fields of a gate public final String name; // textual name of gate, never null! protected final float delay; // the delay of this gate, in seconds private LinkedList outgoing; // set of all wires out of this gate private LinkedList incoming; // set of all wires in to this gate // Bug: Is incoming really needed? // Bug: When are the above ever set to anything? /** The constructor used only from within subclasses * @param name used to initialize the final field * @param delay used to initialize the final field */ protected Gate( String name, float delay ) { this.name = name; this.delay = delay; } /** The public use this factory to construct gates * @param sc the scanner from which the gate description is read * @return the newly constructed gate */ public static Gate factory( Scanner sc ) throws ConstructorFailure { String name = ScanSupport.nextName( sc, ()->"gate ???" ); if ("".equals( name )) throw new ConstructorFailure(); String kind = ScanSupport.nextName( sc, ()->"gate " + name + " ???" ); if ("".equals( kind )) throw new ConstructorFailure(); if (Logic.findGate( name ) != null) { Errors.warn( "Redefinition: gate " + name + " " + kind ); sc.nextLine(); throw new ConstructorFailure(); } final float delay = ScanSupport.nextFloat( sc, ()->"gate " + name + " " + kind + " ???" ); if (Float.isNaN( delay )) throw new ConstructorFailure(); if (delay < 0.0F) Errors.warn( "Negative delay: " + "gate " + name + " " + kind + " " + delay ); final Gate newGate; // initialized by one of the alternatives below if ("and".equals( kind )) { newGate = new AndGate( name, delay ); } else if ("or".equals( kind )) { newGate = new OrGate( name, delay ); } else if ("not".equals( kind )) { newGate = new NotGate( name, delay ); } else if ("const".equals( kind )) { newGate = new ConstGate( name, delay ); } else { Errors.warn( "Unknown gate kind: gate " + name + " " + kind ); sc.nextLine(); throw new ConstructorFailure(); } ScanSupport.lineEnd( sc, ()->newGate.toString() ); return newGate; } /** tell the gate that one of its input pins is in use * @param pinName */ public abstract void registerInput( String pinName ); /** tell the gate that one of its output pins is in use * @param pinName */ public abstract void registerOutput( String pinName ); /** check the sanity of this gate's connections */ public abstract void checkSanity(); } // class Gate class AndGate extends Gate { // usage records for inputs private boolean in1used = false; private boolean in2used = false; /** The constructor used only from within class Gate * @param name used to initialize the final field * @param delay used to initialize the final field */ protected AndGate( String name, float delay ) { super( name, delay ); } /** tell the gate that one of its input pins is in use * @param pinName */ public void registerInput( String pinName ) { if ("in1".equals( pinName )) { if (in1used) Errors.warn( "Multiple uses of input pin: " + name + " in1" ); in1used = true; } else if ("in2".equals( pinName )) { if (in2used) Errors.warn( "Multiple uses of input pin: " + name + " in2" ); in2used = true; } else { Errors.warn( "Illegal input pin: " + name + " " + pinName ); } } /** tell the gate that one of its output pins is in use * @param pinName */ public void registerOutput( String pinName ) { if ("out".equals( pinName )) { // Bug: Do we do anything? } else { Errors.warn( "Illegal output pin: " + name + " " + pinName ); } } /** check the sanity of this gate's connections */ public void checkSanity() { if (!in1used) Errors.warn( "Unused input pin: " + name + " in1" ); if (!in2used) Errors.warn( "Unused input pin: " + name + " in2" ); } /** reconstruct the textual description of this gate * @return the textual description */ public String toString() { return "gate " + name + " and " + delay; } } // class AndGate class OrGate extends Gate { // usage records for inputs private boolean in1used = false; private boolean in2used = false; /** The constructor used only from within class Gate * @param name used to initialize the final field * @param delay used to initialize the final field */ protected OrGate( String name, float delay ) { super( name, delay ); } /** tell the gate that one of its input pins is in use * @param pinName */ public void registerInput( String pinName ) { if ("in1".equals( pinName )) { if (in1used) Errors.warn( "Multiple uses of input pin: " + name + " in1" ); in1used = true; } else if ("in2".equals( pinName )) { if (in2used) Errors.warn( "Multiple uses of input pin: " + name + " in2" ); in2used = true; } else { Errors.warn( "Illegal input pin: " + name + " " + pinName ); } } /** tell the gate that one of its output pins is in use * @param pinName */ public void registerOutput( String pinName ) { if ("out".equals( pinName )) { // Bug: Do we do anything? } else { Errors.warn( "Illegal output pin: " + name + " " + pinName ); } } /** check the sanity of this gate's connections */ public void checkSanity() { if (!in1used) Errors.warn( "Unused input pin: " + name + " in1" ); if (!in2used) Errors.warn( "Unused input pin: " + name + " in2" ); } /** reconstruct the textual description of this gate * @return the textual description */ public String toString() { return "gate " + name + " or " + delay; } } // class OrGate class NotGate extends Gate { // usage records for inputs private boolean inUsed = false; /** The constructor used only from within class Gate * @param name used to initialize the final field * @param delay used to initialize the final field */ protected NotGate( String name, float delay ) { super( name, delay ); } /** tell the gate that one of its input pins is in use * @param pinName */ public void registerInput( String pinName ) { if ("in".equals( pinName )) { if (inUsed) Errors.warn( "Multiple uses of input pin: " + name + " in" ); inUsed = true; } else { Errors.warn( "Illegal input pin: " + name + " " + pinName ); } } /** tell the gate that one of its output pins is in use * @param pinName */ public void registerOutput( String pinName ) { if ("out".equals( pinName )) { // Bug: Do we do anything? } else { Errors.warn( "Illegal output pin: " + name + " " + pinName ); } } /** check the sanity of this gate's connections */ public void checkSanity() { if (!inUsed) Errors.warn( "Unused input pin: " + name + " in" ); } /** reconstruct the textual description of this gate * @return the textual description */ public String toString() { return "gate " + name + " not " + delay; } } // class NotGate class ConstGate extends Gate { /** The constructor used only from within class Gate * @param name used to initialize the final field * @param delay used to initialize the final field */ protected ConstGate( String name, float delay ) { super( name, delay ); } /** tell the gate that one of its input pins is in use * @param pinName */ public void registerInput( String pinName ) { Errors.warn( "Illegal input pin: " + name + " " + pinName ); } /** tell the gate that one of its output pins is in use * @param pinName */ public void registerOutput( String pinName ) { if ("true".equals( pinName )) { // Bug: Do we do anything? } else if ("false".equals( pinName )) { // Bug: Do we do anything? } else { Errors.warn( "Illegal output pin: " + name + " " + pinName ); } } /** check the sanity of this gate's connections */ public void checkSanity() { // no sanity check; there are no input pins to check } /** reconstruct the textual description of this gate * @return the textual description */ public String toString() { return "gate " + name + " const " + delay; } } // class ConstGate public class Logic { // the sets of all wires and all gates private static LinkedList wires = new LinkedList (); private static LinkedList gates = new LinkedList (); /** Find a gate by textual name in the set gates * @param s name of a gate * @return the gate named s or null if none */ public static Gate findGate( String s ) { // quick and dirty implementation for (Gate i: gates) { if (i.name.equals( s )) { return i; } } return null; } /** Initialize this logic circuit by scanning its description */ private static void readCircuit( Scanner sc ) { while (sc.hasNext()) { String command = sc.next(); if ("gate".equals( command )) { try { gates.add( Gate.factory( sc ) ); } catch (Gate.ConstructorFailure e) { // do nothing, the constructor already reported the error } } else if ("wire".equals( command )) { try { wires.add( new Wire( sc ) ); } catch (Wire.ConstructorFailure e) { // do nothing, the constructor already reported the error } } else if ("--".equals( command )) { sc.nextLine(); } else { Errors.warn( "unknown command: " + command ); sc.nextLine(); } } } /** Check that a circuit is properly constructed */ private static void sanityCheck() { for (Gate i: gates) i.checkSanity(); // Bug: Are there any sensible sanity checks on wires? } /** Print out the wire network to system.out */ private static void printCircuit() { for (Gate i: gates) { System.out.println( i.toString() ); } for (Wire r: wires) { System.out.println( r.toString() ); } } /** Main program */ public static void main( String[] args ) { if (args.length < 1) { Errors.fatal( "Missing file name argument" ); } else if (args.length > 1) { Errors.fatal( "Too many arguments" ); } else try { readCircuit( new Scanner( new File( args[0] ) ) ); sanityCheck(); if (Errors.count() == 0) printCircuit(); } catch (FileNotFoundException e) { Errors.fatal( "Can't open the file" ); } } }