# version 2.0 27/1/19
# Lots of bugs corrected, see update2.0.pdf

import re

PREFIX=1; DIRECTIVE=2; NUMBER=3; ORDER=4; STARORDER=5
LABEL=6; FLOAT=7; FIXED=8; FIXED_SCALED=9

# directory for translation of main store addresses
address_map={'ff':'512', 'f*':'1024', 'rf':'256', 'r*':'1536'}

# if interact is True access to the machine state is possible during assembly,
# if False  assember can run independently, basically for devlopment, 
interact=True

# these patterns are used to identify the type of component, not parse them
prefixpat=r'^nn|^[*fn\(\)rt]|^pt|^pn|^pp'
directivepat=r'^[p=s].*$|^[1234]/n?[0123456789.-]+$'
numberpat=r'(^[+-]?[0-9]+\.?[0-9]*[0123456789e%-]*)($|\()'
addresspat=r'([+-]?[0-9]+)?([pn0123456789-]*)?'
modifierpat=r'[frst][rf*]?'
orderpat=r'(^[0-9]+' + modifierpat + addresspat + r')($|\()'
starorderpat=r'^[0-9]+\*' + r'[fn]?([+-]?[0-9]+\.?[0-9]*[0123456789e%-]*)($|\()'
labelpat=r'\(([1-9][0-9]?[0-9]?)$'

def parse_line(x):
    " Parses a line of program. Returns list of tuples of components"
    parsed=[]
# catch missing f modifier in parametric addresses
    x=re.sub(r'^([0-9]+)p', r'\1fp', x)
# prefixes
    match=re.search(prefixpat, x)
    if match: parsed.append((PREFIX, match.group(0)))
# extract rest of item
    x=re.sub(prefixpat, "", x)
# directives
    match=re.search(directivepat, x)
    if match: parsed.append((DIRECTIVE, match.group(0)))
# numbers
    match=re.search(numberpat, x)
    if match:  parsed.append((NUMBER, match.group(1)))
# orders
    match=re.search(orderpat, x)
    if match: parsed.append((ORDER, match.group(1)))
# orders with * address"
    match=re.search(starorderpat, x)
    if match: parsed.append((STARORDER, match.group(0)))
# labels
    match=re.search(labelpat, x)
    if match: parsed.append((LABEL, match.group(1)))
    return parsed

def compute_address(a):
    "Computes an address involving parameters, return numerical value"
    parse_address_pat=r'([+-]?[0-9]+)?([pn0123456789]*)?'
    match=re.search(parse_address_pat, a)
    if match:
        val=0
        numerical_address=match.group(1)
        if numerical_address: val=int(numerical_address)
        parametric_address=match.group(2)
        paramlist=re.findall(r'([np])([0-9]+)', parametric_address)
        if paramlist!=[]:
            for param in paramlist:
                pnum=int(param[1])
                if p[pnum]==None: print(a, "parameters must be set")
                if param[0]=="p": val += p[pnum]
                if param[0]=="n": val -= p[pnum]
    return val % 2048

def evaluate_number(x):
# python handles floats using C doubles which allow 52 significant bits
# so we can form a number as floating point and fix if necessary.
# number is returned in accumulator format, ready to store
    pow10=r'(e-?[0-9][0-9]?)?'
    pow2=r'%?(-?[0-9][0-9]?[0-9]?)?'
    parse_numberpat=r'([+-]?[0-9]*\.?[0-9]*' + pow10 + r')'+ pow2 + r'(\(|$)'
    match=re.search(parse_numberpat, x)
    if match:
        val=float(match.group(1))
# the float function does not handle binary exponents
        if match.group(3): val=val*2**int(match.group(3))
    else:
        print(x, "malformed number")
        return None

    if number_format==FLOAT: return float2M(val)
    if number_format==FIXED:
        if abs(val)<1.0: val=int(val*2**39)
        else:            val=int(val)
    if number_format==FIXED_SCALED: val=int(val*2**59)

    return val

