micropython

Annotated rsvp.py

438:b547786f38c7
2011-07-02 Paul Boddie Moved some code generation methods into a new Assembler class. Separated sequence element storage into a separate method which may form the basis of a native library routine.
paul@68 1
#!/usr/bin/env python
paul@68 2
paul@68 3
"""
paul@68 4
A really simple virtual processor employing a simple set of instructions which
paul@68 5
ignore low-level operations and merely concentrate on variable access, structure
paul@68 6
access, structure allocation and function invocations.
paul@68 7
paul@394 8
Copyright (C) 2007, 2008, 2009, 2010, 2011 Paul Boddie <paul@boddie.org.uk>
paul@68 9
paul@68 10
This program is free software; you can redistribute it and/or modify it under
paul@68 11
the terms of the GNU General Public License as published by the Free Software
paul@68 12
Foundation; either version 3 of the License, or (at your option) any later
paul@68 13
version.
paul@68 14
paul@68 15
This program is distributed in the hope that it will be useful, but WITHOUT
paul@68 16
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@68 17
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@68 18
details.
paul@68 19
paul@68 20
You should have received a copy of the GNU General Public License along with
paul@68 21
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@68 22
paul@68 23
--------
paul@68 24
paul@68 25
The execution model of the virtual processor involves the following things:
paul@68 26
paul@145 27
  * Memory                          contains constants, global variable
paul@145 28
                                    references and program code
paul@145 29
paul@145 30
  * PC (program counter) stack      contains the return address associated
paul@145 31
                                    with each function invocation
paul@145 32
paul@145 33
  * Frame stack                     contains invocation frames in use and in
paul@145 34
                                    preparation plus temporary storage
paul@145 35
paul@145 36
  * Local frame pointer stack       refers to the frames in the frame stack
paul@145 37
paul@117 38
  * Invocation frame pointer stack
paul@145 39
paul@117 40
  * Exception handler stack
paul@68 41
paul@145 42
  * Exception handler locals stack  refers to the state of the local frame
paul@145 43
                                    pointer stack
paul@145 44
paul@145 45
  * Exception handler PC stack      refers to the state of the PC stack
paul@68 46
paul@429 47
  * Registers: current context/value,
paul@429 48
               source context/value,
paul@429 49
               current result context/value,
paul@429 50
               current exception value,
paul@145 51
               boolean status value,
paul@145 52
               current callable
paul@68 53
"""
paul@68 54
paul@264 55
from micropython.program import DataValue, ReplaceableContext, PlaceholderContext, FragmentObject
paul@261 56
from rsvplib import Library
paul@182 57
paul@68 58
class IllegalInstruction(Exception):
paul@68 59
    pass
paul@68 60
paul@68 61
class IllegalAddress(Exception):
paul@180 62
    def __init__(self, address):
paul@180 63
        self.address = address
paul@180 64
    def __repr__(self):
paul@180 65
        return "IllegalAddress(%r)" % self.address
paul@180 66
    def __str__(self):
paul@180 67
        return repr(self)
paul@68 68
paul@68 69
class EmptyPCStack(Exception):
paul@68 70
    pass
paul@68 71
paul@92 72
class EmptyFrameStack(Exception):
paul@68 73
    pass
paul@68 74
paul@194 75
class BreakpointReached(Exception):
paul@194 76
    pass
paul@194 77
paul@68 78
class RSVPMachine:
paul@68 79
paul@68 80
    "A really simple virtual processor."
paul@68 81
paul@248 82
    def __init__(self, memory, objlist, paramlist, pc=None, debug=0, abort_upon_exception=0):
paul@68 83
paul@68 84
        """
paul@68 85
        Initialise the processor with a 'memory' (a list of values containing
paul@196 86
        instructions and data), the object and parameter lists 'objlist' and
paul@248 87
        'paramlist', and the optional program counter 'pc'.
paul@68 88
        """
paul@68 89
paul@68 90
        self.memory = memory
paul@202 91
        self._objlist = objlist
paul@202 92
        self._paramlist = paramlist
paul@181 93
        self.objlist = objlist.as_raw()
paul@181 94
        self.paramlist = paramlist.as_raw()
paul@248 95
        self.library = None
paul@196 96
paul@68 97
        self.pc = pc or 0
paul@68 98
        self.debug = debug
paul@246 99
        self.abort_upon_exception = abort_upon_exception
paul@313 100
paul@313 101
        # Profiling.
paul@313 102
paul@389 103
        self.coverage = [0] * len(self.memory)
paul@285 104
        self.counter = 0
paul@313 105
        self.cost = 0
paul@117 106
paul@117 107
        # Stacks.
paul@117 108
paul@68 109
        self.pc_stack = []
paul@68 110
        self.frame_stack = []
paul@143 111
        self.local_sp_stack = [0]
paul@117 112
        self.invocation_sp_stack = []
paul@398 113
paul@398 114
        # Exception handler stacks are used to reset the state of the main
paul@398 115
        # stacks.
paul@398 116
paul@145 117
        self.handler_stack = [len(self.memory) - 1] # final handler is the end of the code
paul@145 118
        self.handler_local_sp_stack = []
