001package daikon.chicory;
002
003import daikon.plumelib.util.EntryReader;
004import java.io.File;
005import java.io.IOException;
006import java.util.HashMap;
007import java.util.LinkedHashMap;
008import java.util.Scanner;
009import org.checkerframework.checker.lock.qual.GuardSatisfied;
010import org.checkerframework.checker.nullness.qual.Nullable;
011import org.checkerframework.dataflow.qual.SideEffectFree;
012import org.checkerframework.dataflow.qual.TerminatesExecution;
013
014/**
015 * Reads declaration files and provides methods to access the information within them. A declaration
016 * file consists of a number of program points and the variables for each program point.
017 */
018public class DeclReader {
019
020  /** Map from ppt name to corresponding DeclPpt. */
021  public HashMap<String, DeclPpt> ppts = new LinkedHashMap<>();
022
023  /** Information about variables within a program point. */
024  public static class DeclVarInfo {
025    public String name;
026    public String type;
027    public String rep_type;
028    public String comparability;
029    public int index;
030
031    public DeclVarInfo(String name, String type, String rep_type, String comparability, int index) {
032      this.name = name;
033      this.type = type;
034      this.rep_type = rep_type;
035      this.comparability = comparability;
036      this.index = index;
037    }
038
039    /**
040     * Returns the variable's name.
041     *
042     * @return the variable's name
043     */
044    public String get_name() {
045      return name;
046    }
047
048    /**
049     * Returns the comparability value from the decl file.
050     *
051     * @return the comparability value
052     */
053    public String get_comparability() {
054      return comparability;
055    }
056
057    @SideEffectFree
058    @Override
059    public String toString(@GuardSatisfied DeclVarInfo this) {
060      return String.format("%s [%s] %s", type, rep_type, name);
061    }
062  }
063
064  /**
065   * Information about the program point that is contained in the decl file. This consists of the
066   * ppt name and a list of the declared variables.
067   */
068  public static class DeclPpt {
069    /** Program point name. */
070    public String name;
071
072    /** Map from variable name to corresponding DeclVarInfo. */
073    public HashMap<String, DeclVarInfo> vars = new LinkedHashMap<>();
074
075    /**
076     * DeclPpt constructor.
077     *
078     * @param name program point name
079     */
080    public DeclPpt(String name) {
081      this.name = name;
082    }
083
084    /**
085     * Read a single variable declaration from decl_file. The file must be positioned immediately
086     * before the variable name.
087     *
088     * @param decl_file where to read data from
089     * @return DeclVarInfo for the program point variable
090     * @throws IOException if there is trouble reading the file
091     */
092    public DeclVarInfo read_var(EntryReader decl_file) throws java.io.IOException {
093
094      String firstLine = decl_file.readLine();
095      if (firstLine == null) {
096        reportFileError(decl_file, "Expected \"variable <VARNAME>\", found end of file");
097      }
098      Scanner scanner = new Scanner(firstLine);
099      if (!(scanner.hasNext() && scanner.next().equals("variable") && scanner.hasNext())) {
100        reportFileError(decl_file, "Expected \"variable <VARNAME>\", found \"" + firstLine + "\"");
101      }
102      String varName = scanner.next();
103
104      String type = null;
105      String rep_type = null;
106      String comparability = null;
107
108      // read variable data records until next variable or blank line
109      String record = decl_file.readLine();
110      while ((record != null) && (record.length() != 0)) {
111        scanner = new Scanner(record);
112        String token = scanner.next();
113        if (token.equals("variable")) {
114          break;
115        } else if (token.equals("dec-type")) {
116          if (!scanner.hasNext()) {
117            reportFileError(decl_file, "\"dec-type\" not followed by a type");
118          }
119          type = scanner.next();
120        } else if (token.equals("rep-type")) {
121          if (!scanner.hasNext()) {
122            reportFileError(decl_file, "\"rep-type\" not followed by a type");
123          }
124          rep_type = scanner.next();
125        } else if (token.equals("comparability")) {
126          if (!scanner.hasNext()) {
127            reportFileError(decl_file, "\"comparability\" not followed by a comparability-type");
128          }
129          comparability = scanner.next();
130        }
131        // Chicory ignores all other record types (such as flags and enclosing-var) as they are not
132        // needed to calculate comparability values.
133        record = decl_file.readLine();
134      }
135      // push back the variable or blank line record
136      if (record != null) {
137        decl_file.putback(record);
138      }
139
140      if (type == null) {
141        reportFileError(decl_file, "No type for variable " + varName);
142      }
143      if (rep_type == null) {
144        reportFileError(decl_file, "No rep-type for variable " + varName);
145      }
146      if (comparability == null) {
147        reportFileError(decl_file, "No comparability for variable " + varName);
148      }
149
150      // I don't see the point of this interning.  No code seems to take
151      // advantage of it.  Is it just for space?  -MDE
152      DeclVarInfo var =
153          new DeclVarInfo(
154              varName.intern(),
155              type.intern(),
156              rep_type.intern(),
157              comparability.intern(),
158              vars.size());
159      vars.put(varName, var);
160      return var;
161    }
162
163    /**
164     * Returns the DeclVarInfo named var_name or null if it doesn't exist.
165     *
166     * @param var_name a variable name
167     * @return DeclVarInfo for the given variable
168     */
169    public @Nullable DeclVarInfo find_var(String var_name) {
170      return vars.get(var_name);
171    }
172
173    /**
174     * Returns the ppt name.
175     *
176     * @return the program point name
177     */
178    public String get_name() {
179      return name;
180    }
181
182    /**
183     * Returns the name without the :::EXIT, :::ENTER, etc.
184     *
185     * @return the program point name
186     */
187    public String get_short_name() {
188      return name.replaceFirst(":::.*", "");
189    }
190
191    @SideEffectFree
192    @Override
193    public String toString(@GuardSatisfied DeclPpt this) {
194      return name;
195    }
196  }
197
198  /** Create a new DeclReader. */
199  public DeclReader() {}
200
201  /**
202   * Read declarations from the specified pathname.
203   *
204   * @param pathname a File for reading data
205   * @throws IOException if there is trouble reading the file
206   */
207  public void read(File pathname) throws IOException {
208
209    // have caller deal with FileNotFound
210
211    try (EntryReader decl_file = new EntryReader(pathname, "^(//|#).*", null)) {
212      for (String line = decl_file.readLine(); line != null; line = decl_file.readLine()) {
213        // Skip all input until we find a ppt.
214        if (!line.startsWith("ppt ")) {
215          continue;
216        }
217        decl_file.putback(line);
218
219        // Read the program point declaration.
220        read_decl(decl_file);
221      }
222    } catch (Exception e) {
223      throw new Error("Error reading comparability decl file " + pathname, e);
224    }
225  }
226
227  /**
228   * Reads a single program point declaration from decl_file.
229   *
230   * @param decl_file EntryReader for reading data
231   * @return the program point declaration
232   * @throws IOException if there is trouble reading the file
233   */
234  protected DeclPpt read_decl(EntryReader decl_file) throws IOException {
235
236    // Read the name of the program point
237    String firstLine = decl_file.readLine();
238    if (firstLine == null) {
239      reportFileError(decl_file, "File ends prematurely, expected \"ppt ...\"");
240    }
241    if (!firstLine.startsWith("ppt ")) {
242      reportFileError(decl_file, "Expected \"ppt ...\", found \"" + firstLine + "\"");
243    }
244    String pptname = firstLine.substring(4); // skip "ppt "
245    assert pptname.contains(":::");
246    DeclPpt ppt = new DeclPpt(pptname);
247    ppts.put(pptname, ppt);
248
249    // Chicory skips the ppt-type record as it is not needed to calculate comparability values.
250    String ppt_type = decl_file.readLine();
251    if (ppt_type == null) {
252      reportFileError(decl_file, "File terminated prematurely while reading decl for " + ppt);
253    }
254
255    // Read each of the variables in this program point.  The variables
256    // are terminated by a blank line.
257    String line = decl_file.readLine();
258    while ((line != null) && (line.length() != 0)) {
259      if (!line.startsWith("variable ")) {
260        reportFileError(decl_file, "Expected \"variable ...\", found \"" + line + "\"");
261      }
262      decl_file.putback(line);
263      ppt.read_var(decl_file);
264      line = decl_file.readLine();
265    }
266
267    return ppt;
268  }
269
270  // This can return null.  Example:  when DynComp is run to compute
271  // comparability information, it produces no information (not even a
272  // declaration) for program points that are never executed.  But, Chicory
273  // outputs a declaration for every program point, and this lookup can
274  // fail when using the --comparability-file=... command-line argument
275  // with a file produced by DynComp.
276  public @Nullable DeclPpt find_ppt(String ppt_name) {
277    DeclPpt result = ppts.get(ppt_name);
278    return result;
279  }
280
281  /**
282   * Report an error while reading from an EntryReader, with file name and line number.
283   *
284   * @param er an EntryReader, from which file name and line number are obtained
285   * @param message the error message
286   */
287  @TerminatesExecution
288  private static void reportFileError(EntryReader er, String message) {
289    throw new Error(message + " at " + er.getFileName() + " line " + er.getLineNumber());
290  }
291}