def parse_order(o):
    "Parses order in ascii and constructs integer"
    parse_orderpat=r'^([0-9]+)([frst][rf*]?)(' + addresspat + r')($|\()'
    match=re.search(parse_orderpat, o)
    if match:
        function=match.group(1)
        modifier=match.group(2)
        address=compute_address(match.group(3))
# address NOT main store
        if modifier in modifier_binary.keys():
            frst=modifier_binary[modifier]
# main store addresses
        elif modifier in address_map.keys():
            address=int(address) + int(address_map[modifier])
            frst=modifier_binary['r']
# check main store is ON
            if interact:
                if not get_main()[0]: print("main store address with mainstore off")
# orders which should not have main store address detected
            if int(function) in [4, 5, 100, 110]:
                print(function, "order with main store address is deprecated")
        else: print("syntax error")
        return (frst << 18) + (int(function) << 11) + address

def run_assembler(debug):
    global p, number_format, store
# initially set float format
    number_format=FLOAT
# read input
    File=open(input_tape_name[0], 'r')
    prog=File.read()
    File.close()
# ignore titles.  This is because the 'output tape' is in internal code
    pat=re.compile(r'^t.*$', re.MULTILINE)
    prog=re.sub(pat, '', prog)
# convert double space to newline
    prog=re.sub(r'  ', '\n', prog)
# ignore single space
    prog=re.sub(r' ', '', prog)
# handle optional sections
    if not (read_manual_register() & 0x8000000000):
        pat=re.compile(r'pt.*pn', re.DOTALL)
        prog=re.sub(pat, "", prog)
# convert to separate lines
    prog=prog.splitlines()
    p=[None for i in range(0,128)]
    p[1]=2; p[2]=2000

#----------------------------------------------------------------------------
# FIRST PASS TO GET PARAMETER VALUES
#----------------------------------------------------------------------------
    print("first pass")
# at this stage we are updating p1, setting parameters and identifying labels
    for line in prog:
        if line=="": continue
        parts=parse_line(line)
        if parts==[]: print("error in ", line, "item not recognised")
        elif debug: print(parts)
# note p1 in case of label
        p1=p[1]
        for item in parts:
            (Type, Str)=item
            if Type==PREFIX:
# PREFIX, only r is relevant at this point. force p1 to be even
                if re.search(r'^r', Str):
                    p[1]+=p[1]%2; p1=p[1]
# DIRECTIVE, only explicit setting or unsetting of parameter is relevant
            if Type==DIRECTIVE:
# setting p1 or p2 to main store is not implemented
                match=re.search(r'p[12]=ff', Str)
                if match: print("setting p1 or p2 to main store address is not implemented")
                match=re.search(r'p1=[0-9]+/[0-9]+', Str)
                if match: print("assemply into main store not implemented")
# check for setting parameter
                param_setpat=r'p([0-9]+)=(' + addresspat + r')$'
                match=re.search(param_setpat, line)
                if match:
                    param_num=int(match.group(1))
                    val=compute_address(match.group(2))
                    if param_num<=127: p[param_num]=val
                    else: print("illegal parameter number")
# check for unsetting parameter
                param_unsetpat=r'=([0-9])+(/)([0-9]+)?'
                match=re.search(param_unsetpat, line)
                if match:
                    first=int(match.group(1)); last=127
                    if match.group(2): last=int(match.group(3))
                    p[first: last+1]=-1
# NUMBER, only concern here is that we may need to increment p1
# a number must go into even location
            if Type==NUMBER:
                p[1]+=(p[1]%2); p1=p[1]; p[1]+=2
# ORDER
            if Type==ORDER: p[1]+=1
# STARORDER
            if Type==STARORDER:
                p[1]+=1; p[2]+=2
# LABEL
            if Type==LABEL:
                label=int(Str)
                if p[label]==None: p[label]=p1
                else: print("parameter ", label, "set twice")

#------------------------------------------------------------------------------
# SECOND PASS TO ASSEMBLE
#------------------------------------------------------------------------------
    print("second pass")
    p[1]=2; p[2]=2000
    tempstore=[]
    for line in prog:
        if line=="": continue
        parts=parse_line(line)
        for item in parts:
            (Type, Str)=item
            if Type==PREFIX:
# PREFIX, need to handle *, f, n, nn, (, ), r, t, pt, pn, pp
                if re.search(r'^f', Str):  number_format=FLOAT
                if re.search(r'^n$', Str): number_format=FIXED
                if re.search(r'^nn', Str): number_format=FIXED_SCALED
                if re.search(r'^r', Str):  p[1]+=p[1]%2
                if re.search(r'^pp', Str): print("pp prefix not implemented") 
# some prefixes require interaction with current machine state
                if interact:
                    if re.search(r'\*', Str):   op102(0)
                    if re.search(r'^\(', Str):  op106(1)
                    if re.search(r'^\)', Str):  op106(2)
# DIRECTIVE
            if Type==DIRECTIVE:
# need to handle p=, =m, =m/n, s m, sr m, 1/ ... 4/
# check for setting parameter, parameters set by labels should now be set
                paramsetpat=r'p([0-9]+)=(' + addresspat + r')$'
                match=re.search(paramsetpat, line)
                if match:
                    param_num=int(match.group(1))
                    val=compute_address(match.group(2))
                    if param_num<=127: p[param_num]=val
                    else: print("illegal parameter number")
# check for unsetting parameter
                paramunsetpat=r'=([0-9])+(/)([0-9]+)?'
                match=re.search(paramunsetpat, line)
                if match:
                    first=int(match.group(1)); last=127
                    if match.group(2): last=int(match.group(3))
                    p[first: last+1]=-1
# detect start
                startpat="^sr?(" + addresspat + ")"
                match=re.search(startpat, Str)
# need to deal with sr here, break is needed because data might follow
                if match:
                     startpoint=compute_address(match.group(1))
                     break
# start and digit and page layout require interaction
                if interact:
                    match=re.search(r'^([1234])/(n?)([.0123456789-]+)', Str)
                    if match:
                        set=2*int(match.group(1))
                        if match.group(2): number_format=FIXED
                        val=evaluate_number(match.group(3))
                        store=reserved
                        put_M_to_store(val, 116+set)
                        store=free
# NUMBER
            if Type==NUMBER:
# put into even location
                if (p[1]%2) == 1:
                    tempstore.append((p[1], convert_order_to_integer("2f0")))
                    p[1]+=1
                val=evaluate_number(Str)
                tempstore.append((p[1]+4096, val))
                p[1]+=2
# ORDER
            if Type==ORDER:
                order=parse_order(Str)
                tempstore.append((p[1], order))
                p[1]+=1
# STARORDER
            if Type==STARORDER:
                parse_starorderpat='^([0-9]+)\*' + r'([fn]?)([+-]?[0-9]+\.?[0-9]*[0123456789e%-]*)($|\()'
                match=re.search(parse_starorderpat, Str)
                if match:
                    function=match.group(1)
                    if match.group(2)=="f": number_format=FLOAT
                    if match.group(2)=="n": number_format=FIXED
                    num=match.group(3)
                    val=evaluate_number(num)
                    order=(int(function) << 11) + p[2]
                    tempstore.append((p[1], order))
                    tempstore.append((p[2]+4096, val))
                    p[1]+=1; p[2]+=2
    return (tempstore, startpoint)

def load_store(tempstore, debug):
    global store, free, use_main_store
    store=free
    use_main_store=False
    for (place, content) in tempstore:
        register=place & 0x7FF
        if place > 2047:
# a number goes into two locations
            put_M_to_store(content, register)
# print the content as a fraction, integer and a float
            frac="%1.9f" % (float(content)*2**(-39))
            integer=content
# need to handle non-standard floats
            if content==0: Float=str(0.0)
            if (content & 0xc000000000) and (~content & 0xc000000000):
                Float=M2float(content)
            else: Float="****"
            if debug: print(register, "\t", frac, "\t", integer, "\t", Float)
        else:
# an order goes into one location
            free[register]=content
            if debug: print(register, "\t", convert_integer_to_order(content))

