paul@627 | 1 | A Systems Programming Language Target for Micropython
|
paul@627 | 2 | =====================================================
|
paul@627 | 3 |
|
paul@627 | 4 | Python-compatible syntax for processing using the compiler module.
|
paul@627 | 5 |
|
paul@627 | 6 | The principal focus is on specific machine code generation and not
|
paul@627 | 7 | analysis. Thus, only block generation, address reference generation,
|
paul@627 | 8 | temporary storage administration and other code generation tasks are to be
|
paul@627 | 9 | left to the systems programming language compiler.
|
paul@627 | 10 |
|
paul@670 | 11 | Special Functions
|
paul@670 | 12 | -----------------
|
paul@670 | 13 |
|
paul@670 | 14 | In syspython, the function invocation notation is reserved to specify
|
paul@670 | 15 | primitive operations such as attribute access and actual function invocations,
|
paul@670 | 16 | with the latter being expressed as follows:
|
paul@670 | 17 |
|
paul@670 | 18 | fn(y) # original Python
|
paul@670 | 19 | apply(fn, y) # syspython
|
paul@670 | 20 |
|
paul@670 | 21 | Thus, in syspython, whenever the invocation notation is used, the target of
|
paul@670 | 22 | the invocation is always a special function and not a general Python function
|
paul@670 | 23 | or method. Note that the apply function resembles the Python function of the
|
paul@670 | 24 | same name but is not actually that particular function.
|
paul@670 | 25 |
|
paul@760 | 26 | apply(fn, ...) # general invocation
|
paul@760 | 27 |
|
paul@679 | 28 | A family of special functions for invocations exists, addressing optimisation
|
paul@747 | 29 | situations identified by program analysis:
|
paul@747 | 30 |
|
paul@760 | 31 | applyclass(cls, ...) # direct invocation of an instantiator
|
paul@760 | 32 | applyfunction(fn, ...) # function-specific invocation
|
paul@760 | 33 | applystaticmethod(fn, obj, ...) # specific invocation of a method via a class
|
paul@760 | 34 | applymethod(fn, obj, ...) # specific invocation of a method via self
|
paul@760 | 35 |
|
paul@763 | 36 | Where dynamic functions are to be invoked, the object providing the defaults
|
paul@763 | 37 | needs to be supplied to the function or method, but this should be done by the
|
paul@763 | 38 | invocation mechanism implemented on the target architecture, exposing only a
|
paul@763 | 39 | special __context__ local name providing access to the defaults.
|
paul@760 | 40 |
|
paul@760 | 41 | Where optimisation possibilities cannot be identified in advance, the apply
|
paul@760 | 42 | function must deal with the following aspects of invocation:
|
paul@760 | 43 |
|
paul@763 | 44 | * Whether a dynamic function is being invoked, thus requiring an object for
|
paul@763 | 45 | access to defaults
|
paul@760 | 46 | * Whether a context argument is required
|
paul@760 | 47 | * Whether an appropriate number of arguments have been provided
|
paul@679 | 48 |
|
paul@678 | 49 | Low-Level Code
|
paul@678 | 50 | --------------
|
paul@678 | 51 |
|
paul@678 | 52 | Most Python-level program code should be wrapped in special function
|
paul@678 | 53 | invocations, and as a result other syntax features might be used to express
|
paul@678 | 54 | low-level concepts. Low-level operations may also be expressed using other
|
paul@678 | 55 | special functions. For example:
|
paul@678 | 56 |
|
paul@678 | 57 | storelocal(element, loadobjtable(loadattr(obj, classcode), attrcode))
|
paul@678 | 58 |
|
paul@678 | 59 | Here, element holds the raw data provided by the table access involving a base
|
paul@678 | 60 | defined by the classcode of an object and an offset defined by the supplied
|
paul@678 | 61 | attrcode.
|
paul@678 | 62 |
|
paul@678 | 63 | Note that all low-level functions deal only with addresses and offsets, not
|
paul@678 | 64 | symbols. In the above example, loadattr combines the address of obj with the
|
paul@678 | 65 | symbol classcode whose actual value must be substituted by the compiler.
|
paul@678 | 66 | However, the loadobjtable function requires a genuine offset value for the
|
paul@678 | 67 | classcode (which is why loadattr is being used to obtain it), and a genuine
|
paul@678 | 68 | offset for the attrcode (which is provided directly).
|
paul@678 | 69 |
|
paul@636 | 70 | Program Data and Data Structure Definition
|
paul@636 | 71 | ------------------------------------------
|
paul@636 | 72 |
|
paul@627 | 73 | Given that micropython has already deduced object and parameter details,
|
paul@627 | 74 | such information must be communicated in the systems programming language
|
paul@627 | 75 | so that the compiler does not have to deduce it again.
|
paul@627 | 76 |
|
paul@627 | 77 | Explicit constant declaration shall be done at the start of the main
|
paul@627 | 78 | module:
|
paul@627 | 79 |
|
paul@670 | 80 | constants(...)
|
paul@627 | 81 |
|
paul@675 | 82 | Each module may feature keyword arguments, and a list of such names is
|
paul@675 | 83 | provided as follows:
|
paul@675 | 84 |
|
paul@675 | 85 | keywords(...)
|
paul@675 | 86 |
|
paul@627 | 87 | Explicit structure declaration is still performed using class statements,
|
paul@627 | 88 | but base classes are omitted and attributes are declared explicitly as
|
paul@627 | 89 | follows:
|
paul@627 | 90 |
|
paul@627 | 91 | class C:
|
paul@670 | 92 | instattrs(member...)
|
paul@670 | 93 | classattrs(member...)
|
paul@627 | 94 |
|
paul@627 | 95 | Other object table information, such as inherited class attributes and
|
paul@627 | 96 | class compatibility (to support isinstance) are also declared explicitly:
|
paul@627 | 97 |
|
paul@670 | 98 | inherited(superclass, member...)
|
paul@670 | 99 | descendants(class...)
|
paul@627 | 100 |
|
paul@627 | 101 | Other than function definitions, no other code statements shall appear in
|
paul@627 | 102 | class definitions; such statements will appear after classes have been
|
paul@638 | 103 | defined.
|
paul@638 | 104 |
|
paul@638 | 105 | For classes in the module namespace or within other classes, the __main__
|
paul@638 | 106 | function collects together all "loose" (module-level) statements; class
|
paul@638 | 107 | attribute assignments will occur in the __main__ function, and where a name
|
paul@638 | 108 | is associated with a function definition and another object, the function will
|
paul@638 | 109 | also be explicitly assigned in the __main__ function using its full name.
|
paul@638 | 110 |
|
paul@638 | 111 | For classes in function namespaces, the containing function could contain the
|
paul@638 | 112 | "loose" statements at the point at which the class appears. However, such
|
paul@638 | 113 | classes are not currently supported in micropython.
|
paul@637 | 114 |
|
paul@637 | 115 | Any class or function defined once in a namespace need not be assigned to that
|
paul@637 | 116 | namespace in the __main__ function, but where multiple definitions exist and
|
paul@637 | 117 | program logic determines which definition prevails, such definitions must be
|
paul@637 | 118 | assigned in the __main__ function.
|
paul@637 | 119 |
|
paul@637 | 120 | For example:
|
paul@637 | 121 |
|
paul@637 | 122 | class C:
|
paul@637 | 123 | def method(self, ...):
|
paul@637 | 124 | ...
|
paul@637 | 125 | if something:
|
paul@637 | 126 | method = something
|
paul@637 | 127 |
|
paul@637 | 128 | This is represented as follows:
|
paul@637 | 129 |
|
paul@637 | 130 | class C:
|
paul@637 | 131 | ...
|
paul@637 | 132 | def method(self, ...):
|
paul@637 | 133 | ...
|
paul@637 | 134 |
|
paul@637 | 135 | def __main__():
|
paul@670 | 136 | globalnames(...)
|
paul@637 | 137 | ...
|
paul@637 | 138 | if something:
|
paul@670 | 139 | storeattr(module.C, method, something)
|
paul@627 | 140 |
|
paul@756 | 141 | Local class or function definitions are also handled in a similar fashion to
|
paul@756 | 142 | those at the module level, although there is no explicit __main__ function
|
paul@756 | 143 | within each function.
|
paul@756 | 144 |
|
paul@756 | 145 | For example:
|
paul@756 | 146 |
|
paul@756 | 147 | def outer(x):
|
paul@756 | 148 | if something:
|
paul@756 | 149 | def inner(...):
|
paul@756 | 150 | ...
|
paul@756 | 151 | else:
|
paul@756 | 152 | def inner(...):
|
paul@756 | 153 | ...
|
paul@756 | 154 | return inner
|
paul@756 | 155 |
|
paul@756 | 156 | This is represented as follows:
|
paul@756 | 157 |
|
paul@756 | 158 | def outer(x):
|
paul@756 | 159 | def inner(...):
|
paul@756 | 160 | ...
|
paul@756 | 161 | def inner(...):
|
paul@756 | 162 | ...
|
paul@756 | 163 |
|
paul@756 | 164 | if something:
|
paul@761 | 165 | storelocal(inner, static(module.outer.inner))
|
paul@756 | 166 | else:
|
paul@761 | 167 | storelocal(inner, static("module.outer.inner#2"))
|
paul@756 | 168 | return inner
|
paul@756 | 169 |
|
paul@756 | 170 | Where functions are dynamic - that is they have additional state associated
|
paul@756 | 171 | with them, such as defaults (or potentially closures if supported) that are
|
paul@756 | 172 | not static (such as constant values) - suitable objects must be created using
|
paul@756 | 173 | references to such functions.
|
paul@756 | 174 |
|
paul@756 | 175 | For example:
|
paul@756 | 176 |
|
paul@756 | 177 | def outer(x):
|
paul@756 | 178 | def inner(y, z=x):
|
paul@756 | 179 | ...
|
paul@756 | 180 | return inner
|
paul@756 | 181 |
|
paul@756 | 182 | This is represented as follows:
|
paul@756 | 183 |
|
paul@756 | 184 | def outer(x):
|
paul@763 | 185 | def inner(y, z=x):
|
paul@763 | 186 | localnames(y, z, __context__)
|
paul@756 | 187 | ...
|
paul@761 | 188 | storelocal(inner, makedynamic(static(module.outer.inner), x))
|
paul@756 | 189 | return inner
|
paul@756 | 190 |
|
paul@756 | 191 | The special makedynamic invocation creates an object referring to the function
|
paul@756 | 192 | and incorporating any specified defaults as attributes of that object. The
|
paul@763 | 193 | function itself will use a special __context__ local that acts somewhat like
|
paul@763 | 194 | the self parameter in methods: in the invoked function, __context__ provides
|
paul@763 | 195 | access to any default information that needs to be transferred to the local
|
paul@756 | 196 | namespace.
|
paul@756 | 197 |
|
paul@761 | 198 | Function defaults that do not require a dynamic function are initialised using
|
paul@761 | 199 | the setdefaults special function as follows:
|
paul@761 | 200 |
|
paul@761 | 201 | class C:
|
paul@761 | 202 | const = ...
|
paul@761 | 203 | def f(a, b=const):
|
paul@761 | 204 | ...
|
paul@761 | 205 |
|
paul@761 | 206 | ...
|
paul@761 | 207 | setdefaults(module.f, module.C.const)
|
paul@761 | 208 |
|
paul@761 | 209 | Where multiple definitions of a name are involved, setdefaults may also be
|
paul@761 | 210 | used in a store operation and can be assumed to return a reference to the
|
paul@761 | 211 | function whose defaults are being set.
|
paul@761 | 212 |
|
paul@636 | 213 | Imports
|
paul@636 | 214 | -------
|
paul@636 | 215 |
|
paul@627 | 216 | Imports act as invocations of module code and name assignments within a
|
paul@627 | 217 | particular scope and are defined as follows:
|
paul@627 | 218 |
|
paul@627 | 219 | # import package
|
paul@627 | 220 | package.__main__()
|
paul@670 | 221 | storelocal(package, static(package))
|
paul@627 | 222 |
|
paul@627 | 223 | # import package.module
|
paul@627 | 224 | package.__main__()
|
paul@627 | 225 | package.module.__main__()
|
paul@670 | 226 | storelocal(package, static(package))
|
paul@627 | 227 |
|
paul@627 | 228 | # from package.module import cls
|
paul@627 | 229 | package.__main__()
|
paul@627 | 230 | package.module.__main__()
|
paul@670 | 231 | storelocal(cls, loadattribute(package.module, cls)) # see below
|
paul@627 | 232 |
|
paul@627 | 233 | Since import statements can appear in code that may be executed more than
|
paul@627 | 234 | once, __main__ functions should test and set a flag indicating whether the
|
paul@627 | 235 | function has already been called.
|
paul@627 | 236 |
|
paul@627 | 237 | Python would arguably be more sensible as a language if imports were
|
paul@627 | 238 | processed separately, but this would then rule out logic controlling the
|
paul@627 | 239 | use of modules.
|
paul@627 | 240 |
|
paul@636 | 241 | Name and Attribute Declarations
|
paul@636 | 242 | -------------------------------
|
paul@636 | 243 |
|
paul@629 | 244 | Assignments and name usage involve locals and globals but usage is declared
|
paul@629 | 245 | explicitly:
|
paul@627 | 246 |
|
paul@670 | 247 | localnames(...)
|
paul@627 | 248 |
|
paul@627 | 249 | At the function level, locals are genuine local name definitions whereas
|
paul@627 | 250 | globals refer to module globals:
|
paul@627 | 251 |
|
paul@670 | 252 | globalnames(...)
|
paul@627 | 253 |
|
paul@670 | 254 | At the module level, locals are effectively equivalent to module globals and
|
paul@670 | 255 | are declared as such.
|
paul@629 | 256 |
|
paul@629 | 257 | Each module's __main__ function will declare any referenced module globals as
|
paul@629 | 258 | globals. Note that the __main__ function is not a genuine attribute of any
|
paul@629 | 259 | module but an internal construct used to initialise modules appropriately.
|
paul@627 | 260 |
|
paul@627 | 261 | Such declarations must appear first in a program unit (module, function).
|
paul@627 | 262 | For example:
|
paul@627 | 263 |
|
paul@627 | 264 | def f(a, b):
|
paul@670 | 265 | localnames(a, b, x, y)
|
paul@670 | 266 | globalnames(f, g)
|
paul@627 | 267 |
|
paul@670 | 268 | storelocal(x, 1)
|
paul@670 | 269 | storelocal(y, x)
|
paul@670 | 270 | storelocal(a, b)
|
paul@670 | 271 | storeattr(module, g, f)
|
paul@627 | 272 |
|
paul@734 | 273 | Assignments
|
paul@734 | 274 | -----------
|
paul@734 | 275 |
|
paul@734 | 276 | Since assignments can rebind names used in the value expression, the evaluated
|
paul@734 | 277 | expression must be captured and referenced when setting the targets. This is
|
paul@734 | 278 | done using the special $expr variable, and so the swap assignment...
|
paul@734 | 279 |
|
paul@734 | 280 | a, b = b, a
|
paul@734 | 281 |
|
paul@734 | 282 | ...would be written (more or less) as...
|
paul@734 | 283 |
|
paul@734 | 284 | $expr = (b, a)
|
paul@734 | 285 | storelocal(a, apply(operator.getitem, $expr, 0))
|
paul@734 | 286 | storelocal(b, apply(operator.getitem, $expr, 1))
|
paul@734 | 287 |
|
paul@636 | 288 | Names and Attributes
|
paul@636 | 289 | --------------------
|
paul@636 | 290 |
|
paul@670 | 291 | Bare names refer to locals or globals according to the localnames and
|
paul@670 | 292 | globalnames declarations, or to constants such as None, True, False and
|
paul@638 | 293 | NotImplemented. Storage of local or global names is done using explicit
|
paul@638 | 294 | functions as follows:
|
paul@638 | 295 |
|
paul@670 | 296 | storelocal(name, value)
|
paul@670 | 297 | storeattr(module, name, value) # see below
|
paul@638 | 298 |
|
paul@627 | 299 | No operator usage: all operators are converted to invocations, including
|
paul@637 | 300 | all attribute access except static references to modules or particular class
|
paul@637 | 301 | or function definitions using the following notation:
|
paul@637 | 302 |
|
paul@670 | 303 | static(package)
|
paul@670 | 304 | static(package.module)
|
paul@670 | 305 | static(package.module.cls)
|
paul@670 | 306 | static(package.module.cls.function)
|
paul@627 | 307 |
|
paul@637 | 308 | A shorthand dot notation could be employed:
|
paul@637 | 309 |
|
paul@637 | 310 | package.module
|
paul@637 | 311 | package.module.cls
|
paul@637 | 312 | package.module.cls.function
|
paul@637 | 313 |
|
paul@637 | 314 | Where multiple definitions of static objects occur, the dot notation cannot be
|
paul@637 | 315 | used, and the full name of such definitions must be quoted. For example:
|
paul@637 | 316 |
|
paul@670 | 317 | static("package.module.cls#1.function")
|
paul@627 | 318 |
|
paul@627 | 319 | In general, attribute access must use an explicit function indicating the
|
paul@627 | 320 | kind of access operation being performed. For example:
|
paul@627 | 321 |
|
paul@676 | 322 | # Instance-related operations:
|
paul@676 | 323 |
|
paul@677 | 324 | loadattr(obj, attrname) # preserve retrieved context
|
paul@676 | 325 |
|
paul@711 | 326 | # Constant attribute operations:
|
paul@711 | 327 |
|
paul@711 | 328 | static(value) # see above
|
paul@711 | 329 | loadconstant(value, obj) # replace context with obj
|
paul@711 | 330 |
|
paul@676 | 331 | # Static attribute operations:
|
paul@675 | 332 |
|
paul@677 | 333 | loadaddress(parent, attrname) # preserve retrieved context
|
paul@676 | 334 | loadaddresscontext(parent, attrname, obj) # replace context with obj
|
paul@676 | 335 | loadaddresscontextcond(parent, attrname, obj) # run-time context decision
|
paul@676 | 336 |
|
paul@676 | 337 | # Unoptimised operations:
|
paul@675 | 338 |
|
paul@677 | 339 | loadattrindex(obj, attrname) # preserve retrieved context
|
paul@676 | 340 | loadattrindexcontextcond(obj, attrname) # run-time context decision
|
paul@676 | 341 |
|
paul@676 | 342 | # Instance-related operations:
|
paul@676 | 343 |
|
paul@677 | 344 | storeattr(obj, attrname, value) # preserve context for value
|
paul@627 | 345 |
|
paul@676 | 346 | # Static attribute operations:
|
paul@676 | 347 |
|
paul@677 | 348 | storeaddress(parent, attrname, value) # preserve context for value
|
paul@676 | 349 | storeaddresscontext(parent, attrname, value, obj) # replace context with obj
|
paul@676 | 350 |
|
paul@676 | 351 | # Unoptimised operations:
|
paul@676 | 352 |
|
paul@677 | 353 | storeattrindex(obj, attrname, value) # preserve context for value
|
paul@627 | 354 |
|
paul@675 | 355 | Recall that for loadattrindex family functions, the location of the attribute
|
paul@675 | 356 | is obtained from the object table and the nature of the attribute is
|
paul@675 | 357 | determined from the stored context value.
|
paul@675 | 358 |
|
paul@638 | 359 | Temporary variables could employ similar functions:
|
paul@638 | 360 |
|
paul@670 | 361 | loadtemp(0)
|
paul@670 | 362 | storetemp(0, value)
|
paul@638 | 363 |
|
paul@636 | 364 | Operators and Invocations
|
paul@636 | 365 | -------------------------
|
paul@636 | 366 |
|
paul@627 | 367 | Conventional operators use the operator functions.
|
paul@627 | 368 |
|
paul@627 | 369 | Special operators could also use the operator functions (where available)
|
paul@627 | 370 | but might as well be supported directly:
|
paul@627 | 371 |
|
paul@627 | 372 | __is__(a, b)
|
paul@670 | 373 | __is_not__(a, b)
|
paul@627 | 374 |
|
paul@627 | 375 | Logical operators involving short-circuit evaluation could be represented
|
paul@627 | 376 | as function calls, but the evaluation semantics would be preserved:
|
paul@627 | 377 |
|
paul@627 | 378 | __and__(...) # returns the first non-true value or the final value
|
paul@627 | 379 | __not__(obj) # returns the inverse of the boolean interpretation of obj
|
paul@627 | 380 | __or__(...) # returns the first true value or the final value
|
paul@627 | 381 |
|
paul@627 | 382 | Comparisons could be rephrased in a verbose fashion:
|
paul@627 | 383 |
|
paul@627 | 384 | a < b < c becomes lt(a, b) and lt(b, c)
|
paul@627 | 385 | or __and__(lt(a, b), lt(b, c))
|
paul@627 | 386 |
|
paul@636 | 387 | Advanced Control-Flow
|
paul@636 | 388 | ---------------------
|
paul@636 | 389 |
|
paul@627 | 390 | Any statements requiring control-flow definition in terms of blocks must
|
paul@627 | 391 | be handled in the language as the notions of labels and blocks are not
|
paul@627 | 392 | introduced earlier apart from the special case of jumping to another
|
paul@627 | 393 | callable (described below).
|
paul@627 | 394 |
|
paul@627 | 395 | Special functions for low-level operations:
|
paul@627 | 396 |
|
paul@670 | 397 | check(obj, type)
|
paul@670 | 398 | jump(callable)
|
paul@627 | 399 |
|
paul@627 | 400 | Function/subroutine definition with entry points for checked and unchecked
|
paul@627 | 401 | parameters.
|
paul@627 | 402 |
|
paul@627 | 403 | def fn_checked(self, ...):
|
paul@670 | 404 | check(self, Type) # raises a TypeError if not isinstance(self, Type)
|
paul@670 | 405 | jump(fn_unchecked) # preserves the frame and return address
|
paul@627 | 406 |
|
paul@627 | 407 | def fn_unchecked(self, ...):
|
paul@627 | 408 | ...
|
paul@636 | 409 |
|
paul@670 | 410 | The jump function might also be used for inlining appropriate functions.
|
paul@644 | 411 |
|
paul@636 | 412 | Exceptions must also be handled in the language.
|
paul@644 | 413 |
|
paul@644 | 414 | Object Type Detection
|
paul@644 | 415 | ---------------------
|
paul@644 | 416 |
|
paul@644 | 417 | Occasionally, the type of an object (instance of a particular class, class,
|
paul@644 | 418 | and so on) needs to be determined at run-time:
|
paul@644 | 419 |
|
paul@670 | 420 | isclass(obj)
|