#!/usr/bin/env python
#-*- coding:utf-8 -*-

"""
    Virtual Machine

    Usage: vsl [options] <filename> [args]

    Options:
      --version                     show program's version number and exit
      -h, --help                    show this help message and exit
      -d, --debug                   run program step by step
      -m MEMORY, --memory=MEMORY    sets size of central memory

    each line must have the form :
            label: opcode params
    label and params are optional. Params can be integer, float, string or label.

    opcodes :
        CALL <label> <number of parameters>
            Jumps to the given label with a new context. Parameters must be pushed into the stack before
            calling.
        RETURN <number of locals>
            Returns to the previous context. Locals ars cleared. Number of locals must be the same as the number
            of locals allocated in the context.
        ALLOC <number of locals>
            Allocates n locals into the stack in the current context. This opcode must be called just after a label
            who define a function. Or allocates global variables, for this use its as the first opcode in the program.
        JMP <label>
            Jumps to the given label.
        JZ <label>
            Jumps to the given label if the top of the stack is false.
        JIZ <label>
            Jumps to the given label if the top of the stack is true.
        PUSHI <int>, PUSHF <float>, PUSHS <string>
            Pushes the constant on the stack.
        POP
            Pops the top of the stack.
        SETL <int>, SETG <int>, SETP <int>
            Pops the top of the stack and sets the local, global or parameter corresponding
            at the given number.
        GETL <int>, GETG <int>, GETP <int>
            Gets the local, global or parameter corresponding at the given number and
            pushes its on the stack.
        SETNL <int>, SETNG <int>, SETNP <int>
            Makes the same as SETL, SETG and SETP but the value on the top of the stack
            isn't poped'.
        ADD, SUB, MUL, DIV, POW, MOD
            Pops the two values on the top of the stack and pushes their sum, difference,
            product, quotient, power and modulo respectively.
            For int and float types, all operators work.
            For string, add and mul only. "a" + "b" = "ab", 3*"a" = "aaa"
        NEG, INC, DEC
            Pops the top of the stack and pushes its negative,
            its incremented by one or its decremented by one.
        AND, OR
            Pops two values from the stack and pushes the result of the operator.
        NOT
            Pops the top of the stack and pushes its inverse.
        LT, GT, LE, GE, EQ, NE
            Pops two values from the stack and pushes the result of operator.
            Less Than, Greather Than, Less or Equal, Greather or Equal, Equal, Not Equal.
        READ, WRITE
            Reads the standard input and pushes its on the stack. Pops the top of the stack and write its
            on the standard output.
        EXIT
            Pops a value from the stack and exits the program with its.
        TOI, TOF, TOS
            Pops a value from the stack and pushes the result of the casting in integer,
            float or string respectively.
        COS, SIN, TAN, ARCCOS, ARCSIN, ARCTAN
            Pops a value from the stack and pushes the result of operation.
        ABS, SQRT, LOG, EXP
            Pops a value from the stack and puches its absolute value, square, log or
            exponential respectively.
        CREATEARRAY
            Allocates a dynamic array and pushes its reference on the stack.
        GET
            Pops the reference and the indice from the stack and pushes the value of
            indice in array or string.
        SET
            Pops the reference, the indice and the value from the stack and set the indice
            in given array's reference with the value.
        LEN
            Pops the reference of an array or a string from the stack and pushes its lenght.
        APPEND
            Pops the reference of an array and a value from the stack and append the
            given value at the given array.
        INSERT
            Pops the reference, the indice and the value and inserts the given value at
            the given indice in the array.
        REMOVE
            Pops the reference and the indice and removes the given indice in the array.
        GETPROGARGS
            Pushes the reference of the array representing the program's arguments.
        ISARRAY, ISINT, ISFLOAT, ISSTR
            Pops a value from the stack and pushes the result.
        OPEN
            Pops the filename and the mode and pushes the pointer to the file.
        CLOSE
            Pops a pointer to a file and close its.
        WRITEFILE
            Pops a reference to a file and and string or an array and writes its to the file.
        READFILE
            Pops a reference to a file and pushes its content as a string.
        SPLIT
            Pops a string and a pattern and pushes the result of splitting the string
            with the pattern as an array.

    Add an opcode :
        For adding a opcode, just add it in the declaration of opcode and add an alternative in the 
        main loop.

    Documentation :
        Defining a function :
            A function is a simple label in the code. So you must add a jump before the function who jumps
            to the end otherwise the code inside the function will be executed.

                             JUMP endfunction
                myfunction:  ALLOC 2
                             ....
                             RETURN 2
                endfunction: ....
            
            Another possibility is to define
            only functions and call main function at the begin of the program. See exemple below...

            Local variables must be allocated at the end of the function with "ALLOC" and can be accessed
            with "GETL", "SETL" and "SETNL".

            Parameters can be accessed with "GETP", "SETP" and "SETNP".

            The resturn value must be on the top of stack before returning. A value must always be present.
            So if the function return nothind, pushes a value and pop its a the return.

        Calling a function:
            For calling a function just use the opcode "CALL". You must push parameters
            before calling the function. Number of parameters must be specify.

                ....
                PUSHI 0
                PUSHI 1
                CALL myfunction 2
                ....

    Exemple of a program with a main function :
        GETPROGRARGS
        CALL main 1
        EXIT
        function1: ALLOC 2
                   .....
                   RETURN 2
        function2: ALLOC 0
                   .....
                   RETURN 0
        main:      ALLOC 10
                   .....
                   RETURN 10

    VM 0.2 - David Jacot, 2009
"""
from math import *
import sys
import re
from optparse import OptionParser