paul@398 119
        self.handler_invocation_sp_stack = []
paul@145 120
        self.handler_pc_stack = []
paul@117 121
paul@117 122
        # Registers.
paul@117 123
paul@429 124
        self.value = None
paul@429 125
        self.context = None
paul@429 126
paul@429 127
        self.source_value = None
paul@429 128
        self.source_context = None
paul@429 129
paul@429 130
        self.result_value = None
paul@429 131
        self.result_context = None
paul@429 132
paul@429 133
        self.exception = None
paul@429 134
paul@119 135
        self.instruction = None
paul@117 136
        self.status = None
paul@132 137
        self.callable = None
paul@68 138
paul@146 139
        # Constants.
paul@146 140
paul@221 141
        cls = self._get_class("__builtins__", "AttributeError")
paul@221 142
        self.attr_error = cls.location
paul@221 143
        self.attr_error_instance = cls.instance_template_location
paul@221 144
        cls = self._get_class("__builtins__", "TypeError")
paul@221 145
        self.type_error = cls.location
paul@221 146
        self.type_error_instance = cls.instance_template_location
paul@256 147
        cls = self._get_class("__builtins__", "tuple")
paul@255 148
        self.tuple_class = cls.location
paul@255 149
        self.tuple_instance = cls.instance_template_location
paul@181 150
paul@194 151
        # Debugging attributes.
paul@194 152
paul@194 153
        self.breakpoints = set()
paul@194 154
paul@221 155
    def _get_class(self, module, name):
paul@346 156
        attr = self._objlist.access(module, name)
paul@346 157
        if attr is not None:
paul@346 158
            return attr.get_value()
paul@346 159
        else:
paul@346 160
            return None
paul@221 161
paul@162 162
    # Debugging methods.
paul@162 163
paul@137 164
    def dump(self):
paul@138 165
        print "PC", self.pc, "->", self.load(self.pc)
paul@137 166
        print "PC stack", self.pc_stack
paul@401 167
        print "Frame stack:"
paul@401 168
        if self.local_sp_stack:
paul@401 169
            start = self.local_sp_stack[0]
paul@401 170
            for end in self.local_sp_stack[1:]:
paul@401 171
                print "  %2d" % start, self.frame_stack[start:end]
paul@401 172
                start = end
paul@401 173
            else:
paul@401 174
                print "  %2d" % start, self.frame_stack[start:]
paul@401 175
        print
paul@137 176
        print "Local stack pointers", self.local_sp_stack
paul@137 177
        print "Invocation stack pointers", self.invocation_sp_stack
paul@137 178
        print "Handler stack", self.handler_stack
paul@145 179
        print "Handler frame stack", self.handler_local_sp_stack
paul@145 180
        print "Handler PC stack", self.handler_pc_stack
paul@137 181
        print
paul@429 182
        print "Value", self.value
paul@429 183
        print "Context", self.context
paul@429 184
        print
paul@429 185
        print "Source value", self.source_value
paul@429 186
        print "Source context", self.source_context
paul@429 187
        print
paul@429 188
        print "Result value", self.result_value
paul@429 189
        print "Result context", self.result_context
paul@429 190
        print
paul@429 191
        print "Exception", self.exception
paul@429 192
        print
paul@137 193
        print "Instruction", self.instruction
paul@137 194
        print "Status", self.status
paul@137 195
        print "Callable", self.callable
paul@137 196
paul@399 197
    def show(self, start=None, end=None):
paul@399 198
        self.show_memory(self.memory[start:end], self.coverage[start:end], start or 0)
paul@162 199
paul@162 200
    def show_pc(self, run_in=10):
paul@162 201
        start = max(0, self.pc - run_in)
paul@162 202
        end = self.pc + run_in
paul@162 203
        memory = self.memory[start:end]
paul@389 204
        coverage = self.coverage[start:end]
paul@389 205
        self.show_memory(memory, coverage, start)
paul@162 206
paul@389 207
    def show_memory(self, memory, coverage, start):
paul@389 208
        for i, (c, x) in enumerate(map(None, coverage, memory)):
paul@162 209
            location = start + i
paul@162 210
            if location == self.pc:
paul@162 211
                print "->",
paul@381 212
            elif location in self.pc_stack:
paul@381 213
                print "..",
paul@162 214
            else:
paul@162 215
                print "  ",
paul@389 216
            print "%-5s %5d %r" % (c or "", location, x)
paul@162 217
paul@162 218
    def step(self, dump=0):
paul@138 219
        self.execute()
paul@162 220
        self.show_pc()
paul@162 221
        if dump:
paul@162 222
            self.dump()
paul@162 223
paul@194 224
    def set_break(self, location):
paul@194 225
        self.breakpoints.add(location)
paul@194 226
paul@399 227
    def up(self):
paul@399 228
        retaddr = self.pc_stack[-1]
paul@401 229
        self.set_break(retaddr)
paul@399 230
        self.run()
paul@399 231
paul@162 232
    # Internal operations.
paul@138 233
paul@68 234
    def load(self, address):
paul@68 235
paul@68 236
        "Return the value at the given 'address'."
paul@68 237
paul@68 238
        try:
paul@68 239
            return self.memory[address]
paul@68 240
        except IndexError:
paul@180 241
            raise IllegalAddress(address)
paul@180 242
        except TypeError:
paul@180 243
            raise IllegalAddress(address)
paul@68 244
paul@68 245
    def save(self, address, value):
paul@68 246
paul@68 247
        "Save to the given 'address' the specified 'value'."
paul@68 248
paul@68 249
        try:
paul@68 250
            self.memory[address] = value
paul@68 251
        except IndexError:
paul@180 252
            raise IllegalAddress(address)
paul@180 253
        except TypeError:
paul@180 254
            raise IllegalAddress(address)
paul@68 255
paul@68 256
    def new(self, size):
paul@68 257
paul@68 258
        """
paul@68 259
        Allocate space of the given 'size', returning the address of the space.
paul@68 260
        """
paul@68 261
paul@68 262
        addr = len(self.memory)
paul@68 263
        for i in range(0, size):
paul@68 264
            self.memory.append(None)
paul@68 265
        return addr
paul@68 266
paul@68 267
    def push_pc(self, data):
paul@68 268
paul@68 269
        "Push 'data' onto the PC stack."
paul@68 270
paul@68 271
        self.pc_stack.append(data)
paul@68 272
paul@68 273
    def pull_pc(self):
paul@68 274
paul@68 275
        "Pull a value from the PC stack and return it."
paul@68 276
paul@68 277
        try:
paul@68 278
            return self.pc_stack.pop()
paul@68 279
        except IndexError:
paul@68 280
            raise EmptyPCStack
paul@68 281
paul@90 282
    def run(self):
paul@68 283
paul@68 284
        "Execute code in the memory, starting from the current PC address."
paul@68 285
paul@228 286
        breakpoint = 0
paul@228 287
paul@68 288
        try:
paul@68 289
            while 1:
paul@90 290
                self.execute()
paul@68 291
        except EmptyPCStack:
paul@68 292
            pass
paul@228 293
        except BreakpointReached:
paul@228 294
            breakpoint = 1
paul@68 295
paul@221 296
        print "Execution terminated",
paul@221 297
        if self.exception is not None:
paul@222 298
            ref = self.exception
paul@399 299
            addr = self.load(ref + Library.instance_data_offset)
paul@221 300
            print "with exception:", self.load(ref)
paul@222 301
            print "At address %d: %r" % (addr, self.load(addr))
paul@228 302
        elif breakpoint:
paul@228 303
            print "with breakpoint."
paul@228 304
            print "At address", self.pc
paul@221 305
        else:
paul@221 306
            print "successfully."
paul@313 307
        print "After", self.counter, "instructions at cost", self.cost, "units."
paul@221 308
paul@228 309
    def test(self, module):
paul@228 310
paul@228 311
        """
paul@228 312
        Test the code in the memory by running the code and investigating the
paul@228 313
        contents of variables. Use 'module' to identify result variables.
paul@228 314
        """
paul@228 315
paul@228 316
        self.run()
paul@228 317
        success = 1
paul@228 318
paul@229 319
        if self.exception is None:
paul@229 320
            for name in module.keys():
paul@229 321
                if name.startswith("result"):
paul@229 322
                    label, expected = name.split("_")
paul@229 323
                    attr = module[name]
paul@228 324
paul@229 325
                    # NOTE: Assumptions about headers and content made.
paul@228 326
paul@229 327
                    attr_location = module.location + 1 + attr.position
paul@264 328
                    value = self.load(attr_location)
paul@228 329
paul@264 330
                    if value is not None:
paul@395 331
                        content = self.load(value.ref + Library.instance_data_offset)
paul@243 332
                        print label, expected, content
paul@243 333
                        success = success and (int(expected) == content)
paul@243 334
                    else:
paul@243 335
                        print label, expected, "missing"
paul@243 336
                        success = 0
paul@228 337
paul@229 338
            return success
paul@229 339
        else:
paul@229 340
            return 0
paul@228 341
paul@90 342
    def execute(self):
paul@90 343
paul@90 344
        "Execute code in the memory at the current PC address."
paul@90 345
paul@194 346
        if self.pc in self.breakpoints:
paul@194 347
            self.breakpoints.remove(self.pc)
paul@194 348
            raise BreakpointReached
paul@194 349
paul@119 350
        self.instruction = self.load(self.pc)
paul@118 351
paul@118 352
        # Process any inputs of the instruction.
paul@118 353
paul@119 354
        self.process_inputs()
paul@137 355
paul@137 356
        # Perform the instruction itself.
paul@137 357
paul@137 358
        next_pc = self.perform(self.instruction)
paul@118 359
paul@389 360
        # Update the coverage.
paul@389 361
paul@389 362
        self.coverage[self.pc] += 1
paul@389 363
paul@119 364
        # Update the program counter.
paul@118 365
paul@119 366
        if next_pc is None:
paul@119 367
            self.pc += 1
paul@119 368
        else:
paul@119 369
            self.pc = next_pc
paul@118 370
paul@137 371
    def get_method(self, instruction):
paul@137 372
paul@137 373
        "Return the handler method for the given 'instruction'."
