| | 1 | #charset "us-ascii" |
| | 2 | |
| | 3 | /* |
| | 4 | * Copyright (c) 2008 Michael J. Roberts. All Rights Reserved. |
| | 5 | * |
| | 6 | * This module provides the run-time component of "multi-methods" in TADS |
| | 7 | * 3. This works with the compiler to implement a multiple-dispatch |
| | 8 | * system. |
| | 9 | * |
| | 10 | * Multi-methods are essentially a combination of regular object methods |
| | 11 | * and "overloaded functions" in languages like C++. Like a regular |
| | 12 | * object method, multi-methods are polymorphic: you can define several |
| | 13 | * incarnations of the same function name, with different parameter |
| | 14 | * types, the system picks the right binding for each invocation |
| | 15 | * dynamically, based on the actual argument values at run-time. Unlike |
| | 16 | * regular methods, though, the selection is made on ALL of the argument |
| | 17 | * types, not just a special "self" argument. In that respect, |
| | 18 | * multi-methods are like overloaded functions in C++; but multi-methods |
| | 19 | * differ from C++ overloading in that the selection of which method to |
| | 20 | * call is made dynamically at run-time, not at compile time. |
| | 21 | * |
| | 22 | * There are two main uses for multi-methods. |
| | 23 | * |
| | 24 | * First, most obviously, multi-methods provide what's known as "multiple |
| | 25 | * dispatch" semantics. There are some situations (actually, quite a |
| | 26 | * few) where the ordinary Object Oriented notion of polymorphism - |
| | 27 | * selecting a method based on a single target object - doesn't quite do |
| | 28 | * the trick, because what you really want to do is select a particular |
| | 29 | * method based on the *combination* of objects involved in an operation. |
| | 30 | * Some canonical examples are calculating intersections of shapes in a |
| | 31 | * graphics program, where you want to select a specialized "Rectangle + |
| | 32 | * Circle" routine in one case and a "Line + Polygon" routine in another; |
| | 33 | * or performing file format conversions, where you want to select, say, |
| | 34 | * a specialized "JPEG to PNG" routine. In an IF context, the obvious |
| | 35 | * use is for carrying out multi-object verbs, where you might want a |
| | 36 | * special routine for PUT (liquid) IN (vessel), and another for PUT |
| | 37 | * (object) IN (container). |
| | 38 | * |
| | 39 | * Second, multi-methods offer a way of extending a class without having |
| | 40 | * to change the class's source code. Since a multi-method is defined |
| | 41 | * externally to any classes it refers to, you can create a method that's |
| | 42 | * polymorphic on class type - just like a regular method - but as a |
| | 43 | * syntactically stand-alone function. This feature isn't as important |
| | 44 | * in TADS as in some other languages, since TADS lets you do essentially |
| | 45 | * the same thing with the "modify" syntax; but for some purposes the |
| | 46 | * multi-method approach might be preferable aesthetically, since it's |
| | 47 | * wholly external to the class rather than a sort of lexically separate |
| | 48 | * continuation of the class's code. (However, as a practical matter, |
| | 49 | * it's not all that different; our implementation of multi-methods does |
| | 50 | * in fact modify the original class object, since we store the binding |
| | 51 | * information in the class objects.) |
| | 52 | */ |
| | 53 | |
| | 54 | #include <tads.h> |
| | 55 | |
| | 56 | |
| | 57 | /* ------------------------------------------------------------------------ */ |
| | 58 | /* |
| | 59 | * Invoke a multi-method function. For an expression of the form |
| | 60 | * |
| | 61 | *. f(a, b, ...) |
| | 62 | * |
| | 63 | * where 'f' has been declared as a multi-method, the compiler will |
| | 64 | * actually generate code that invokes this function, like so: |
| | 65 | * |
| | 66 | *. _multiMethodCall(baseFunc, params); |
| | 67 | * |
| | 68 | * 'baseFunc' is a function pointer giving the base function; this is a |
| | 69 | * pointer to the common stub function that the compiler generates to |
| | 70 | * identify all of the multi-methods with a given name. 'params' is a |
| | 71 | * list giving the actual parameter values for invoking the function. |
| | 72 | * |
| | 73 | * Our job is to find the actual run-time binding for the function given |
| | 74 | * the actual parameters, and invoke it. |
| | 75 | */ |
| | 76 | _multiMethodCall(baseFunc, args) |
| | 77 | { |
| | 78 | /* get the function binding lookup information */ |
| | 79 | local info = _multiMethodRegistry.boundFuncTab_[baseFunc]; |
| | 80 | |
| | 81 | /* it's an error if there's no binding */ |
| | 82 | if (info == nil) |
| | 83 | throw new UnboundMultiMethod(baseFunc, args); |
| | 84 | |
| | 85 | /* |
| | 86 | * Look up the function binding based on the arguments. To ensure |
| | 87 | * that we match a function with the correct number of argument, we |
| | 88 | * have to explicitly add the last-argument placeholder to the list. |
| | 89 | */ |
| | 90 | local func = _multiMethodSelect(info, args + _multiMethodEndOfList); |
| | 91 | |
| | 92 | /* if we found a binding, invoke it; otherwise throw an error */ |
| | 93 | if (func == nil) |
| | 94 | throw new UnboundMultiMethod(baseFunc, args); |
| | 95 | else |
| | 96 | return (func)(args...); |
| | 97 | } |
| | 98 | |
| | 99 | /* |
| | 100 | * Invoke the base multi-method inherited from the given multi-method. |
| | 101 | * 'fromFunc' is a pointer to a multi-method, presumably the one |
| | 102 | * currently running; we look up the next in line in inheritance order |
| | 103 | * and invoke it with the given argument list. |
| | 104 | */ |
| | 105 | _multiMethodCallInherited(fromFunc, [args]) |
| | 106 | { |
| | 107 | #ifdef MULTMETH_STATIC_INHERITED |
| | 108 | |
| | 109 | /* static mode - get the cached inheritance information */ |
| | 110 | local inh = _multiMethodRegistry.inhTab_[fromFunc]; |
| | 111 | |
| | 112 | #else |
| | 113 | |
| | 114 | /* dynamic mode - get the base function binding */ |
| | 115 | local info = _multiMethodRegistry.boundFuncTab_[ |
| | 116 | _multiMethodRegistry.baseFuncTab_[fromFunc]]; |
| | 117 | |
| | 118 | /* it's an error if it doesn't exist */ |
| | 119 | if (info == nil) |
| | 120 | throw new UnboundInheritedMultiMethod(fromFunc, args); |
| | 121 | |
| | 122 | /* look up the inherited function based on the actual parameters */ |
| | 123 | local inh = _multiMethodInherit( |
| | 124 | fromFunc, info, args + _multiMethodEndOfList); |
| | 125 | |
| | 126 | #endif |
| | 127 | |
| | 128 | /* it's an error if there's no inherited binding */ |
| | 129 | if (inh == nil) |
| | 130 | throw new UnboundInheritedMultiMethod(fromFunc, args); |
| | 131 | |
| | 132 | /* call it */ |
| | 133 | return (inh)(args...); |
| | 134 | } |
| | 135 | |
| | 136 | |
| | 137 | /* ------------------------------------------------------------------------ */ |
| | 138 | /* |
| | 139 | * Get a pointer to a resolved multi-method function. This takes a |
| | 140 | * pointer to the base function for the multi-method and a list of actual |
| | 141 | * argument values, and returns a function pointer to the specific |
| | 142 | * version of the multi-method that would be invoked if you called the |
| | 143 | * multi-method with that argument list. |
| | 144 | * |
| | 145 | * For example, if you want to get a pointer to the function that would |
| | 146 | * be called if you were to call foo(x, y, z), you'd use: |
| | 147 | * |
| | 148 | *. local func = getMultiMethodPointer(foo, x, y, z); |
| | 149 | * |
| | 150 | * We return a pointer to the individual multi-method function that |
| | 151 | * matches the argument list, or nil if there's no matching multi-method. |
| | 152 | */ |
| | 153 | getMultiMethodPointer(baseFunc, [args]) |
| | 154 | { |
| | 155 | /* get the function binding lookup information */ |
| | 156 | local info = _multiMethodRegistry.boundFuncTab_[baseFunc]; |
| | 157 | |
| | 158 | /* if there's no binding information, return failure */ |
| | 159 | if (info == nil) |
| | 160 | return nil; |
| | 161 | |
| | 162 | /* look up and return the function binding based on the arguments */ |
| | 163 | return _multiMethodSelect(info, args + _multiMethodEndOfList); |
| | 164 | } |
| | 165 | |
| | 166 | |
| | 167 | /* ------------------------------------------------------------------------ */ |
| | 168 | /* |
| | 169 | * Resolve a multi-method binding. This function takes a binding |
| | 170 | * property ID (the property we assign during the registration process to |
| | 171 | * generate the binding tables) and a "remaining" argument list. This |
| | 172 | * function invokes itself recursively to traverse the arguments from |
| | 173 | * left to right, so at each recursive invocation, we lop off the |
| | 174 | * leftmost argument (the one we're working on currently) and pass in the |
| | 175 | * remaining arguments in the list. |
| | 176 | * |
| | 177 | * We look up the binding property on the first argument in the remaining |
| | 178 | * argument list. This can yield one of three things: |
| | 179 | * |
| | 180 | * - The trivial result is nil, which means that this binding property |
| | 181 | * has no definition on the first argument. This doesn't necessarily |
| | 182 | * mean that the whole function is undefined on the arguments; it only |
| | 183 | * means that the current inheritance level we're looking at for the |
| | 184 | * previous argument(s) has no binding. If we get this result we simply |
| | 185 | * return nil to tell the caller that it must look at an inherited |
| | 186 | * binding for the previous argument. |
| | 187 | * |
| | 188 | * - If the result is a function pointer, it's the bound function. This |
| | 189 | * is the final result for the recursion, so we simply return it. |
| | 190 | * |
| | 191 | * - Otherwise, the result will be a new property ID, giving the property |
| | 192 | * that resolves the binding for the *next* argument. In this case, we |
| | 193 | * use this property to resolve the next argument in the list by a |
| | 194 | * recursive invocation. If that recursive call succeeds (i.e., returns |
| | 195 | * a non-nil value), we're done - we simply return the recursive result |
| | 196 | * as though it were our own. If it fails, it means that there's no |
| | 197 | * binding for the particular subclass we're currently working on for the |
| | 198 | * first argument - however, there could still be a binding for a parent |
| | 199 | * class of the first argument. So, we iterate up to any inherited |
| | 200 | * binding for the first argument, and if we find one, we try again with |
| | 201 | * the same recursive call. We continue up our first argument's class |
| | 202 | * tree until we either find a binding (in which case we return it) or |
| | 203 | * exhaust the class tree (in which case we return nil). |
| | 204 | */ |
| | 205 | _multiMethodSelect(prop, args) |
| | 206 | { |
| | 207 | local obj, binding; |
| | 208 | |
| | 209 | /* |
| | 210 | * Get the first argument from the remaining arguments. If it's not |
| | 211 | * an object, use the placeholder object for non-object parameter |
| | 212 | * bindings. |
| | 213 | */ |
| | 214 | local orig = args[1]; |
| | 215 | if (dataType(orig) not in (TypeObject, TypeList, TypeSString)) |
| | 216 | orig = _multiMethodNonObjectBindings; |
| | 217 | |
| | 218 | /* get the remaining arguments */ |
| | 219 | args = args.sublist(2); |
| | 220 | |
| | 221 | /* |
| | 222 | * Look up the initial binding - this is simply the value of the |
| | 223 | * binding property for the first argument. In order to process the |
| | 224 | * inheritance tree later, we'll need to know where we got this |
| | 225 | * definition from, so look up the specific defining object. |
| | 226 | * |
| | 227 | * If the initial binding property isn't defined, or its value is |
| | 228 | * explicitly nil, the function isn't bound (or, in the case of nil, |
| | 229 | * is explicitly unbound). Inheritance won't help in these cases, so |
| | 230 | * we can immediately return nil to indicate that we don't have a |
| | 231 | * binding. |
| | 232 | */ |
| | 233 | if ((obj = orig.propDefined(prop, PropDefGetClass)) == nil |
| | 234 | || (binding = obj.(prop)) == nil) |
| | 235 | return nil; |
| | 236 | |
| | 237 | /* |
| | 238 | * If there are no more arguments, but we didn't just find a final |
| | 239 | * function binding, we don't have enough arguments to match the |
| | 240 | * current multi-method path. Return failure. |
| | 241 | */ |
| | 242 | if (args.length() == 0 && dataType(binding) != TypeFuncPtr) |
| | 243 | return nil; |
| | 244 | |
| | 245 | /* |
| | 246 | * starting at our current defining object for the first argument, |
| | 247 | * scan up its superclass tree until we find a binding |
| | 248 | */ |
| | 249 | for (;;) |
| | 250 | { |
| | 251 | local ret; |
| | 252 | |
| | 253 | /* if we have a function pointer, we've found our binding */ |
| | 254 | if (dataType(binding) == TypeFuncPtr) |
| | 255 | return binding; |
| | 256 | |
| | 257 | /* |
| | 258 | * Recursively bind the binding for the remaining arguments. If |
| | 259 | * we find a binding, we're done - simply return it. |
| | 260 | */ |
| | 261 | if ((ret = _multiMethodSelect(binding, args)) != nil) |
| | 262 | return ret; |
| | 263 | |
| | 264 | /* |
| | 265 | * We didn't find a binding for the remaining arguments, so we |
| | 266 | * must have chosen too specific a binding for the first |
| | 267 | * argument. Look for an inherited value of the binding property |
| | 268 | * in the next superclass of the object where we found the last |
| | 269 | * binding value. |
| | 270 | */ |
| | 271 | obj = orig.propInherited(prop, orig, obj, PropDefGetClass); |
| | 272 | if (obj == nil) |
| | 273 | return nil; |
| | 274 | |
| | 275 | /* we found an inherited value, so retrieve it from the superclass */ |
| | 276 | binding = obj.(prop); |
| | 277 | } |
| | 278 | } |
| | 279 | |
| | 280 | /* |
| | 281 | * Select the INHERITED version of a multi-method. This takes a |
| | 282 | * particular version of the multi-method, and finds the next version in |
| | 283 | * inheritance order. |
| | 284 | * |
| | 285 | * This is basically a copy of _multiMethodSelect(), with a small amount |
| | 286 | * of extra logic. This code repetition isn't good maintenance-wise, and |
| | 287 | * the two functions could in principle be merged into one. However, |
| | 288 | * doing so would have an efficiency cost to _multiMethodSelect(), which |
| | 289 | * we want to keep as lean as possible. |
| | 290 | */ |
| | 291 | _multiMethodInherit(fromFunc, prop, args) |
| | 292 | { |
| | 293 | return _multiMethodInheritMain( |
| | 294 | new _MultiMethodInheritCtx(), fromFunc, prop, args); |
| | 295 | } |
| | 296 | |
| | 297 | class _MultiMethodInheritCtx: object |
| | 298 | foundFromFunc = nil |
| | 299 | ; |
| | 300 | |
| | 301 | _multiMethodInheritMain(ctx, fromFunc, prop, args) |
| | 302 | { |
| | 303 | local obj, binding; |
| | 304 | |
| | 305 | /* |
| | 306 | * Get the first argument from the remaining arguments. If it's not |
| | 307 | * an object, use the placeholder object for non-object parameter |
| | 308 | * bindings. |
| | 309 | */ |
| | 310 | local orig = args[1]; |
| | 311 | if (dataType(orig) not in (TypeObject, TypeList, TypeSString)) |
| | 312 | orig = _multiMethodNonObjectBindings; |
| | 313 | |
| | 314 | /* get the remaining arguments */ |
| | 315 | args = args.sublist(2); |
| | 316 | |
| | 317 | /* |
| | 318 | * Look up the initial binding - this is simply the value of the |
| | 319 | * binding property for the first argument. In order to process the |
| | 320 | * inheritance tree later, we'll need to know where we got this |
| | 321 | * definition from, so look up the specific defining object. |
| | 322 | * |
| | 323 | * If the initial binding property isn't defined, or its value is |
| | 324 | * explicitly nil, the function isn't bound (or, in the case of nil, |
| | 325 | * is explicitly unbound). Inheritance won't help in these cases, so |
| | 326 | * we can immediately return nil to indicate that we don't have a |
| | 327 | * binding. |
| | 328 | */ |
| | 329 | if ((obj = orig.propDefined(prop, PropDefGetClass)) == nil |
| | 330 | || (binding = obj.(prop)) == nil) |
| | 331 | return nil; |
| | 332 | |
| | 333 | /* |
| | 334 | * If there are no more arguments, but we didn't just find a final |
| | 335 | * function binding, we don't have enough arguments to match the |
| | 336 | * current multi-method path. Return failure. |
| | 337 | */ |
| | 338 | if (args.length() == 0 && dataType(binding) != TypeFuncPtr) |
| | 339 | return nil; |
| | 340 | |
| | 341 | /* |
| | 342 | * starting at our current defining object for the first argument, |
| | 343 | * scan up its superclass tree until we find a binding |
| | 344 | */ |
| | 345 | for (;;) |
| | 346 | { |
| | 347 | /* we haven't found a function binding yet */ |
| | 348 | local ret = nil; |
| | 349 | |
| | 350 | /* |
| | 351 | * we either have a function pointer, in which case it's the |
| | 352 | * actual binding, or a property, in which case it's the next |
| | 353 | * binding level |
| | 354 | */ |
| | 355 | if (dataType(binding) == TypeFuncPtr) |
| | 356 | { |
| | 357 | /* this is the binding */ |
| | 358 | ret = binding; |
| | 359 | } |
| | 360 | else |
| | 361 | { |
| | 362 | /* if there are no more arguments, return failure */ |
| | 363 | if (args.length() == 0) |
| | 364 | return nil; |
| | 365 | |
| | 366 | /* |
| | 367 | * Recursively bind the binding for the remaining arguments. |
| | 368 | * If we find a binding, we're done - simply return it. |
| | 369 | */ |
| | 370 | ret = _multiMethodInheritMain(ctx, fromFunc, binding, args); |
| | 371 | } |
| | 372 | |
| | 373 | /* check to see if we found a binding */ |
| | 374 | if (ret != nil) |
| | 375 | { |
| | 376 | /* |
| | 377 | * We found a binding. If we've already found the inheriting |
| | 378 | * version, return the first thing we find, since that's the |
| | 379 | * next inheriting level. Otherwise, if this is the |
| | 380 | * inheriting version, note that we've found it, but keep |
| | 381 | * looking, since we want to find the next one after that. |
| | 382 | * Otherwise, just keep looking, since we haven't even |
| | 383 | * reached the overriding version yet. |
| | 384 | */ |
| | 385 | if (ctx.foundFromFunc) |
| | 386 | return ret; |
| | 387 | else if (ret == fromFunc) |
| | 388 | ctx.foundFromFunc = true; |
| | 389 | } |
| | 390 | |
| | 391 | /* |
| | 392 | * We didn't find a binding for the remaining arguments, so we |
| | 393 | * must have chosen too specific a binding for the first |
| | 394 | * argument. Look for an inherited value of the binding property |
| | 395 | * in the next superclass of the object where we found the last |
| | 396 | * binding value. |
| | 397 | */ |
| | 398 | obj = orig.propInherited(prop, orig, obj, PropDefGetClass); |
| | 399 | if (obj == nil) |
| | 400 | return nil; |
| | 401 | |
| | 402 | /* we found an inherited value, so retrieve it from the superclass */ |
| | 403 | binding = obj.(prop); |
| | 404 | } |
| | 405 | } |
| | 406 | |
| | 407 | /* ------------------------------------------------------------------------ */ |
| | 408 | /* |
| | 409 | * Unbound multi-method exception. This is thrown when a call to resolve |
| | 410 | * a multi-method fails to find a binding, meaning that there's no |
| | 411 | * definition of the method that matches the types of the arguments. |
| | 412 | */ |
| | 413 | class UnboundMultiMethod: Exception |
| | 414 | construct(func, args) |
| | 415 | { |
| | 416 | /* note the function name and argument list */ |
| | 417 | func_ = func; |
| | 418 | args_ = args; |
| | 419 | |
| | 420 | /* look up the function's name */ |
| | 421 | name_ = _multiMethodRegistry.funcNameTab_[func]; |
| | 422 | } |
| | 423 | |
| | 424 | /* display an error message describing the exception */ |
| | 425 | displayException() |
| | 426 | { |
| | 427 | "Unbound multi-method \"<<name_>>\" (<<args_.length()>> argument(s))"; |
| | 428 | } |
| | 429 | |
| | 430 | /* the base function pointer */ |
| | 431 | func_ = nil |
| | 432 | |
| | 433 | /* the symbol name of the base function */ |
| | 434 | name_ = '' |
| | 435 | |
| | 436 | /* the number of arguments */ |
| | 437 | args_ = 0 |
| | 438 | ; |
| | 439 | |
| | 440 | class UnboundInheritedMultiMethod: UnboundMultiMethod |
| | 441 | displayException() |
| | 442 | { |
| | 443 | "No inherited multi-method for \"<<name_>>\" (<<args_.length()>> |
| | 444 | arguments(s))"; |
| | 445 | } |
| | 446 | ; |
| | 447 | |
| | 448 | |
| | 449 | /* ------------------------------------------------------------------------ */ |
| | 450 | /* |
| | 451 | * Base class for our internal placeholder objects for argument list |
| | 452 | * matching. |
| | 453 | */ |
| | 454 | class _MultiMethodPlaceholder: object |
| | 455 | ; |
| | 456 | |
| | 457 | /* |
| | 458 | * A placeholder object for bindings for non-object arguments. Whenever |
| | 459 | * we have an actual argument value that's not an object, we'll look here |
| | 460 | * for bindings for that parameter. When registering a function, we'll |
| | 461 | * register a binding here for any parameter that doesn't have a type |
| | 462 | * specification. |
| | 463 | */ |
| | 464 | _multiMethodNonObjectBindings: _MultiMethodPlaceholder |
| | 465 | ; |
| | 466 | |
| | 467 | /* |
| | 468 | * A placeholder object for end-of-list bindings. When we're matching an |
| | 469 | * argument list, we'll use this to represent the end of the list so that |
| | 470 | * we can match the "..." in any varargs functions in the multi-method |
| | 471 | * set that we're matching against. |
| | 472 | */ |
| | 473 | _multiMethodEndOfList: _MultiMethodPlaceholder |
| | 474 | ; |
| | 475 | |
| | 476 | |
| | 477 | /* ------------------------------------------------------------------------ */ |
| | 478 | /* |
| | 479 | * Register a multi-method. |
| | 480 | * |
| | 481 | * The compiler automatically generates a call to this function during |
| | 482 | * pre-initialization for each defined multi-method. 'baseFunc' is a |
| | 483 | * pointer to the "base" function - this is a stub function that the |
| | 484 | * compiler generates to refer to the whole collection of multi-methods |
| | 485 | * with a given name. 'func' is the pointer to the specific multi-method |
| | 486 | * we're registering; this is the actual function defined in the code |
| | 487 | * with a given set of parameter types. 'params' is a list of the |
| | 488 | * parameter type values; each parameter type in the list is given as a |
| | 489 | * class object (meaning that the parameter matches that class), nil |
| | 490 | * (meaning that the parameter matches ANY type of value), or the string |
| | 491 | * '...' (meaning that this is a "varargs" function, and any number of |
| | 492 | * additional parameters can be supplied at this point in the parameters; |
| | 493 | * this is always the last parameter in the list if it's present). |
| | 494 | */ |
| | 495 | _multiMethodRegister(baseFunc, func, params) |
| | 496 | { |
| | 497 | /* if there's no hash entry for the function yet, add one */ |
| | 498 | local tab = _multiMethodRegistry.funcTab_; |
| | 499 | if (tab[baseFunc] == nil) |
| | 500 | tab[baseFunc] = new Vector(10); |
| | 501 | |
| | 502 | /* add the entry to the list of variants for this function */ |
| | 503 | tab[baseFunc].append([func, params]); |
| | 504 | |
| | 505 | /* add the mapping from the function to the base function */ |
| | 506 | _multiMethodRegistry.baseFuncTab_[func] = baseFunc; |
| | 507 | |
| | 508 | /* also add the function to the direct parameter table */ |
| | 509 | _multiMethodRegistry.funcParamTab_[func] = params; |
| | 510 | } |
| | 511 | |
| | 512 | /* |
| | 513 | * Build the method bindings. The compiler generates a call to this |
| | 514 | * after all methods have been registered; we run through the list of |
| | 515 | * registered methods and generate the binding properties in the |
| | 516 | * referenced objects. |
| | 517 | */ |
| | 518 | _multiMethodBuildBindings() |
| | 519 | { |
| | 520 | /* no errors yet */ |
| | 521 | local errs = []; |
| | 522 | |
| | 523 | /* |
| | 524 | * build a lookup table that maps function pointers to symbol names, |
| | 525 | * so we can look up our function names for diagnostic purposes |
| | 526 | */ |
| | 527 | local nameTab = new LookupTable(128, 256); |
| | 528 | t3GetGlobalSymbols().forEachAssoc(new function(key, val) |
| | 529 | { |
| | 530 | /* if it's a function, store a value-to-name association */ |
| | 531 | if (dataType(val) == TypeFuncPtr) |
| | 532 | nameTab[val] = key; |
| | 533 | }); |
| | 534 | |
| | 535 | /* run through each entry in the method table */ |
| | 536 | _multiMethodRegistry.funcTab_.forEachAssoc(new function(baseFunc, val) |
| | 537 | { |
| | 538 | /* look up the base function's name */ |
| | 539 | local name = nameTab[baseFunc]; |
| | 540 | |
| | 541 | /* add this to the saved name table */ |
| | 542 | _multiMethodRegistry.funcNameTab_[baseFunc] = name; |
| | 543 | |
| | 544 | /* note the number of registered instances of this function */ |
| | 545 | local funcCnt = val.length(); |
| | 546 | |
| | 547 | /* |
| | 548 | * Assign the initial binding property for this function. This |
| | 549 | * is the property that gives us the binding for the first |
| | 550 | * variant argument. Each unique multi-method (which is defined |
| | 551 | * as a multi-method with a given name and a given number of |
| | 552 | * parameters) has a single initial binding property. |
| | 553 | */ |
| | 554 | local initProp = t3AllocProp(); |
| | 555 | |
| | 556 | /* |
| | 557 | * store the binding starter information for the function - to |
| | 558 | * find the binding on invocation, we'll need the initial binding |
| | 559 | * property so that we can trace the argument list |
| | 560 | */ |
| | 561 | _multiMethodRegistry.boundFuncTab_[baseFunc] = initProp; |
| | 562 | |
| | 563 | /* build the argument binding tables */ |
| | 564 | for (local i = 1 ; i <= funcCnt ; i++) |
| | 565 | { |
| | 566 | /* get the function binding */ |
| | 567 | local func = val[i][1]; |
| | 568 | |
| | 569 | /* get the formal parameter type list for this function */ |
| | 570 | local params = val[i][2]; |
| | 571 | local paramCnt = params.length(); |
| | 572 | |
| | 573 | /* |
| | 574 | * If the last formal isn't a varargs placeholder, then we |
| | 575 | * must explicitly find the end of the list in the actual |
| | 576 | * parameters in order to match a call. To match the end of |
| | 577 | * the list, add the special End-Of-List placeholder to the |
| | 578 | * formals list. |
| | 579 | * |
| | 580 | * This isn't necessary when there's a varargs placeholder |
| | 581 | * because the placeholder can match zero or more - so it |
| | 582 | * doesn't matter where the list ends as long as we get to |
| | 583 | * the varargs slot. |
| | 584 | */ |
| | 585 | if (paramCnt == 0 || params[paramCnt] != '...') |
| | 586 | { |
| | 587 | params += _multiMethodEndOfList; |
| | 588 | ++paramCnt; |
| | 589 | } |
| | 590 | |
| | 591 | /* start at the initial binding property */ |
| | 592 | local prop = initProp; |
| | 593 | |
| | 594 | /* run through the parameters and build the bindings */ |
| | 595 | for (local j = 1 ; j <= paramCnt ; j++) |
| | 596 | { |
| | 597 | /* get this parameter type */ |
| | 598 | local origTyp = params[j], typ = origTyp; |
| | 599 | |
| | 600 | /* |
| | 601 | * If the type is nil, it means that this parameter slot |
| | 602 | * can accept any type. So, map the slot to the generic |
| | 603 | * Object type - this will catch everything, since we |
| | 604 | * handle non-objects by mapping them to the |
| | 605 | * _multiMethodNonObjectBindings placeholder object, |
| | 606 | * which like all objects inherits from Object. This |
| | 607 | * means we'll match argument values that are objects or |
| | 608 | * non-objects, thus fulfilling our requirement to match |
| | 609 | * all values. |
| | 610 | * |
| | 611 | * If the type is the string '...', it means that this is |
| | 612 | * a varargs placeholder argument. In this case, we need |
| | 613 | * to set up a match for the generic Object, in case we |
| | 614 | * have one or more actual arguments for the varargs |
| | 615 | * portion. This will also automatically match the case |
| | 616 | * where we have no extra arguments, because in this case |
| | 617 | * the matcher will try to match the End-Of-List |
| | 618 | * placeholder object _multiMethodEndOfList, which (as |
| | 619 | * above) inherits from Object and thus picks up the |
| | 620 | * any-type binding. |
| | 621 | * |
| | 622 | * The one tricky bit is that when we have a parameter |
| | 623 | * explicitly bound to Object, or an explicit End-Of-List |
| | 624 | * flag object, we'll get an undesired side effect of |
| | 625 | * this otherwise convenient arrangement: we'll |
| | 626 | * effectively bind non-object types to the Object by |
| | 627 | * virtue of the inheritance. To deal with this, we'll |
| | 628 | * explicitly set the placeholders' binding to nil in |
| | 629 | * this situation - this makes non-object types |
| | 630 | * explicitly *not* bound to the function, overriding any |
| | 631 | * binding we'd otherwise inherit from Object. |
| | 632 | */ |
| | 633 | if (typ == nil || typ == '...') |
| | 634 | typ = Object; |
| | 635 | |
| | 636 | /* |
| | 637 | * Figure the binding. |
| | 638 | * |
| | 639 | * - If this is the last parameter, it's the end of the |
| | 640 | * line, so bind directly to the function pointer. |
| | 641 | * |
| | 642 | * - If this isn't the variant parameter, the binding is |
| | 643 | * the next binding property. At invocation, we'll |
| | 644 | * continue on to the next argument value, evaluating |
| | 645 | * this next property to get its binding in the context |
| | 646 | * established by the current argument and property. The |
| | 647 | * next binding property is specific to the current class |
| | 648 | * in the current position, so we might already have |
| | 649 | * assigned a property for it from another version of |
| | 650 | * this function. Look it up, or create a new one if we |
| | 651 | * haven't assigned it already. |
| | 652 | */ |
| | 653 | local binding; |
| | 654 | if (j == paramCnt) |
| | 655 | { |
| | 656 | /* end of the line - the binding is the actual function */ |
| | 657 | binding = func; |
| | 658 | |
| | 659 | /* |
| | 660 | * if this type is already bound to a different |
| | 661 | * definition for this function, we have a |
| | 662 | * conflicting definition |
| | 663 | */ |
| | 664 | if (typ.propDefined(prop, PropDefDirectly) |
| | 665 | && typ.(prop) != binding) |
| | 666 | errs += [baseFunc, func, params, name]; |
| | 667 | } |
| | 668 | else |
| | 669 | { |
| | 670 | /* check for an existing binding property here */ |
| | 671 | if (typ.propDefined(prop, PropDefDirectly)) |
| | 672 | { |
| | 673 | /* we already have a forward binding here - use it */ |
| | 674 | binding = typ.(prop); |
| | 675 | } |
| | 676 | else |
| | 677 | { |
| | 678 | /* it's not defined here, so create a new property */ |
| | 679 | binding = t3AllocProp(); |
| | 680 | } |
| | 681 | } |
| | 682 | |
| | 683 | /* set the binding */ |
| | 684 | typ.(prop) = binding; |
| | 685 | |
| | 686 | /* |
| | 687 | * As we mentioned above, if the original type is |
| | 688 | * explicitly Object, we *don't* want to allow non-object |
| | 689 | * types (int, true, nil, property pointers, etc) and |
| | 690 | * End-Of-List placeholders to match - without some kind |
| | 691 | * of explicit intervention here, the placeholders would |
| | 692 | * match by inheritance because the placeholders are just |
| | 693 | * objects themselves. To handle this properly, set the |
| | 694 | * non-object placeholder bindings explicitly to nil. |
| | 695 | */ |
| | 696 | if (origTyp == Object) |
| | 697 | _MultiMethodPlaceholder.(prop) = nil; |
| | 698 | |
| | 699 | /* |
| | 700 | * if there's another argument, this binding is the |
| | 701 | * binding property for the next argument |
| | 702 | */ |
| | 703 | prop = binding; |
| | 704 | } |
| | 705 | } |
| | 706 | }); |
| | 707 | |
| | 708 | #ifdef MULTMETH_STATIC_INHERITED |
| | 709 | /* |
| | 710 | * If we're operating in static inheritance mode, cache the |
| | 711 | * next-override information for inherited() calls. Since we're |
| | 712 | * using static inheritance, we can figure this at startup and just |
| | 713 | * look up the cached information whenever we need to perform an |
| | 714 | * inherited() call. |
| | 715 | */ |
| | 716 | _multiMethodRegistry.funcTab_.forEachAssoc(new function(baseFunc, val) |
| | 717 | { |
| | 718 | /* get the binding property for the base function */ |
| | 719 | local prop = _multiMethodRegistry.boundFuncTab_[baseFunc]; |
| | 720 | |
| | 721 | /* run through the functions registered under this function name */ |
| | 722 | for (local i = 1 ; i <= val.length() ; i++) |
| | 723 | { |
| | 724 | /* get this function binding and the type list */ |
| | 725 | local func = val[i][1]; |
| | 726 | local params = val[i][2]; |
| | 727 | local paramCnt = params.length(); |
| | 728 | |
| | 729 | /* |
| | 730 | * Add the end-of-list marker if applicable. For a varargs |
| | 731 | * function, add one generic Object parameter in place of the |
| | 732 | * variable list - but we'll check later to make sure that |
| | 733 | * any match we find is really varargs, since a varargs |
| | 734 | * function can only inherit from another varargs function. |
| | 735 | * (This is because, in order to actually invoke the |
| | 736 | * inherited function from an overrider, the callee must be |
| | 737 | * varargs to be able to handle varargs from the caller.) |
| | 738 | */ |
| | 739 | local varargs = (paramCnt != 0 && params[paramCnt] == '...'); |
| | 740 | if (varargs) |
| | 741 | params[paramCnt] = Object; |
| | 742 | else |
| | 743 | params += _multiMethodEndOfList; |
| | 744 | |
| | 745 | /* look up the inherited version of the method */ |
| | 746 | local inh = _multiMethodInherit(func, prop, params); |
| | 747 | |
| | 748 | /* varargs can only inherit from varargs */ |
| | 749 | if (inh != nil && varargs) |
| | 750 | { |
| | 751 | /* look up the inherited parameters */ |
| | 752 | local inhParams = _multiMethodRegistry.funcParamTab_[inh]; |
| | 753 | local inhParamCnt = inhParams.length(); |
| | 754 | |
| | 755 | /* make sure it's varargs, too */ |
| | 756 | if (inhParamCnt == 0 || inhParams[inhParamCnt] != '...') |
| | 757 | inh = nil; |
| | 758 | } |
| | 759 | |
| | 760 | /* remember the inherited function */ |
| | 761 | _multiMethodRegistry.inhTab_[func] = inh; |
| | 762 | } |
| | 763 | }); |
| | 764 | #endif /* MULTMETH_STATIC_INHERITED */ |
| | 765 | |
| | 766 | /* we're done with the source bindings - discard them to save memory */ |
| | 767 | _multiMethodRegistry.funcTab_ = nil; |
| | 768 | _multiMethodRegistry.funcParamTab_ = nil; |
| | 769 | } |
| | 770 | |
| | 771 | /* |
| | 772 | * Multi-method registry. This is where we keep the registry information |
| | 773 | * that we build during initialization. |
| | 774 | */ |
| | 775 | _multiMethodRegistry: object |
| | 776 | /* table of registered functions, indexed by base function */ |
| | 777 | funcTab_ = static new LookupTable(128, 256) |
| | 778 | |
| | 779 | /* table of function parameter lists, indexed by function */ |
| | 780 | funcParamTab_ = static new LookupTable(128, 256) |
| | 781 | |
| | 782 | /* function name table */ |
| | 783 | funcNameTab_ = static new LookupTable(64, 128) |
| | 784 | |
| | 785 | /* base function -> initial binding property */ |
| | 786 | boundFuncTab_ = static new LookupTable(64, 128) |
| | 787 | |
| | 788 | /* function -> base function */ |
| | 789 | baseFuncTab_ = static new LookupTable(64, 128) |
| | 790 | |
| | 791 | #ifdef MULTMETH_STATIC_INHERITED |
| | 792 | /* table of cached inherited() information, indexed by function */ |
| | 793 | inhTab_ = static new LookupTable(64, 128) |
| | 794 | #endif |
| | 795 | ; |