try:
    import psyco
    psyco.full()
except :
    pass

class label(str):
    """
        Custom type representing label.
    """
    pass

# Declaration of opcodes with their signature
ops = (
    ('CALL', label, int), ('RETURN', int), ('ALLOC', int),
    ('JMP', label), ('JZ', label), ('JNZ', label),
    ('PUSHI', int), ('PUSHF', float), ('PUSHS', str), ('POP'),
    ('SETL', int), ('GETL', int), ('SETG', int), ('GETG', int), ('SETP', int), ('GETP', int),
    ('SETNL', int), ('SETNG', int), ('SETNP', int),
    ('ADD'), ('SUB'), ('MUL'), ('DIV'), ('POW'), ('MOD'),
    ('NEG'), ('INC'), ('DEC'),
    ('AND'), ('OR'), ('NOT'),
    ('LT'), ('GT'), ('LE'), ('GE'), ('EQ'), ('NE'),
    ('READ'), ('WRITE'), ('EXIT'),
    ('TOI'), ('TOF'), ('TOS'),
    ('COS'), ('SIN'), ('TAN'), ('ARCCOS'), ('ARCSIN'), ('ARCTAN'),
    ('ABS'), ('SQRT'), ('LOG'), ('EXP'),
    ('CREATEARRAY'), ('GET'), ('SET'), ('LEN'), ('APPEND'), ('INSERT'), ('REMOVE'),
    ('GETPROGARGS'),
    ('ISARRAY'), ('ISINT'), ('ISSTR'), ('ISFLOAT'),
    ('OPEN'), ('CLOSE'), ('WRITEFILE'), ('READFILE'), ('SPLIT'),
);

# Regex for types
regex = {
    int   : r'([-+]?\d+)',
    float : r'([-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?)',
    str   : r'"(.*)"',
    label : r'([A-Za-z0-9_]+)',
}

# Signature of opcodes
signature = {}

# Transform opcode as OPCODE = number
for i,o in enumerate(ops):
    if isinstance(o, str):
        setattr(sys.modules[__name__],o,i)
    else:
        setattr(sys.modules[__name__],o[0],i)
        signature[o[0]] = o[1:]

def parse(file):
    """
        Parses and checks the code.
    """
    ok = True

    adresses = {}
    code = []

    # Regex for program line <LABEL>: <opcode> <params>
    reLine = re.compile(r'^\s*(?:(?P<label>[A-Za-z0-9_]+)\s*:)?\s*(?P<opcode>[A-Za-z]+)(?:\s+(?P<params>.+))?$')
    for n,i in enumerate(file):
        res = reLine.match(i)
        if res:
            # Get label's line number
            lab = res.group('label')
            if lab: adresses[lab] = n

            # Check if opcode exists and check params
            try:
                opcode = getattr(sys.modules[__name__], res.group('opcode').upper())
            except AttributeError:
                print "*** Parse error: Unknown opcode %s in line %i" % (res.group('opcode'), n+1)
                ok = False
                continue

            try:
                ptypes = signature[res.group('opcode').upper()]

                # Regex for types
                pregex = [regex[t] for t in ptypes]
                tmp = r'\s+'.join(pregex)
                res = re.match(r'^'+tmp+r'\s*$', res.group('params'))
                if res:
                    try:
                        # Casts parameters with given types
                        params = [t(a) for t,a in zip(ptypes,res.groups())]
                        code.append((opcode,params))
                    except ValueError:
                        print "*** Parse error: Parameters in line %d haven't right types" % (n+1)
                        ok = False
                else:
                    print "*** Parse error: Parameters in line %d isn't correctly" % (n+1)
                    ok = False
            except KeyError:
                code.append((opcode,[]))
        else:
            print "*** Parse error: Line %d isn't correctly formatted" % (n+1)
            ok = False

    # Replace label with corresponding instruction pointer
    for n,(o,params) in enumerate(code):
        for i,p in enumerate(params):
            if isinstance(p, label):
                try:
                    params[i] = adresses[p]
                except KeyError:
                    print "*** Parse error: Unknown label %s at line %d" % (p,n+1)
                    ok = False

    if ok:
        return code
    else:
        return None