paul@119 374
paul@137 375
        instruction_name = instruction.__class__.__name__
paul@137 376
        if self.debug:
paul@137 377
            print "%8d %s" % (self.pc, instruction_name)
paul@119 378
        method = getattr(self, instruction_name, None)
paul@90 379
        if method is None:
paul@119 380
            raise IllegalInstruction, (self.pc, instruction_name)
paul@118 381
        return method
paul@118 382
paul@313 383
    def perform(self, instruction, is_input=0):
paul@137 384
paul@137 385
        "Perform the 'instruction', returning the next PC value or None."
paul@137 386
paul@313 387
        if not is_input:
paul@313 388
            self.counter += 1
paul@313 389
            self.cost += instruction.cost
paul@430 390
        operand = instruction.get_operand()
paul@137 391
        method = self.get_method(instruction)
paul@430 392
        return method(operand)
paul@137 393
paul@119 394
    def process_inputs(self):
paul@118 395
paul@118 396
        """
paul@119 397
        Process any inputs of the current instruction. This permits any directly
paul@118 398
        connected sub-instructions to produce the effects that separate
paul@118 399
        instructions would otherwise have.
paul@118 400
        """
paul@118 401
paul@429 402
        value, context = self.value, self.context
paul@174 403
        if self.instruction.source is not None:
paul@313 404
            self.perform(self.instruction.source, 1)
paul@429 405
            self.source_value, self.source_context = self.value, self.context
paul@429 406
        self.value, self.context = value, context
paul@174 407
        if self.instruction.input is not None:
paul@313 408
            self.perform(self.instruction.input, 1)
paul@90 409
paul@68 410
    def jump(self, addr, next):
paul@68 411
paul@68 412
        """
paul@68 413
        Jump to the subroutine at (or identified by) 'addr'. If 'addr'
paul@68 414
        identifies a library function then invoke the library function and set
paul@68 415
        PC to 'next' afterwards; otherwise, set PC to 'addr'.
paul@68 416
        """
paul@68 417
paul@181 418
        # Trap library functions introduced through the use of strings instead
paul@181 419
        # of proper locations.
paul@181 420
paul@68 421
        if isinstance(addr, str):
paul@248 422
            handler = self.library and self.library.native_functions[addr](self.library)
paul@196 423
            if handler is None:
paul@196 424
                return next
paul@196 425
            else:
paul@196 426
                return handler
paul@68 427
        else:
paul@138 428
            self.push_pc(self.pc + 1)
paul@119 429
            return addr
paul@68 430
paul@429 431
    # Low-level instructions.
paul@429 432
paul@429 433
    def LoadValue(self, operand):
paul@429 434
        self.value = operand
paul@429 435
paul@429 436
    def LoadContext(self, operand):
paul@429 437
        self.context = operand
paul@429 438
paul@430 439
    def LoadSourceValue(self, operand):
paul@430 440
        self.source_value = operand
paul@430 441
paul@430 442
    def LoadSourceContext(self, operand):
paul@430 443
        self.source_context = operand
paul@430 444
paul@68 445
    # Instructions.
paul@68 446
paul@430 447
    def LoadConst(self, operand):
paul@430 448
        self.LoadContext(operand)
paul@430 449
        self.LoadValue(operand)
paul@223 450
paul@430 451
    def LoadClass(self, operand):
paul@429 452
        self.LoadContext(PlaceholderContext)
paul@430 453
        self.LoadValue(operand)
paul@237 454
paul@430 455
    def LoadFunction(self, operand):
paul@429 456
        self.LoadContext(ReplaceableContext)
paul@430 457
        self.LoadValue(operand)
paul@68 458
paul@430 459
    def LoadName(self, operand):
paul@117 460
        frame = self.local_sp_stack[-1]
paul@430 461
        data = self.frame_stack[frame + operand]
paul@429 462
        self.LoadContext(data.context)
paul@429 463
        self.LoadValue(data.ref)
paul@68 464
paul@430 465
    def StoreName(self, operand):
paul@117 466
        frame = self.local_sp_stack[-1]
paul@430 467
        self.frame_stack[frame + operand] = DataValue(self.source_context, self.source_value)
paul@68 468
paul@72 469
    LoadTemp = LoadName
paul@203 470
paul@430 471
    def StoreTemp(self, operand):
paul@203 472
        frame = self.local_sp_stack[-1]
paul@430 473
        self.frame_stack[frame + operand] = DataValue(self.context, self.value)
paul@72 474
paul@430 475
    def LoadAddress(self, operand):
paul@119 476
        # Preserve context (potentially null).
paul@430 477
        data = self.load(operand)
paul@429 478
        self.LoadContext(data.context)
paul@429 479
        self.LoadValue(data.ref)
paul@72 480
paul@430 481
    def LoadAddressContext(self, operand):
paul@429 482
        # Value becomes context.
paul@430 483
        data = self.load(operand)
paul@429 484
        self.LoadContext(self.value)
paul@429 485
        self.LoadValue(data.ref)
paul@194 486
paul@430 487
    def LoadAddressContextCond(self, operand):
paul@430 488
        data = self.load(operand)
