micropython

Annotated rsvplib.py

519:814bd122d84d
2012-06-04 Paul Boddie Updated the documentation to reflect class attribute assignment policies. Added tests to demonstrate class attribute rebinding.
paul@261 1
#!/usr/bin/env python
paul@261 2
paul@261 3
"""
paul@261 4
A native function library for a really simple virtual processor.
paul@450 5
NOTE: Ultimately, this should only implement only operations at the lowest
paul@450 6
NOTE: possible level, with generated native methods providing the functionality
paul@450 7
NOTE: instead.
paul@261 8
paul@401 9
Copyright (C) 2007, 2008, 2009, 2010, 2011 Paul Boddie <paul@boddie.org.uk>
paul@261 10
paul@261 11
This program is free software; you can redistribute it and/or modify it under
paul@261 12
the terms of the GNU General Public License as published by the Free Software
paul@261 13
Foundation; either version 3 of the License, or (at your option) any later
paul@261 14
version.
paul@261 15
paul@261 16
This program is distributed in the hope that it will be useful, but WITHOUT
paul@261 17
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@261 18
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@261 19
details.
paul@261 20
paul@261 21
You should have received a copy of the GNU General Public License along with
paul@261 22
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@261 23
"""
paul@261 24
paul@264 25
from micropython.program import DataValue
paul@261 26
import operator
paul@261 27
paul@261 28
class Library:
paul@261 29
paul@261 30
    "Native function implementations."
paul@261 31
paul@395 32
    # NOTE: These attributes need changing if the instance layout changes.
paul@395 33
paul@401 34
    instance_template_size = instance_data_offset = 1
paul@401 35
    instance_size = instance_template_size + 1
paul@408 36
    fragment_data_offset = 1
paul@395 37
paul@354 38
    def __init__(self, machine, constants):
paul@261 39
paul@261 40
        """
paul@354 41
        Initialise the library with the 'machine' and the 'constants' addresses
paul@354 42
        dictionary.
paul@261 43
        """
paul@261 44
paul@261 45
        self.machine = machine
paul@354 46
        self.constants = constants
paul@261 47
paul@261 48
        # Native class constants.
paul@261 49
paul@412 50
        self.int_class, self.int_instance = self._get_builtin_class_and_template("int")
paul@412 51
        self.list_class, self.list_instance = self._get_builtin_class_and_template("list")
paul@412 52
        self.index_error, self.index_error_instance = self._get_builtin_class_and_template("IndexError")
paul@412 53
        self.str_class, self.str_instance = self._get_builtin_class_and_template("basestring")
paul@412 54
        self.accessor_class, self.accessor_instance = self._get_builtin_class_and_template("_accessor")
paul@261 55
paul@261 56
        self.tuple_class = self.machine.tuple_class
paul@408 57
        self.tuple_instance = self.machine.tuple_instance
paul@412 58
paul@412 59
        self.attr_error_instance = self.machine.attr_error_instance
paul@261 60
        self.type_error_instance = self.machine.type_error_instance
paul@261 61
paul@412 62
    def _get_builtin_class_and_template(self, name):
paul@412 63
        cls = self.machine._get_class("__builtins__", name)
paul@412 64
        if cls is not None:
paul@412 65
            return cls.location, cls.instance_template_location
paul@412 66
        else:
paul@412 67
            return None, None
paul@412 68
paul@391 69
    def _check_index(self, pos, nelements):
paul@391 70
        return pos >= 0 and pos < nelements
paul@390 71
paul@431 72
    # Native functionality.
paul@261 73
paul@431 74
    def builtins_no_op(self):
paul@431 75
        pass
paul@261 76
paul@431 77
    def native_int_arithmetic_op(self, op):
paul@450 78
        left = self.machine.load_from_frame(0)
paul@450 79
        left_data = left.ref + self.instance_data_offset
paul@450 80
        right = self.machine.load_from_frame(1)
paul@450 81
        right_data = right.ref + self.instance_data_offset
paul@261 82
paul@261 83
        # Make a new object.
paul@261 84
paul@395 85
        addr = self.machine._MakeObject(self.instance_size, self.int_instance)
