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@627 | 11 | Given that micropython has already deduced object and parameter details,
|
paul@627 | 12 | such information must be communicated in the systems programming language
|
paul@627 | 13 | so that the compiler does not have to deduce it again.
|
paul@627 | 14 |
|
paul@627 | 15 | Explicit constant declaration shall be done at the start of the main
|
paul@627 | 16 | module:
|
paul@627 | 17 |
|
paul@627 | 18 | __constants__(...)
|
paul@627 | 19 |
|
paul@627 | 20 | Explicit structure declaration is still performed using class statements,
|
paul@627 | 21 | but base classes are omitted and attributes are declared explicitly as
|
paul@627 | 22 | follows:
|
paul@627 | 23 |
|
paul@627 | 24 | class C:
|
paul@627 | 25 | __instattrs__(member...)
|
paul@627 | 26 | __classattrs__(member...)
|
paul@627 | 27 |
|
paul@627 | 28 | Other object table information, such as inherited class attributes and
|
paul@627 | 29 | class compatibility (to support isinstance) are also declared explicitly:
|
paul@627 | 30 |
|
paul@627 | 31 | __inherited__(superclass, member...)
|
paul@627 | 32 | __descendants__(class...)
|
paul@627 | 33 |
|
paul@627 | 34 | Other than function definitions, no other code statements shall appear in
|
paul@627 | 35 | class definitions; such statements will appear after classes have been
|
paul@627 | 36 | defined in a __main__ function collecting together all "loose"
|
paul@627 | 37 | (module-level) statements.
|
paul@627 | 38 |
|
paul@627 | 39 | Imports act as invocations of module code and name assignments within a
|
paul@627 | 40 | particular scope and are defined as follows:
|
paul@627 | 41 |
|
paul@627 | 42 | # import package
|
paul@627 | 43 | package.__main__()
|
paul@627 | 44 | package = __module__(package)
|
paul@627 | 45 |
|
paul@627 | 46 | # import package.module
|
paul@627 | 47 | package.__main__()
|
paul@627 | 48 | package.module.__main__()
|
paul@627 | 49 | package = __module__(package)
|
paul@627 | 50 |
|
paul@627 | 51 | # from package.module import cls
|
paul@627 | 52 | package.__main__()
|
paul@627 | 53 | package.module.__main__()
|
paul@627 | 54 | cls = __loadattribute__(package.module, cls) # see below
|
paul@627 | 55 |
|
paul@627 | 56 | Since import statements can appear in code that may be executed more than
|
paul@627 | 57 | once, __main__ functions should test and set a flag indicating whether the
|
paul@627 | 58 | function has already been called.
|
paul@627 | 59 |
|
paul@627 | 60 | Python would arguably be more sensible as a language if imports were
|
paul@627 | 61 | processed separately, but this would then rule out logic controlling the
|
paul@627 | 62 | use of modules.
|
paul@627 | 63 |
|
paul@627 | 64 | Assignments and name usage involves locals and globals but usage is
|
paul@627 | 65 | declared explicitly:
|
paul@627 | 66 |
|
paul@627 | 67 | __localnames__(...)
|
paul@627 | 68 |
|
paul@627 | 69 | At the function level, locals are genuine local name definitions whereas
|
paul@627 | 70 | globals refer to module globals:
|
paul@627 | 71 |
|
paul@627 | 72 | __globalnames__(...)
|
paul@627 | 73 |
|
paul@627 | 74 | Although at the module level, locals are effectively equivalent to module
|
paul@627 | 75 | globals, each module's __main__ function will declare them as globals.
|
paul@627 | 76 |
|
paul@627 | 77 | Such declarations must appear first in a program unit (module, function).
|
paul@627 | 78 | For example:
|
paul@627 | 79 |
|
paul@627 | 80 | def f(a, b):
|
paul@627 | 81 | __localnames__(a, b, x, y)
|
paul@627 | 82 | __globalnames__(f, g)
|
paul@627 | 83 |
|
paul@627 | 84 | x = 1 # local
|
paul@627 | 85 | y = x # locals
|
paul@627 | 86 | a = b # locals
|
paul@627 | 87 | g = f # globals
|
paul@627 | 88 |
|
paul@627 | 89 | No operator usage: all operators are converted to invocations, including
|
paul@627 | 90 | all attribute access except static references to modules using the
|
paul@627 | 91 | following notation:
|
paul@627 | 92 |
|
paul@627 | 93 | __module__(package)
|
paul@627 | 94 | __module__(package.module)
|
paul@627 | 95 | package.module # shorthand where dot notation is used
|
paul@627 | 96 |
|
paul@627 | 97 | In general, attribute access must use an explicit function indicating the
|
paul@627 | 98 | kind of access operation being performed. For example:
|
paul@627 | 99 |
|
paul@627 | 100 | __loadaddress__(obj, attrname)
|
paul@627 | 101 | __loadaddresscontext__(obj, attrname)
|
paul@627 | 102 | __loadaddresscontextcond__(obj, attrname)
|
paul@627 | 103 | __loadattr__(obj, attrname)
|
paul@627 | 104 | __loadattrindex__(obj, attrname)
|
paul@627 | 105 | __loadattrindexcontext__(obj, attrname)
|
paul@627 | 106 | __loadattrindexcontextcond__(obj, attrname)
|
paul@627 | 107 |
|
paul@627 | 108 | __storeaddress__(obj, attrname, value)
|
paul@627 | 109 | __storeaddresscontext__(obj, attrname, value)
|
paul@627 | 110 | __storeattr__(obj, attrname, value)
|
paul@627 | 111 | __storeattrindex__(obj, attrname, value)
|
paul@627 | 112 |
|
paul@627 | 113 | Conventional operators use the operator functions.
|
paul@627 | 114 |
|
paul@627 | 115 | Special operators could also use the operator functions (where available)
|
paul@627 | 116 | but might as well be supported directly:
|
paul@627 | 117 |
|
paul@627 | 118 | __is__(a, b)
|
paul@627 | 119 |
|
paul@627 | 120 | Logical operators involving short-circuit evaluation could be represented
|
paul@627 | 121 | as function calls, but the evaluation semantics would be preserved:
|
paul@627 | 122 |
|
paul@627 | 123 | __and__(...) # returns the first non-true value or the final value
|
paul@627 | 124 | __not__(obj) # returns the inverse of the boolean interpretation of obj
|
paul@627 | 125 | __or__(...) # returns the first true value or the final value
|
paul@627 | 126 |
|
paul@627 | 127 | Comparisons could be rephrased in a verbose fashion:
|
paul@627 | 128 |
|
paul@627 | 129 | a < b < c becomes lt(a, b) and lt(b, c)
|
paul@627 | 130 | or __and__(lt(a, b), lt(b, c))
|
paul@627 | 131 |
|
paul@627 | 132 | Any statements requiring control-flow definition in terms of blocks must
|
paul@627 | 133 | be handled in the language as the notions of labels and blocks are not
|
paul@627 | 134 | introduced earlier apart from the special case of jumping to another
|
paul@627 | 135 | callable (described below).
|
paul@627 | 136 |
|
paul@627 | 137 | Special functions for low-level operations:
|
paul@627 | 138 |
|
paul@627 | 139 | __check__(obj, type)
|
paul@627 | 140 | __jump__(callable)
|
paul@627 | 141 |
|
paul@627 | 142 | Function/subroutine definition with entry points for checked and unchecked
|
paul@627 | 143 | parameters.
|
paul@627 | 144 |
|
paul@627 | 145 | def fn_checked(self, ...):
|
paul@627 | 146 | __check__(self, Type) # raises a TypeError if not isinstance(self, Type)
|
paul@627 | 147 | __jump__(fn_unchecked) # preserves the frame and return address
|
paul@627 | 148 |
|
paul@627 | 149 | def fn_unchecked(self, ...):
|
paul@627 | 150 | ...
|