paul@429 489
        data = self._LoadAddressContextCond(data.context, data.ref, self.value)
paul@429 490
        self.LoadContext(data.context)
paul@429 491
        self.LoadValue(data.ref)
paul@72 492
paul@430 493
    def StoreAddress(self, operand):
paul@119 494
        # Preserve context.
paul@430 495
        self.save(operand, DataValue(self.source_context, self.source_value))
paul@72 496
paul@430 497
    def StoreAddressContext(self, operand):
paul@223 498
        # Overwrite context if null.
paul@430 499
        self._StoreAddressContext(operand, self.context, self.value, self.source_context, self.source_value)
paul@223 500
paul@430 501
    def MakeInstance(self, size):
paul@184 502
        # NOTE: Referencing the instance template.
paul@429 503
        addr = self._MakeObject(size, self.value - Library.instance_template_size)
paul@184 504
        # Introduce object as context for the new object.
paul@429 505
        self.LoadContext(addr)
paul@429 506
        self.LoadValue(addr)
paul@72 507
paul@430 508
    def MakeFragment(self, size):
paul@336 509
        # Reserve twice the amount of space.
paul@336 510
        addr = self._MakeFragment(size, size * 2)
paul@246 511
        # NOTE: Context is not relevant for fragments.
paul@429 512
        self.LoadContext(None)
paul@429 513
        self.LoadValue(addr)
paul@246 514
paul@430 515
    def LoadAttr(self, pos):
paul@127 516
        # Retrieved context should already be appropriate for the instance.
paul@184 517
        # NOTE: Adding 1 to skip any header.
paul@430 518
        data = self.load(self.value + pos + 1)
paul@429 519
        self.LoadContext(data.context)
paul@429 520
        self.LoadValue(data.ref)
paul@118 521
paul@430 522
    def StoreAttr(self, pos):
paul@129 523
        # Target should already be an instance.
paul@184 524
        # NOTE: Adding 1 to skip any header.
paul@430 525
        self.save(self.value + pos + 1, DataValue(self.source_context, self.source_value))
paul@119 526
paul@430 527
    def LoadAttrIndex(self, index):
paul@429 528
        data = self.load(self.value)
paul@430 529
        element = self.objlist[data.classcode + index]
paul@238 530
paul@238 531
        if element is not None:
paul@242 532
            attr_index, static_attr, offset = element
paul@430 533
            if attr_index == index:
paul@242 534
                if static_attr:
paul@429 535
                    data = self.load(offset) # offset is address of class/module attribute
paul@238 536
                else:
paul@429 537
                    data = self.load(self.value + offset)
paul@429 538
                self.LoadContext(data.context)
paul@429 539
                self.LoadValue(data.ref)
paul@238 540
                return
paul@238 541
paul@399 542
        self.exception = self._MakeObject(Library.instance_size, self.attr_error_instance)
paul@238 543
        return self.RaiseException()
paul@118 544
paul@226 545
    # LoadAttrIndexContext not defined.
paul@194 546
paul@430 547
    def LoadAttrIndexContextCond(self, index):
paul@429 548
        data = self.load(self.value)
paul@430 549
        element = self.objlist[data.classcode + index]
paul@238 550
paul@238 551
        if element is not None:
paul@242 552
            attr_index, static_attr, offset = element
paul@430 553
            if attr_index == index:
paul@242 554
                if static_attr:
paul@429 555
                    loaded_data = self.load(offset) # offset is address of class/module attribute
paul@429 556
                    # Absent attrcode == class/module.
paul@429 557
                    if data.attrcode is not None:
paul@429 558
                        loaded_data = self._LoadAddressContextCond(loaded_data.context, loaded_data.ref, self.value)
paul@223 559
                else:
paul@429 560
                    loaded_data = self.load(self.value + offset)
paul@429 561
                self.LoadContext(loaded_data.context)
paul@429 562
                self.LoadValue(loaded_data.ref)
paul@238 563
                return
paul@238 564
paul@399 565
        self.exception = self._MakeObject(Library.instance_size, self.attr_error_instance)
paul@238 566
        return self.RaiseException()
paul@194 567
paul@430 568
    def StoreAttrIndex(self, index):
paul@429 569
        data = self.load(self.value)
paul@430 570
        element = self.objlist[data.classcode + index]
paul@238 571
paul@238 572
        if element is not None:
paul@242 573
            attr_index, static_attr, offset = element
paul@430 574
            if attr_index == index:
paul@242 575
                if static_attr:
paul@429 576
                    self._StoreAddressContext(offset, self.context, self.value, self.source_context, self.source_value)
paul@405 577
                    return
paul@238 578
                else:
paul@429 579
                    self.save(self.value + offset, DataValue(self.source_context, self.source_value))
paul@238 580
                    return
paul@238 581
paul@399 582
        self.exception = self._MakeObject(Library.instance_size, self.attr_error_instance)
paul@238 583
        return self.RaiseException()
paul@118 584
paul@127 585
    # NOTE: LoadAttrIndexContext is a possibility if a particular attribute can always be overridden.
paul@127 586
paul@430 587
    def MakeFrame(self, size):
paul@117 588
        self.invocation_sp_stack.append(len(self.frame_stack))