paul@261 86
paul@261 87
        # Store the result.
paul@261 88
        # NOTE: The data is considered ready to use.
paul@261 89
paul@395 90
        self.machine.save(addr + self.instance_data_offset, op(self.machine.load(left_data), self.machine.load(right_data)))
paul@261 91
paul@261 92
        # Return the new object.
paul@261 93
        # Introduce object as context for the new object.
paul@261 94
paul@455 95
        self.machine.LoadImmediate(addr, "working_context")
paul@455 96
        self.machine.LoadImmediate(addr, "working")
paul@261 97
paul@431 98
    def native_logical_op(self, op):
paul@450 99
        left = self.machine.load_from_frame(0)
paul@450 100
        left_data = left.ref + self.instance_data_offset
paul@450 101
        right = self.machine.load_from_frame(1)
paul@450 102
        right_data = right.ref + self.instance_data_offset
paul@262 103
paul@262 104
        # Test the data.
paul@262 105
        # NOTE: The data is considered ready to use.
paul@262 106
paul@262 107
        if op(self.machine.load(left_data), self.machine.load(right_data)):
paul@455 108
            self.machine.LoadImmediate(self.constants[True], "working_context")
paul@455 109
            self.machine.LoadImmediate(self.constants[True], "working")
paul@262 110
        else:
paul@455 111
            self.machine.LoadImmediate(self.constants[False], "working_context")
paul@455 112
            self.machine.LoadImmediate(self.constants[False], "working")
paul@262 113
paul@262 114
    # Operators.
paul@262 115
    # Although this takes a short-cut by using the operator module, testing is
paul@262 116
    # still performed on the operands to ensure that they qualify for these
paul@262 117
    # native operations.
paul@262 118
paul@431 119
    def native_int_add(self):
paul@431 120
        return self.native_int_arithmetic_op(operator.add)
paul@262 121
paul@431 122
    def native_int_sub(self):
paul@431 123
        return self.native_int_arithmetic_op(operator.sub)
paul@262 124
paul@431 125
    def native_int_pow(self):
paul@431 126
        return self.native_int_arithmetic_op(operator.pow)
paul@262 127
paul@431 128
    def native_int_and(self):
paul@431 129
        return self.native_int_arithmetic_op(operator.and_)
paul@262 130
paul@431 131
    def native_int_or(self):
paul@431 132
        return self.native_int_arithmetic_op(operator.or_)
paul@262 133
paul@431 134
    def native_int_lt(self):
paul@431 135
        return self.native_logical_op(operator.lt)
paul@262 136
paul@431 137
    def native_int_gt(self):
paul@431 138
        return self.native_logical_op(operator.gt)
paul@262 139
paul@431 140
    def native_int_eq(self):
paul@431 141
        return self.native_logical_op(operator.eq)
paul@262 142
paul@431 143
    def native_str_lt(self):
paul@431 144
        return self.native_logical_op(operator.lt)
paul@262 145
paul@431 146
    def native_str_gt(self):
paul@431 147
        return self.native_logical_op(operator.gt)
paul@262 148
paul@431 149
    def native_str_eq(self):
paul@431 150
        return self.native_logical_op(operator.eq)
paul@262 151
paul@262 152
    # Specific operator methods.
paul@262 153
paul@261 154
    def builtins_int_neg(self):
paul@450 155
        left = self.machine.load_from_frame(0)
paul@450 156
        left_data = left.ref + self.instance_data_offset
paul@261 157
paul@261 158
        # Make a new object.
paul@261 159
paul@395 160
        addr = self.machine._MakeObject(self.instance_size, self.int_instance)
paul@261 161
paul@261 162
        # Store the result.
paul@261 163
        # NOTE: The data is considered ready to use.
paul@261 164
paul@395 165
        self.machine.save(addr + self.instance_data_offset, -self.machine.load(left_data))
paul@261 166
paul@261 167
        # Return the new object.
paul@261 168
        # Introduce object as context for the new object.
paul@261 169
paul@455 170
        self.machine.LoadImmediate(addr, "working_context")
