/* * xtc - The eXTensible Compiler * Copyright (C) 2009-2012 New York University * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ package xtc.lang.cpp; import java.io.File; import java.io.FileReader; import java.io.BufferedReader; import java.util.Iterator; import xtc.lang.cpp.Syntax.Kind; import xtc.lang.cpp.Syntax.Language; /** * Token-based diff. * * @author Paul Gazzillo * @version $Revision: 1.31 $ */ public class cdiff { /** The error code for when the input files are different. */ public static final int DIFFERENT = 1; /** The error code for invalid arguments. */ public static final int INVALID_ARGUMENTS = 2; /** The error code for invalid option flag. */ public static final int INVALID_FLAG = 3; /** * Print cdiff usage info. */ public static void printUsage() { System.err.println( "cdiff - Compare two C files token-by-token, ignoring directives by default." + "\n" + "\n" + "USAGE:\n" + "\n" + " java xtc.lang.cpp.cdiff -?\n" + "\n" + " java xtc.lang.cpp.cdiff [-l|-d] [-s] [-e #] file1 file2\n" + "\n" + "FLAGS:\n" + " -l\tTreat directives as individual tokens.\n" + " -d\tParse directives and compare them.\n" + " -s\tIgnore whitespace inside of string literals.\n" + " -e n\tExpect n errors.\n" + " -?\tPrint this help message.\n" + "\n" + "EXIT CODES:\n" + " 0 No difference\n" + " " + DIFFERENT + " Different\n" + " " + INVALID_ARGUMENTS + " Invalid arguments\n" + " " + INVALID_FLAG + " Invalid flag\n" + " * Non-cdiff system exit code\n" + ""); } /** * Signal an error. This method terminates cdiff. * * @param errcode The error code. */ public static void error(int errcode) { switch (errcode) { case DIFFERENT: System.err.println("error: the files are different."); break; case INVALID_ARGUMENTS: System.err.println("error: the arguments are invalid."); break; case INVALID_FLAG: System.err.println("error: the flag is invalid."); break; default: System.err.println("error: unknown error"); break; } System.exit(errcode); } // get around capture of ? to CTag warning /** * Determine whether two files' tokens differ. Each file is lexed * into C tokens and compared token-by-token, ignoring whitespace. * If the files are the same, the exit code is 0. If they differ, * the location in each file of the first difference is emitted and * the return code is 1. */ public static void main(String[] args) { String filename1 = null; String filename2 = null; // Whether directives should be compare token-by-token. boolean pureLexer = false; // Whether directives should be parsed first, then compared. boolean directives = false; // Whether whitespace in string literals should be ignored. boolean ignoreStringWhitespace = false; // How many errors to allow before terminating. int expect = 0; if (args.length == 0 || args.length == 1 && args[0].equals("-?")) { printUsage(); // Don't display error message because displaying usage with // zero arguments is customary. Still set the right error code // though to avoid falsely reporting equality. System.exit(INVALID_ARGUMENTS); } if (args.length < 2 || args.length > 6) { error(INVALID_ARGUMENTS); } // Gather command-line arguments. int i; for (i = 0; i < args.length - 2; i++) { String flag = args[i]; if (flag.length() == 0 || flag.length() > 2 || flag.charAt(0) != '-') { error(INVALID_FLAG); } else if (flag.charAt(1) == 'l') { if (pureLexer || directives) error(INVALID_ARGUMENTS); pureLexer = true; } else if (flag.charAt(1) == 'd') { if (pureLexer || directives) error(INVALID_ARGUMENTS); directives = true; } else if (flag.charAt(1) == 's') { ignoreStringWhitespace = true; } else if (flag.charAt(1) == 'e') { if ((i+1) >= (args.length - 2)) { error(INVALID_FLAG); } try { expect = Integer.parseInt(args[i+1]); if (expect < -1) { error(INVALID_FLAG); } i++; } catch (Exception e) { error(INVALID_FLAG); } } else { error(INVALID_FLAG); } } filename1 = args[i]; filename2 = args[i + 1]; try { File file1 = new File(filename1); File file2 = new File(filename2); BufferedReader reader1 = new BufferedReader(new FileReader(file1)); BufferedReader reader2 = new BufferedReader(new FileReader(file2)); final CLexer clexer1 = new CLexer(reader1); clexer1.setFileName(filename1); Iterator<Syntax> stream1 = new Iterator<Syntax>() { Syntax syntax; public Syntax next() { try { syntax = clexer1.yylex(); } catch (java.io.IOException e) { e.printStackTrace(); throw new RuntimeException(); } return syntax; } public boolean hasNext() { return syntax.kind() != Kind.EOF; } public void remove() { throw new UnsupportedOperationException(); } }; final CLexer clexer2 = new CLexer(reader2); clexer2.setFileName(filename2); Iterator<Syntax> stream2 = new Iterator<Syntax>() { Syntax syntax; public Syntax next() { try { syntax = clexer2.yylex(); } catch (java.io.IOException e) { e.printStackTrace(); throw new RuntimeException(); } return syntax; } public boolean hasNext() { return syntax.kind() != Kind.EOF; } public void remove() { throw new UnsupportedOperationException(); } }; boolean end1 = false; boolean end2 = false; Syntax syntax1 = null; Syntax syntax2 = null; if ( ! pureLexer) { stream1 = new DirectiveParser(stream1, filename1); stream2 = new DirectiveParser(stream2, filename2); } while (true) { do { syntax1 = stream1.next(); } while (null == syntax1 || syntax1.kind() != Kind.LANGUAGE && syntax1.kind() != Kind.EOF && (! directives || syntax1.kind() != Kind.DIRECTIVE)); do { syntax2 = stream2.next(); } while (null == syntax2 || syntax2.kind() != Kind.LANGUAGE && syntax2.kind() != Kind.EOF && (! directives || syntax2.kind() != Kind.DIRECTIVE)); end1 = syntax1.kind() == Kind.EOF; end2 = syntax2.kind() == Kind.EOF; if (end1 || end2) { if (!end1 || !end2) { System.out.println(syntax1.getLocation()); System.out.println(syntax1); System.out.println(); System.out.println(syntax2.getLocation()); System.out.println(syntax2); if (0 == expect && -1 != expect) { System.exit(DIFFERENT); } else { expect--; } } break; } else { @SuppressWarnings("unchecked") Language<CTag> lang1 = (Language<CTag>)syntax1; @SuppressWarnings("unchecked") Language<CTag> lang2 = (Language<CTag>)syntax2; if (ignoreStringWhitespace && syntax1.kind() == Kind.LANGUAGE && syntax2.kind() == Kind.LANGUAGE && lang1.toLanguage().tag() == CTag.STRINGliteral && lang2.toLanguage().tag() == CTag.STRINGliteral && syntax1.getTokenText().replaceAll(" ", "") .equals(syntax2.getTokenText().replaceAll(" ", ""))) { // Don't take into account whitespace when comparing // strings. } else if (! syntax1.getTokenText().equals(syntax2.getTokenText())) { System.out.println(syntax1.getLocation()); System.out.println(syntax1); System.out.println(); System.out.println(syntax2.getLocation()); System.out.println(syntax2); if (0 == expect && -1 != expect) { System.exit(DIFFERENT); } else { expect--; } } } } System.exit(0); } catch (Exception e) { e.printStackTrace(); System.exit(-1); } } }