1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 }