paul@455 171
        self.machine.LoadImmediate(addr, "working")
paul@261 172
paul@262 173
    # Various built-in methods.
paul@261 174
paul@261 175
    def builtins_list_new(self):
paul@450 176
        list_value = self.machine.load_from_frame(0)
paul@450 177
        list_addr = list_value.ref + self.instance_data_offset
paul@261 178
paul@332 179
        # Make a new sequence.
paul@332 180
        # NOTE: Using an arbitrary size.
paul@261 181
paul@408 182
        new_fragment = self.machine._MakeFragment(self.fragment_data_offset, 5) # include the header
paul@261 183
paul@332 184
        # Complete the list instance by saving the fragment reference.
paul@336 185
        # NOTE: This requires an attribute in the list structure.
paul@261 186
paul@450 187
        self.machine.save(list_addr, DataValue(None, new_fragment))
paul@261 188
paul@390 189
    def builtins_list_get_single_item(self):
paul@450 190
        obj_value = self.machine.load_from_frame(0)
paul@450 191
        fragment_member = obj_value.ref + self.instance_data_offset
paul@450 192
        item_value = self.machine.load_from_frame(1)
paul@450 193
        item_pos_member = item_value.ref + self.instance_data_offset
paul@261 194
paul@261 195
        # Get the fragment address.
paul@261 196
paul@450 197
        fragment = self.machine.load(fragment_member)
paul@261 198
paul@261 199
        # Get the fragment header.
paul@261 200
paul@264 201
        header = self.machine.load(fragment.ref)
paul@408 202
        nelements = header.occupied_size - self.fragment_data_offset
paul@261 203
paul@395 204
        # Get the item position.
paul@261 205
paul@450 206
        item_pos = self.machine.load(item_pos_member)
paul@261 207
paul@391 208
        if not self._check_index(item_pos, nelements):
paul@450 209
            self.machine.LoadImmediate(self.machine._MakeObject(self.instance_size, self.index_error_instance), "exception")
paul@261 210
            return self.machine.RaiseException()
paul@261 211
paul@395 212
        # Get the item itself.
paul@261 213
paul@429 214
        data = self.machine.load(fragment.ref + self.fragment_data_offset + item_pos)
paul@455 215
        self.machine.LoadImmediate(data.context, "working_context")
paul@455 216
        self.machine.LoadImmediate(data.ref, "working")
paul@261 217
paul@261 218
    def builtins_list_len(self):
paul@450 219
        obj_value = self.machine.load_from_frame(0)
paul@450 220
        fragment_member = obj_value.ref + self.instance_data_offset
paul@261 221
paul@261 222
        # Get the fragment address.
paul@261 223
paul@450 224
        fragment = self.machine.load(fragment_member)
paul@261 225
paul@261 226
        # Get the fragment header.
paul@261 227
paul@264 228
        header = self.machine.load(fragment.ref)
paul@408 229
        nelements = header.occupied_size - self.fragment_data_offset
paul@261 230
paul@261 231
        # Make a new object.
paul@261 232
paul@395 233
        addr = self.machine._MakeObject(self.instance_size, self.int_instance)
paul@261 234
paul@261 235
        # Store the result.
paul@261 236
        # NOTE: The data is considered ready to use.
paul@261 237
paul@395 238
        self.machine.save(addr + self.instance_data_offset, nelements)
paul@261 239
paul@261 240
        # Return the new object.
paul@261 241
        # Introduce object as context for the new object.
paul@261 242
paul@455 243
        self.machine.LoadImmediate(addr, "working_context")
paul@455 244
        self.machine.LoadImmediate(addr, "working")
paul@261 245
paul@261 246
    def builtins_list_append(self):
paul@450 247
        obj_value = self.machine.load_from_frame(0)
paul@450 248
        fragment_member = obj_value.ref + self.instance_data_offset
paul@450 249
        arg_value = self.machine.load_from_frame(1)
paul@261 250
paul@261 251
        # Get the fragment address.
paul@261 252
paul@450 253
        fragment = self.machine.load(fragment_member)
