package edu.stanford.nlp.sempre.tables.test; import java.io.*; import java.util.*; import java.util.regex.*; import edu.stanford.nlp.sempre.*; import edu.stanford.nlp.sempre.tables.StringNormalizationUtils; import edu.stanford.nlp.sempre.tables.TableTypeSystem; import fig.basic.*; import fig.exec.Execution; /** * Custom version of Example. * * - Allow additional keys "warning", "error", and "alternativeFormula" * - Allow shorthand annotation for targetFormula * * @author ppasupat */ public class CustomExample extends Example { public static class Options { // Format: "3-5,10,12-20" @Option(gloss = "Verbosity") public int verbose = 2; @Option(gloss = "Filter only these examples") public String filterExamples = null; @Option public boolean allowNoAnnotation = false; } public static Options opts = new Options(); public List<String> warnings = new ArrayList<>(); public List<String> errors = new ArrayList<>(); public List<Formula> alternativeFormulas = new ArrayList<>(); public CustomExample(Example ex) { // Copy everything from ex super(ex.id, ex.utterance, ex.context, ex.targetFormula, ex.targetValue, ex.languageInfo); } static final Set<String> usefulTags = new HashSet<String>(Arrays.asList("id", "utterance", "targetFormula", "targetValue", "targetValues", "context")); /** * Convert LispTree to Example with additional tags */ public static CustomExample fromLispTree(LispTree tree, String defaultId) { Builder b = new Builder().setId(defaultId); for (int i = 1; i < tree.children.size(); i++) { LispTree arg = tree.child(i); String label = arg.child(0).value; if ("id".equals(label)) { b.setId(arg.child(1).value); } else if ("utterance".equals(label)) { b.setUtterance(arg.child(1).value); } else if ("targetFormula".equals(label)) { LispTree canonicalized = canonicalizeFormula(arg.child(1)); b.setTargetFormula(Formulas.fromLispTree(canonicalized)); } else if ("targetValue".equals(label) || "targetValues".equals(label)) { if (arg.children.size() != 2) throw new RuntimeException("Expect one target value"); b.setTargetValue(Values.fromLispTree(arg.child(1))); } else if ("context".equals(label)) { b.setContext(new ContextValue(arg)); } } b.setLanguageInfo(new LanguageInfo()); CustomExample ex = new CustomExample(b.createExample()); boolean error = false; for (int i = 1; i < tree.children.size(); i++) { LispTree arg = tree.child(i); String label = arg.child(0).value; if ("warning".equals(label)) { ex.warnings.add(arg.child(1).value); } else if ("error".equals(label)) { error = true; ex.errors.add(arg.child(1).value); } else if ("alternativeFormula".equals(label)) { LispTree canonicalized = canonicalizeFormula(arg.child(1)); ex.alternativeFormulas.add(Formulas.fromLispTree(canonicalized)); } else if (!usefulTags.contains(label)) { throw new RuntimeException("Invalid example argument: " + arg); } } // Check formula and error if (ex.targetFormula == null && !error && !opts.allowNoAnnotation) throw new RuntimeException("Either error or targetFormula must be present."); if (ex.targetFormula != null && error) throw new RuntimeException("Cannot use error when targetFormula is present."); return ex; } static final Map<String, String> formulaMacros; static { formulaMacros = new HashMap<>(); formulaMacros.put("@type", CanonicalNames.TYPE); formulaMacros.put("@row", TableTypeSystem.ROW_TYPE); formulaMacros.put("@next", TableTypeSystem.ROW_NEXT_VALUE.id); formulaMacros.put("@!next", "!" + TableTypeSystem.ROW_NEXT_VALUE.id); formulaMacros.put("@index", TableTypeSystem.ROW_INDEX_VALUE.id); formulaMacros.put("@!index", "!" + TableTypeSystem.ROW_INDEX_VALUE.id); formulaMacros.put("@p.num", TableTypeSystem.CELL_NUMBER_VALUE.id); formulaMacros.put("@!p.num", "!" + TableTypeSystem.CELL_NUMBER_VALUE.id); formulaMacros.put("@p.date", TableTypeSystem.CELL_DATE_VALUE.id); formulaMacros.put("@!p.date", "!" + TableTypeSystem.CELL_DATE_VALUE.id); formulaMacros.put("@p.num2", TableTypeSystem.CELL_NUM2_VALUE.id); formulaMacros.put("@!p.num2", "!" + TableTypeSystem.CELL_NUM2_VALUE.id); formulaMacros.put("@p.str1", TableTypeSystem.CELL_STR1_VALUE.id); formulaMacros.put("@!p.str1", "!" + TableTypeSystem.CELL_STR1_VALUE.id); formulaMacros.put("@p.str2", TableTypeSystem.CELL_STR2_VALUE.id); formulaMacros.put("@!p.str2", "!" + TableTypeSystem.CELL_STR2_VALUE.id); formulaMacros.put("@p.part", TableTypeSystem.CELL_PART_VALUE.id); formulaMacros.put("@!p.part", "!" + TableTypeSystem.CELL_PART_VALUE.id); } static final Pattern regexProperty = Pattern.compile("r\\.(.*)"); static final Pattern regexReversedProperty = Pattern.compile("!r\\.(.*)"); static final Pattern regexEntity = Pattern.compile("c\\.(.*)"); static final Pattern regexPart = Pattern.compile("q\\.(.*)"); /** * Return a new LispTree representing the canonicalized version of the original formula */ public static LispTree canonicalizeFormula(LispTree orig) { if (orig.isLeaf()) { String value = orig.value; // 45 --> (number 45) if (StringNormalizationUtils.parseNumberStrict(value) != null) return LispTree.proto.newList("number", value); // value with "@" --> canonicalized name if (value.contains("@")) { String canonicalName = formulaMacros.get(value); if (canonicalName == null) throw new RuntimeException("Unrecognized macro: " + value); return LispTree.proto.newLeaf(canonicalName); } // c.xxx or c_xxx.yyy --> canonicalized name Matcher match; if ((match = regexProperty.matcher(value)).matches()) return LispTree.proto.newLeaf(TableTypeSystem.getRowPropertyName(match.group(1))); if ((match = regexReversedProperty.matcher(value)).matches()) return LispTree.proto.newLeaf("!" + TableTypeSystem.getRowPropertyName(match.group(1))); if ((match = regexEntity.matcher(value)).matches()) return LispTree.proto.newLeaf(TableTypeSystem.getCellName(match.group(1))); if ((match = regexPart.matcher(value)).matches()) return LispTree.proto.newLeaf(TableTypeSystem.getPartName(match.group(1))); if (value.contains(".") && !(value.startsWith("fb:") || value.startsWith("!fb:"))) throw new RuntimeException("Unhandled '.': " + value); return orig; } else { LispTree answer = LispTree.proto.newList(); // Handle special cases LispTree head = orig.child(0); if ("date".equals(head.value)) { for (LispTree child : orig.children) { answer.addChild(LispTree.proto.newLeaf(child.value)); } } else { for (LispTree child : orig.children) { answer.addChild(canonicalizeFormula(child)); } } return answer; } } // ============================================================ // Read dataset // ============================================================ public interface ExampleProcessor { void run(CustomExample ex); } public static boolean checkFilterExamples(int n) { if (opts.filterExamples == null || opts.filterExamples.isEmpty()) return true; for (String range : opts.filterExamples.split(",")) { String[] tokens = range.split("-"); if (tokens.length == 1) if (Integer.parseInt(tokens[0]) == n) return true; if (tokens.length == 2) if (Integer.parseInt(tokens[0]) <= n && Integer.parseInt(tokens[1]) >= n) return true; } return false; } public static List<CustomExample> getDataset(List<Pair<String, String>> pathPairs, ExampleProcessor processor) { LogInfo.begin_track_printAll("Dataset.read"); Evaluation evaluation = new Evaluation(); List<CustomExample> examples = new ArrayList<>(); for (Pair<String, String> pathPair : pathPairs) { String group = pathPair.getFirst(); String path = pathPair.getSecond(); Execution.putOutput("group", group); LogInfo.begin_track("Reading %s", path); Iterator<LispTree> trees = LispTree.proto.parseFromFile(path); while (trees.hasNext()) { // Format: (example (id ...) (utterance ...) (targetFormula ...) (targetValue ...)) LispTree tree = trees.next(); if ("metadata".equals(tree.child(0).value)) continue; if (!checkFilterExamples(examples.size())) { // Skip -- for debugging examples.add(null); continue; } if (opts.verbose >= 2) LogInfo.begin_track("Reading Example %s", examples.size()); if (tree.children.size() < 2 && !"example".equals(tree.child(0).value)) throw new RuntimeException("Invalid example: " + tree); CustomExample ex = null; Execution.putOutput("example", examples.size()); try { ex = CustomExample.fromLispTree(tree, path + ":" + examples.size()); // Specify a default id if it doesn't exist ex.preprocess(); } catch (Exception e) { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); LogInfo.warnings("Example %s: CONTAINS ERROR! %s:\n%s", ex == null ? ex : ex.id, e, sw); } if (opts.verbose >= 2) { ex.log(); } else if (opts.verbose >= 1) { LogInfo.logs("Example %s (%d): %s => %s", ex.id, examples.size(), ex.getTokens(), ex.targetValue); } examples.add(ex); if (opts.verbose >= 2) { for (String warning : ex.warnings) LogInfo.logs("WARNING: %s", warning); for (String error : ex.errors) LogInfo.logs("ERROR: %s", error); } if (processor != null) processor.run(ex); if (opts.verbose >= 2) LogInfo.end_track(); if (ex != null && ex.evaluation != null) { LogInfo.logs("Current: %s", ex.evaluation.summary()); evaluation.add(ex.evaluation); LogInfo.logs("Cumulative(%s): %s", group, evaluation.summary()); } } LogInfo.end_track(); LogInfo.logs("Stats for %s: %s", group, evaluation.summary()); evaluation.logStats(group); evaluation.putOutput(group); } LogInfo.end_track(); return examples; } public static List<CustomExample> getDataset(List<Pair<String, String>> pathPairs) { return getDataset(pathPairs, null); } public static List<CustomExample> getDataset(String path) { return getDataset(Collections.singletonList(new Pair<>("train", path)), null); } }