CS 3723
 Programming Languages 
   Tiny Compiler:  


Source for Tiny Compiler For information, see the Calendar Page on the CS3723, Fall 2014, web site. The three homeworks: Homework 8, Homework 9, and Homework 10 present this as a class project for CS3723. These pages have links describing the Tiny language and the MIPS assembly language.

Tiny Compiler Source, in Python
# tiny.py: compiler for tiny
import sys
import datetime # for date header
next = None
src = "" # Tiny source printed at end
hatused = False # check for '^', include pow
addr = {'a':10,'b':11,'c':12,'d':13,'e':14,
        'f':15,'g':16,'h':17,'i':18,'j':19,
        'k':20,'l':21,'m':22,'n':23,'o':24,
        'p':25,'q':26,'r':27,'s':28,'t':29,
        'u':30,'v':31,'w':32,'x':33,'y':34,
        'z':35}
op = {'+':"add", '-':"sub", '*':"mul", '/':"div" }

def next_var(): # for temp variables
    next_var.count += 1
    return str(next_var.count)
next_var.count = -1

def next_while(): # for whiles
    next_while.count += 1
    return str(next_while.count)
next_while.count = -1

def next_if(): # for if-then-else's
    next_if.count += 1
    return str(next_if.count)
next_if.count = -1

def M():
    global src
    src = ""
    d = datetime.datetime.today()
    gen("### compiled by Neal Wagner on\n");
    gen("#     " + d.ctime() + "\n")
    genf("pre.s") # 1st part of framework
    scan()
    S()
    while next != '$':
        S()
    genf("post1.s") # 2nd part of framework
    if hatused:
        genf("pow.s") # only if '^' used
    genf("post2.s") # final part of framewk
    write_source()

def S(): # statement
    if next == '[': # if-then-else
        scan()
        I()
    elif next == '{': # while
        scan()
        W()
    elif next.islower(): # assignment
        # don't scan; A wants first char
        A()
    elif next == '<': # output
        scan()
        P()
    elif next == '>': # input
        scan()
        G()
    else:
        error(1)

def I(): # if-then-else
    ind = next_if() # next counter for if labels
    gen("### IfThenElseStart" + ind + ":\n")
    res = E()
    gen("        l.d     $f2, " + str(8*res) + "($s1)\n")
    gen("        l.d     $f4, 0($s1)\n")
    gen("        c.eq.d  $f2, $f4\n")
    gen("        bc1t    ThenEnd" + ind + "\n")
    if next != '?':
        error(2)
    else: # then[-else]
        scan()
        gen("# ThenPart" + ind + "\n")
        S()
        while next != ']' and next != ':':
            scan()
            S()
        if next == ':':
            gen("        j       EndIf" + ind + "\n")
        gen("ThenEnd" + ind + ":\n")
        if next == ':':
            # else part
            scan() # scan past ':'
            S()
            while next != ']':
                S()
            scan() # past ']'
            gen("EndIf" + ind + ":\n")
        else:
            scan() # past ']'
            gen("### EndIf" + ind + "\n")
        

def W(): # while
    ind = next_while() # next counter for while
    gen("WhileStart" + ind + ":\n")
    res = E() # while test
    gen("        l.d     $f2, " + str(8*res) + "($s1)\n")
    gen("        l.d     $f4, 0($s1)\n")
    gen("        c.eq.d  $f2, $f4\n")
    gen("        bc1t    WhileEnd" + ind + "\n")
    if next != '?':
        error(4)
    else:
        scan()
        gen("# WhileBody" + ind + "\n")
        # while body
        S()
        while next != '}':
            S()
        scan()
        gen("        j       WhileStart" + ind + "\n")
        gen("WhileEnd" + ind + ":\n")

def A(): # assignment
    # called because starts with lower-case
    if next.isalpha(): # no need to check
        lhs = addr[next]
    else:
        error(13)
    scan()
    if next != '=':
        error(5)
    else:
        scan()
        res = E()
        if next != ';':
            error(6)
        scan()
        gen_assign(lhs, res);

def P(): # output special char or expr 
    if next.isupper():
        if next == 'N': # newline
            gen_white("NewL")
        elif next == 'B':
            gen_white("Blank")
        elif next == 'T':
            gen_white("Tab")
        else:
            error(14)
        scan()
        if next == ';':
            scan()
        else:
            error(7)
    else:
        res = E()
        if next == ';':
            gen_output_expr(res)
            scan()
        else:
            error(8)
def G(): # get a double
    # already scanned past ">"
    if next.islower():
        gen_input(addr[next])
        scan()
        if next != ';':
            error(9)
        scan()
    else:
        error(10)

def E(): # expression
    res = T()
    while next == '+' or next == '-':
        save = next
        arg1 = res
        scan()
        arg2 = T()
        res = 36 + int(next_var())
        gen_op(res, arg1, save, arg2)
    return res

def T(): # term
    res = U()
    while next == '*' or next == '/' or \
          next == '%' or next == '@':
        save = next
        arg1 = res
        scan()
        arg2 = U()
        res = 36 + int(next_var())
        gen_op(res, arg1, save, arg2)
    return res

def U(): # ugly term
    res = F()
    if next == '^':
        arg1 = res
        scan()
        arg2 = U()
        res = 36 + int(next_var())
        gen_op(res, arg1, '^', arg2)
    return res