paul@261 254
paul@261 255
        # Get the fragment header.
paul@261 256
paul@264 257
        header = self.machine.load(fragment.ref)
paul@261 258
paul@261 259
        # Attempt to add the reference.
paul@261 260
paul@261 261
        if header.occupied_size < header.allocated_size:
paul@264 262
            self.machine.save(fragment.ref + header.occupied_size, arg_value)
paul@261 263
            header.occupied_size += 1
paul@261 264
        else:
paul@261 265
paul@261 266
            # Make a new fragment, maintaining more space than currently
paul@261 267
            # occupied in order to avoid reallocation.
paul@261 268
paul@337 269
            new_fragment = self.machine._MakeFragment(header.occupied_size + 1, header.occupied_size * 2)
paul@261 270
paul@261 271
            # Copy existing elements.
paul@261 272
paul@408 273
            for i in range(self.fragment_data_offset, header.occupied_size):
paul@264 274
                self.machine.save(new_fragment + i, self.machine.load(fragment.ref + i))
paul@261 275
paul@336 276
            self.machine.save(new_fragment + header.occupied_size, arg_value)
paul@261 277
paul@261 278
            # Set the new fragment in the object.
paul@261 279
            # NOTE: The old fragment could be deallocated.
paul@261 280
paul@450 281
            self.machine.save(fragment_member, DataValue(None, new_fragment))
paul@261 282
paul@422 283
    def builtins_tuple_new(self):
paul@422 284
paul@422 285
        # Get the sequence address.
paul@426 286
        # The first argument should be empty since this function acts as an
paul@426 287
        # instantiator, and in instantiators the first argument is reserved so
paul@426 288
        # that it can be filled in for the call to an initialiser without
paul@426 289
        # allocating a new frame.
paul@422 290
paul@450 291
        obj_value = self.machine.load_from_frame(1)
paul@422 292
        return self._builtins_tuple(obj_value)
paul@422 293
paul@408 294
    def builtins_tuple(self):
paul@408 295
paul@422 296
        # Get the sequence address.
paul@408 297
paul@450 298
        obj_value = self.machine.load_from_frame(0)
paul@422 299
        return self._builtins_tuple(obj_value)
paul@408 300
paul@422 301
    def _builtins_tuple(self, obj_value):
paul@450 302
        fragment_member = obj_value.ref + self.instance_data_offset
paul@422 303
paul@422 304
        if self.machine._CheckInstance(obj_value.ref, self.tuple_class):
paul@455 305
            self.machine.LoadImmediate(obj_value.context, "working_context")
paul@455 306
            self.machine.LoadImmediate(obj_value.ref, "working")
paul@422 307
            return
paul@422 308
paul@422 309
        # Reject non-list, non-tuple types.
paul@422 310
        # NOTE: This should probably accept any sequence.
paul@422 311
paul@422 312
        elif not self.machine._CheckInstance(obj_value.ref, self.list_class):
paul@450 313
            self.machine.LoadImmediate(self.machine._MakeObject(self.instance_size, self.type_error_instance), "exception")
paul@408 314
            return self.machine.RaiseException()
paul@408 315
paul@408 316
        # Get the fragment address.
paul@408 317
paul@450 318
        fragment = self.machine.load(fragment_member)
paul@408 319
paul@408 320
        # Get the fragment header.
paul@408 321
paul@408 322
        header = self.machine.load(fragment.ref)
paul@408 323
paul@408 324
        # Make a new object.
paul@408 325
paul@408 326
        addr = self.machine._MakeObject(self.instance_data_offset + header.occupied_size - self.fragment_data_offset, self.tuple_instance)
paul@408 327
paul@408 328
        # Copy the fragment contents into the tuple.
paul@408 329
        # NOTE: This might be done by repurposing the fragment in some situations.
paul@408 330
paul@408 331
        for i in range(self.fragment_data_offset, header.occupied_size):
paul@408 332
            self.machine.save(addr + self.instance_data_offset + i - self.fragment_data_offset, self.machine.load(fragment.ref + i))
paul@408 333
paul@408 334
        # Return the new object.
