micropython

Annotated rsvp.py

78:a550b84e0ea5
2008-05-04 Paul Boddie Made the temporary storage allocation and deallocation more adaptive so that entries are only reserved when actually required and discarded when actually used. Introduced temporary storage usage for invocation targets instead of having a LoadCallable instruction.
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@68 8
Copyright (C) 2007, 2008 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@68 27
  * Memory
paul@68 28
  * PC (program counter) stack
paul@68 29
  * Value stack
paul@68 30
  * Frame stack (containing pointers to the value stack)
paul@68 31
  * Current frame and arguments pointers
paul@68 32
paul@68 33
The memory contains constants, global variable references and program code.
paul@68 34
paul@68 35
The PC stack contains the return address associated with each function
paul@68 36
invocation.
paul@68 37
paul@68 38
The value stack contains references to objects that are being manipulated, along
paul@68 39
with pointers to previous stack frames.
paul@68 40
paul@68 41
The frame stack tracks the position of stack frames within the value stack.
paul@68 42
"""
paul@68 43
paul@68 44
class IllegalInstruction(Exception):
paul@68 45
    pass
paul@68 46
paul@68 47
class IllegalAddress(Exception):
paul@68 48
    pass
paul@68 49
paul@68 50
class EmptyPCStack(Exception):
paul@68 51
    pass
paul@68 52
paul@68 53
class EmptyMetadataStack(Exception):
paul@68 54
    pass
paul@68 55
paul@68 56
class RSVPMachine:
paul@68 57
paul@68 58
    "A really simple virtual processor."
paul@68 59
paul@68 60
    def __init__(self, memory, pc=None, debug=0):
paul@68 61
paul@68 62
        """
paul@68 63
        Initialise the processor with a 'memory' (a list of values containing
paul@68 64
        instructions and data) and the optional program counter 'pc'.
paul@68 65
        """
paul@68 66
paul@68 67
        self.memory = memory
paul@68 68
        self.pc = pc or 0
paul@68 69
        self.debug = debug
paul@68 70
        self.pc_stack = []
paul@68 71
        self.value_stack = []
paul@68 72
        self.frame_stack = []
paul@68 73
        self.frame_sp = 0
paul@68 74
paul@68 75
    def load(self, address):
paul@68 76
paul@68 77
        "Return the value at the given 'address'."
paul@68 78
paul@68 79
        try:
paul@68 80
            return self.memory[address]
paul@68 81
        except IndexError:
paul@68 82
            raise IllegalAddress, address
paul@68 83
paul@68 84
    def save(self, address, value):
paul@68 85
paul@68 86
        "Save to the given 'address' the specified 'value'."
paul@68 87
paul@68 88
        try:
paul@68 89
            self.memory[address] = value
paul@68 90
        except IndexError:
paul@68 91
            raise IllegalAddress, address
paul@68 92
paul@68 93
    def new(self, size):
paul@68 94
paul@68 95
        """
paul@68 96
        Allocate space of the given 'size', returning the address of the space.
paul@68 97
        """
paul@68 98
paul@68 99
        addr = len(self.memory)
paul@68 100
        for i in range(0, size):
paul@68 101
            self.memory.append(None)
paul@68 102
        return addr
paul@68 103
paul@68 104
    def push(self, data):
paul@68 105
paul@68 106
        "Push 'data' onto the value stack."
paul@68 107
paul@68 108
        self.value_stack.append(data)
paul@68 109
paul@68 110
    def pull(self):
paul@68 111
paul@68 112
        "Pull a value from the value stack and return it."
paul@68 113
paul@68 114
        return self.value_stack.pop()
paul@68 115
paul@68 116
    def push_pc(self, data):
paul@68 117
paul@68 118
        "Push 'data' onto the PC stack."
paul@68 119
paul@68 120
        self.pc_stack.append(data)
paul@68 121
paul@68 122
    def pull_pc(self):
paul@68 123
paul@68 124
        "Pull a value from the PC stack and return it."
paul@68 125
paul@68 126
        try:
paul@68 127
            return self.pc_stack.pop()
paul@68 128
        except IndexError:
paul@68 129
            raise EmptyPCStack
paul@68 130
paul@68 131
    def add_frame(self, data):
paul@68 132
paul@68 133
        "Add 'data' to the frame stack without updating the stack pointer."
paul@68 134
paul@68 135
        self.frame_stack.append(data)
paul@68 136
paul@68 137
    def push_frame(self, data):
paul@68 138
paul@68 139
        "Push 'data' onto the frame stack."
paul@68 140
paul@68 141
        self.frame_stack.append(data)
paul@68 142
        self.frame_sp += 1
paul@68 143
paul@68 144
    def pull_frame(self):
paul@68 145
paul@68 146
        "Pull a value from the frame stack and return it."
paul@68 147
paul@68 148
        try:
paul@68 149
            self.frame_sp -= 1
paul@68 150
            return self.frame_stack.pop()
paul@68 151
        except IndexError:
paul@68 152
            raise EmptyMetadataStack
paul@68 153
paul@68 154
    def execute(self):
paul@68 155
paul@68 156
        "Execute code in the memory, starting from the current PC address."
paul@68 157
paul@68 158
        try:
paul@68 159
            while 1:
paul@68 160
                instruction = self.load(self.pc)
paul@68 161
                if self.debug:
paul@68 162
                    print "%8d %s" % (self.pc, instruction)
paul@68 163
                method = getattr(self, instruction, None)
paul@68 164
                if method is None:
paul@68 165
                    raise IllegalInstruction, self.pc
paul@68 166
                else:
paul@68 167
                    method()
paul@68 168
        except EmptyPCStack:
paul@68 169
            pass
paul@68 170
paul@68 171
    def jump(self, addr, next):
paul@68 172
paul@68 173
        """