def F(): # factor
    # possible initial unary +s or -s
    negs = 0 # no unary minus
    while next == '+' or next == '-':
        if next == '-':
            negs += 1
        scan()
    negs = negs % 2; # 1 = same as 1 minus
    if next.isdigit(): # digit
        res = int(next)
        scan()
    elif next.isalpha(): # lower-case
        res = addr[next]
        scan()
    elif next == '(': # expr in parens
        scan()
        res = E()
        if next == ')':
            scan()
        else:
            error(11)
    else:
        error(12)
    if negs == 1: # now negate
        res0 = res
        res = 36 + int(next_var())
        gen_op(res, 0, '-', res0) # use 0.0 - res to negate
    return res

def error(n):
    sys.stdout.write("ERROR:" +
      str(n))
    sys.stdout.write(", SOURCE:" +
      src + "\n")
    sys.exit(1)

def getch():
    global src
    global hatused
    c = sys.stdin.read(1)
    if len(c) > 0:
        if c == '^':
            hatused = True
        src += c
        return c
    else:
        return None

def scan():
    global next
    next = getch()
    if next == None:
        sys.exit(1)
    while next.isspace():
        next = getch()
    # nasty code to handle comments
    while next == '#':
        while next != "\n":
            next = getch()
        while next.isspace():
            next = getch()

def genf(fname): # output file of MIPS
    f = open(fname, 'r')
    for line in f:
        gen(line)

def gen_assign(left, right):
    gen("# M[" + str(left) + "] = M[" + str(right) + "]\n")
    gen("        l.d     $f2, " + str(8*right) + "($s1)\n")
    gen("        s.d     $f2, " + str(8*left) + "($s1)\n")
    
def gen_output_expr(res):
    gen("# Output M[" + str(res) + "]\n")
    gen("        li      $v0, 3\n")
    gen("        l.d     $f12, " + str(8*res) + "($s1)\n")
    gen("        syscall\n")

def gen_input(res):
    gen("# Read M[" + str(res) + "]\n")
    gen("        li      $v0, 7\n")
    gen("        syscall\n")
    gen("        s.d     $f0, " + str(8*res) + "($s1)\n")
    
def gen_white(s): # 3 kinds of whitespace
    gen("# Output " + s + "\n")
    gen("        li      $v0, 4\n")
    gen("        la      $a0, " + s + "\n")
    gen("        syscall\n")

def gen_op(res, arg1, save, arg2): # all binary ops
    gen("# M[" + str(res) + "] = M[" + str(arg1) + "] " +
          save + " M[" + str(arg2) + "]\n")
    gen("        l.d     $f2, " + str(8*arg1) + "($s1)\n")
    gen("        l.d     $f4, " + str(8*arg2) + "($s1)\n")
    if save in op:
        gen("        " + op[save] + ".d   $f6, $f2, $f4\n")
    elif save == '^':
        gen("        jal     pow\n");
    else:
        gen("        div.d      $f6, $f2, $f4\n")
        gen("        trunc.w.d  $f6, $f6\n")
        gen("        cvt.d.w    $f6, $f6\n")
        if save == '%':
            gen("        mul.d      $f6, $f6, $f4\n")
            gen("        sub.d      $f6, $f2, $f6\n")
    gen("        s.d     $f6, " + str(8*res) + "($s1)\n")

def gen(line): # newline already present
    sys.stdout.write(line)

def write_source(): # Tiny source as comments at end
    global src
    gen("### tiny source program:\n")
    srcf = src.split("\n")
    for line in srcf:
        gen("# " + line + "\n")

M()


Tests of the compiler:
Description   Tiny
   Source 
MIPS
 Source 
MIPS
 Output 
Pi as a product
Factoring Fibonacci Numbers
Exponentialtion Test 1
Exponentialtion Test 2
Right justify within given width
Test if-then-(optional)else
Test Unary + and -


Extra files: These are read by the compiler.

Extra files required by the program above

% cat pre.s main: move $s7, $ra la $s1, M # data addr # Print your name li $v0, 4 la $a0, Name syscall ### Compiled code starts here
% cat post1.s # Stuff at end move $ra, $s7 jr $ra # ret to sys
% cat post2.s # data declarations .data .align 3 M: .double 0.,1.,2.,3.,4.,5. .double 6.,7.,8.,9. # cons .space 208 # a to z .space 1000 # 125 temps Blank: .asciiz " " NewL: .asciiz "\n" Tab: .asciiz "\t" Name: .asciiz "Executed by Neal Wagner\n"

% cat pow.s ### double to truncated double pow: # truncate $f4 trunc.w.d $f4, $f4 cvt.d.w $f4, $f4 l.d $f6, 8($s1) # check if $f4 == 0 l.d $f8, 0($s1) c.eq.d $f4, $f8 bc1t end # check if $f4 > 0 l.d $f8, 0($s1) c.lt.d $f8, $f4 bc1t next l.d $f8, 8($s1) div.d $f2, $f8, $f2 neg.d $f4, $f4 # loop as long as $f4 == 0 next: l.d $f8, 0($s1) c.eq.d $f4, $f8 bc1t end mul.d $f6, $f6, $f2 l.d $f8, 8($s1) sub.d $f4, $f4, $f8 b next end: jr $ra

(Revision date: 2014-12-12. Please use ISO 8601, the International Standard.)