def run(code, argv, mem=100, debug=False, verbose=False):
    """
        Execute code.
    """
    # Stack
    mem = range(1,mem)

    # Registers
    sp = 0
    lp = 0
    pp = 0
    ip = 0

    # Number of instruction
    nbInstructions = len(code)

    # Main loop
    while ip < nbInstructions:
        # Gets opcode and parameters
        i, p = code[ip]

        # Increments instruction pointer
        ip += 1

        try:
            if i<=POP:
                if i==CALL:
                    # Store registers
                    mem[sp] = (ip, sp-p[1], lp, pp)
                    # Sets paramters pointers at the begin
                    # of the parameters in stack
                    pp = sp-p[1]
                    # Increments stack pointer
                    sp += 1
                    # Sets locals pointer to the current stack pointer
                    lp = sp
                    # Sets the instruction pointer with the adresse of
                    # the function in the code segment
                    ip = p[0]

                elif i==RETURN:
                    # Gets the return value
                    r = mem[sp-1]
                    # Adjuts the stack pointer to clear locals
                    sp = sp-p[0]-2
                    # Gets backuped registers
                    ip, sp, lp, pp = mem[sp]
                    # Pushes return value
                    mem[sp] = r
                    sp += 1

                elif i==ALLOC:
                    # Increments the stack pointer for allocating space
                    sp += p[0]

                elif i==JMP:
                    # Sets the instruction pointer with the given adresse in the code segment.
                    ip = p[0]

                elif i==JZ:
                    # Pops and jumps at the given adresse if the value if false
                    sp -= 1
                    if not mem[sp]:
                        ip = p[0]

                elif i==JNZ:
                    # Pops and jumps at the given adresse if the value if true
                    sp -= 1
                    if mem[sp]:
                        ip = p[0]

                elif i==PUSHI:
                    # Pushes the integer
                    mem[sp] = p[0]
                    sp += 1

                elif i==PUSHF:
                    # Pushes the float
                    mem[sp] = p[0]
                    sp += 1

                elif i==PUSHS:
                    # Pusches the string
                    mem[sp] = p[0]
                    sp += 1

                elif i==POP:
                    # Pops, value on the top is lost
                    sp -= 1

            elif i<=SETNP:
                if i==SETL:
                    # Pops a value and sets the given local
                    sp -= 1
                    mem[lp+p[0]] = mem[sp]

                elif i==GETL:
                    # Pushes the value of the given local
                    mem[sp] = mem[lp+p[0]]
                    sp += 1

                elif i==SETG:
                    # Pops a value and sets the given global
                    sp -= 1
                    mem[p[0]] = mem[sp]

                elif i==GETG:
                    # Pushes the value of the given global
                    mem[sp] = mem[p[0]]
                    sp += 1

                elif i==SETP:
                    # Pops a value and sets the given parameter
                    sp -= 1
                    mem[pp+p[0]] = mem[sp]

                elif i==GETP:
                    # Pushes the value of the given parameter
                    mem[sp] = mem[pp+p[0]]
                    sp += 1

                elif i==SETNL:
                    # Sets the given local with the top of the stack.
                    mem[lp+p[0]] = mem[sp-1]

                elif i==SETNG:
                    # Sets the given global with the top of the stack.
                    mem[p[0]] = mem[sp-1]

                elif i==SETNP:
                    # Sets the given parameter with the top of the stack.
                    mem[pp+p[0]] = mem[sp-1]

            elif i<=DEC:
                if i==ADD:
                    # Pops two values and pushes the addition.
                    sp -= 1
                    mem[sp-1] = mem[sp-1] + mem[sp]

                elif i==SUB:
                    # Pops two values and pushes the substraction
                    sp -= 1
                    mem[sp-1] = mem[sp-1] - mem[sp]

                elif i==MUL:
                    # Pops two values and pushes the multiplication
                    sp -= 1
                    mem[sp-1] = mem[sp-1] * mem[sp]

                elif i==DIV:
                    # Pops two values and pushes the division
                    sp -= 1
                    mem[sp-1] = mem[sp-1] / mem[sp]

                elif i==POW:
                    # Pops two values and pushes the power
                    sp -= 1
                    mem[sp-1] = mem[sp-1] ** mem[sp]

                elif i==MOD:
                    # Pops two values and pushes the modulo
                    sp -= 1
                    mem[sp-1] = mem[sp-1] % mem[sp]

                elif i==NEG:
                    # Pops a value and pushes the negative
                    mem[sp-1] = -mem[sp-1]

                elif i==INC:
                    # Increments the top by one
                    mem[sp-1] += 1

                elif i==DEC:
                    # Decrements the top by one
                    mem[sp-1] -= 1

            elif i<=NE:
                if i==AND:
                    # Pops two values and pushes the result
                    sp -= 1
                    mem[sp-1] = mem[sp-1] and mem[sp]

                elif i==OR:
                    # Pops two values and pushes the result
                    sp -= 1
                    mem[sp-1] = mem[sp-1] or mem[sp]

                elif i==NOT:
                    # Pops a value and pushes "not" this value
                    mem[sp-1] = not mem[sp-1]

                elif i==LT:
                    # Pops two values and pushes true if the first is less than the second
                    sp -= 1
                    mem[sp-1] = mem[sp-1] < mem[sp]

                elif i==GT:
                    # Pops two values and pushes true if the first is greather than the second
                    sp -= 1
                    mem[sp-1] = mem[sp-1] > mem[sp]

                elif i==LE:
                    # Pops two values and pushes true if the first is less or equal than the second
                    sp -= 1
                    mem[sp-1] = mem[sp-1] <= mem[sp]

                elif i==GE:
                    # Pops two values and pushes true if the first is greathe or equal than the second
                    sp -= 1
                    mem[sp-1] = mem[sp-1] >= mem[sp]

                elif i==EQ:
                    # Pops two values and pushes true if the values are equal
                    sp -= 1
                    mem[sp-1] = mem[sp-1] == mem[sp]

                elif i==NE:
                    # Pops two values and pushes true if the values arem't equal
                    sp -= 1
                    mem[sp-1] = mem[sp-1] != mem[sp]

            elif i<=TOS:
                if i==READ:
                    # Reads the standard input and pushes
                    mem[sp] = raw_input()
                    sp += 1

                elif i==WRITE:
                    # Pops a value and write it
                    sp -= 1
                    print mem[sp]

                elif i==EXIT:
                    # Pops a value and exit the program with the value as parameter
                    sp -= 1
                    sys.exit(mem[sp])

                elif i==TOI:
                    # Casts the top of the stack in integer
                    mem[sp-1] = int(mem[sp-1])

                elif i==TOF:
                    # Casts the top of the stack in float
                    mem[sp-1] = float(mem[sp-1])

                elif i==TOS:
                    # Casts the top of the stack in string
                    mem[sp-1] = str(mem[sp-1])

            elif i<=EXP:
                if i==COS:
                    # Computes the cosinus of the top of the stack
                    mem[sp-1] = cos(mem[sp-1])

                elif i==SIN:
                    # Computes the sinus of the top of the stack
                    mem[sp-1] = sin(mem[sp-1])

                elif i==TAN:
                    # Computes the tan of the top of the stack
                    mem[sp-1] = tan(mem[sp-1])

                elif i==ARCCOS:
                    # Computes the arccod of the top of the stack
                    mem[sp-1] = acos(mem[sp-1])

                elif i==ARCSIN:
                    # Computes the arcsin of the top of the stack
                    mem[sp-1] = asin(mem[sp-1])

                elif i==ARCTAN:
                    # Computes the arctan of the top of the stack
                    mem[sp-1] = atan(mem[sp-1])

                elif i==ABS:
                    # Computes the absolute value of the top of the stack
                    mem[sp-1] = fabs(mem[sp-1])

                elif i==SQRT:
                    # Computes the square of the top of the stack
                    mem[sp-1] = sqrt(mem[sp-1])

                elif i==LOG:
                    # Computes the log of the top of the stack
                    mem[sp-1] = log(mem[sp-1])

                elif i==EXP:
                    # Computes the exponential of the top of the stack
                    mem[sp-1] = exp(mem[sp-1])

            elif i<=GETPROGARGS:
                if i==CREATEARRAY:
                    # Allocates an array and pushes the reference
                    mem[sp] = []
                    sp += 1

                elif i==GET:
                    # In stack : reference, indice
                    # Pops and pushes the value
                    sp -= 1
                    mem[sp-1] = mem[sp-1][mem[sp]]

                elif i==SET:
                    # In stack : reference, indice, value
                    # Pops values and sets the given indice in array
                    sp -= 2
                    mem[sp-1][mem[sp]] = mem[sp+1]

                elif i==LEN:
                    # In stack : reference
                    # Pops ans pushes the lenght
                    mem[sp-1] = len(mem[sp-1])

                elif i==APPEND:
                    # In stack : reference, value
                    # Pops and append the value in the array
                    sp -= 2
                    mem[sp].append(mem[sp+1])

                elif i==INSERT:
                    # In stack : reference, indice, value
                    # Pops and inserts the value at the given index
                    sp -= 3
                    mem[sp].insert(mem[sp+1], mem[sp+2])

                elif i==REMOVE:
                    # In stack : reference, indice
                    # Pops and remove the given indice
                    sp -= 2
                    mem[sp].pop(mem[sp+1])

                elif i==GETPROGARGS:
                    # Pushes the reference of the array who contains parameters of the program
                    mem[sp] = argv
                    sp += 1

            else:
                if i==ISARRAY:
                    # Pops a value and pushes True if its an array
                    mem[sp-1] = isinstance(mem[sp-1],list)

                elif i==ISINT:
                    # Pops a value and pushes True if its an integer
                    mem[sp-1] = isinstance(mem[sp-1],int)

                elif i==ISSTR:
                    # Pops a value and pushes True if its a string
                    mem[sp-1] = isinstance(mem[sp-1],str)

                elif i==ISFLOAT:
                    # Pops a value and pushes True if its a float
                    mem[sp-1] = isinstance(mem[sp-1],float)

                elif i==OPEN:
                    # In stack : filename, mode
                    # Pops values and pushes the reference of the file object opened
                    # with the given mode
                    sp -= 1
                    mem[sp-1] = open(mem[sp-1], mem[sp])

                elif i==CLOSE:
                    # In stack : file object
                    # Pops value and close file
                    sp -= 1
                    mem[sp].close()

                elif i==WRITEFILE:
                    # In stack : file object, string or array
                    # Pops and write the given string or array
                    sp -= 2
                    if isinstance(mem[sp+1], list):
                        mem[sp].writelines(mem[sp+1])
                    else:
                        mem[sp].write(mem[sp+1])

                elif i==READFILE:
                    # In stack : file object
                    # Pops and pushes the content of the file as string
                    mem[sp-1] = mem[sp-1].read()

                elif i==SPLIT:
                    # In stack : string, pattern
                    # Pops values and pushes the result of split as an array
                    sp -= 1
                    # Little tip for '\n'
                    mem[sp] = mem[sp].replace(r'\n', '\n')
                    mem[sp-1] = mem[sp-1].split(mem[sp])

        except (IndexError, ValueError, TypeError):
            (type, value, tb) = sys.exc_info()
            print "*** Runtime Error : "
            print "***     Opcode    :", 
            if(isinstance(ops[i], str)): print ops[i]
            else:                        print ops[i][0]
            print "***     Stack     :", mem[:sp+1]
            print "***     Registers : IP : %d, LP : %d, PP : %d, SP : %d" % (ip,lp,pp,sp)
            print "***     Type      : " + str(type)
            print "***     Error     : " + str(value)
            sys.exit()

        if debug or verbose:
            print ""
            print "*************************************************************************"
            print "***     Opcode    :", 
            if(isinstance(ops[i], str)): print ops[i]
            else:                        print ops[i][0]
            print "***     Params    :", p
            print "***     Stack     :", mem[0:sp+1]
            print "***     Registers : IP : %d, LP : %d, PP : %d, SP : %d" % (ip,lp,pp,sp)
            if debug:
                print "*** Hit enter to continue"
                raw_input()

if __name__ == '__main__':
    # Command line parser
    parser = OptionParser(usage="usage: %prog [options] <filename> [args]", version="%prog 0.2")
    parser.add_option('-d','--debug', action='store_true', dest='debug', default=False, help='run program step by step')
    parser.add_option('-v','--verbose', action='store_true', dest='verbose', default=False, help='display all informations during runtime')
    parser.add_option('-m','--memory', type='int', dest='memory', default=100, help='sets size of central memory')
    (options, args) = parser.parse_args()

    if len(args)==0:
        print parser.error("you must specify a filename")

    try:
        # Parse file
        code = parse(file(args[0]).readlines())

        # Run code
        if code:
            run(code, argv=args, debug=options.debug, mem=options.memory, verbose=options.verbose)

    # Catch "CTRL-C"
    except (KeyboardInterrupt, SystemExit):
        pass

