micropython

Annotated rsvplib.py

440:cf329206154e
2011-07-03 Paul Boddie Introduced constant usage tracking.
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@261 5
paul@401 6
Copyright (C) 2007, 2008, 2009, 2010, 2011 Paul Boddie <paul@boddie.org.uk>
paul@261 7
paul@261 8
This program is free software; you can redistribute it and/or modify it under
paul@261 9
the terms of the GNU General Public License as published by the Free Software
paul@261 10
Foundation; either version 3 of the License, or (at your option) any later
paul@261 11
version.
paul@261 12
paul@261 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@261 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@261 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@261 16
details.
paul@261 17
paul@261 18
You should have received a copy of the GNU General Public License along with
paul@261 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@261 20
"""
paul@261 21
paul@264 22
from micropython.program import DataValue
paul@261 23
import operator
paul@261 24
paul@261 25
class Library:
paul@261 26
paul@261 27
    "Native function implementations."
paul@261 28
paul@395 29
    # NOTE: These attributes need changing if the instance layout changes.
paul@395 30
paul@401 31
    instance_template_size = instance_data_offset = 1
paul@401 32
    instance_size = instance_template_size + 1
paul@408 33
    fragment_data_offset = 1
paul@395 34
paul@354 35
    def __init__(self, machine, constants):
paul@261 36
paul@261 37
        """
paul@354 38
        Initialise the library with the 'machine' and the 'constants' addresses
paul@354 39
        dictionary.
