/** TernaryLogic.java -- classes that describe a ternary logic system * @author Douglas Jones * @version mp3, 2017-03-12 * * This code borrows heavily from TernaryLogic.java distributed on 2017-02-21, * and from RoadNetwork.java distributed on 2017-03-01. * The goal in writing this code was to minimally but adequately meet the * requirements of the assignment while avoiding any creative additions to * the framework established by that code. */ import java.util.LinkedList; import java.io.File; import java.io.FileNotFoundException; import java.util.Scanner; import java.util.regex.Pattern; /** Utility package for error handling */ class Errors { private Errors(){}; // you may never instantiate this class /** Call this to warn of non fatal errors */ public static void warn( String message ) { System.err.println( "Warning: " + message ); } /** Call this to report fatal errors */ public static void fatal( String message ) { System.err.println( "Fatal error: " + message ); System.exit( -1 ); } } /** Utility package for more interesting scanner behaviors */ class ScanSupport { /** Pattern for identifers */ public static final Pattern name = Pattern.compile( "[a-zA-Z0-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(); } /** Advance to next line and complain if is junk at the line end * @see Errors * @param message gives a prefix to give context to error messages * @param sc the scanner from which end of line is scanned */ public static void lineEnd( Scanner sc, String message ) { sc.skip( whitespace ); String lineEnd = sc.nextLine(); if ( (!lineEnd.equals( "" )) && (!lineEnd.startsWith( "--" )) ) { Errors.warn( message + " followed unexpected by '" + lineEnd + "'" ); } } } /** 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." ); } if (sc.hasNextFloat()) { delay = sc.nextFloat(); if (delay < 0.0f) { Errors.warn( "Wire '" + srcName + "' '" + dstName + "' '" + delay + "' has negative delay." ); } } else { Errors.warn( "Wire '" + srcName + "' '" + dstName + "' has no delay." ); delay = 99.999f; } ScanSupport.lineEnd( sc, this.toString() ); } /** 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 ); } } /** Gates are linked by wires * @see Wire * @see MinGate * @see MaxGate * @see NotGate * @see IsTGate * @see IsFGate * @see IsUGate * @see TernaryLogic#findGate(String) */ abstract class Gate { private final LinkedList outgoing = new LinkedList (); // Bug: Does the gate need a list of incoming wires? 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 ) { if (sc.hasNextFloat()) { delay = sc.nextFloat(); if (delay < 0.0f) { Errors.warn( this.myString() + " -- has negative delay." ); } } else { Errors.warn( this.myString() + " -- has no 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 ); } } /** 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 ); } } /** 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 ); } } /** 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 ); } } /** 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 ); } } /** 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 ); } } /** 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 ); } } /** 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 } } } /** 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] ) ) ); writeTernary(); } catch (FileNotFoundException e) { Errors.fatal( "Could not read '" + args[0] + "'" ); } } }