/* File: MatchTable.java * Contains: A class for pattern-matching * Author: Jeff Dalton * Created: January 1998 * Updated: Sun Aug 23 23:54:28 1998 by Jeff Dalton * Copyright: (c) 1998, AIAI, University of Edinburgh */ package ix.util.match; import java.util.*; /** * A MatchTable acts as a kind of pattern-matching case statement. * The various cases are represented by MatchCase objects. Each * MatchCase contains a pattern and provides methods tryMatch and * ifSelected. tryMatch is given an object to match against the pattern, * and ifSelected is called if the match succeeds. ifSelected is given * two arguments: the object that was matched and the result from * tryMatch. * * A MatchTable also contains a default MatchCase that can be set by * setDefault. If none of the ordinary cases matches, and there is * a default MatchCase, the default's ifSelected method is called with * the object to match as its first argument and null as its second. * Otherwise, the result is null.

* * The table and the cases it contains can cooperate in the matching * process, with some work done by each. The patterns in the cases are * normally accessible to table. This allows the table to do clever * indexing or even "compilation" of part of the match. For instance, * if all of the patterns were strings, and the table was asked to * match a non-string against those patterns, it might know that the * match was guaranteed to fail; and so it could just return null * right away, without bothering to do any further matching.

* * This ability of the table to exploit information obtained by * examining all of the cases is an important reason for using a * table object rather than an if-statement.

* * Moreover, the table contains MatchCase objects, rather than just * patterns, because that allows some things to be done when the * case object is created, rather than being repeated each time a * match is tried. For instance, patterns that are regular expressions * might be translated into descriptions of finite-state machines.

* * For instances of the MatchTable class itself, all of the matching * is done by the cases. The MatchTable's match method simply calls * each case's tryMatch in turn. However, a subclass might provide * something more interesting, even to the point where all the work * was done by the table without calling the tryMatch methods at all.

* * It might be asked what happens if the table does something that's * incompatible with the matching the cases expect to perform. The * answer is that it's up to the programmer to select compatible * table and case classes when constructing a table, so that such * things do not happen. This need for compatibility should also be * kept in mind when designing new table and case classes. Some * classes might even check various compatibility issues when they're * used. * * @see MatchCase * @see MatchEnv */ // Note that we can't use "case" as a variable. public class MatchTable { /** * The MatchCases in this MatchTable. */ protected Vector cases = new Vector(); /** * This table's default case. */ protected MatchCase defaultCase = null; /** * addCase adds a new MatchCase after those that were * added earlier. */ public void addCase(MatchCase c) { cases.addElement(c); } /** * setDefault assigns a MatchCase as the table's default case. */ public void setDefault(MatchCase c) { defaultCase = c; } /** * match tries to find a MatchCase in the MatchTable that matches * an object. It calls the tryMatch method of each MatchCase in turn * until one returns a non-null result. Then match returns the result * of calling the same MatchCase's ifSelected method, passing it the * object that was matched and the non-null result. If all the * tryMatch calls return null, then the default case is examined. * If the default is a MatchCase, its ifSelected method is is called * on the object the table's been trying to match and null. If * the default is null, match returns null. */ public Object match(Object data) { Object result = null; int n = cases.size(); MatchCase c; /* Try each case */ for (int i = 0; i < n; i++) { c = (MatchCase)cases.elementAt(i); result = c.tryMatch(data); if (result != null) return c.ifSelected(data, result); } /* No match */ if (defaultCase == null) return null; // the default default else return defaultCase.ifSelected(data, null); } /** * A variant of match that can be used internally to match a subset * of the cases that has been selected in some way (such as by indexing * on a "key" in the data). The MatchTable's default case is not * considered. Instead, if there's no match among the selected cases, * matchCases returns null. */ protected Object matchCases(Object data, Vector selectedCases) { Object result = null; int n = selectedCases.size(); MatchCase c; /* Try each case */ for (int i = 0; i < n; i++) { c = (MatchCase)selectedCases.elementAt(i); result = c.tryMatch(data); if (result != null) return c.ifSelected(data, result); } /* No match */ return null; } /** * Applies the default case, if there is one. matchDefault is * typically used with matchCases. */ protected Object matchDefault(Object data) { if (defaultCase == null) return null; // the default default else return defaultCase.ifSelected(data, null); } }