paul@199 | 1 | Concepts
|
paul@199 | 2 | ========
|
paul@199 | 3 |
|
paul@201 | 4 | This document describes the underlying concepts employed in micropython.
|
paul@201 | 5 |
|
paul@201 | 6 | * Namespaces and attribute definition
|
paul@570 | 7 | * Attribute usage observations
|
paul@785 | 8 | * Active code deduction
|
paul@584 | 9 | * Imports and circular import detection
|
paul@199 | 10 | * Contexts and values
|
paul@200 | 11 | * Tables, attributes and lookups
|
paul@200 | 12 | * Parameters and lookups
|
paul@199 | 13 |
|
paul@201 | 14 | Namespaces and Attribute Definition
|
paul@201 | 15 | ===================================
|
paul@201 | 16 |
|
paul@201 | 17 | Namespaces are any objects which can retain attributes.
|
paul@201 | 18 |
|
paul@201 | 19 | * Module attributes are defined either at the module level or by global
|
paul@201 | 20 | statements.
|
paul@201 | 21 | * Class attributes are defined only within class statements.
|
paul@534 | 22 | * Instance attributes are defined only by assignments to attributes of self
|
paul@534 | 23 | or tentatively as references to attributes of self.
|
paul@732 | 24 | * Locals are effectively attributes of the local namespace and are not
|
paul@732 | 25 | accessible externally (and thus cannot support closures).
|
paul@201 | 26 |
|
paul@201 | 27 | These restrictions apply because such attributes are thus explicitly declared,
|
paul@201 | 28 | permitting the use of tables (described below). Module and class attributes
|
paul@201 | 29 | can also be finalised in this way in order to permit certain optimisations.
|
paul@201 | 30 |
|
paul@519 | 31 | Rebinding of attributes outside classes and modules can be allowed if
|
paul@519 | 32 | attribute usage observations are being used to detect such external
|
paul@519 | 33 | modifications to such objects. Without such observations, such rebinding
|
paul@519 | 34 | should be forbidden since apparently constant attributes might be modified in
|
paul@519 | 35 | a running program, but code may have been generated that provides specific
|
paul@519 | 36 | objects for those attributes under the assumption that they will not be
|
paul@519 | 37 | changed.
|
paul@519 | 38 |
|
paul@732 | 39 | Observations during initial program inspection populate namespaces in a
|
paul@732 | 40 | simplistic fashion. If a loop is active, definitions record that the value of
|
paul@732 | 41 | a name can be set potentially many times. In local namespaces, definitions are
|
paul@732 | 42 | also recorded using the mechanisms employed to track attribute usage, and such
|
paul@732 | 43 | observations may provide a more sophisticated view of the potential values of
|
paul@732 | 44 | local names.
|
paul@732 | 45 |
|
paul@201 | 46 | See rejected.txt for complicating mechanisms which could be applied to
|
paul@201 | 47 | mitigate the effects of these restrictions on optimisations.
|
paul@201 | 48 |
|
paul@550 | 49 | Attribute Usage Observations
|
paul@550 | 50 | ============================
|
paul@550 | 51 |
|
paul@550 | 52 | Within a scope, a name may be used in conjunction with attribute names in
|
paul@550 | 53 | order to access attributes on objects referenced by the name. However, such
|
paul@550 | 54 | observations can only be regarded as reliable if the object referenced is not
|
paul@550 | 55 | changed independently by some other mechanism or activity.
|
paul@550 | 56 |
|
paul@550 | 57 | With conventional functions and methods, any locally defined names can be
|
paul@550 | 58 | considered private to that scope and thus immune to independent modification,
|
paul@550 | 59 | at least within reasonable features of the language. Although stack
|
paul@550 | 60 | modification may be possible, it seems appropriate to reject such features,
|
paul@550 | 61 | especially since they lend themselves to unmaintainable programs.
|
paul@550 | 62 |
|
paul@550 | 63 | For names defined during the initialisation of a class, since the class itself
|
paul@550 | 64 | cannot be referenced by name until its declaration has been completely
|
paul@550 | 65 | evaluated, no independent modification can occur from outside the class scope.
|
paul@550 | 66 |
|
paul@550 | 67 | For names defined during the initialisation of a module, global declarations
|
paul@550 | 68 | in functions permit the rebinding of global variables and since functions may
|
paul@550 | 69 | be invoked during module initialisation, independent modification can
|
paul@550 | 70 | potentially occur if any functions are called.
|
paul@550 | 71 |
|
paul@550 | 72 | Module globals can be accessed from other modules that can refer to a module
|
paul@550 | 73 | by its name. Initially, an import is required to make a module available for
|
paul@550 | 74 | modification, but there is no restriction on whether a module has been
|
paul@550 | 75 | completely imported (and thus defined) before an import statement can make it
|
paul@550 | 76 | available to other modules. Consider the following package root definition:
|
paul@550 | 77 |
|
paul@557 | 78 | # Module changed:
|
paul@550 | 79 | def f():
|
paul@557 | 80 | import changed.modifier
|
paul@550 | 81 | x = 123
|
paul@550 | 82 | f()
|
paul@550 | 83 |
|
paul@557 | 84 | # Module changed.modifier:
|
paul@557 | 85 | import changed
|
paul@557 | 86 | changed.x = 456
|
paul@550 | 87 |
|
paul@557 | 88 | Here, an import of changed will initially set x to 123, but then the function
|
paul@557 | 89 | f will be invoked and cause the changed.modifier module to be imported. Since
|
paul@557 | 90 | the changed module is already being imported, the import statement will not
|
paul@557 | 91 | try to perform the import operation again, but it will make the partially
|
paul@557 | 92 | defined module available for access. Thus, the changed.modifier module will
|
paul@557 | 93 | then set x to 456, and independent modification of the changed namespace will
|
paul@557 | 94 | have been performed.
|
paul@550 | 95 |
|
paul@550 | 96 | In conclusion, module globals cannot therefore be regarded as immune to
|
paul@550 | 97 | operations that would disrupt usage observations. Consequently, only locals
|
paul@550 | 98 | and class definition "locals" can be reliably employed in attribute usage
|
paul@550 | 99 | observations.
|
paul@550 | 100 |
|
paul@785 | 101 | Active Code Deduction
|
paul@785 | 102 | =====================
|
paul@785 | 103 |
|
paul@785 | 104 | With information about the attributes provided by each object, it becomes
|
paul@785 | 105 | possible to deduce which code is active and which code is unused in a program.
|
paul@785 | 106 | This process involves the collection of attribute references, the
|
paul@785 | 107 | identification of currently unused objects, and the propagation of this new
|
paul@785 | 108 | object information to parts of the program where attribute usage may now
|
paul@785 | 109 | involve such objects.
|
paul@785 | 110 |
|
paul@584 | 111 | Imports and Circular Import Detection
|
paul@584 | 112 | =====================================
|
paul@584 | 113 |
|
paul@584 | 114 | The matter of whether any given module is potentially modified by another
|
paul@584 | 115 | module before it has been completely imported can be addressed by separating
|
paul@584 | 116 | the import process into distinct phases:
|
paul@584 | 117 |
|
paul@584 | 118 | 1. Discovery/loading
|
paul@584 | 119 | 2. Parsing/structure processing
|
paul@584 | 120 | 3. Completion/code processing
|
paul@584 | 121 |
|
paul@584 | 122 | Upon discovering a module, a test is made to determine whether it is already
|
paul@584 | 123 | being imported; if not, it is then loaded and its structure inspected to
|
paul@584 | 124 | determine whether it may import other modules, which will then in turn be
|
paul@584 | 125 | discovered and loaded. Once no more modules can be loaded, they will then be
|
paul@584 | 126 | completed by undergoing the more thorough systematic processing of each
|
paul@584 | 127 | module's contents, defining program units and requesting the completion of
|
paul@584 | 128 | other modules when import statements are encountered.
|
paul@584 | 129 |
|
paul@584 | 130 | The motivation for such a multi-phase approach is to detect circular imports
|
paul@584 | 131 | in the structure processing phase before modules are populated and deductions
|
paul@584 | 132 | made about their objects' behaviour. Thus, globals belonging to a module known
|
paul@584 | 133 | to be imported in a circular fashion will already be regarded as potentially
|
paul@584 | 134 | modifiable by other modules and attribute usage observations will not be
|
paul@584 | 135 | recorded.
|
paul@584 | 136 |
|
paul@199 | 137 | Contexts and Values
|
paul@199 | 138 | ===================
|
paul@199 | 139 |
|
paul@199 | 140 | Values are used as the common reference representation in micropython: as
|
paul@199 | 141 | stored representations of attributes (of classes, instances, modules, and
|
paul@199 | 142 | other objects supporting attribute-like entities) as well as the stored values
|
paul@199 | 143 | associated with names in functions and methods.
|
paul@199 | 144 |
|
paul@199 | 145 | Unlike other implementations, micropython does not create things like bound
|
paul@199 | 146 | method objects for individual instances. Instead, all objects are referenced
|
paul@199 | 147 | using a context, reference pair:
|
paul@199 | 148 |
|
paul@199 | 149 | Value Layout
|
paul@199 | 150 | ------------
|
paul@199 | 151 |
|
paul@199 | 152 | 0 1
|
paul@199 | 153 | context object
|
paul@199 | 154 | reference reference
|
paul@199 | 155 |
|
paul@199 | 156 | Specific implementations might reverse this ordering for optimisation
|
paul@199 | 157 | purposes.
|
paul@199 | 158 |
|
paul@199 | 159 | Rationale
|
paul@199 | 160 | ---------
|
paul@199 | 161 |
|
paul@199 | 162 | To reduce the number of created objects whilst retaining the ability to
|
paul@199 | 163 | support bound method invocations. The context indicates the context in which
|
paul@199 | 164 | an invocation is performed, typically the owner of the method.
|
paul@199 | 165 |
|
paul@199 | 166 | Usage
|
paul@199 | 167 | -----
|
paul@199 | 168 |
|
paul@199 | 169 | The context may be inserted as the first argument when a value is involved in
|
paul@199 | 170 | an invocation. This argument may then be omitted from the invocation if its
|
paul@199 | 171 | usage is not appropriate.
|
paul@199 | 172 |
|
paul@199 | 173 | See invocation.txt for details.
|
paul@199 | 174 |
|
paul@237 | 175 | Context Value Types
|
paul@237 | 176 | -------------------
|
paul@237 | 177 |
|
paul@237 | 178 | The following types of context value exist:
|
paul@237 | 179 |
|
paul@237 | 180 | Type Usage Transformations
|
paul@237 | 181 | ---- ----- ---------------
|
paul@237 | 182 |
|
paul@237 | 183 | Replaceable With functions (not methods) May be replaced with an
|
paul@237 | 184 | instance or a class when a
|
paul@237 | 185 | value is stored on an
|
paul@237 | 186 | instance or class
|
paul@237 | 187 |
|
paul@237 | 188 | Placeholder With classes May not be replaced
|
paul@237 | 189 |
|
paul@237 | 190 | Instance With instances (and constants) May not be replaced
|
paul@237 | 191 | or functions as methods
|
paul@237 | 192 |
|
paul@237 | 193 | Class With functions as methods May be replaced when a
|
paul@237 | 194 | value is loaded from a
|
paul@237 | 195 | class attribute via an
|
paul@237 | 196 | instance
|
paul@237 | 197 |
|
paul@199 | 198 | Contexts in Acquired Values
|
paul@199 | 199 | ---------------------------
|
paul@199 | 200 |
|
paul@237 | 201 | There are four classes of instructions which provide values:
|
paul@199 | 202 |
|
paul@199 | 203 | Instruction Purpose Context Operations
|
paul@199 | 204 | ----------- ------- ------------------
|
paul@199 | 205 |
|
paul@237 | 206 | 1) LoadConst Load module, constant Use loaded object with itself
|
paul@237 | 207 | as context
|
paul@199 | 208 |
|
paul@237 | 209 | 2) LoadFunction Load function Combine replaceable context
|
paul@237 | 210 | with loaded object
|
paul@223 | 211 |
|
paul@237 | 212 | 3) LoadClass Load class Combine placeholder context
|
paul@237 | 213 | with loaded object
|
paul@237 | 214 |
|
paul@237 | 215 | 4) LoadAddress* Load attribute from Preserve or override stored
|
paul@201 | 216 | LoadAttr* class, module, context (as described in
|
paul@201 | 217 | instance assignment.txt)
|
paul@199 | 218 |
|
paul@199 | 219 | In order to comply with traditional Python behaviour, contexts may or may not
|
paul@199 | 220 | represent the object from which an attribute has been acquired.
|
paul@199 | 221 |
|
paul@199 | 222 | See assignment.txt for details.
|
paul@199 | 223 |
|
paul@199 | 224 | Contexts in Stored Values
|
paul@199 | 225 | -------------------------
|
paul@199 | 226 |
|
paul@223 | 227 | There are two classes of instruction for storing values:
|
paul@199 | 228 |
|
paul@223 | 229 | Instruction Purpose Context Operations
|
paul@223 | 230 | ----------- ------- ------------------
|
paul@199 | 231 |
|
paul@223 | 232 | 1) StoreAddress Store attribute in a Preserve context; note that no
|
paul@223 | 233 | known object test for class attribute
|
paul@223 | 234 | assignment should be necessary
|
paul@223 | 235 | since this instruction should only
|
paul@223 | 236 | be generated for module globals
|
paul@199 | 237 |
|
paul@223 | 238 | StoreAttr Store attribute in an Preserve context; note that no
|
paul@223 | 239 | instance test for class attribute
|
paul@223 | 240 | assignment should be necessary
|
paul@223 | 241 | since this instruction should only
|
paul@223 | 242 | be generated for self accesses
|
paul@199 | 243 |
|
paul@223 | 244 | StoreAttrIndex Store attribute in an Preserve context; since the index
|
paul@223 | 245 | unknown object lookup could yield a class
|
paul@223 | 246 | attribute, a test of the nature of
|
paul@223 | 247 | the nature of the structure is
|
paul@223 | 248 | necessary in order to prevent
|
paul@223 | 249 | assignments to classes
|
paul@199 | 250 |
|
paul@223 | 251 | 2) StoreAddressContext Store attribute in a Override context if appropriate;
|
paul@237 | 252 | known object if the value has a replaceable
|
paul@237 | 253 | context, permit the target to
|
paul@237 | 254 | take ownership of the value
|
paul@199 | 255 |
|
paul@199 | 256 | See assignment.txt for details.
|
paul@199 | 257 |
|
paul@200 | 258 | Tables, Attributes and Lookups
|
paul@200 | 259 | ==============================
|
paul@199 | 260 |
|
paul@199 | 261 | Attribute lookups, where the exact location of an object attribute is deduced,
|
paul@199 | 262 | are performed differently in micropython than in other implementations.
|
paul@199 | 263 | Instead of providing attribute dictionaries, in which attributes are found,
|
paul@766 | 264 | attributes are located at fixed places in object structures (described in
|
paul@766 | 265 | lowlevel.txt) and their locations are stored using a special representation
|
paul@766 | 266 | known as a table.
|
paul@199 | 267 |
|
paul@199 | 268 | For a given program, a table can be considered as being like a matrix mapping
|
paul@199 | 269 | classes to attribute names. For example:
|
paul@199 | 270 |
|
paul@199 | 271 | class A:
|
paul@200 | 272 | # instances have attributes x, y
|
paul@199 | 273 |
|
paul@199 | 274 | class B(A):
|
paul@200 | 275 | # introduces attribute z for instances
|
paul@199 | 276 |
|
paul@199 | 277 | class C:
|
paul@200 | 278 | # instances have attributes a, b, z
|
paul@199 | 279 |
|
paul@200 | 280 | This would provide the following table, referred to as an object table in the
|
paul@200 | 281 | context of classes and instances:
|
paul@199 | 282 |
|
paul@199 | 283 | Class/attr a b x y z
|
paul@199 | 284 |
|
paul@199 | 285 | A 1 2
|
paul@199 | 286 | B 1 2 3
|
paul@199 | 287 | C 1 2 3
|
paul@199 | 288 |
|
paul@199 | 289 | A limitation of this representation is that instance attributes may not shadow
|
paul@199 | 290 | class attributes: if an attribute with a given name is not defined on an
|
paul@199 | 291 | instance, an attribute with the same name cannot be provided by the class of
|
paul@401 | 292 | the instance or any superclass of the instance's class. This impacts the
|
paul@401 | 293 | provision of the __class__ attribute, as described below.
|
paul@199 | 294 |
|
paul@199 | 295 | The table can be compacted using a representation known as a displacement
|
paul@200 | 296 | list (referred to as an object list in this context):
|
paul@199 | 297 |
|
paul@199 | 298 | Classes with attribute offsets
|
paul@199 | 299 |
|
paul@199 | 300 | classcode A
|
paul@199 | 301 | attrcode a b x y z
|
paul@199 | 302 |
|
paul@199 | 303 | B
|
paul@199 | 304 | a b x y z
|
paul@199 | 305 |
|
paul@199 | 306 | C
|
paul@199 | 307 | a b x y z
|
paul@199 | 308 |
|
paul@199 | 309 | List . . 1 2 1 2 3 1 2 . . 3
|
paul@199 | 310 |
|
paul@199 | 311 | Here, the classcode refers to the offset in the list at which a class's
|
paul@199 | 312 | attributes are defined, whereas the attrcode defines the offset within a
|
paul@199 | 313 | region of attributes corresponding to a single attribute of a given name.
|
paul@199 | 314 |
|
paul@200 | 315 | Attribute Locations
|
paul@200 | 316 | -------------------
|
paul@200 | 317 |
|
paul@394 | 318 | The locations stored in table/list elements are generally for instance
|
paul@394 | 319 | attributes relative to the location of the instance, whereas those for class
|
paul@394 | 320 | attributes and module attributes are generally absolute addresses. Thus, each
|
paul@394 | 321 | occupied table cell has the following structure:
|
paul@242 | 322 |
|
paul@242 | 323 | attrcode, uses-absolute-address, address (or location)
|
paul@200 | 324 |
|
paul@247 | 325 | This could be given instead as follows:
|
paul@247 | 326 |
|
paul@667 | 327 | attrcode, is-class-or-module-attribute, location
|
paul@247 | 328 |
|
paul@667 | 329 | Since uses-absolute-address corresponds to is-class-or-module-attribute, and
|
paul@667 | 330 | since there is a need to test for classes and modules to prevent assignment to
|
paul@667 | 331 | attributes of such objects, this particular information is always required.
|
paul@247 | 332 |
|
paul@394 | 333 | The __class__ Attribute
|
paul@394 | 334 | -----------------------
|
paul@394 | 335 |
|
paul@401 | 336 | In Python 2.x, at least with new-style classes, instances have __class__
|
paul@401 | 337 | attributes which indicate the class from which they have been instantiated,
|
paul@401 | 338 | whereas classes have __class__ attributes which reference the type class.
|
paul@401 | 339 | With the object table, it is not possible to provide absolute addresses which
|
paul@401 | 340 | can be used for both classes and instances, since this would result in classes
|
paul@401 | 341 | and instances having the same class, and thus the class of a class would be
|
paul@401 | 342 | the class itself.
|
paul@397 | 343 |
|
paul@401 | 344 | One solution is to use object-relative values in the table so that referencing
|
paul@401 | 345 | the __class__ attribute of an instance produces a value which can be combined
|
paul@401 | 346 | with an instance's address to yield the address of the attribute, which itself
|
paul@401 | 347 | refers to the instance's class, whereas referencing the __class__ attribute of
|
paul@401 | 348 | a class produces a similar object-relative value that is combined with the
|
paul@401 | 349 | class's address to yield the address of the attribute, which itself refers to
|
paul@401 | 350 | the special type class.
|
paul@401 | 351 |
|
paul@401 | 352 | Obviously, the above solution requires both classes and instances to retain an
|
paul@401 | 353 | attribute location specifically to hold the value appropriate for each object
|
paul@401 | 354 | type, whereas a scheme which omits the __class__ attribute on classes would be
|
paul@401 | 355 | able to employ an absolute address in the table and maintain only a single
|
paul@401 | 356 | address to refer to the class for all instances. The only problem with not
|
paul@401 | 357 | providing a sensible __class__ attribute entry for classes would be the need
|
paul@401 | 358 | for special treatment of __class__ to prevent inappropriate consultation of
|
paul@401 | 359 | the table for classes.
|
paul@394 | 360 |
|
paul@247 | 361 | Comparing Tables as Matrices with Displacement Lists
|
paul@247 | 362 | ----------------------------------------------------
|
paul@247 | 363 |
|
paul@247 | 364 | Although displacement lists can provide reasonable levels of compaction for
|
paul@247 | 365 | attribute data, the element size is larger than that required for a simple
|
paul@247 | 366 | matrix: the attribute code (attrcode) need not be stored since each element
|
paul@247 | 367 | unambiguously refers to the availability of an attribute for a particular
|
paul@247 | 368 | class or instance of that class, and so the data at a given element need not
|
paul@247 | 369 | be tested for relevance to a given attribute access operation.
|
paul@247 | 370 |
|
paul@247 | 371 | Given a program with 20 object types and 100 attribute types, a matrix would
|
paul@247 | 372 | occupy the following amount of space:
|
paul@247 | 373 |
|
paul@247 | 374 | number of object types * number of attribute types * element size
|
paul@247 | 375 | = 20 * 100 * 1 (assuming that a single location is sufficient for an element)
|
paul@247 | 376 | = 2000
|
paul@247 | 377 |
|
paul@247 | 378 | In contrast, given a compaction to 40% of the matrix size (without considering
|
paul@247 | 379 | element size) in a displacement list, the amount of space would be as follows:
|
paul@247 | 380 |
|
paul@247 | 381 | number of elements * element size
|
paul@247 | 382 | = 40% * (20 * 100) * 2 (assuming that one additional location is required)
|
paul@247 | 383 | = 1600
|
paul@247 | 384 |
|
paul@247 | 385 | Consequently, the principal overhead of using a displacement list is likely to
|
paul@247 | 386 | be in the need to check element relevance when retrieving values from such a
|
paul@247 | 387 | list.
|
paul@247 | 388 |
|
paul@200 | 389 | Parameters and Lookups
|
paul@200 | 390 | ======================
|
paul@200 | 391 |
|
paul@200 | 392 | Since Python supports keyword arguments when making invocations, it becomes
|
paul@200 | 393 | necessary to record the parameter names associated with each function or
|
paul@200 | 394 | method. Just as object tables record attributes positions on classes and
|
paul@200 | 395 | instances, parameter tables record parameter positions in function or method
|
paul@200 | 396 | parameter lists.
|
paul@200 | 397 |
|
paul@200 | 398 | For a given program, a parameter table can be considered as being like a
|
paul@200 | 399 | matrix mapping functions/methods to parameter names. For example:
|
paul@200 | 400 |
|
paul@200 | 401 | def f(x, y, z):
|
paul@200 | 402 | pass
|
paul@200 | 403 |
|
paul@200 | 404 | def g(a, b, c):
|
paul@200 | 405 | pass
|
paul@200 | 406 |
|
paul@200 | 407 | def h(a, x):
|
paul@200 | 408 | pass
|
paul@200 | 409 |
|
paul@200 | 410 | This would provide the following table, referred to as a parameter table in
|
paul@200 | 411 | the context of functions and methods:
|
paul@200 | 412 |
|
paul@200 | 413 | Function/param a b c x y z
|
paul@200 | 414 |
|
paul@200 | 415 | f 1 2 3
|
paul@200 | 416 | g 1 2 3
|
paul@200 | 417 | h 1 2
|
paul@200 | 418 |
|
paul@233 | 419 | Confusion can occur when functions are adopted as methods, since the context
|
paul@233 | 420 | then occupies the first slot in the invocation frame:
|
paul@233 | 421 |
|
paul@233 | 422 | def f(x, y, z):
|
paul@233 | 423 | pass
|
paul@233 | 424 |
|
paul@233 | 425 | f(x=1, y=2, z=3) -> f(<context>, 1, 2, 3)
|
paul@233 | 426 | -> f(1, 2, 3)
|
paul@233 | 427 |
|
paul@233 | 428 | class C:
|
paul@233 | 429 | f = f
|
paul@233 | 430 |
|
paul@233 | 431 | def g(x, y, z):
|
paul@233 | 432 | pass
|
paul@233 | 433 |
|
paul@233 | 434 | c = C()
|
paul@233 | 435 |
|
paul@233 | 436 | c.f(y=2, z=3) -> f(<context>, 2, 3)
|
paul@233 | 437 | c.g(y=2, z=3) -> C.g(<context>, 2, 3)
|
paul@233 | 438 |
|
paul@200 | 439 | Just as with parameter tables, a displacement list can be prepared from a
|
paul@200 | 440 | parameter table:
|
paul@200 | 441 |
|
paul@200 | 442 | Functions with parameter (attribute) offsets
|
paul@200 | 443 |
|
paul@200 | 444 | funccode f
|
paul@200 | 445 | attrcode a b c x y z
|
paul@200 | 446 |
|
paul@200 | 447 | g
|
paul@200 | 448 | a b c x y z
|
paul@200 | 449 |
|
paul@200 | 450 | h
|
paul@200 | 451 | a b c x y z
|
paul@200 | 452 |
|
paul@200 | 453 | List . . . 1 2 3 1 2 3 1 . . 2 . .
|
paul@200 | 454 |
|
paul@200 | 455 | Here, the funccode refers to the offset in the list at which a function's
|
paul@200 | 456 | parameters are defined, whereas the attrcode defines the offset within a
|
paul@200 | 457 | region of attributes corresponding to a single parameter of a given name.
|