micropython

Annotated rsvplib.py

506:d5f5db3d3636
2012-05-18 Paul Boddie Added support for the inspection and generation of list comprehensions. Moved various common code generation routines into separate methods and adjusted the list and sequence population methods for wider re-use.
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