View Javadoc

1   ////////////////////////////////////////////////////////////////////////////////
2   // MillScript: an Open Spice interpreter and batch website creation tool
3   // Copyright (C) 2004-2005 Kevin Rogers
4   //
5   // This file is part of MillScript.
6   //
7   // MillScript is free software; you can redistribute it and/or modify it under
8   // the terms of the GNU General Public License as published by the Free
9   // Software Foundation; either version 2 of the License, or (at your option)
10  // any later version.
11  //
12  // MillScript is distributed in the hope that it will be useful, but WITHOUT
13  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  // more details.
16  //
17  // You should have received a copy of the GNU General Public License along with
18  // MillScript; if not, write to the Free Software Foundation, Inc., 59 Temple
19  // Place, Suite 330, Boston, MA  02111-1307  USA
20  ////////////////////////////////////////////////////////////////////////////////
21  package org.millscript.millscript.functions;
22  
23  import org.millscript.commons.util.map.EHashMap;
24  import org.millscript.millscript.alert.Alerts;
25  import org.millscript.millscript.datatypes.SpiceClass;
26  import org.millscript.millscript.datatypes.SpiceObject;
27  import org.millscript.millscript.tools.CastLibrary;
28  import org.millscript.millscript.vm.Machine;
29  
30  /**
31   * This class provides multiple dispatch functions for MillScript. The point
32   * here is to allow multiple function definitions with different type
33   * signatures, that dispatch on specified arguments.
34   */
35  public class ManyBodyFunction extends Function {
36  
37      /**
38       * Maps the type signature of a function definition to it's Function. We
39       * currently support single dispatch on the first argument, so this is a
40       * simple mapping of a Spice class to a Function.
41       */
42      private final EHashMap< SpiceClass, Function > definitions = new EHashMap< SpiceClass, Function >();
43  
44      /**
45       * Maps the type signature of the calling arguments to the relevant
46       * Function. This is slightly different to the definition map, as
47       * we might dispatch on a subclass. e.g. if a definition is made for a
48       * method in class <code>Foo</code>, there will be a mapping for
49       * <code>Foo</code> in the definitions. Assume a sub-class is made of type
50       * <code>Bar</code>, which doesn't override this method. If we dispatch
51       * this function on type <code>Bar</code>, we will actually have to lookup
52       * and call the method associated with type <code>Foo</code>. This map
53       * exists to cache this lookup.
54       */
55      private final EHashMap< SpiceClass, Function > callCache = new EHashMap< SpiceClass, Function >();
56  
57      /**
58       * Constructs a new ManyBodyFunction with the specified initial method
59       * entry.
60       *
61       * @param mc    the current Machine
62       * @param sc    the spice class to dispatch on
63       * @param lf    the Function to execute for the specified type signature
64       */
65      public ManyBodyFunction( final Machine mc, final SpiceClass sc, final Function lf ) {
66          this.setMethodEntry( mc, sc, lf );
67      }
68  
69      /**
70       * @see org.millscript.millscript.functions.Function#apply(org.millscript.millscript.vm.Machine, int)
71       */
72      @Override
73      public void apply( final Machine mc, final int nargs ) {
74          // TODO - Single dispatch for now...
75          checkNargsGT( mc, 1, nargs );
76          // Get the dispatch object
77          final SpiceObject dispatchObject = CastLibrary.toSpiceObject( mc.getIndex( mc.getCount() - nargs ) );
78          // Get the class of the object
79          final SpiceClass sc = dispatchObject.getSpiceClass();
80          // Get the specific method body for this type
81          final Function specificBodyFunction = findMethod( sc );
82          // Call the found method body
83          specificBodyFunction.apply( mc, nargs );
84      }
85  
86      /**
87       * Returns the specific Function body for the specified type signature.
88       * This method will search for a body Function which matches the specified
89       * type, or one of it's parents.
90       *
91       * @param sc    the type to dispatch on
92       * @return  a Function for the specific body to use for the specified type
93       */
94      private Function findMethod( final SpiceClass sc ) {
95          // TODO - Revisit this no we have default actions for maps, we should
96          // be able to accommodate the alert into a custom default action
97          // Check if we've already cached this result
98          // NOTE - The cast is safe as we only ever put Function's into the callcache
99          final Function cachedFunc = callCache.get( sc );
100         if ( cachedFunc == null ) {
101             // No cached value, so we have to search through the complete type
102             // hierarchy for the correct one
103             // Step one: Look for an uncached definition using the specified
104             // type
105             final Function func = definitions.get( sc );
106             // Check if we have a Function for this type
107             if ( func == null ) {
108                 // We don't have a definition, so we need to check using any
109                 // available parent type.
110                 final SpiceClass parentSpiceClass = sc.getParentSpiceClass();
111                 if ( parentSpiceClass == null ) {
112                     // Ah, unfortunately no parent, so...
113                     throw(
114                         Alerts.eval(
115                             "No matching method definition",
116                             "There is no suitable method for the specified type signature"
117                         ).culprit( "type", sc.getName() ).mishap()
118                     );
119                 } else {
120                     // Good, there is a parent, so check it...
121                     return findMethod( parentSpiceClass );
122                 }
123             } else {
124                 // We do have a definition, so cache it
125                 callCache.insert( sc, func );
126                 // Then return it.
127                 return func;
128             }
129         } else {
130             return cachedFunc;
131         }
132     }
133 
134     /**
135      * Sets the Function body for the specified type signature.
136      *
137      * @param mc    the current Machine
138      * @param sc    the spice class to dispatch on
139      * @param func  the Function to execute for the specified type signature
140      */
141     public void setMethodEntry( final Machine mc, final SpiceClass sc, final Function func ) {
142         // TODO - Revisit this no we have default actions for maps, we should
143         // be able to accommodate the alert into a custom default action
144         // Check if we already have a definition for this signature
145         if ( definitions.containsKey( sc ) ) {
146             mc.getConfig().reportAlertAsWarning(
147                 Alerts.compile(
148                     "A method with this type signature is already declared",
149                     null
150                 ).culprit( "method", this ).culprit( "type", sc )
151             );
152         }
153         // Most importantly clear the call cache. We have to do this as we may
154         // be adding a new dispatch type that has previously been cached.
155         callCache.removeAll();
156         // Set the new method definition for this dispatch type
157         definitions.insert( sc, func );
158     }
159 
160 }