paul@430 589
        self.frame_stack.extend([None] * size)
paul@98 590
paul@430 591
    def DropFrame(self, operand=None):
paul@117 592
        self.local_sp_stack.pop()
paul@117 593
        frame = self.invocation_sp_stack.pop()
paul@248 594
        del self.frame_stack[frame:] # reset stack before call
paul@98 595
paul@430 596
    def StoreFrame(self, pos):
paul@129 597
        frame = self.invocation_sp_stack[-1] # different from the current frame after MakeFrame
paul@430 598
        self.frame_stack[frame + pos] = DataValue(self.context, self.value)
paul@129 599
paul@430 600
    def StoreFrameIndex(self, index):
paul@129 601
        frame = self.invocation_sp_stack[-1] # different from the current frame after MakeFrame
paul@429 602
        data = self.load(self.value)
paul@430 603
        element = self.paramlist[data.funccode + index]
paul@238 604
paul@238 605
        if element is not None:
paul@238 606
            # NOTE: Need to ensure correct positioning where a context has been generated.
paul@238 607
            param_index, offset = element
paul@430 608
            if param_index == index:
paul@429 609
                self.frame_stack[frame + offset] = DataValue(self.source_context, self.source_value)
paul@238 610
                return
paul@238 611
paul@399 612
        self.exception = self._MakeObject(Library.instance_size, self.type_error_instance)
paul@238 613
        return self.RaiseException()
paul@129 614
paul@430 615
    def LoadCallable(self, operand=None):
paul@429 616
        data = self.load(self.value)
paul@219 617
        self.callable = data.codeaddr
paul@132 618
paul@430 619
    def StoreCallable(self, operand=None):
paul@134 620
        # NOTE: Should improve the representation and permit direct saving.
paul@429 621
        data = self.load(self.value)
paul@429 622
        self.save(self.value, data.with_callable(self.callable))
paul@132 623
paul@430 624
    def LoadContextIntoValue(self, operand=None):
paul@218 625
        # NOTE: Omission of the context of the context would make things like
paul@218 626
        # NOTE: self() inside methods impossible.
paul@429 627
        self.LoadValue(self.context)
paul@132 628
paul@430 629
    def CheckContext(self, operand=None):
paul@429 630
        self.status = self.value is not ReplaceableContext
paul@222 631
paul@430 632
    def CheckClass(self, operand=None):
paul@429 633
        if self.value in (ReplaceableContext, PlaceholderContext):
paul@237 634
            self.status = 0
paul@237 635
            return
paul@237 636
paul@429 637
        data = self.load(self.value)
paul@222 638
paul@222 639
        # Classes are not themselves usable as the self argument.
paul@222 640
        # NOTE: This may change at some point.
paul@222 641
        # However, where classes appear as the context, instance
paul@222 642
        # compatibility is required in the first argument.
paul@222 643
paul@234 644
        self.status = data.attrcode is None # absent attrcode == class
paul@222 645
paul@430 646
    def CheckFrame(self, operand):
paul@430 647
        (nargs, ndefaults) = operand
paul@215 648
paul@215 649
        # The frame is actually installed as the locals.
paul@215 650
        # Retrieve the context from the first local.
paul@215 651
paul@215 652
        frame = self.local_sp_stack[-1]
paul@215 653
        nlocals = len(self.frame_stack[frame:])
paul@134 654
paul@234 655
        if not ((nargs - ndefaults) <= nlocals):
paul@430 656
            raise Exception, "CheckFrame %r (%r <= %r <= %r)" % (operand, nargs - ndefaults, nlocals, nargs)
paul@399 657
            self.exception = self._MakeObject(Library.instance_size, self.type_error_instance)
paul@221 658
            return self.RaiseException()
paul@215 659
paul@430 660
    def CheckExtra(self, nargs):
paul@256 661
paul@256 662
        # The frame is actually installed as the locals.
paul@256 663
        # Retrieve the context from the first local.
paul@256 664
paul@256 665
        frame = self.local_sp_stack[-1]
paul@256 666
        nlocals = len(self.frame_stack[frame:])
paul@256 667
paul@256 668
        # Provide the extra star parameter if necessary.
paul@256 669
paul@256 670
        if nlocals == nargs:
paul@256 671
            self.frame_stack.extend([None]) # ExtendFrame(1)
paul@256 672
paul@430 673
    def FillDefaults(self, operand):
paul@430 674
        (nargs, ndefaults) = operand
paul@215 675
paul@215 676
        # The frame is actually installed as the locals.
paul@215 677
paul@215 678
        frame = self.local_sp_stack[-1]
paul@215 679
        nlocals = len(self.frame_stack[frame:])
paul@134 680
paul@190 681
        # Support population of defaults.
paul@190 682
        # This involves copying the "attributes" of a function into the frame.
paul@190 683
paul@215 684
        default = nlocals - (nargs - ndefaults)
paul@215 685
        self.frame_stack.extend([None] * (nargs - nlocals))
paul@215 686
        pos = nlocals
paul@190 687
paul@215 688
        while pos < nargs:
paul@429 689
            self.frame_stack[frame + pos] = self.load(self.value + default + 1) # skip header