paul@408 335
        # Introduce object as context for the new object.
paul@408 336
paul@455 337
        self.machine.LoadImmediate(addr, "working_context")
paul@455 338
        self.machine.LoadImmediate(addr, "working")
paul@408 339
paul@261 340
    def builtins_tuple_len(self):
paul@450 341
        obj_value = self.machine.load_from_frame(0)
paul@261 342
paul@261 343
        # Get the header.
paul@261 344
paul@264 345
        header = self.machine.load(obj_value.ref)
paul@395 346
        nelements = header.size - self.instance_data_offset
paul@261 347
paul@261 348
        # Make a new object.
paul@261 349
paul@395 350
        addr = self.machine._MakeObject(self.instance_size, self.int_instance)
paul@261 351
paul@261 352
        # Store the result.
paul@261 353
        # NOTE: The data is considered ready to use.
paul@261 354
paul@395 355
        self.machine.save(addr + self.instance_data_offset, nelements)
paul@261 356
paul@261 357
        # Return the new object.
paul@261 358
        # Introduce object as context for the new object.
paul@261 359
paul@455 360
        self.machine.LoadImmediate(addr, "working_context")
paul@455 361
        self.machine.LoadImmediate(addr, "working")
paul@261 362
paul@390 363
    def builtins_tuple_get_single_item(self):
paul@450 364
        obj_value = self.machine.load_from_frame(0)
paul@450 365
        fragment_member = obj_value.ref + self.instance_data_offset
paul@450 366
        item_value = self.machine.load_from_frame(1)
paul@450 367
        item_pos_member = item_value.ref + self.instance_data_offset
paul@261 368
paul@261 369
        # Get the header.
paul@261 370
paul@264 371
        header = self.machine.load(obj_value.ref)
paul@395 372
        nelements = header.size - self.instance_data_offset
paul@261 373
paul@261 374
        # NOTE: Assume single location for data and header.
paul@261 375
paul@450 376
        item_pos = self.machine.load(item_pos_member)
paul@261 377
paul@391 378
        if not self._check_index(item_pos, nelements):
paul@450 379
            self.machine.LoadImmediate(self.machine._MakeObject(self.instance_size, self.index_error_instance), "exception")
paul@261 380
            return self.machine.RaiseException()
paul@261 381
paul@395 382
        # Get the item.
paul@261 383
paul@450 384
        data = self.machine.load(fragment_member + item_pos)
paul@455 385
        self.machine.LoadImmediate(data.context, "working_context")
paul@455 386
        self.machine.LoadImmediate(data.ref, "working")
paul@261 387
paul@412 388
    def builtins_getattr(self):
paul@450 389
        obj_value = self.machine.load_from_frame(0)
paul@450 390
        name_value = self.machine.load_from_frame(1)
paul@450 391
        index_member = name_value.ref + self.instance_data_offset + 1
paul@412 392
paul@412 393
        if not self.machine._CheckInstance(name_value.ref, self.accessor_class):
paul@450 394
            self.machine.LoadImmediate(self.machine._MakeObject(self.instance_size, self.attr_error_instance), "exception")
paul@412 395
            return self.machine.RaiseException()
paul@412 396
paul@412 397
        # Get the object table index from the name. It is a bare integer, not a reference.
paul@412 398
paul@450 399
        index = self.machine.load(index_member)
paul@412 400
paul@416 401
        # NOTE: This is very much like LoadAttrIndexContextCond.
paul@412 402
paul@412 403
        data = self.machine.load(obj_value.ref)
paul@460 404
        element = self.machine.load(self.machine.registers["objlist"] + data.classcode + index)
paul@412 405
paul@412 406
        if element is not None:
paul@412 407
            attr_index, static_attr, offset = element
paul@412 408
            if attr_index == index:
paul@412 409
                if static_attr:
paul@429 410
                    loaded_data = self.machine.load(offset) # offset is address of class/module attribute
paul@429 411
                    if data.attrcode is not None: # absent attrcode == class/module
paul@429 412
                        loaded_data = self.machine._LoadAddressContextCond(loaded_data.context, loaded_data.ref, obj_value.ref)