paul@68 174
        Jump to the subroutine at (or identified by) 'addr'. If 'addr'
paul@68 175
        identifies a library function then invoke the library function and set
paul@68 176
        PC to 'next' afterwards; otherwise, set PC to 'addr'.
paul@68 177
        """
paul@68 178
paul@68 179
        if isinstance(addr, str):
paul@68 180
            getattr(self, addr)()
paul@68 181
            self.pc = next
paul@68 182
        else:
paul@68 183
            self.push_pc(self.pc + 2)
paul@68 184
            self.pc = addr
paul@68 185
paul@68 186
    # Instructions.
paul@68 187
paul@68 188
    def MakeFrame(self):
paul@68 189
        top = len(self.value_stack)
paul@68 190
        self.add_frame(top)
paul@68 191
        self.pc += 1
paul@68 192
paul@68 193
    def DropFrame(self):
paul@68 194
        result = self.pull()
paul@68 195
        frame = self.pull_frame()
paul@68 196
        self.value_stack = self.value_stack[:frame] # reset stack before call
paul@68 197
        self.push(result)
paul@68 198
        self.pc += 1
paul@68 199
paul@68 200
    def JumpWithFrame(self):
paul@68 201
        addr = self.load(self.pc + 1)
paul@68 202
        self.frame_sp += 1 # adopt the added frame
paul@68 203
        self.jump(addr, self.pc + 2)
paul@68 204
paul@68 205
    def LoadName(self):
paul@68 206
paul@68 207
        """
paul@68 208
        LoadName #n
paul@68 209
        Load from position n in frame: get position n from the current stack
paul@68 210
        frame, push the retrieved value onto the stack.
paul@68 211
        """
paul@68 212
paul@68 213
        n = self.load(self.pc + 1)
paul@68 214
        frame = self.frame_stack[self.frame_sp]
paul@68 215
        self.push(self.value_stack[frame + n])
paul@68 216
        self.pc += 2
paul@68 217
paul@68 218
    def StoreName(self):
paul@68 219
paul@68 220
        """
paul@68 221
        StoreName #n
paul@68 222
        Save to position n in frame: pull a value from the stack and set it as
paul@68 223
        position n in the current stack frame.
paul@68 224
        """
paul@68 225
paul@68 226
        n = self.load(self.pc + 1)
paul@68 227
        frame = self.frame_stack[self.frame_sp]
paul@68 228
        self.value_stack[frame + n] = self.pull()
paul@68 229
        self.pc += 2
paul@68 230
paul@72 231
    LoadTemp = LoadName
paul@72 232
    StoreTemp = StoreName
paul@72 233
paul@68 234
    def LoadConst(self):
paul@68 235
paul@68 236
        """
paul@68 237
        LoadConst addr
paul@68 238
        Load the reference to memory: get the address addr, push the value onto
paul@68 239
        the stack.
paul@68 240
paul@68 241
        This is useful for loading constants.
paul@68 242
        """
paul@68 243
paul@68 244
        addr = self.load(self.pc + 1)
paul@68 245
        self.push(addr)
paul@68 246
        self.pc += 2
paul@68 247
paul@68 248
    def LoadAttr(self):
paul@68 249
paul@68 250
        """
paul@68 251
        LoadAttr #n
paul@68 252
        Load from position n in reference: get the contents of position n in the
paul@68 253
        memory referenced by the value on the top of the stack, replacing the
paul@68 254
        value on the top of the stack with the retrieved value.
paul@68 255
        """
paul@68 256
paul@68 257
        n = self.load(self.pc + 1)
paul@68 258
        ref = self.pull()
paul@68 259
        self.push(self.load(ref + n))
paul@68 260
        self.pc += 2
paul@68 261
paul@68 262
    def StoreAttr(self):
paul@68 263
paul@68 264
        """
paul@68 265
        StoreAttr #n
paul@68 266
        Save to position n in reference: pull a value from the stack and save it
paul@68 267
        to position n in the memory referenced by the next value on the stack,