paul@261 40
        """
paul@261 41
paul@261 42
        self.machine = machine
paul@354 43
        self.constants = constants
paul@261 44
paul@261 45
        # Native class constants.
paul@261 46
paul@412 47
        self.int_class, self.int_instance = self._get_builtin_class_and_template("int")
paul@412 48
        self.list_class, self.list_instance = self._get_builtin_class_and_template("list")
paul@412 49
        self.index_error, self.index_error_instance = self._get_builtin_class_and_template("IndexError")
paul@412 50
        self.str_class, self.str_instance = self._get_builtin_class_and_template("basestring")
paul@412 51
        self.accessor_class, self.accessor_instance = self._get_builtin_class_and_template("_accessor")
paul@261 52
paul@261 53
        self.tuple_class = self.machine.tuple_class
paul@408 54
        self.tuple_instance = self.machine.tuple_instance
paul@412 55
paul@412 56
        self.attr_error_instance = self.machine.attr_error_instance
paul@261 57
        self.type_error_instance = self.machine.type_error_instance
paul@261 58
paul@261 59
        self.frame_stack = self.machine.frame_stack
paul@261 60
        self.local_sp_stack = self.machine.local_sp_stack
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@431 78
        self.machine.LoadName(0) # left value
paul@431 79
        left_data = self.machine.value + self.instance_data_offset
paul@431 80
        self.machine.LoadName(1) # right value
paul@431 81
        right_data = self.machine.value + 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@429 95
        self.machine.result_context = addr
paul@429 96
        self.machine.result_value = addr
paul@261 97
paul@431 98
    def native_logical_op(self, op):
paul@431 99
        self.machine.LoadName(0) # left value
paul@431 100
        left_data = self.machine.value + self.instance_data_offset
paul@431 101
        self.machine.LoadName(1) # right value
paul@431 102
        right_data = self.machine.value + 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@429 108
            self.machine.result_context = self.constants[True]
paul@429 109
            self.machine.result_value = self.constants[True]
paul@262 110
        else:
paul@429 111
            self.machine.result_context = self.constants[False]
paul@429 112
            self.machine.result_value = self.constants[False]
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@431 155
        self.machine.LoadName(0) # left value
paul@431 156
        left_data = self.machine.value + 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@429 170
        self.machine.result_context = addr
paul@429 171
        self.machine.result_value = addr
paul@261 172
paul@262 173
    # Various built-in methods.
paul@261 174
paul@261 175
    def builtins_list_new(self):
paul@261 176
        frame = self.local_sp_stack[-1]
paul@261 177
paul@332 178
        # The first parameter should be the instance.
paul@261 179
paul@332 180
        list_value = self.frame_stack[frame]
paul@261 181
paul@332 182
        # Make a new sequence.
paul@332 183
        # NOTE: Using an arbitrary size.
paul@261 184
paul@408 185
        new_fragment = self.machine._MakeFragment(self.fragment_data_offset, 5) # include the header
paul@261 186
paul@332 187
        # Complete the list instance by saving the fragment reference.
paul@336 188
        # NOTE: This requires an attribute in the list structure.
paul@261 189
paul@395 190
        addr = list_value.ref + self.instance_data_offset
paul@332 191
        self.machine.save(addr, DataValue(None, new_fragment))
paul@261 192
paul@390 193
    def builtins_list_get_single_item(self):
paul@261 194
        frame = self.local_sp_stack[-1]
paul@261 195
paul@261 196
        # Get the operand address.
paul@261 197
paul@264 198
        item_value = self.frame_stack[frame + 1]
paul@261 199
paul@261 200
        # Get the list address.
paul@261 201
paul@264 202
        obj_value = self.frame_stack[frame]
paul@261 203
paul@261 204
        # Get the fragment address.
paul@261 205
paul@395 206
        fragment = self.machine.load(obj_value.ref + self.instance_data_offset)
paul@261 207
paul@261 208
        # Get the fragment header.
paul@261 209
paul@264 210
        header = self.machine.load(fragment.ref)
paul@408 211
        nelements = header.occupied_size - self.fragment_data_offset
paul@261 212
paul@395 213
        # Get the item position.
paul@261 214
paul@395 215
        item_pos = self.machine.load(item_value.ref + self.instance_data_offset)
paul@261 216
paul@391 217
        if not self._check_index(item_pos, nelements):
paul@395 218
            self.machine.exception = self.machine._MakeObject(self.instance_size, self.index_error_instance)
paul@261 219
            return self.machine.RaiseException()
paul@261 220
paul@395 221
        # Get the item itself.
paul@261 222
paul@429 223
        data = self.machine.load(fragment.ref + self.fragment_data_offset + item_pos)
paul@429 224
        self.machine.result_context = data.context
paul@429 225
        self.machine.result_value = data.ref
paul@261 226
paul@261 227
    def builtins_list_len(self):
paul@261 228
        frame = self.local_sp_stack[-1]
paul@261 229
paul@261 230
        # Get the list address.
paul@261 231
paul@264 232
        obj_value = self.frame_stack[frame]
paul@261 233
paul@261 234
        # Get the fragment address.
paul@261 235
paul@395 236
        fragment = self.machine.load(obj_value.ref + self.instance_data_offset)
paul@261 237
paul@261 238
        # Get the fragment header.
paul@261 239
paul@264 240
        header = self.machine.load(fragment.ref)
paul@408 241
        nelements = header.occupied_size - self.fragment_data_offset
paul@261 242
paul@261 243
        # Make a new object.
paul@261 244
paul@395 245
        addr = self.machine._MakeObject(self.instance_size, self.int_instance)
paul@261 246
paul@261 247
        # Store the result.
paul@261 248
        # NOTE: The data is considered ready to use.
paul@261 249
paul@395 250
        self.machine.save(addr + self.instance_data_offset, nelements)
paul@261 251
paul@261 252
        # Return the new object.
paul@261 253
        # Introduce object as context for the new object.
paul@261 254
paul@429 255
        self.machine.result_context = addr
paul@429 256
        self.machine.result_value = addr
paul@261 257
paul@261 258
    def builtins_list_append(self):
paul@261 259
        frame = self.local_sp_stack[-1]
paul@261 260
paul@261 261
        # Get operand address.
paul@261 262
paul@264 263
        arg_value = self.frame_stack[frame + 1]
paul@261 264
paul@261 265
        # Get the list address.
paul@261 266
paul@264 267
        obj_value = self.frame_stack[frame]
paul@261 268
paul@261 269
        # Get the fragment address.
paul@261 270
paul@395 271
        fragment = self.machine.load(obj_value.ref + self.instance_data_offset)
paul@261 272
paul@261 273
        # Get the fragment header.
paul@261 274
paul@264 275
        header = self.machine.load(fragment.ref)
paul@261 276
paul@261 277
        # Attempt to add the reference.
paul@261 278
paul@261 279
        if header.occupied_size < header.allocated_size:
paul@264 280
            self.machine.save(fragment.ref + header.occupied_size, arg_value)
paul@261 281
            header.occupied_size += 1
paul@261 282
        else:
paul@261 283
paul@261 284
            # Make a new fragment, maintaining more space than currently
paul@261 285
            # occupied in order to avoid reallocation.
paul@261 286
paul@337 287
            new_fragment = self.machine._MakeFragment(header.occupied_size + 1, header.occupied_size * 2)
paul@261 288
paul@261 289
            # Copy existing elements.
paul@261 290
paul@408 291
            for i in range(self.fragment_data_offset, header.occupied_size):
paul@264 292
                self.machine.save(new_fragment + i, self.machine.load(fragment.ref + i))
paul@261 293
paul@336 294
            self.machine.save(new_fragment + header.occupied_size, arg_value)
paul@261 295
paul@261 296
            # Set the new fragment in the object.
paul@261 297
            # NOTE: The old fragment could be deallocated.
paul@261 298
paul@395 299
            self.machine.save(obj_value.ref + self.instance_data_offset, DataValue(None, new_fragment))
paul@261 300
paul@422 301
    def builtins_tuple_new(self):
paul@422 302
        frame = self.local_sp_stack[-1]
paul@422 303
paul@422 304
        # Get the sequence address.
paul@426 305
        # The first argument should be empty since this function acts as an
paul@426 306
        # instantiator, and in instantiators the first argument is reserved so
paul@426 307
        # that it can be filled in for the call to an initialiser without
paul@426 308
        # allocating a new frame.
paul@422 309
paul@422 310
        obj_value = self.frame_stack[frame + 1]
paul@422 311
        return self._builtins_tuple(obj_value)
paul@422 312
paul@408 313
    def builtins_tuple(self):
paul@408 314
        frame = self.local_sp_stack[-1]
paul@408 315
paul@422 316
        # Get the sequence address.
paul@408 317
paul@408 318
        obj_value = self.frame_stack[frame]
paul@422 319
        return self._builtins_tuple(obj_value)
paul@408 320
paul@422 321
    def _builtins_tuple(self, obj_value):
paul@422 322
paul@422 323
        if self.machine._CheckInstance(obj_value.ref, self.tuple_class):
paul@429 324
            self.machine.result_context = obj_value.context
paul@429 325
            self.machine.result_value = obj_value.ref
paul@422 326
            return
paul@422 327
paul@422 328
        # Reject non-list, non-tuple types.
paul@422 329
        # NOTE: This should probably accept any sequence.
paul@422 330
paul@422 331
        elif not self.machine._CheckInstance(obj_value.ref, self.list_class):
paul@408 332
            self.machine.exception = self.machine._MakeObject(self.instance_size, self.type_error_instance)
paul@408 333
            return self.machine.RaiseException()
paul@408 334
paul@408 335
        # Get the fragment address.
paul@408 336
paul@408 337
        fragment = self.machine.load(obj_value.ref + self.instance_data_offset)
paul@408 338
paul@408 339
        # Get the fragment header.
paul@408 340
paul@408 341
        header = self.machine.load(fragment.ref)
paul@408 342
paul@408 343
        # Make a new object.
paul@408 344
paul@408 345
        addr = self.machine._MakeObject(self.instance_data_offset + header.occupied_size - self.fragment_data_offset, self.tuple_instance)
paul@408 346
paul@408 347
        # Copy the fragment contents into the tuple.
paul@408 348
        # NOTE: This might be done by repurposing the fragment in some situations.
paul@408 349
paul@408 350
        for i in range(self.fragment_data_offset, header.occupied_size):
paul@408 351
            self.machine.save(addr + self.instance_data_offset + i - self.fragment_data_offset, self.machine.load(fragment.ref + i))
paul@408 352
paul@408 353
        # Return the new object.
paul@408 354
        # Introduce object as context for the new object.
paul@408 355
paul@429 356
        self.machine.result_context = addr
paul@429 357
        self.machine.result_value = addr
paul@408 358
paul@261 359
    def builtins_tuple_len(self):
paul@261 360
        frame = self.local_sp_stack[-1]
paul@261 361
paul@261 362
        # Get the tuple address.
paul@261 363
paul@264 364
        obj_value = self.frame_stack[frame]
paul@261 365
paul@261 366
        # Get the header.
paul@261 367
paul@264 368
        header = self.machine.load(obj_value.ref)
paul@395 369
        nelements = header.size - self.instance_data_offset
paul@261 370
paul@261 371
        # Make a new object.
paul@261 372
paul@395 373
        addr = self.machine._MakeObject(self.instance_size, self.int_instance)
paul@261 374
paul@261 375
        # Store the result.
paul@261 376
        # NOTE: The data is considered ready to use.
paul@261 377
paul@395 378
        self.machine.save(addr + self.instance_data_offset, nelements)
paul@261 379
paul@261 380
        # Return the new object.
paul@261 381
        # Introduce object as context for the new object.
paul@261 382
paul@429 383
        self.machine.result_context = addr
paul@429 384
        self.machine.result_value = addr
paul@261 385
paul@390 386
    def builtins_tuple_get_single_item(self):
paul@261 387
        frame = self.local_sp_stack[-1]
paul@261 388
paul@261 389
        # Get the operand address.
paul@261 390
paul@264 391
        item_value = self.frame_stack[frame + 1]
paul@261 392
paul@261 393
        # Get the tuple address.
paul@261 394
paul@264 395
        obj_value = self.frame_stack[frame]
paul@261 396
paul@261 397
        # Get the header.
paul@261 398
paul@264 399
        header = self.machine.load(obj_value.ref)
paul@395 400
        nelements = header.size - self.instance_data_offset
paul@261 401
paul@261 402
        # NOTE: Assume single location for data and header.
paul@261 403
paul@395 404
        item_pos = self.machine.load(item_value.ref + self.instance_data_offset)
paul@261 405
paul@391 406
        if not self._check_index(item_pos, nelements):
paul@395 407
            self.machine.exception = self.machine._MakeObject(self.instance_size, self.index_error_instance)
paul@261 408
            return self.machine.RaiseException()
paul@261 409
paul@395 410
        # Get the item.
paul@261 411
paul@429 412
        data = self.machine.load(obj_value.ref + self.instance_data_offset + item_pos)
paul@429 413
        self.machine.result_context = data.context
paul@429 414
        self.machine.result_value = data.ref
paul@261 415
paul@412 416
    def builtins_getattr(self):
paul@412 417
        frame = self.local_sp_stack[-1]
paul@412 418
paul@412 419
        # Get the object, attribute name.
paul@412 420
paul@412 421
        obj_value = self.frame_stack[frame]
paul@412 422
        name_value = self.frame_stack[frame + 1]
paul@412 423
paul@412 424
        if not self.machine._CheckInstance(name_value.ref, self.accessor_class):
paul@412 425
            self.machine.exception = self.machine._MakeObject(self.instance_size, self.attr_error_instance)
paul@412 426
            return self.machine.RaiseException()
paul@412 427
paul@412 428
        # Get the object table index from the name. It is a bare integer, not a reference.
paul@412 429
paul@412 430
        index = self.machine.load(name_value.ref + self.instance_data_offset + 1)
paul@412 431
paul@416 432
        # NOTE: This is very much like LoadAttrIndexContextCond.
paul@412 433
paul@412 434
        data = self.machine.load(obj_value.ref)
paul@412 435
        element = self.machine.objlist[data.classcode + index]
paul@412 436
paul@412 437
        if element is not None:
paul@412 438
            attr_index, static_attr, offset = element
paul@412 439
            if attr_index == index:
paul@412 440
                if static_attr:
paul@429 441
                    loaded_data = self.machine.load(offset) # offset is address of class/module attribute
paul@429 442
                    if data.attrcode is not None: # absent attrcode == class/module
paul@429 443
                        loaded_data = self.machine._LoadAddressContextCond(loaded_data.context, loaded_data.ref, obj_value.ref)
paul@412 444
                else:
paul@429 445
                    loaded_data = self.machine.load(obj_value.ref + offset)
paul@429 446
                self.machine.result_context = loaded_data.context
paul@429 447
                self.machine.result_value = loaded_data.ref
paul@412 448
                return
paul@412 449
paul@412 450
        self.machine.exception = self.machine._MakeObject(self.instance_size, self.attr_error_instance)
paul@412 451
        return self.machine.RaiseException()
paul@412 452
paul@377 453
    def builtins_isinstance(self):
paul@377 454
        frame = self.local_sp_stack[-1]
paul@377 455
paul@377 456
        # Get the operand addresses.
paul@377 457
paul@377 458
        obj_value = self.frame_stack[frame]
paul@377 459
        cls_value = self.frame_stack[frame + 1]
paul@377 460
paul@377 461
        if self.machine._CheckInstance(obj_value.ref, cls_value.ref):
paul@429 462
            self.machine.result_context = self.constants[True]
paul@429 463
            self.machine.result_value = self.constants[True]
paul@377 464
        else:
paul@429 465
            self.machine.result_context = self.constants[False]
paul@429 466
            self.machine.result_value = self.constants[False]
paul@377 467
paul@402 468
    def builtins_print(self):
paul@402 469
        # NOTE: Do nothing for now.
paul@402 470
        pass
paul@402 471
paul@402 472
    def builtins_printnl(self):
paul@402 473
        # NOTE: Do nothing for now.
paul@402 474
        pass
paul@402 475
paul@261 476
    native_functions = {
paul@261 477
paul@261 478
        # Native method implementations:
paul@261 479
paul@431 480
        "native._int_add" : native_int_add,
paul@431 481
        "native._int_sub" : native_int_sub,
paul@431 482
        "native._int_pow" : native_int_pow,
paul@431 483
        "native._int_and" : native_int_and,
paul@431 484
        "native._int_or" : native_int_or,
paul@431 485
        "native._int_lt" : native_int_lt,
paul@431 486
        "native._int_gt" : native_int_gt,
paul@431 487
        "native._int_eq" : native_int_eq,
paul@431 488
        "native._str_lt" : native_str_lt,
paul@431 489
        "native._str_gt" : native_str_gt,
paul@431 490
        "native._str_eq" : native_str_eq,
paul@261 491
        "__builtins__.int.__neg__" : builtins_int_neg,
paul@390 492
        "__builtins__.list.__get_single_item__" : builtins_list_get_single_item,
paul@261 493
        "__builtins__.list.__len__" : builtins_list_len,
paul@261 494
        "__builtins__.list.append" : builtins_list_append,
paul@422 495
        "__builtins__.tuple" : builtins_tuple_new,
paul@261 496
        "__builtins__.tuple.__len__" : builtins_tuple_len,
paul@390 497
        "__builtins__.tuple.__get_single_item__" : builtins_tuple_get_single_item,
paul@261 498
paul@261 499
        # Native initialisers:
paul@261 500
paul@431 501
        "__builtins__.BaseException.__init__" : builtins_no_op, # NOTE: To be made distinct, potentially in the builtins module.
paul@261 502
paul@412 503
        # Native functions:
paul@412 504
paul@412 505
        "__builtins__._getattr" : builtins_getattr,
paul@412 506
paul@332 507
        # Native instantiator helpers:
paul@261 508
paul@332 509
        "__builtins__.list.__new__" : builtins_list_new,
paul@377 510
paul@377 511
        # Native helper functions:
paul@377 512
paul@377 513
        "__builtins__._isinstance" : builtins_isinstance,
paul@402 514
        "__builtins__._print" : builtins_print,
paul@402 515
        "__builtins__._printnl" : builtins_printnl,
paul@408 516
        "__builtins__._tuple" : builtins_tuple,
paul@261 517
        }
paul@261 518
paul@261 519
# vim: tabstop=4 expandtab shiftwidth=4