paul@412 413
                else:
paul@429 414
                    loaded_data = self.machine.load(obj_value.ref + offset)
paul@455 415
                self.machine.LoadImmediate(loaded_data.context, "working_context")
paul@455 416
                self.machine.LoadImmediate(loaded_data.ref, "working")
paul@412 417
                return
paul@412 418
paul@450 419
        self.machine.LoadImmediate(self.machine._MakeObject(self.instance_size, self.attr_error_instance), "exception")
paul@412 420
        return self.machine.RaiseException()
paul@412 421
paul@377 422
    def builtins_isinstance(self):
paul@450 423
        obj_value = self.machine.load_from_frame(0)
paul@450 424
        cls_value = self.machine.load_from_frame(1)
paul@377 425
paul@377 426
        if self.machine._CheckInstance(obj_value.ref, cls_value.ref):
paul@455 427
            self.machine.LoadImmediate(self.constants[True], "working_context")
paul@455 428
            self.machine.LoadImmediate(self.constants[True], "working")
paul@377 429
        else:
paul@455 430
            self.machine.LoadImmediate(self.constants[False], "working_context")
paul@455 431
            self.machine.LoadImmediate(self.constants[False], "working")
paul@377 432
paul@402 433
    def builtins_print(self):
paul@402 434
        # NOTE: Do nothing for now.
paul@402 435
        pass
paul@402 436
paul@402 437
    def builtins_printnl(self):
paul@402 438
        # NOTE: Do nothing for now.
paul@402 439
        pass
paul@402 440
paul@261 441
    native_functions = {
paul@261 442
paul@261 443
        # Native method implementations:
paul@261 444
paul@450 445
        "native._int_add"                           : native_int_add,
paul@450 446
        "native._int_sub"                           : native_int_sub,
paul@450 447
        "native._int_pow"                           : native_int_pow,
paul@450 448
        "native._int_and"                           : native_int_and,
paul@450 449
        "native._int_or"                            : native_int_or,
paul@450 450
        "native._int_lt"                            : native_int_lt,
paul@450 451
        "native._int_gt"                            : native_int_gt,
paul@450 452
        "native._int_eq"                            : native_int_eq,
paul@450 453
        "native._str_lt"                            : native_str_lt,
paul@450 454
        "native._str_gt"                            : native_str_gt,
paul@450 455
        "native._str_eq"                            : native_str_eq,
paul@450 456
        "__builtins__.int.__neg__"                  : builtins_int_neg,
paul@450 457
        "__builtins__.list.__get_single_item__"     : builtins_list_get_single_item,
paul@450 458
        "__builtins__.list.__len__"                 : builtins_list_len,
paul@450 459
        "__builtins__.list.append"                  : builtins_list_append,
paul@450 460
        "__builtins__.tuple"                        : builtins_tuple_new,
paul@450 461
        "__builtins__.tuple.__len__"                : builtins_tuple_len,
paul@450 462
        "__builtins__.tuple.__get_single_item__"    : builtins_tuple_get_single_item,
paul@261 463
paul@261 464
        # Native initialisers:
paul@261 465
paul@450 466
        "__builtins__.BaseException.__init__"       : builtins_no_op, # NOTE: To be made distinct, potentially in the builtins module.
paul@261 467
paul@412 468
        # Native functions:
paul@412 469
paul@450 470
        "__builtins__._getattr"                     : builtins_getattr,
paul@412 471
paul@332 472
        # Native instantiator helpers:
paul@261 473
paul@450 474
        "__builtins__.list.__new__"                 : builtins_list_new,
paul@377 475
paul@377 476
        # Native helper functions:
paul@377 477
paul@450 478
        "__builtins__._isinstance"                  : builtins_isinstance,
paul@450 479
        "__builtins__._print"                       : builtins_print,
paul@450 480
        "__builtins__._printnl"                     : builtins_printnl,
paul@450 481
        "__builtins__._tuple"                       : builtins_tuple,
paul@261 482
        }
paul@261 483
paul@261 484
# vim: tabstop=4 expandtab shiftwidth=4