paul@68 268
        also removing the next value on the stack.
paul@68 269
        """
paul@68 270
paul@68 271
        n = self.load(self.pc + 1)
paul@68 272
        value = self.pull()
paul@68 273
        self.save(self.pull() + n, value)
paul@68 274
        self.pc += 2
paul@68 275
paul@72 276
    def LoadAddress(self):
paul@72 277
paul@72 278
        """
paul@72 279
        LoadAddress addr, #n
paul@72 280
        Load from position n in reference at addr: get the contents of position
paul@72 281
        n in the memory referenced by addr, adding the retrieved value to the
paul@72 282
        top of the stack.
paul@72 283
        """
paul@72 284
paul@72 285
        red = self.load(self.pc + 1)
paul@72 286
        n = self.load(self.pc + 2)
paul@72 287
        self.push(self.load(ref + n))
paul@72 288
        self.pc += 3
paul@72 289
paul@72 290
    def StoreAddress(self):
paul@72 291
paul@72 292
        """
paul@72 293
        StoreAddress addr, #n
paul@72 294
        Save to position n in reference at addr: pull a value from the stack and
paul@72 295
        save it to position n in the memory referenced by addr, also removing
paul@72 296
        the value on the top of the stack.
paul@72 297
        """
paul@72 298
paul@72 299
        ref = self.load(self.pc + 1)
paul@72 300
        n = self.load(self.pc + 2)
paul@72 301
        value = self.pull()
paul@72 302
        self.save(ref + n, value)
paul@72 303
        self.pc += 3
paul@72 304
paul@68 305
    def Return(self):
paul@68 306
paul@68 307
        """
paul@68 308
        Return
paul@68 309
        Return to the address of the caller: pull a value from the PC stack and
paul@68 310
        set it in the PC.
paul@68 311
        """
paul@68 312
paul@68 313
        self.pc = self.pull_pc()
paul@68 314
paul@68 315
    def JumpIfTrue(self):
paul@68 316
paul@68 317
        """
paul@68 318
        JumpIfTrue addr
paul@68 319
        Jump to address addr if true: pull a value from the stack and if it
paul@68 320
        represents a true value, jump to address addr.
paul@68 321
        """
paul@68 322
paul@68 323
        addr = self.load(self.pc + 1)
paul@68 324
        value = self.pull()
paul@68 325
        if value:
paul@68 326
            self.pc = addr
paul@68 327
        else:
paul@68 328
            self.pc += 2
paul@68 329
paul@68 330
    def JumpIfFalse(self):
paul@68 331
paul@68 332
        """
paul@68 333
        JumpIfFalse addr
paul@68 334
        Jump to address addr if false: pull a value from the stack and if it
paul@68 335
        represents a false value, jump to address addr.
paul@68 336
        """
paul@68 337
paul@68 338
        addr = self.load(self.pc + 1)
paul@68 339
        value = self.pull()
paul@68 340
        if not value:
paul@68 341
            self.pc = addr
paul@68 342
        else:
paul@68 343
            self.pc += 2
paul@68 344
paul@68 345
    def StoreFrame(self):
paul@68 346
paul@68 347
        """
paul@68 348
        StoreFrame #n
paul@68 349
        Move the value from the top of the stack to the argument in position n:
paul@68 350
        pull a value from the stack and store it in the argument frame at the
paul@68 351
        given position.
paul@68 352
        """
paul@68 353
paul@68 354
        pos = self.load(self.pc + 1)
paul@68 355
        value = self.pull()
paul@68 356
        frame = self.frame_stack[-1] # different from the current frame after MakeFrame
paul@68 357
        self.value_stack[frame + pos] = value
paul@68 358
        self.pc += 2
paul@68 359
paul@68 360
    # Library functions.
paul@68 361
paul@68 362
    def rsvp___printnl(self):
paul@68 363
        print self.load(self.value_stack[-1])
paul@68 364
paul@68 365
    def rsvp___print(self):
paul@68 366
        print self.load(self.value_stack[-1]),
paul@68 367
paul@68 368
    def int_____gt__(self):
paul@68 369
        self.push(self.load(self.value_stack[-2]) > self.load(self.value_stack[-1]))
paul@68 370
paul@68 371
    def int_____sub__(self):
paul@68 372
        result = self.load(self.value_stack[-2]) - self.load(self.value_stack[-1])
paul@68 373
        addr = self.new(1)
paul@68 374
        self.push(addr)
paul@68 375
        self.save(addr, result)
paul@68 376
paul@68 377
    def int_____mul__(self):
paul@68 378
        result = self.load(self.value_stack[-2]) * self.load(self.value_stack[-1])
paul@68 379
        addr = self.new(1)
paul@68 380
        self.push(addr)
paul@68 381
        self.save(addr, result)
paul@68 382
paul@68 383
# vim: tabstop=4 expandtab shiftwidth=4