paul@190 690
            default += 1
paul@190 691
            pos += 1
paul@117 692
paul@430 693
    def CopyExtra(self, start):
paul@255 694
paul@255 695
        # The frame is the source of the extra arguments.
paul@255 696
paul@255 697
        frame = self.local_sp_stack[-1]
paul@255 698
        nlocals = len(self.frame_stack[frame:])
paul@255 699
paul@255 700
        # Make a tuple to hold the arguments.
paul@255 701
paul@255 702
        ref = self._MakeObject(nlocals - start + 1, self.tuple_instance)
paul@255 703
paul@255 704
        extra = 0
paul@255 705
        pos = start
paul@255 706
paul@255 707
        while pos < nlocals:
paul@255 708
            self.save(ref + extra + 1, self.frame_stack[frame + pos]) # skip header when storing
paul@255 709
            extra += 1
paul@255 710
            pos += 1
paul@255 711
paul@429 712
        self.LoadContext(ref)
paul@429 713
        self.LoadValue(ref)
paul@255 714
paul@430 715
    def CheckInstance(self, operand=None):
paul@285 716
        # For the 'self' parameter in an invoked function, the proposed context
paul@285 717
        # ('self') is checked against the target's context.
paul@429 718
        self.status = self._CheckInstance(self.value, self.source_value)
paul@308 719
paul@430 720
    def JumpInFrame(self, operand=None):
paul@230 721
        codeaddr = self.callable
paul@230 722
        return self.jump(codeaddr, self.pc + 1) # return to the instruction after this one
paul@230 723
paul@430 724
    def JumpWithFrame(self, operand=None):
paul@219 725
        codeaddr = self.callable
paul@132 726
        self.local_sp_stack.append(self.invocation_sp_stack[-1]) # adopt the invocation frame
paul@138 727
        return self.jump(codeaddr, self.pc + 1) # return to the instruction after this one
paul@98 728
paul@430 729
    def JumpWithFrameDirect(self, addr):
paul@215 730
        self.local_sp_stack.append(self.invocation_sp_stack[-1]) # adopt the invocation frame
paul@430 731
        return self.jump(addr, self.pc + 1) # return to the instruction after this one
paul@215 732
paul@430 733
    def ExtendFrame(self, size):
paul@430 734
        self.frame_stack.extend([None] * size)
paul@98 735
paul@430 736
    def AdjustFrame(self, size):
paul@430 737
        self.invocation_sp_stack[-1] += size
paul@137 738
paul@430 739
    def Return(self, operand=None):
paul@138 740
        return self.pull_pc()
paul@68 741
paul@430 742
    def LoadResultIntoValue(self, operand=None):
paul@429 743
        self.LoadContext(self.result_context)
paul@429 744
        self.LoadValue(self.result_value)
paul@117 745
paul@430 746
    def LoadValueIntoResult(self, operand=None):
paul@429 747
        self.result_context = self.context
paul@429 748
        self.result_value = self.value
paul@117 749
paul@430 750
    def Jump(self, addr):
paul@430 751
        return addr
paul@98 752
paul@430 753
    def JumpIfTrue(self, addr):
paul@117 754
        if self.status:
paul@430 755
            return addr
paul@68 756
paul@430 757
    def JumpIfFalse(self, addr):
paul@117 758
        if not self.status:
paul@430 759
            return addr
paul@68 760
paul@430 761
    def LoadException(self, operand=None):
paul@429 762
        self.LoadContext(self.exception)
paul@429 763
        self.LoadValue(self.exception)
paul@129 764
paul@430 765
    def StoreException(self, operand=None):
paul@429 766
        self.exception = self.value
paul@129 767
paul@430 768
    def ClearException(self, operand=None):
paul@232 769
        self.exception = None
paul@232 770
paul@430 771
    def RaiseException(self, operand=None):
paul@399 772
        # NOTE: Adding the program counter as the first attribute after __class__.
paul@401 773
        self.save(self.exception + Library.instance_data_offset, self.pc)
paul@221 774
        # Jumping to the current handler.
paul@246 775
        if self.abort_upon_exception:
paul@246 776
            raise Exception
paul@145 777
        return self.handler_stack[-1]
paul@129 778
paul@430 779
    def PushHandler(self, addr):
paul@430 780
        self.handler_stack.append(addr)
paul@145 781
        self.handler_local_sp_stack.append(len(self.local_sp_stack))
paul@398 782
        self.handler_invocation_sp_stack.append(len(self.invocation_sp_stack))
paul@145 783
        self.handler_pc_stack.append(len(self.pc_stack))
paul@129 784
paul@430 785
    def PopHandler(self, nframes):
paul@398 786
        # Get the new local frame pointer and PC stack references.
paul@398 787
        local_sp_top = self.handler_local_sp_stack[-nframes]
paul@398 788
        invocation_sp_top = self.handler_invocation_sp_stack[-nframes]
paul@398 789
        pc_top = self.handler_pc_stack[-nframes]
paul@145 790
        # Reduce the local frame pointer stack to refer to the handler's frame.
paul@398 791
        del self.local_sp_stack[local_sp_top:]
