View Javadoc

1   ////////////////////////////////////////////////////////////////////////////////
2   // MillScript: an Open Spice interpreter and batch website creation tool
3   // Copyright (C) 2001-2004 Open World Ltd
4   // Copyright (C) 2005 Kevin Rogers
5   //
6   // This file is part of MillScript.
7   //
8   // MillScript is free software; you can redistribute it and/or modify it under
9   // the terms of the GNU General Public License as published by the Free
10  // Software Foundation; either version 2 of the License, or (at your option)
11  // any later version.
12  //
13  // MillScript is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
16  // more details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // MillScript; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA  02111-1307  USA
21  ////////////////////////////////////////////////////////////////////////////////
22  package org.millscript.millscript.functions;
23  
24  import org.millscript.millscript.alert.Alerts;
25  import org.millscript.millscript.datatypes.Deferred;
26  import org.millscript.millscript.vm.Machine;
27  import org.millscript.millscript.vm.Package;
28  
29  import java.lang.reflect.InvocationTargetException;
30  import java.lang.reflect.Method;
31  
32  /**
33   * This is the base class of all MillScript Functions.
34   * <p>
35   * Two constructors are provided here, no-args or a single
36   * {@link org.millscript.millscript.vm.Package} argument. When adding functions
37   * via a package configuration file, the single arg version will be tried first
38   * and then the no-arg version. Hence if both are provided, the single argument
39   * version would not be used, unless an explicit reference is made.
40   * </p>
41   */
42  public abstract class Function {
43  
44      /**
45       * The name of this function.
46       */
47      private String name;
48  
49      /**
50       * A empty array for calling an objects clone method.
51       */
52      private static final Object[] NULL_ARGS = new Object[ 0 ];
53  
54      /**
55       * Constructs a new function with no additional parameters.
56       * <p>
57       * This constructor is for general use and is the most commonly required.
58       * If the function requires access to the configuration during construction
59       * it should provide the single argument version instead.
60       * </p>
61       */
62      protected Function() {
63      }
64  
65      /**
66       * Constructs a new function to be part of the specified pacakge.
67       * <p>
68       * This constructor will be tried first when adding a function to a package
69       * via a configuration file. If this constructor is not available, the no
70       * argument version will be tried. This constructor should only be used if
71       * the package argument is required. 
72       * </p>
73       *
74       * @param p the {@linkplain org.millscript.millscript.vm.Package} this new
75       * function will be part of
76       */
77      protected Function( final Package p ) {
78          
79      }
80  
81      /**
82       * Performs this function using the specified machine, with the specified
83       * number of arguments.
84       *
85       * @param mc    the machine to perform this function on
86       * @param nargs the number of arguments the function is being invoked with
87       */
88      public abstract void apply( final Machine mc, final int nargs );
89  
90      /**
91       * Performs this functions updater. The function is performed on the
92       * specified machine, with the specified number of arguments and update
93       * values. You can override this method if you are confident that zero
94       * results are pushed, otherwise override the auxillary method update.
95       *
96       * @param mc    the machine to perform this function on
97       * @param nargs the number of arguments the function is being invoked with
98       * @param unargs    the number of update arguments
99       */
100     public void applyUpdater( final Machine mc, final int nargs, final int unargs ) {
101         int n = mc.getCount();
102         update( mc, nargs, unargs );
103         if ( mc.getCount() == n ) {
104             return;
105         }
106         throw(
107             FunctionAlert.eval(
108                 "Updater trying to return results",
109                 "Updaters must return zero results"
110             ).culpritUArgs( this, mc, nargs, unargs ).mishap()
111         );
112     }
113 
114     /**
115      * Returns this functions name.
116      *
117      * @return  a String holding this functions name
118      */
119     public String getName() {
120         return name;
121     }
122 
123     /**
124      * Returns this function after having changed it's name to the specified
125      * value.
126      *
127      * @param s the new name for this function
128      * @return  this function with it's name changed
129      */
130     public Function modName( final String s ) {
131         name = s;
132         return this;
133     }
134 
135     /**
136      * Sets this functions name to the specified value.
137      *
138      * @param s the new name for this function
139      */
140     public void setName( final String s ) {
141         modName( s );
142     }
143 
144     /**
145      * @see java.lang.Object#toString()
146      */
147     @Override
148     public String toString() {
149         String n = getName();
150         return (
151             n == null ?
152             "<function>" :
153             "<function " + n + ">"
154         );
155     }
156 
157     /**
158      * Auxillary method to perform a functions updater. The function is
159      * performed on the specified machine, with the specified number of
160      * arguments and update values.
161      *
162      * @param mc    the machine to perform this function on
163      * @param nargs the number of arguments the function is being invoked with
164      * @param unargs    the number of update arguments
165      */
166     void update( final Machine mc, final int nargs, final int unargs ) {
167         throw(
168             Alerts.eval(
169                 "Missing updater",
170                 "Only updateable functions can be called in update mode"
171             ).culprit( "function", this ).mishap()
172         );
173     }
174 
175     /**
176      * Convenience method for checking if the right number of arguments are
177      * supplied when calling this function. This method would be used when the
178      * required number of arguments is a specific value.
179      *
180      * @param mc    the machine to gather arguments from
181      * @param required  the required number of arguments to this function
182      * @param actual    the actual number of arguments to this function
183      */
184     public void checkNargs( final Machine mc, final int required, final int actual ) {
185         if ( required == actual ) {
186             return;
187         }
188         reportNargsError( mc, required, actual );
189     }
190 
191     /**
192      * Convenience method for checking if the right number of arguments are
193      * supplied when calling this function. This method would be used when the
194      * required number of arguments is at least a certain value.
195      *
196      * @param mc    the machine to gather arguments from
197      * @param required  the minimum number of arguments to this function
198      * @param actual    the actual number of arguments to this function
199      */
200     public void checkNargsGT( final Machine mc, final int required, final int actual ) {
201         if ( required <= actual ) {
202             return;
203         }
204         reportNargsGTError( mc, required, actual );
205     }
206 
207     /**
208      * Convenience method for checking if the right number of arguments are
209      * supplied when calling this functions updater.
210      *
211      * @param mc    the machine to gather arguments from
212      * @param req   the required number of arguments to this function
213      * @param actual    the actual number of arguments to this function
214      * @param ureq  the required number of update values for this updater
215      * @param uactual   the actual number of update values for this updater
216      */
217     public void checkUNargs( final Machine mc, final int req, final int actual, final int ureq, final int uactual ) {
218         if ( req == actual && ureq == uactual ) {
219             return;
220         }
221         if ( req == actual ) {
222             throw(
223                 FunctionAlert.eval(
224                     "Too " + ( ureq > uactual ? "few" : "many" ) +
225                     " arguments on right hand side of assignment",
226                     "Exactly " + ureq + " required"
227                 ).culpritUArgs( this, mc, actual, uactual ).mishap()
228             );
229         }
230         throw(
231             FunctionAlert.eval(
232                 "Too " + ( req > actual ? "few" : "many" ) +
233                 " arguments on left hand side of assignment",
234                 "Exactly " + req + " required"
235             ).culpritUArgs( this, mc, actual, uactual ).mishap()
236         );
237     }
238 
239     /**
240      * Generates a mishap for use when the wrong number of arguments are
241      * supplied when calling this function. This method would be used when the
242      * required number of arguments should be a specific value.
243      *
244      * @param mc    the machine to gather arguments from
245      * @param required  the required number of arguments to this function
246      * @param actual    the actual number of arguments to this function
247      */
248     public void reportNargsError( final Machine mc, final int required, final int actual ) {
249         if ( required != actual ) {
250             throw(
251                 FunctionAlert.eval(
252                     "Too " +
253                     ( required > actual ? "few" : "many" ) +
254                     " arguments when calling function",
255                     "Exactly " + required + " required"
256                 ).culpritArgs( this, mc, actual ).mishap()
257             );
258         } else {
259             throw(
260                 Alerts.fault( "No error to report!" ).mishap()
261             );
262         }
263     }
264 
265     /**
266      * Generates a mishap for use when the wrong number of arguments are
267      * supplied when calling this function. This method would be used when the
268      * required number of arguments must be at least a certain value.
269      *
270      * @param mc    the machine to gather arguments from
271      * @param required  the minimum number of arguments to this function
272      * @param actual    the actual number of arguments to this function
273      */
274     public void reportNargsGTError( final Machine mc, final int required, final int actual ) {
275         if ( required > actual ) {
276             throw(
277                 FunctionAlert.eval(
278                     "Too few arguments when calling function",
279                     required + " or more required"
280                 ).culpritArgs( this, mc, actual ).mishap()
281             );
282         } else {
283             throw(
284                 Alerts.fault( "No error (GT) to report!" ).mishap()
285             );
286         }
287     }
288 
289     /**
290      * Attempts to clone the specified object by calling its public clone
291      * method. If the object is successfully cloned, its clone will be returned
292      * otherwise the specified object is itself returned.
293      *
294      * @param x the object to try and clone
295      * @return  the supplied object, or it's clone if successfully cloned
296      */
297     public static Object tryClone( final Object x ) {
298         Class c = x.getClass();
299         try {
300             Method m = c.getMethod( "clone" );
301             return m.invoke( x, NULL_ARGS );
302         } catch ( NoSuchMethodException ex ) {
303             try {
304                 return tryClone( ((Deferred) x).get() );
305             } catch ( ClassCastException ex2 ) {
306                 return x;
307             }
308         } catch ( IllegalAccessException ex ) {
309             return x;
310         } catch ( InvocationTargetException ex ) {
311             return x;
312         }
313     }
314 
315 }