paul@398 792
        # Reduce the invocation frame pointer stack to refer to an outer frame.
paul@398 793
        del self.invocation_sp_stack[invocation_sp_top:]
paul@145 794
        # Reduce the PC stack to discard all superfluous return addresses.
paul@398 795
        del self.pc_stack[pc_top:]
paul@398 796
        # Remove elements from the handler stacks.
paul@398 797
        del self.handler_pc_stack[-nframes:]
paul@398 798
        del self.handler_local_sp_stack[-nframes:]
paul@398 799
        del self.handler_invocation_sp_stack[-nframes:]
paul@398 800
        del self.handler_stack[-nframes:]
paul@129 801
paul@430 802
    def CheckException(self, operand=None):
paul@429 803
        self.status = self.exception is not None and self._CheckInstance(self.exception, self.value)
paul@129 804
paul@430 805
    def TestIdentity(self, operand=None):
paul@429 806
        self.status = self.value == self.source_value
paul@129 807
paul@430 808
    def TestIdentityAddress(self, addr):
paul@430 809
        self.status = self.value == addr
paul@129 810
paul@129 811
    # LoadBoolean is implemented in the generated code.
paul@129 812
    # StoreBoolean is implemented by testing against the True value.
paul@68 813
paul@430 814
    def InvertBoolean(self, operand=None):
paul@132 815
        self.status = not self.status
paul@132 816
paul@145 817
    # Common implementation details.
paul@145 818
paul@145 819
    def _CheckInstance(self, ref, cls):
paul@182 820
        data = self.load(ref)
paul@182 821
        target_data = self.load(cls)
paul@145 822
paul@223 823
        # Insist on instance vs. class.
paul@223 824
paul@242 825
        if data.attrcode is None: # absent attrcode == class/module
paul@223 826
            return 0
paul@191 827
paul@207 828
        if target_data.attrcode is not None: # present attrcode == instance
paul@191 829
            return 0
paul@191 830
paul@145 831
        # Find the table entry for the descendant.
paul@145 832
paul@182 833
        element = self.objlist[target_data.classcode + data.attrcode]
paul@238 834
paul@220 835
        if element is not None:
paul@242 836
            attr_index, static_attr, offset = element
paul@220 837
            return attr_index == data.attrcode
paul@220 838
        else:
paul@220 839
            return 0
paul@181 840
paul@181 841
    def _MakeObject(self, size, ref):
paul@203 842
        # Load the template.
paul@182 843
        data = self.load(ref)
paul@181 844
        addr = self.new(size)
paul@203 845
        # Save the header, overriding the size.
paul@203 846
        self.save(addr, data.with_size(size))
paul@181 847
        return addr
paul@181 848
paul@336 849
    def _MakeFragment(self, occupied, size):
paul@336 850
        addr = self.new(size)
paul@246 851
        # Save the header, overriding the size.
paul@336 852
        self.save(addr, FragmentObject(occupied, size))
paul@246 853
        return addr
paul@246 854
paul@429 855
    def _LoadAddressContextCond(self, context, value, inst_value):
paul@191 856
        # Check the instance context against the target's context.
paul@230 857
        # This provides the context overriding for methods.
paul@429 858
        if context is ReplaceableContext or context is not PlaceholderContext and self._CheckInstance(inst_value, context):
paul@191 859
            # Replace the context with the instance.
paul@429 860
            return DataValue(inst_value, value)
paul@191 861
        else:
paul@429 862
            return DataValue(context, value)
paul@191 863
paul@429 864
    def _StoreAddressContext(self, location, context, value, source_context, source_value):
paul@429 865
        if source_context is ReplaceableContext:
paul@429 866
            context = value
paul@405 867
        else:
paul@429 868
            context = source_context
paul@429 869
        self.save(location, DataValue(context, source_value))
paul@405 870
paul@228 871
# Convenience functions.
paul@228 872
paul@246 873
def machine(program, with_builtins=0, debug=0, abort_upon_exception=0):
paul@228 874
    print "Making the image..."
paul@228 875
    code = program.get_image(with_builtins)
paul@228 876
    print "Getting raw structures..."
paul@228 877
    ot = program.get_object_table()
paul@228 878
    pt = program.get_parameter_table()
paul@228 879
    objlist = ot.as_list()
paul@228 880
    paramlist = pt.as_list()
paul@228 881
    print "Getting raw image..."
paul@228 882
    rc = program.get_raw_image()
paul@228 883
    print "Initialising the machine..."
paul@228 884
    importer = program.get_importer()
paul@354 885
    constants = {}
paul@354 886
    for x in (True, False, NotImplemented):
paul@354 887
        constants[x] = importer.get_constant(x).location
paul@248 888
    rm = RSVPMachine(rc, objlist, paramlist, debug=debug, abort_upon_exception=abort_upon_exception)
paul@354 889
    library = Library(rm, constants)
paul@248 890
    rm.library = library
paul@228 891
    rm.pc = program.code_location
paul@241 892
    print "Returning program occupying %d locations." % len(rm.memory)
paul@228 893
    return rm
paul@228 894
paul@68 895
# vim: tabstop=4 expandtab shiftwidth=4