Difference between revisions of "CSC231 Homework 2 Fall 2017"

From dftwiki3
Jump to: navigation, search
(Problem #2)
(Your Assignment)
 
(8 intermediate revisions by the same user not shown)
Line 14: Line 14:
 
=Problem #1=
 
=Problem #1=
  
Answer the quizzes in the Moodle Homework 2 section.
+
Answer the quizzes in the Moodle Homework 2 section.  When you are asked whether an instruction or piece of assembly language code is ''syntactically correct'', the question is not whether it logically makes sense, but rather, will nasm accept to assemble it without reporting a warning or an error.
  
 
=Problem #2=
 
=Problem #2=
Line 41: Line 41:
 
  > <u>11</u>
 
  > <u>11</u>
 
  > <u>20</u>
 
  > <u>20</u>
  ans = 41
+
  ans = 0
 
   
 
   
 
<br />
 
<br />
 
==Your Assignment==
 
==Your Assignment==
 
<br />
 
<br />
Modify hw2.asm and make it compute ans = 3*(a+b-c) + 2*(c-1).
+
Modify hw2.asm and make it compute:  ''ans'' = 3*(''a''+''b''-''c'') + 2*(''c''-1).
  
 
'''Note 1''': The '''sub''' instruction works similarly to '''add''':
 
'''Note 1''': The '''sub''' instruction works similarly to '''add''':
Line 74: Line 74:
 
I ran my solution program a few times with different numbers.  Your program should behave exactly the same!
 
I ran my solution program a few times with different numbers.  Your program should behave exactly the same!
 
<br />
 
<br />
  231b@aurora ~/hw/hw2 $ ./hw2
+
  cs231a@aurora ~ $ ./hw2
  > 3
+
  > 0
 
  > 1
 
  > 1
 
  > 10
 
  > 10
  ans = 34
+
  ans = -9
 
231b@aurora ~/hw/hw2 $ ./hw2
 
> 10
 
> 10
 
> 0
 
ans = 0
 
 
   
 
   
  231b@aurora ~/hw/hw2 $ ./hw2
+
  cs231a@aurora ~ $ ./hw2  
 
  > 1
 
  > 1
 
  > 2
 
  > 2
 
  > 3
 
  > 3
  ans = 7
+
  ans = 4
 
   
 
   
  231b@aurora ~/hw/hw2 $ ./hw2
+
  cs231a@aurora ~ $ ./hw2
 
  > 10
 
  > 10
  > 0
+
  > 1
  > 10
+
  > 3
  ans = 50
+
  ans = 28
 
   
 
   
 +
cs231a@aurora ~ $ ./hw2
 +
> 100
 +
> 1000
 +
> 6
 +
ans = 3292
 +
 
==Submission==
 
==Submission==
 
<br />
 
<br />
Line 108: Line 108:
 
<br />
 
<br />
  
=Assembly File=
+
=Assembly Files=
 
<br />
 
<br />
 
Below are the two files used in Problem 2, for reference.
 
Below are the two files used in Problem 2, for reference.
Line 196: Line 196:
 
<br />
 
<br />
 
::<source lang="asm">
 
::<source lang="asm">
cs231a@aurora ~/HWs/HW1/pb2 $ cat ~/handout_231b/231Lib.asm
+
   
 
;;; 231Lib.asm
 
;;; 231Lib.asm
 
;;; A simple I/O library for CSC231.
 
;;; A simple I/O library for CSC231.
Line 758: Line 758:
 
 
 
                 ret
 
                 ret
cs231a@aurora ~/HWs/HW1/pb2 $
 
  
  
Line 764: Line 763:
  
 
<br />
 
<br />
 +
<br />
 +
<br />
 +
<br />
 +
<br />
 +
<br />
 +
<showafterdate after="20171003 12:00" before="20171231 00:00">
 +
=Solution=
 +
==Problem 2==
 +
::<source lang="asm">
 +
;;; ; ; hw2sol.asm
 +
;;; ; ; D. Thiebaut
 +
;;; ; ;
 +
;;; ; ;
 +
 +
 +
        extern  _printDec
 +
        extern  _printString
 +
        extern  _println
 +
        extern  _getInput
 +
 +
section .data
 +
prompt db "> "
 +
promptLen equ $-prompt
 +
ansStr          db      "ans = "
 +
ansStrLen equ $-ansStr
 +
 +
a dd 0
 +
b dd 0
 +
c dd 0
 +
ans dd 0
 +
 +
section .text
 +
global _start
 +
_start:
 +
;; display prompt
 +
mov ecx, prompt
 +
mov edx, promptLen
 +
call _printString
 +
;; get a
 +
call _getInput
 +
mov dword[a], eax
 +
 +
;; display prompt
 +
mov ecx, prompt
 +
mov edx, promptLen
 +
call _printString
 +
;; get b
 +
call _getInput
 +
mov dword[b], eax
 +
 +
;; display prompt
 +
mov ecx, prompt
 +
mov edx, promptLen
 +
call _printString
 +
;; get c
 +
call _getInput
 +
mov dword[c], eax
 +
 +
;; -----------------------------------
 +
;; computation: ans = 2*(a-b) + 3*c
 +
;; -----------------------------------
 +
 +
mov eax, dword[a] ;eax <- a
 +
sub eax, dword[b] ;eax <- a-b
 +
add eax, eax      ;eax <- a-b + a-b
 +
 +
add eax, dword[c]
 +
add eax, dword[c]
 +
add eax, dword[c] ;eax <- 2*(a-b) +3*c
 +
mov dword[ans], eax
 +
;ans <- eax
 +
;; -----------------------------------
 +
;; display "ans ="
 +
;; -----------------------------------
 +
mov ecx, ansStr
 +
mov edx, ansStrLen
 +
call _printString
 +
 +
;; -----------------------------------
 +
;; display ans variable
 +
;; -----------------------------------
 +
mov eax, dword[ans]
 +
call _printDec
 +
call _println
 +
call _println
 +
 +
;;; exit
 +
mov ebx, 0
 +
mov eax, 1
 +
int80
 +
 +
</source>
 +
</showafterdate>
 +
<br />
 +
<br />
 +
<br />
 +
<br />
 +
<onlydft>
 +
=VPL=
 +
 +
==vpl_run.sh==
 +
::<source lang="bash">
 +
<br />
 +
#! /bin/bash
 +
 
 +
cat > vpl_execution <<EOF
 +
#! /bin/bash
 +
prog=hw2
 +
 +
nasm -f elf 231Lib.asm
 +
nasm -f elf  \$prog.asm
 +
ld -melf_i386 -o \$prog \$prog.o 231Lib.o
 +
./\$prog | cat -v
 +
EOF
 +
 
 +
chmod +x vpl_execution
 +
</source>
 +
<br />
 +
==vpl_evaluate.sh==
 +
::<source lang="bash">
 +
<br />
 +
#! /bin/bash
 +
 +
cat  > vpl_execution <<EOF
 +
#! /bin/bash
 +
 +
# --- Python ----
 +
if [[ `hostname -s` = "aurora" ]]; then
 +
  python=/usr/bin/python3.3
 +
else
 +
  python=/usr/local/bin/python3.4
 +
fi
 +
 +
 +
\$python evaluate2.py
 +
 +
EOF
 +
 +
chmod +x vpl_execution</source>
 +
<br />
 +
==evaluate2.py==
 +
::<source lang="python">
 +
<br />
 +
'''
 +
Created on Jun 10, 2015
 +
Updated 9/17/15 for assembly
 +
This version is for assembly language programs,
 +
and will require nasm, ld as assembler and linker.
 +
 +
Updated  9/24/15 for nasm, added -w-all to turn off all warnings
 +
Updated 10/1/15 for objdump, to get just the data section, and
 +
    to remove the address field from the data section of objdump.
 +
    because the code of the solution might be different from the
 +
    code of the student's program, and could result in the addresses
 +
    of the start of the data sections to be different, although the
 +
    sections could be identical in contents.
 +
Updated 12/4/15.  Created new runModule function, and deprecated
 +
    old one.
 +
@author: thiebaut
 +
'''
 +
#--- imports ---
 +
from __future__ import print_function
 +
import subprocess
 +
import sys
 +
import os
 +
import random
 +
import imp
 +
import re
 +
import difflib
 +
import binascii  # for binary to ascii conversion
 +
 +
#--- GLOBALS ---
 +
interpreter = sys.executable
 +
userModuleAlreadyImported = False
 +
import subprocess
 +
 +
#--- use a different command line for nasm and ld if this runs on
 +
#--- a mac, as opposed to Linux.
 +
useNasmOnMac = subprocess.check_output( ["uname", "-s"] ).decode( 'ascii' ).find( "Darwin" ) != -1
 +
 +
#--- READ PARAMS ---
 +
from params import *
 +
if removeComments or ( 'patternsMustBePresent' in globals() and len( patternsMustBePresent ) != 0) \
 +
    or ( 'patternsMustNotBePresent' in globals() and len( patternsMustNotBePresent ) != 0 ):
 +
    #import pyminifier
 +
    pass
 +
 +
#--- debugging ---
 +
debug = False
 +
 +
#--- Grade scale ---
 +
def letter2Number( letter ):
 +
    """takes a letter and returns a number.  The scale is the default scale on Moodle.
 +
    """
 +
    dico = {'A': (93.00, 100.00),'A-': (90.00, 92.99),
 +
            'B': (83.00, 86.99), 'B+': (87.00, 89.99),
 +
            'B-': (80.00, 82.99),'C': (73.00, 76.99),
 +
            'C+': (77.00, 79.99),'C-': (70.00, 72.99),
 +
            'D': (60.00, 66.99), 'D+': (67.00, 69.99),
 +
            'F': (0.00, 59.99)}
 +
    return dico[letter.upper().strip()][1]
 +
 +
#--- UTILITY FUNCTIONS ---
 +
def commentLong( text ):
 +
    lines = text.split( "\n" )
 +
    text = "\n".join( [ " " + k for k in lines ] )
 +
    print( "<|--\n" + text  + "\n --|>" )
 +
 +
def commentLongStart( ):
 +
    print( "<|--\n" )
 +
   
 +
def commentLongEnd( ):
 +
    print( "\n --|>" )
 +
   
 +
def commentShort( text ):
 +
    print( "Comment :=>> " + text )
 +
 +
def comment( text ):
 +
    commentShort( text )
 +
 +
def printGrade( grade ):
 +
    commentShort( "Your grade is " + grade + " (which Moodle will translate in " + str( letter2Number( grade ) )+")" )
 +
    print( "Grade :=>> ", letter2Number( grade ) )
 +
 +
def createFileWithString( fileName, string ):
 +
    file = open( fileName, "w" )
 +
    file.write( string)
 +
    file.close()
 +
 +
def removeBlankLinesFrom( lines ):
 +
    newLines = []
 +
    for line in lines:
 +
        if len( line.strip() )==0:
 +
            continue
 +
        newLines.append( line )
 +
 +
    return newLines
 +
 +
#----------------------------------------------------------------------
 +
#                        ASSEMBLY REALTED FILES
 +
#----------------------------------------------------------------------
 +
def assemble( fileNameNoExt ):
 +
    global useNasmOnMac
 +
    command = "nasm -f elf -w-all %s.asm" % fileNameNoExt
 +
    if useNasmOnMac:
 +
        command = "nasm -f macho  %s.asm -DMAC" % fileNameNoExt
 +
    command = command.split()
 +
    #print( "command = ", command )
 +
    p = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 +
    (output, err) = p.communicate()
 +
   
 +
    err =  err.decode('ascii')
 +
    output =  output.decode('ascii')
 +
    log( "assemble: output = " + output + "\nassemble: err = " + err + "\n" )
 +
 +
    if len( err ) >= 1:
 +
        return False, err
 +
 +
    return True, output
 +
 +
def link( fileNameNoExt, testModuleNames ):
 +
    global useNasmOnMac
 +
   
 +
    objects = " ".join( [ k +".o" for k in [fileNameNoExt] + testModuleNames ] )
 +
    command = "ld -melf_i386 %s -o %s" % ( objects, fileNameNoExt )
 +
    if useNasmOnMac:
 +
        command = "ld -e _start -macosx_version_min 10.6 -o %s %s" % (fileNameNoExt, objects )
 +
       
 +
    log( "link command = " + command )
 +
    command = command.split()
 +
   
 +
    p = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 +
    (output, err) = p.communicate()
 +
   
 +
    err =  err.decode('ascii')
 +
    output =  output.decode('ascii')
 +
 +
    log( "Link: output = " + output + "\nlink: err = " + err + "\n" )
 +
   
 +
    if len( err ) >= 1:
 +
        return False, err
 +
    return True, output
 +
 +
def runExecutable( fileNameNoExt, input = None ):
 +
    command = "./%s" % fileNameNoExt
 +
    log( "run command = " + command )
 +
 +
    command = command.split()
 +
    if input != None:
 +
        log( "feeding file %s to stdin" % input )
 +
        fileInput = open( input, "r" )
 +
        p = subprocess.Popen( command, stdin=fileInput,
 +
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 +
    else:
 +
        p = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 +
    (output, err) = p.communicate()
 +
   
 +
    log( "runExecutable: ")
 +
    err =  err.decode('ascii')
 +
    output =  output.decode('ascii')
 +
   
 +
    if len( err ) >= 1:
 +
        return False, err
 +
    return True, output
 +
 +
def runModule( module, inputFileName, outputFileName, testModuleNames=[] ):
 +
    global userOutSplitPattern
 +
    global moduleContainsMain
 +
    global programOutputInFile
 +
 +
    Ok, output = runExecutable( module, inputFileName )
 +
 +
    open( outputFileName, "w" ).write( output + "\n" )
 +
   
 +
    #--- if output to test is in file, open the file and print its contents
 +
    #--- as if the program had sent it to the output
 +
    if Ok and ('programOutputInFile' in globals()) and programOutputInFile == True:
 +
        outText = open( outputDataFile, "r" ).read()
 +
        log( outText )
 +
 
 +
    log( "runModule: ")
 +
    index = output.find( userOutSplitPattern )
 +
    log( "runModule: index = %d text = %s" % ( index, output ) )
 +
    if (userOutSplitPattern != "" and index != -1) or (userOutSplitPattern == "" ):
 +
        output = output[index + len( userOutSplitPattern ): ]
 +
        file = open( outputFileName, "w" )
 +
        file.write( output )
 +
        file.close()
 +
 +
    return not Ok, output, 0
 +
   
 +
   
 +
def assembleLinkAndRun( asmFile, testModuleNames = [], input=None ):
 +
    # assemble
 +
    log( "assembleLinkAndRun( %s, %s )" % ( asmFile, testModuleNames ) )
 +
    for asmFile1 in [asmFile] + testModuleNames:
 +
        Ok, text = assemble( asmFile1 )
 +
        if not Ok:
 +
            log( "**** Assembly Error: " + text )
 +
            return False, "Nasm Error\n" + text
 +
        log( "assembleLinkAndRun: assemble( " + asmFile1 + ") returned " + text + "\n")
 +
   
 +
    # link
 +
    Ok, text = link( asmFile, testModuleNames )
 +
    if not Ok:
 +
        log( "**** link Error: " + text )
 +
        return False, "Link Error\n" + text
 +
    log( "assembleLinkAndRun: ld( " + asmFile + ") returned " + text + "\n")
 +
   
 +
    #print( "assembly/linking successful" )
 +
   
 +
    Ok, text = runExecutable( asmFile, input )
 +
    if not Ok:
 +
        #print( "Run-Time Error: "  + text )
 +
        return False, "Run-time Error\n" + text
 +
    log( "assembleLinkAndRun: run( " + asmFile + ") returned " + text + "\n")
 +
 +
    return True, text
 +
 +
def getFilesInCurrentDirectory():   
 +
    return "\n".join(  [f for f in os.listdir('.') if os.path.isfile(f)] )
 +
 +
def hexdump( fileNameNoExt ):
 +
    command = "hexdump -v -C %s" % fileNameNoExt
 +
    command = command.split()
 +
    #print( "command = ", command )
 +
    p = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 +
    (output, err) = p.communicate()
 +
   
 +
    err =  err.decode('ascii')
 +
    return  output.decode('ascii')
 +
 +
def objdump( fileNameNoExt, sections = [".text", ".data" ] ):
 +
    objdumpCommand = "objdump -s -j %s " + fileNameNoExt
 +
    if useNasmOnMac == True:
 +
        objdumpCommand = "/opt/local/bin/gobjdump -s -j %s " + fileNameNoExt
 +
   
 +
    s = ""
 +
    for part in sections: #[ ".text", ".data" ]:
 +
        commandList = ( objdumpCommand % part ).split()
 +
        p = subprocess.Popen( commandList, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 +
        (output, err) = p.communicate()
 +
   
 +
        err =  err.decode('ascii')
 +
        output =  output.decode('ascii') + "\n"
 +
        log( "objdump = " + output )
 +
        s += output
 +
       
 +
    newOut = []
 +
    for line in s.split( "\n" ):
 +
        if line.find( "file" ) != -1: continue
 +
        if line.find( "Contents" ) != -1: continue
 +
        if len( line ) <= 1: continue
 +
        # remove the address from output
 +
        line = " ".join( line.split()[1:] )
 +
        newOut.append( line )
 +
       
 +
    return "\n".join( newOut )
 +
 +
def compareBinaryFiles( binFile1, binFile2, sections = [".text", ".data" ] ):
 +
    """compare the contents of two executables, and return False and a
 +
    message if different, or True and "" if the same."""
 +
    # get the contents
 +
   
 +
    log( "compareBinaryFiles( %s, %s, sections=%s):\n objdump( %s ) = \n%s\nobjdump( %s ) =\n%s\n"
 +
          % ( binFile1, binFile2, str(sections), binFile1, objdump( binFile1, sections ),
 +
              binFile2, objdump( binFile2, sections ) ) )
 +
 +
    objdump1 = objdump( binFile1, sections )
 +
    objdump2 = objdump( binFile2, sections )
 +
               
 +
    if objdump1 != objdump2:
 +
        log( "CompareBinaryFiles: Mismatched between user:\n" + objdump1 + "\nExpected code and data:\n"
 +
                    + objdump2 + "\n" )
 +
        return False, "Your code and data sections below:\n" \
 +
                    + objdump1 + "\nExpected code and data:\n" \
 +
                    + objdump2 + "\n"
 +
   
 +
    log( "objdump1 = " + objdump1 )
 +
    log( "objdump2 = " + objdump2 )
 +
   
 +
    # all good!
 +
    return ( True, "" )
 +
 +
#----------------------------------------------------------------------
 +
# Logging
 +
def log( message ):
 +
    open( "log.txt", "a" ).write( message + "\n" )
 +
   
 +
def resetLog( ):
 +
    open( "log.txt", "w" )
 +
   
 +
#----------------------------------------------------------------------
 +
# Functions running other modules
 +
#----------------------------------------------------------------------
 +
def checkModuleRunsOk( module, inputFileName, testModuleNames = [] ):
 +
    """ just runs the module with a dummy input file or dummy
 +
    stdin to make sure the module runs without crashing.
 +
   
 +
    """
 +
    #global interpreter
 +
    log( "checkModuleRunsOk( %s, %s, %s )" %
 +
        ( module, inputFileName, testModuleNames ) )
 +
 +
    #--- create data files if needed ---
 +
    createDataFiles()
 +
 +
    #--- create executable ---
 +
    Ok, text = assembleLinkAndRun( module, testModuleNames, inputFileName )
 +
   
 +
    # --- run program once ---
 +
    #p = subprocess.Popen( [ "./" +  module ],
 +
    #                      stdout=subprocess.PIPE,
 +
    #                      stderr=subprocess.PIPE,
 +
    #                      stdin=subprocess.PIPE)
 +
 +
    #print( "inputFileName = ", inputFileName )
 +
    #print( "open( inputFileName, r).read() = ", open( inputFileName, "r" ).read() )
 +
   
 +
    #p.stdin.write( bytes( open( inputFileName, "r" ).read(), 'UTF-8' ) )
 +
    #data = p.communicate( )
 +
    #p.stdin.close()
 +
 +
    #error = data[1].decode( 'UTF-8' )
 +
    #if len( error ) > 1:
 +
    #    return False, error
 +
    #return True, None
 +
    if not Ok:
 +
        return False, text
 +
    else:
 +
        return True, None
 +
 +
def extractTextFromErrorMessage( sys_exc_info ):
 +
    text = []
 +
    try:
 +
        for field in sys_exc_info:
 +
            k = str( field )
 +
            if k.find( "traceback" ) != -1: continue
 +
            if k[0]=="<" and k[-1]==">": k = k[1:-1]
 +
            text.append( k )
 +
        text = ": ".join( text )
 +
    except:
 +
        text = sys_exc_info
 +
    log( "extractTextFromErrorMessage( %s ) returns %s" % ( str( sys_exc_info ), text ) )
 +
    return text
 +
 +
def runModuleOld( module, inputFileName, outputFileName, testModuleNames=[] ): # deprecated
 +
    """
 +
    runModule: runs the module, passes it data from the input file on its stdin
 +
    and get its output on stdout captured in outputFileName.
 +
    We assume the module will not crash, because we already tested
 +
    it with checkModuleRunsOk()."""
 +
   
 +
    global userOutSplitPattern
 +
    global moduleContainsMain
 +
    global programOutputInFile
 +
    global mustCallMain
 +
   
 +
    error = False
 +
 +
    #--- create data files if needed ---
 +
    createDataFiles()
 +
   
 +
    #--- make stdin read information from the text file
 +
    sys.stdin = open( inputFileName, "r" )
 +
 +
    #--- run the student program ---
 +
    try:
 +
        Ok, text = assembleLinkAndRun( module, testModuleNames )                 
 +
        log( "returned from assembleLinkAndRun: text = " + text )
 +
        #log( "After assembling and linking " + module + ", files in dir = " + getFilesInCurrentDirectory() )
 +
    except:
 +
        error = True
 +
        #sys.stderr.close()
 +
        #sys.stderr = saveStdErr
 +
        #sys.stdout.close()
 +
        #sys.stdout = saveStdOut
 +
       
 +
        try:
 +
            text = open( outputFileName, "r" ).read() + "\n" + text
 +
        except:
 +
            pass
 +
        return error, text, 0
 +
     
 +
    if not Ok:
 +
        return True, text, 0
 +
   
 +
    #--- if output to test is in file, open the file and print its contents
 +
    #--- as if the program had sent it to the output
 +
    if Ok and ('programOutputInFile' in globals()) and programOutputInFile == True:
 +
        outText = open( outputDataFile, "r" ).read()
 +
        print( "\n<<< Contents of file %s >>>" % outputDataFile )
 +
        print( outText )
 +
        print( "<<< End of file %s >>>" % outputDataFile )
 +
        log( outText )
 +
 
 +
    log( "runModule: ")
 +
    index = text.find( userOutSplitPattern )
 +
    log( "runModule: index = %d text = %s" % ( index, text ) )
 +
    if (userOutSplitPattern != "" and index != -1) or (userOutSplitPattern == "" ):
 +
        text = text[index + len( userOutSplitPattern ): ]
 +
        file = open( outputFileName, "w" )
 +
        file.write( text )
 +
        file.close()
 +
 +
   
 +
    #print( "**B: text = ", text )
 +
    return False, text, 0
 +
 +
 +
def getNumbers( s ):
 +
    """returns 3 lists of numbers from a string.
 +
    """
 +
    ints = []
 +
    floats = []
 +
    intFloats = []
 +
    for word in s.replace(',', ' ').split():
 +
        try:
 +
            ints.append( int( word ) )
 +
            intFloats.append( int( word ) )
 +
            continue
 +
        except:
 +
            pass
 +
        try:
 +
            floats.append( float( word ) )
 +
            intFloats.append( float( word ) )
 +
            continue
 +
        except:
 +
            pass
 +
 +
    return ints, floats, intFloats
 +
 +
def compareNumbers( line1, line2 ):
 +
    dummy, dummy, numbers1 = getNumbers( line1 )
 +
    dummy, dummy, numbers2 = getNumbers( line2 )
 +
    #print( "numbers1 = ", numbers1 )
 +
    #print( "numbers2 = ", numbers2 )
 +
    if len( numbers1 ) != len( numbers2 ):
 +
        return False
 +
   
 +
    for i in range( len( numbers1 ) ):
 +
        x1 = numbers1[i]
 +
        x2 = numbers2[i]
 +
        #print( "compareNumbers(): x1 = ", x1, " x2 = ", x2, end=" " )
 +
        if x1==0 or x1==0.0:
 +
            if abs( (x1-x2) ) > 0.01:
 +
                #print( "returning False <--------------------------")
 +
                return False
 +
            continue
 +
        if abs( (x1-x2)/x1 ) > 0.01:
 +
            #print( "returning False <--------------------------" )
 +
            return False
 +
       
 +
    #print( "returning True" )
 +
    return True
 +
 +
def removeLinesWithNoNumbers( lines ):
 +
    """ gets a list of lines and returns a list of lines that only contained
 +
    numbers (ints or floats), and removes words from line."""
 +
    newLines = []
 +
    for line in lines:
 +
        dummy, dummy, numbers = getNumbers( line )
 +
        if len( numbers ) == 0: continue
 +
        newLines.append( line )
 +
    return newLines
 +
   
 +
def removeAllExceptNumbers( lines ):
 +
    """ gets a list of lines and returns a list of lines that only contained
 +
    numbers (ints or floats), and removes words from line."""
 +
    newLines = []
 +
    for line in lines:
 +
        dummy, dummy, numbers = getNumbers( line )
 +
        #print( "numbers = " , numbers )
 +
        if len( numbers ) == 0: continue
 +
        newLines.append( " ".join( [str(k) for k in numbers] ) )
 +
    return newLines
 +
   
 +
"""
 +
def compareUserExpectedOld( inputLines, userOutText, expectedOutText ):
 +
    global stripOutputLines, compareOnlyNumbersInOutput
 +
    global removeBlankLines, removeExtaSpaces
 +
    print( "removeBlankLines = ", removeBlankLines )
 +
    # split in lines
 +
    userOutTextLines = userOutText.split( "\n" )
 +
    expectedOutTextLines = expectedOutText.split( "\n" )
 +
   
 +
    if stripOutputLines:
 +
        userOutTextLines = [k.strip() for k in userOutTextLines ]
 +
        expectedOutTextLines = [k.strip() for k in expectedOutTextLines ]
 +
       
 +
    if removeBlankLines:
 +
        userOutTextLines = removeBlankLinesFrom( userOutTextLines )
 +
        expectedOutTextLines = removeBlankLinesFrom( expectedOutTextLines )
 +
        print( "removeBlankLines: ", "\n".join( expectedOutTextLines ) )
 +
       
 +
    if removeExtaSpaces:
 +
        userOutTextLines = [' '.join( k.split()) for k in userOutTextLines]
 +
        expectedOutTextLines = [' '.join( k.split()) for k in expectedOutTextLines]
 +
       
 +
    if sortOutputLines:
 +
        userOutTextLines.sort()
 +
        expectedOutTextLines.sort()
 +
 +
    if compareOnlyNumbersInOutput:
 +
        userOutTextLines = removeLinesWithNoNumbers( userOutTextLines )
 +
        expectedOutTextLines = removeLinesWithNoNumbers( expectedOutTextLines )
 +
       
 +
    #print( "userOutTextLines =", userOutTextLines )
 +
    #print( "expectedOutTextLines =", expectedOutTextLines )
 +
    report = []
 +
    misMatchLineNumbers = []
 +
   
 +
    for i in range( 0, min( len(userOutTextLines), len( expectedOutTextLines))):
 +
        if compareOnlyNumbersInOutput:
 +
            #print( "compareNumbers( ", userOutTextLines[i], ", ", expectedOutTextLines[i], ") = ",
 +
            #      compareNumbers( userOutTextLines[i], expectedOutTextLines[i] ) )
 +
            if compareNumbers( userOutTextLines[i], expectedOutTextLines[i] ) == False:
 +
                misMatchLineNumbers.append( i )
 +
                report.append( "**ERROR***>" +  showSpaces( userOutTextLines[i] ) )
 +
                report.append( "*EXPECTED*>" +  showSpaces( expectedOutTextLines[i] ) )
 +
            else:
 +
                report.append( "*CORRECT**>" + showSpaces( userOutTextLines[i] ) )
 +
               
 +
        elif userOutputMustContainExpectedOutpout:
 +
            if userOutTextLines[i].find( expectedOutTextLines[i] ) == -1:
 +
                misMatchLineNumbers.append( i )
 +
                report.append( "**ERROR***>" +  showSpaces( userOutTextLines[i] ) )
 +
                report.append( "*SHOULD HAVE CONTAINED*>" +  showSpaces( expectedOutTextLines[i] ) )
 +
            else:
 +
                report.append( "*CORRECT**>" +  showSpaces( userOutTextLines[i] ) )
 +
                         
 +
        else:
 +
            if userOutTextLines[i] != expectedOutTextLines[i]:
 +
                #print( i, userOutTextLines[i], expectedOutTextLines[i], sep="\n\t")
 +
                misMatchLineNumbers.append( i )
 +
                report.append( "**ERROR***>" +  showSpaces( userOutTextLines[i] ) )
 +
                report.append( "*EXPECTED*>" +  showSpaces( expectedOutTextLines[i] ))
 +
            else:
 +
                report.append( "*CORRECT**>" +  showSpaces( userOutTextLines[i] ) )
 +
   
 +
    if  len(userOutTextLines) != len( expectedOutTextLines):
 +
        misMatchLineNumbers.append( min( len(userOutTextLines), len( expectedOutTextLines)) )
 +
        report.append( "\n**ERROR***> your output contains %d lines, but %d lines are expected."
 +
                      % ( len(userOutTextLines), len( expectedOutTextLines) ) )
 +
       
 +
    return misMatchLineNumbers, report
 +
"""
 +
 +
def compareUserExpected( inputLines, userOutText, expectedOutText ):
 +
    log( "compareUserExpected()")
 +
    log( "userOutText = " + str( userOutText ) )
 +
    log( "expectedOutText = " + str( expectedOutText ) )
 +
    global stripOutputLines, compareOnlyNumbersInOutput
 +
    global removeBlankLines, removeExtaSpaces
 +
       
 +
    # split in lines
 +
    userOutTextLines = userOutText.split( "\n" )
 +
    expectedOutTextLines = expectedOutText.split( "\n" )
 +
   
 +
    if stripOutputLines:
 +
        userOutTextLines = [k.strip() for k in userOutTextLines ]
 +
        expectedOutTextLines = [k.strip() for k in expectedOutTextLines ]
 +
       
 +
    #print( "removeBlankLines = ", removeBlankLines )
 +
    if removeBlankLines:
 +
        userOutTextLines = removeBlankLinesFrom( userOutTextLines )
 +
        expectedOutTextLines = removeBlankLinesFrom( expectedOutTextLines )
 +
        #print( "expectedOutTextLines = " + "\n".join(expectedOutTextLines ))
 +
       
 +
    if removeExtaSpaces:
 +
        userOutTextLines = [' '.join( k.split()) for k in userOutTextLines]
 +
        expectedOutTextLines = [' '.join( k.split()) for k in expectedOutTextLines]
 +
       
 +
    if sortOutputLines:
 +
        userOutTextLines.sort()
 +
        expectedOutTextLines.sort()
 +
 +
    if compareOnlyNumbersInOutput:
 +
        userOutTextLines = removeAllExceptNumbers( userOutTextLines )
 +
        expectedOutTextLines = removeAllExceptNumbers( expectedOutTextLines )
 +
   
 +
    log( "compareUserExpected: userOutTextLines =" + str( userOutTextLines ) )
 +
    log( "compareUserExpected: expectedOutTextLines =" + str( expectedOutTextLines ) )
 +
    misMatchLineNumbers = []
 +
   
 +
    # create a difference report
 +
    d = difflib.Differ()
 +
    diff = d.compare( userOutTextLines, expectedOutTextLines )
 +
    reportLines  =[k.strip() for k in diff]
 +
    for i in range( len( reportLines ) ):
 +
        if reportLines[i].startswith( '+' ):
 +
            misMatchLineNumbers.append( i+1 )
 +
           
 +
    #print( "misMatchLineNumbers = ", misMatchLineNumbers )
 +
    #print( "report = ", "\n".join( reportLines ) )
 +
    return misMatchLineNumbers, reportLines
 +
 +
 +
def testProgramWithInputFiles():
 +
    """compare output of user program to solution program.
 +
    One or more input files are used"""
 +
    global moduleName
 +
    global solutionFileName
 +
    global noInputFiles
 +
    global input1, input2, input3, input4, input5
 +
    global GRADE_CORRECTOUTPUT
 +
    global GRADE_INCORRECTOUTPUT
 +
    global GRADE_PROGRAMCRASHES
 +
    global FEEDBACK_INCORRECTOUTPUT
 +
    global FEEDBACK_CORRECTOUTPUT
 +
    global FEEDBACK_PROGRAMCRASHES
 +
    global FEEDBACK_OUTPUTNOTFORMATTEDCORRECTLY
 +
    global gradeLinearlyWithNumberCorrectLines
 +
   
 +
    log( "testProgramWithInputFiles()" )
 +
 +
    input = [input1, input2, input3, input4, input5]
 +
 +
    # test if user program crashes on its own
 +
    createFileWithString( "input.txt", input[0] )
 +
 +
    log( "\nCreating file input.txt for checkModuleRunsOk(), containing: " + input[0] + "\n")
 +
   
 +
    if "testModuleNames" in globals() and len( testModuleNames ) != 0:
 +
        Ok, errorMessage = checkModuleRunsOk( moduleName, "input.txt", testModuleNames )
 +
    else:
 +
        Ok, errorMessage = checkModuleRunsOk( moduleName, "input.txt" )
 +
 +
    if not Ok:
 +
        commentLong( FEEDBACK_PROGRAMCRASHES )
 +
        commentLong( "- The Assemble/Link/Run process stopped with this error:\n"
 +
                    + "Error message:\n"
 +
                    + errorMessage + "\n" )
 +
        log( "- The Assemble/Link/Run process stopped with this error:\n"
 +
                    + "Error message:\n"
 +
                    + errorMessage + "\n" )
 +
        printGrade( GRADE_PROGRAMCRASHES )
 +
        return False
 +
 +
 +
 +
    numberTestsPassed = 0
 +
   
 +
    for testNo in range( noInputFiles ):
 +
        comment( "- Test No. " + str( testNo+1 ) )
 +
        log( "- Test No. " + str( testNo+1 ) )
 +
        createFileWithString( "input.txt", input[testNo] )
 +
       
 +
        # get solution program's output       
 +
        dummy, expectedOutText = assembleLinkAndRun( solutionFileName, testModuleNames, "input.txt" )
 +
 +
 +
        # get user's program output 
 +
        if "testModuleNames" in globals() and len( testModuleNames ) != 0:
 +
            error, userOutText, score2  = runModule( moduleName, "input.txt",
 +
                                                    "userOut", testModuleNames )
 +
        else:
 +
            error, userOutText, score2  = runModule( moduleName, "input.txt", "userOut" )
 +
 +
   
 +
        if error:
 +
            commentLong( FEEDBACK_PROGRAMCRASHES )
 +
            commentLong( "- Your program crashed with this error:\n"
 +
                        + "Error message:\n"
 +
                        + userOutText + "\n" )
 +
            printGrade( GRADE_PROGRAMCRASHES )
 +
            return False
 +
 +
        missMatchLineNumbers, report = \
 +
            compareUserExpected( "", userOutText, expectedOutText )
 +
 +
        if len( missMatchLineNumbers ) != 0:
 +
            commentLong( FEEDBACK_INCORRECTOUTPUT + "\nThe input was:\n"
 +
                        + open( "input.txt", "r").read()  )               
 +
            commentLong( "\n".join( report ))
 +
        else:
 +
            commentLong( "Test passed: input was: " + open( "input.txt", "r").read() )
 +
            numberTestsPassed += 1
 +
 +
    if numberTestsPassed == noInputFiles:       
 +
        commentLong( FEEDBACK_CORRECTOUTPUT )
 +
        printGrade( GRADE_CORRECTOUTPUT )
 +
    else:     
 +
        commentLong( FEEDBACK_INCORRECTOUTPUT )
 +
        printGrade( GRADE_INCORRECTOUTPUT )
 +
       
 +
    return True
 +
 +
def showSpaces( text ):
 +
    newText = []
 +
    for line in text.split( "\n" ):
 +
        newText.append( line.replace( ' ', '~') )
 +
    return '\n'.join( newText )
 +
 +
def testProgramWithNoInputFiles():
 +
    """compare output of user program to solution program.
 +
    no input files are used"""
 +
    global moduleName
 +
    global testModuleNames
 +
    global solutionFileName
 +
    global GRADE_CORRECTOUTPUT
 +
    global GRADE_INCORRECTOUTPUT
 +
    global GRADE_PROGRAMCRASHES
 +
    global FEEDBACK_INCORRECTOUTPUT
 +
    global FEEDBACK_CORRECTOUTPUT
 +
    global FEEDBACK_PROGRAMCRASHES
 +
    global GRADE_OUTPUTNOTFORMATTEDCORRECTLY
 +
    global GRADE_LINEARGRADINGMINGRADE
 +
    global GRADE_LINEARGRADINGMAXGRADE
 +
    global FEEDBACK_OUTPUTNOTFORMATTEDCORRECTLY
 +
    global gradeLinearlyWithNumberCorrectLines
 +
   
 +
    # test if user program crashes on its own
 +
    createFileWithString( "input.txt", "\n\n\n" )
 +
    if "testModuleNames" in globals() and len( testModuleNames ) != 0:
 +
        Ok, errorMessage = checkModuleRunsOk( moduleName, "input.txt", testModuleNames )
 +
    else:
 +
        Ok, errorMessage = checkModuleRunsOk( moduleName, "input.txt" )
 +
       
 +
    if not Ok:
 +
        commentLong( FEEDBACK_PROGRAMCRASHES )
 +
        commentLong( "- Your program crashed with this error:\n"
 +
                    + "Error message:\n"
 +
                    + errorMessage + "\n" )
 +
        printGrade( GRADE_PROGRAMCRASHES )
 +
        return False
 +
         
 +
    # get solution program's output
 +
    if "testModuleNames" in globals() and len( testModuleNames ) != 0:
 +
        dummy, expectedOutText, score2 = runModule( solutionFileName, "input.txt", "expectedOut", testModuleNames )
 +
    else:
 +
        dummy, expectedOutText, score2 = runModule( solutionFileName, "input.txt", "expectedOut" )
 +
       
 +
    # get user's program output
 +
    if "testModuleNames" in globals() and len( testModuleNames ) != 0:
 +
        error, userOutText, score2  = runModule( moduleName, "input.txt", "userOut", testModuleNames )
 +
    else:
 +
        error, userOutText, score2  = runModule( moduleName, "input.txt", "userOut" )
 +
       
 +
    if debug: print( "userOutText =" , userOutText )
 +
    if error:
 +
        commentLong( FEEDBACK_PROGRAMCRASHES )
 +
        commentLong( "- Your program crashed with this error:\n"
 +
                    + "Error message:\n"
 +
                    + userOutText + "\n" )
 +
        printGrade( GRADE_PROGRAMCRASHES )
 +
        return False
 +
 +
    # test if executables are identical
 +
    if 'compareExecutables' in globals() and compareExecutables == True:
 +
        Ok, errorMessage = compareBinaryFiles( moduleName, solutionFileName,
 +
                                              sections=[".data", ".text" ] )
 +
        log( "compareExecutable returns %s, %s" % (str(Ok), errorMessage))
 +
        if not Ok:
 +
            commentLong( FEEDBACK_EXECUTABLESDIFFERENTSIZE )
 +
            commentLong( "- Your executable does not match the solution executable:\n"
 +
                        "Below are the hex versions of your code and data sections,\n"
 +
                        +"against the expected code and data sections.\n"
 +
                        + errorMessage )
 +
            printGrade( GRADE_EXECUTABLESDIFFERENTSIZE )
 +
            return False
 +
 +
    # test if data sections are identical
 +
    if 'compareDataSections' in globals() and compareDataSections == True:
 +
        Ok, errorMessage = compareBinaryFiles( moduleName, solutionFileName,
 +
                                              sections=[ ".data" ] )
 +
        log( "compareDataSections returns %s, %s" % (str(Ok), errorMessage))
 +
        if not Ok:
 +
            commentLong( FEEDBACK_DATASECTIONSDIFFERENTSIZE )
 +
            commentLong( "- Your data section has been modified.\n"
 +
                        +"This is not allowed for this problem.\n"
 +
                        + errorMessage )
 +
            printGrade( GRADE_DATASECTIONSDIFFERENTSIZE )
 +
            return False
 +
 +
 
 +
    if checkForBoxedOutput:
 +
        Ok = testForBoxedOutpout( userOutText )
 +
        if not Ok:
 +
            commentLong( FEEDBACK_OUTPUTNOTFORMATTEDCORRECTLY + "\n"
 +
                    + showSpaces( userOutText ) + "\n" )
 +
            printGrade( GRADE_OUTPUTNOTFORMATTEDCORRECTLY )
 +
            return False
 +
        else:
 +
            comment( "Output correctly formatted.\nTesting contents.")
 +
 +
    #--- if linear grade required, compute degree of matching between student
 +
    #--- program output and solution outout.
 +
    if gradeLinearlyWithNumberCorrectLines:
 +
            percent, report = computePercentGoodOutput( userOutText, expectedOutText )
 +
            if percent < 1.0:
 +
                commentLong( FEEDBACK_INCORRECTOUTPUT )
 +
                commentLong( report )
 +
                grade = computeLinearGrade(  percent, GRADE_LINEARGRADINGMINGRADE, GRADE_LINEARGRADINGMAXGRADE )
 +
                printGrade( grade )
 +
                return False
 +
            else:
 +
                commentLong( FEEDBACK_CORRECTOUTPUT )
 +
                printGrade( GRADE_CORRECTOUTPUT )
 +
                return True
 +
               
 +
    else:
 +
        log( "compareUserExpected: All good! testing userOutText = " + str( userOutText ) +
 +
                " against expectedOutText = " + str( expectedOutText ) )
 +
        missMatchLineNumbers, report = \
 +
            compareUserExpected( "", userOutText, expectedOutText )
 +
               
 +
        if len( missMatchLineNumbers ) != 0:
 +
            commentLong( FEEDBACK_INCORRECTOUTPUT )
 +
            commentLong( "\n".join( report ))
 +
            printGrade( GRADE_INCORRECTOUTPUT )
 +
            return False
 +
 +
 +
    commentLong( FEEDBACK_CORRECTOUTPUT )
 +
    printGrade( GRADE_CORRECTOUTPUT )
 +
    return True
 +
 +
def testForBoxedOutpout( userOut ):
 +
    """returns True if output has lines of same length (after being
 +
    stripped.  returns False otherwise."""
 +
    lines = userOut.split( "\n" )
 +
    lines = [k.strip() for k in lines if len(k.strip()) != 0 ]
 +
    if len( lines ) == 0:
 +
        return False
 +
    boxLength = len( lines[0] )
 +
    for line in lines:
 +
        if len( line ) != boxLength:
 +
            return False
 +
    return True
 +
 +
"""
 +
def findLineNo( pattern, text ):
 +
    import re
 +
    for i, line in enumerate( text.split( "\n" ) ):
 +
        #if line.find( pattern ) != -1:
 +
        #    return i+1
 +
        res = re.search( pattern, line )
 +
        if res != None:
 +
            return i
 +
       
 +
    return -1
 +
"""
 +
 +
def createInputFiles():
 +
    """create input data files for the program.  This should be called
 +
    before every call to the solution or student's program, as the assignment
 +
    may require the program to modify the input data file."""
 +
    # create data files if necessary
 +
    if 'noInputFiles' in globals():
 +
        #print( "found noDataFiles in globals" )
 +
        for i in range( noInputFiles ):
 +
            inputName = "input%d" % (i+1)
 +
            fileText = globals()[ inputName ]
 +
            log( "writing to " + inputName +  ":" + fileText + "\n" )
 +
            open( inputName, "w" ).write( fileText )
 +
    else:
 +
        log( "createDataFiles(): no files found to create!" )
 +
#     
 +
def createDataFiles():
 +
    """create input data files for the program.  This should be called
 +
    before every call to the solution or student's program, as the assignment
 +
    may require the program to modify the input data file."""
 +
    # create data files if necessary
 +
    if 'noDataFiles' in globals():
 +
        #print( "found noDataFiles in globals" )
 +
        for i in range( noDataFiles ):
 +
            fileName = "dataFile%dName" % (i+1)
 +
            fileName = globals()[ fileName ]
 +
            fileText = "dataFile%d" % (i+1)
 +
            fileText = globals()[ fileText ]
 +
            log( "writing to " + fileName +  ":" + fileText + "\n" )
 +
            open( fileName, "w" ).write( fileText )
 +
    else:
 +
        log( "createDataFiles(): no files found to create!" )
 +
#----------------------------------------------------------------------
 +
# functions related to finding several string patterns on the same
 +
# line in the source code
 +
# For example, to see if the program contains "def main():" left
 +
# aligned, in the source, create a list [ "^def ", " main():" ]
 +
# and pass it to patternsAppearOnSameLine().
 +
def findLineNos( pattern, text ):
 +
    """returns a list of line numbers where the pattern
 +
    appears in text"""
 +
    lineNos = []
 +
    pattern = escapeString( pattern )
 +
    for i, line in enumerate( text.split( "\n" ) ):
 +
        res = re.search( pattern, line )
 +
        if res != None:
 +
            #print( "  re.search( %s, %s ) --> %s" % ( pattern, line, res ) )
 +
            lineNos.append( i )
 +
 +
    #print( "findLineNos( '%s' ) = %s" % ( pattern, lineNos ) )     
 +
    return lineNos
 +
 +
def escapeString( s ):
 +
    s = s.replace( "(", "\(" ).replace( ")", "\)" )
 +
    s = s.replace( "[", "\[" ).replace( "]", "\]" )
 +
    return s
 +
 +
def patternsAppearOnSameLine( patterns, text ):
 +
    if len( patterns )== 0:
 +
        #print( "patternsAppearOnSameLine: case 0" )
 +
        return False # trivial case
 +
 +
    lineNos = set( findLineNos( patterns[0], text ) )
 +
    #print( "lineNos = ", lineNos )
 +
    if len( patterns )==1:
 +
        if lineNos != set():
 +
            #print( "patternsAppearOnSameLine: case 1a" )
 +
            return True
 +
        else:
 +
            #print( "patternsAppearOnSameLine: case 1b" )
 +
            return False
 +
   
 +
    for pattern in patterns[1:]:
 +
        lineNos2 = set( findLineNos( pattern, text ) )
 +
        if lineNos2.intersection( lineNos ) == set():
 +
            #print( "patternsAppearOnSameLine: case 2" )
 +
            #print( "lineNos2 = ", list(lineNos2), " lineNos = ", list(lineNos) )
 +
            return False
 +
       
 +
    #print( "patternsAppearOnSameLine: case 3" )
 +
    return True
 +
 +
def addIfNameEqMainFunc( fileName ):
 +
    """ add if __name__=="__main__": to the program
 +
    """
 +
    file = open( fileName, "r" )
 +
    lines = file.read()
 +
    file.close()
 +
 +
    newLines = ""
 +
    for line in lines.split( "\n" ):
 +
        if line.find( "main()" )==0:
 +
            line = 'if __name__ == "__main__":\n\tmain()'
 +
        newLines += line + "\n"
 +
 +
    # write it back                                                                                                   
 +
    file = open( fileName, "w" )
 +
    file.write( newLines )
 +
    file.close()
 +
 +
def computeLinearGrade( percent, minGrade, maxGrade ):
 +
    """computes the grade as a linear expression of the number of correct
 +
    lines output t0 to the expected lines, relative to the min and maxGrades
 +
    passed.  Percent should be a float between 0 and 1.0.  minGrade and maxGrade
 +
    should be a string between 'F' and 'A' ('C-', 'B+' type grades are allowed). 
 +
    """
 +
    # create list of grades, from 'F' to 'A'.
 +
    #print( "computeLinearGrade( %1.2f, %s, %s )" % ( percent, minGrade, maxGrade) )
 +
    grades = ['F']
 +
    for g in ['D','C','B','A']:
 +
        for x in ['-', '', '+' ]:
 +
            grades.append( g+x )
 +
    grades = grades[0:-1]
 +
   
 +
    # compute grade based on linear fit.  if percent is 0.0, then the grade
 +
    # will be minGrade.  If percent is 1.0, then the grade will be maxGrade.
 +
    indexLow = grades.index( minGrade.strip() )
 +
    indexHigh= grades.index( maxGrade.strip() )
 +
    index = int( round( indexLow + (indexHigh-indexLow)*percent ) )
 +
    grade = grades[index]
 +
    return grade
 +
 +
def computePercentGoodOutput( studentOut, expectedOut ):
 +
    """ computePercentGoodOutput: given two textual outputs
 +
    that should be mostly identical, computes the percentage
 +
    of matching lines between the two.  This percentage,
 +
    between 0 and 1.0 can be used to generate a grade.
 +
    """
 +
    global stripOutputLines, compareOnlyNumbersInOutput
 +
    global removeBlankLines, removeExtaSpaces
 +
       
 +
    # split in lines
 +
    userOutTextLines = studentOut.splitlines()
 +
    expectedOutTextLines = expectedOut.splitlines()
 +
   
 +
    if stripOutputLines:
 +
        userOutTextLines = [k.strip() for k in userOutTextLines ]
 +
        expectedOutTextLines = [k.strip() for k in expectedOutTextLines ]
 +
       
 +
    if removeBlankLines:
 +
        userOutTextLines = removeBlankLinesFrom( userOutTextLines )
 +
        expectedOutTextLines = removeBlankLinesFrom( expectedOutTextLines )
 +
       
 +
    if removeExtaSpaces:
 +
        userOutTextLines = [' '.join( k.split()) for k in userOutTextLines]
 +
        expectedOutTextLines = [' '.join( k.split()) for k in expectedOutTextLines]
 +
       
 +
    if sortOutputLines:
 +
        userOutTextLines.sort()
 +
        expectedOutTextLines.sort()
 +
 +
    if compareOnlyNumbersInOutput:
 +
        userOutTextLines = removeAllExceptNumbers( userOutTextLines )
 +
        expectedOutTextLines = removeAllExceptNumbers( expectedOutTextLines )
 +
   
 +
       
 +
    seq1lines = expectedOutTextLines
 +
    seq2lines = userOutTextLines
 +
   
 +
    d = difflib.Differ()
 +
    diff = d.compare( seq1lines, seq2lines )
 +
    unified_diffOutputLines = [k.strip() for k in diff]
 +
 +
    countTotal = 0 # counts matching lines
 +
    countPlus  = 0 # counts lines that start with '+'
 +
    countMinus = 0 # counts lines that start with '-'
 +
 +
    # go through each line from the comparison and perform
 +
    # some accounting.
 +
    for line in unified_diffOutputLines:
 +
        if len( line.strip() )==0: continue
 +
        if line.find( "?" )==0: continue
 +
        if line.find( "+++" )==0 or line.find( "---" )==0 \
 +
          or line.find( "@@@" )==0: continue
 +
        #print( "line: ", line )
 +
        if line[0]=="+": countPlus += 1
 +
        elif line[0]=="-": countMinus += 1
 +
        else: countTotal += 1
 +
    #print( countPlus, countMinus, countTotal )
 +
    return countTotal * 1.0 / ( countTotal + countMinus ), "\n".join( unified_diffOutputLines )
 +
   
 +
def displayInputFilesForUser():
 +
    """display the contents of the input files for the user."""
 +
    input = [input1, input2, input3, input4, input5]
 +
   
 +
    createDataFiles()
 +
    createInputFiles()
 +
 +
    commentLongStart()
 +
    print( "Your program was tested with %d input data or files:" % (noInputFiles) )
 +
    for i in range( noInputFiles ):
 +
        for file in input[i].splitlines():
 +
            print( "Input #%d: %s" % ((i+1), file) )
 +
            try:
 +
                print( open( file, "r" ).read() )
 +
            except:
 +
                pass
 +
                #print( "(invalid file name)" )
 +
        print()
 +
   
 +
    commentLongEnd()
 +
#----------------------------------------------------------------------
 +
#                                  MAIN
 +
#----------------------------------------------------------------------
 +
def main():
 +
    global noInputFiles
 +
    global moduleName
 +
    global solutionFileName
 +
    global checkForBoxedOutput
 +
    global testModuleNames
 +
           
 +
    # reset log
 +
    resetLog()
 +
   
 +
           
 +
    # test code for various patterns
 +
    """
 +
    if ('patternsMustBePresent' in globals()) \
 +
        and len( patternsMustBePresent ) != 0 or len( patternsMustNotBePresent ) != 0 or removeComments:
 +
       
 +
        # remove all comments first
 +
        #bareSource = pyminifier.removeComments( open( moduleName + ".py", "r" ).read() )
 +
        bareSource = open( moduleName + ".asm", "r" ).read()
 +
       
 +
        # deal with patterns that must be present, if any       
 +
        for i, patternGroup in enumerate( patternsMustBePresent ):
 +
            if patternsAppearOnSameLine( patternGroup, bareSource ) == False:
 +
                commentLong( FEEDBACK_PATTERNMUSTBEPRESENT[i] + "\n" )
 +
                printGrade( GRADE_ERRORPATTERNMUSTBEPRESENT )
 +
                return
 +
           
 +
        # deal with patterns that must NOT be present, if any       
 +
        for i, patternGroup in enumerate( patternsMustNotBePresent ):
 +
            if patternsAppearOnSameLine( patternGroup, bareSource ) == True:
 +
                commentLong( FEEDBACK_PATTERNMUSTNOTBEPRESENT[i] + "\n" )
 +
                printGrade( GRADE_ERRORPATTERNMUSTNOTBEPRESENT )
 +
                return
 +
    """
 +
                   
 +
    # test program runs
 +
    Ok = False
 +
    if noInputFiles == 0:
 +
        log( "calling testProgramWithNoInputFiles()")
 +
        testProgramWithNoInputFiles()
 +
        return
 +
    else: 
 +
        if 'displayContentsOfInputFiles' in globals() and displayContentsOfInputFiles==True:
 +
            displayInputFilesForUser()
 +
        log( "calling testProgramWithInputFiles()")
 +
        testProgramWithInputFiles()
 +
       
 +
main()
 +
 +
 +
</source>
 +
<br />
 +
==params.py==
 +
::<source lang="python">
 +
<br />
 +
"""
 +
params.py
 +
D. Thiebaut
 +
 +
This module contains only constants used by evaluate.py to run a student program
 +
and a solution program for a given assignment.  The outputs of the two programs
 +
are compared and used to generate a grade.
 +
 +
Edit the different constants according to how the student program is supposed
 +
to operate, and what is important in the output.  Options allow to test only
 +
the numbers in the output, skipping words, to remove all blank lines from
 +
the output, to remove all extra whitespace, or to sort the output lines
 +
alphabetically. 
 +
 +
06/21/15 Version 1: first release
 +
"""
 +
 +
 +
# moduleName: this is the name of the student program.  Do not include
 +
# the .py at the end.
 +
moduleName = "hw2"
 +
testModuleNames = [ "231Lib" ]
 +
 +
# solutionFileName: the name of the solution program. Do not include the
 +
# .py extension at the end
 +
solutionFileName = "hw2sol"
 +
 +
 +
# removeBlankLines: set to True if blank lines should be removed from
 +
# the student program and from the solution program output.
 +
removeBlankLines = True
 +
 +
 +
# removeComments: set to False if comments should no be removed.  This
 +
# option is important if special text patterns will be tested in the
 +
# source code.  For example, ensuring that students use a main() function
 +
# or that the source code does not use min() or max() for some
 +
# assignment.
 +
removeComments = False
 +
 +
 +
# removeExtraSpaces: always useful when the output format is not important
 +
# and student program's output is not format-specific.
 +
removeExtaSpaces = True
 +
 +
 +
# addIfNameEqMain: if True, evaluate.py will search if the student
 +
# or solution program calls main() directly, left-aligned, and if so
 +
# will add "if __name__ == "__main__": in front of it.  This insures that
 +
# the student program can be run only by calling main().
 +
addIfNameEqMain = False
 +
 +
 +
# stripIfNameEqualMain: if the student program uses "if __name__ == "__main__":
 +
# before calling main(), then remove the if statement and unindent the call
 +
# to main()
 +
stripIfNameEqualMain = False
 +
 +
 +
# sortOutputLines: if the order in which the output lines are generated
 +
# is unimportant, and could vary depending on the student's approach, then
 +
# set this boolean to False.  This is done before the student and solutions
 +
# outputs are compared.
 +
sortOutputLines = False
 +
 +
 +
# stripOutputLines: if True, then all lines output by the student or solution
 +
# program will be stripped of leading & trailing whitespace before comparison.
 +
stripOutputLines = True
 +
 +
 +
# compareOnlyNumbersInOutput: if True, all words that are not numbers will
 +
# be removed from the outputs of the student and solution programs.
 +
compareOnlyNumbersInOutput = False
 +
 +
 +
# userOutputMustContainExpectedOutpout: if True the output of the student program
 +
# is valid as long as it contains the output of the solution program.  All other
 +
# lines in the student program are discarded.
 +
userOutputMustContainExpectedOutpout = True
 +
 +
 +
# checkForBoxedOutput: Set this to False if the output of the programs must fit in
 +
# a +-----+
 +
#  | box |
 +
#  +-----+
 +
#
 +
checkForBoxedOutput = False
 +
 +
# compareExecutables: if True, the size of the executable generated by nasm and ld
 +
# is compared to the size of the solution executable.  As well, the byte to byte
 +
# comparison is performed, except for the last 100 bytes, where the name of the
 +
# program might reside.
 +
compareExecutables = False
 +
compareDataSections = False
 +
 +
# -----------------------------------------------------------------------------
 +
# gradeLinearlyWithNumberCorrectLines: if True, the grade will be linearly
 +
# determined by the number of output lines that do not match the output of the
 +
# solution program, over the total number of lines.
 +
gradeLinearlyWithNumberCorrectLines = False
 +
 +
 +
# GRADE_LINEARGRADINGMINGRADE: the lowest possible grade when using linear
 +
# grading (see above flag)
 +
GRADE_LINEARGRADINGMINGRADE = "F"    # Min grade for linear grading
 +
 +
 +
# GRADE_LINEARGRADINGMAXGRADE: the highest possible grade when using linear
 +
# grading (see above flag).  Should normally be 'A'
 +
GRADE_LINEARGRADINGMAXGRADE = "A"    # Max grade for linear grading
 +
 +
 +
# userOutSplitPattern: the pattern used to separate the input section of
 +
# the programs, when the programs use input() statements, and the output
 +
# resulting from the computation.  The simplest is to make all programs
 +
# print a blank line after the input section and before displaying the
 +
# results.  This way, separating the input from the output is simply
 +
# done by looking for a \n in the output and splitting there.
 +
userOutSplitPattern = ""
 +
 +
 +
# mustCallMain: makes sure the program calls the main() function somewhere
 +
# in the code.
 +
mustCallMain = False
 +
 +
 +
# displayContentsOfInputFiles: if True the contents of the input files is
 +
# displayed in the response to the student. 
 +
displayContentsOfInputFiles = True
 +
 +
 +
# programOutputInFile: if True, the output of the student and solution
 +
# programs are in a text file.  The name of the text file should be
 +
# constant and not depend on any input data, and is specified in the
 +
# outputDataFile variable (see below).
 +
programOutputInFile = False
 +
 +
 +
# outputDataFile: the name of the file containing the output of the program.
 +
# controlled by the programOutputInFile boolean (above)
 +
outputDataFile = ""
 +
 +
 +
# -----------------------------------------------------------------------------
 +
# noDataFiles: defines the number of auxiliary files the student and solution
 +
# programs will need.  The file names and their contents will be defined in
 +
# the dataFile?Name and dataFile? variables (see below).  Up to 4 data files
 +
# are allowed.
 +
noDataFiles = 0
 +
 +
 +
# dataFile1Name: name of Data file 1
 +
dataFile1Name = "data1.txt"
 +
 +
 +
# dataFile1: contents of Data File 1
 +
dataFile1 = """
 +
dummy
 +
"""
 +
 +
 +
# dataFile2Name: name of Data file 2
 +
dataFile2Name = "data2.txt"
 +
 +
 +
# dataFile2: contents of Data File 2
 +
dataFile2 = """ """
 +
 +
 +
# dataFile3Name: name of Data file 3
 +
dataFile3Name = " "
 +
 +
 +
# dataFile3: contents of Data File 3
 +
dataFile3 = """ """
 +
 +
 +
# dataFile4Name: name of Data file 4
 +
dataFile4Name = ""
 +
 +
 +
# dataFile4: contents of Data File 4
 +
dataFile4 = """ """
 +
 +
 +
# -----------------------------------------------------------------------------
 +
# noInputFiles: the number of files containing the contents of stdin, as it will
 +
# be fed to the student and solution programs.  Maximum value 5.
 +
noInputFiles = 1
 +
 +
 +
# input1: contents of Input File 1
 +
input1 = """10
 +
2
 +
3
 +
 +
 +
"""
 +
 +
 +
 +
# input2: contents of Input File 2
 +
input2 = """
 +
"""
 +
 +
 +
# input3: contents of Input File 3
 +
input3 = """
 +
"""
 +
 +
 +
# input4: contents of Input File 4
 +
input4 = """
 +
"""
 +
 +
 +
# input5: contents of Input File 5
 +
input5 = """
 +
"""
 +
 +
 +
# -----------------------------------------------------------------------------
 +
# GRADE_CORRECTOUTPUT: the grade for a correct output: normally 'A'
 +
GRADE_CORRECTOUTPUT = "A"
 +
 +
 +
# GRADE_INCORRECTOUTPUT: the grade for an  incorrect output.
 +
GRADE_INCORRECTOUTPUT = "C+"
 +
 +
 +
# GRADE_PROGRAMCRASHES: grade if program crashes
 +
GRADE_PROGRAMCRASHES = "C-"
 +
 +
 +
# GRADE_OUTPUTNOTFORMATTEDCORRECTLY: grade if output format of output is not correct
 +
# This is used for boxed output.
 +
GRADE_OUTPUTNOTFORMATTEDCORRECTLY = "C"
 +
 +
 +
# GRADE_ERRORPATTERNMUSTBEPRESENT: grade received if the program must contain a patter
 +
# but that pattern is not found.  For example, declaring a main function, or calling a
 +
# main function (see pattern lists below).
 +
GRADE_ERRORPATTERNMUSTBEPRESENT = "C"
 +
 +
 +
# GRADE_ERRORPATTERNMUSTNOTBEPRESENT: grade received if the program contains a pattern
 +
# that is not allowed.  For example, if the program uses the min() or max() function
 +
# (see pattern lists below) when it shouldn't.
 +
GRADE_ERRORPATTERNMUSTNOTBEPRESENT = "C"
 +
 +
# GRADE_EXECUTABLESDIFFERENTSIZE: grade for executable not matching the executable of
 +
# the solution.  To be used in assignment where students have to reverse engineer an
 +
# executable into an assembly program.
 +
GRADE_EXECUTABLESDIFFERENTSIZE = "C"
 +
GRADE_DATASECTIONSDIFFERENTSIZE = "C"
 +
 +
# -----------------------------------------------------------------------------
 +
# FEEDBACK_CORRECTOUTPUT: message printed when the output of the student and solution
 +
# programs match.  Adapt to the assignment in question.
 +
FEEDBACK_CORRECTOUTPUT = """Congrats!  the output of your program is correct."""
 +
 +
 +
# FEEDBACK_PROGRAMCRASHES: message printed when student program crashes.  The error
 +
# produced by the interpreter will also be printed.
 +
FEEDBACK_PROGRAMCRASHES = """Your program generated an error when assembled, linked, or when it was run.
 +
This might be because of a syntax error in your program.  Make
 +
sure your program assembles, links, and runs correctly before submitting it.
 +
"""
 +
 +
 +
# FEEDBACK_INCORRECTOUTPUT: message printed when the output is not correct. Adapt this
 +
# message to the assignment.
 +
FEEDBACK_INCORRECTOUTPUT = """Your output is not correct.
 +
A line output by your program does not match the expected output.
 +
"""
 +
 +
 +
# FEEDBACK_OUTPUTNOTFORMATTEDCORRECTLY: message printed if the output is not formatted in a box.
 +
FEEDBACK_OUTPUTNOTFORMATTEDCORRECTLY = """The output of your program is not correctly formatted in a box.\n"""
 +
 +
 +
# FEEDBACK_OUTPUTFORMATTEDCORRECTLY: message printed if the output is nicely formatted as a box.
 +
FEEDBACK_OUTPUTFORMATTEDCORRECTLY = """Output correctly formatted in a box.\n"""
 +
 +
 +
# -----------------------------------------------------------------------------
 +
# patternsMustBePresent:
 +
# these are list of patterns that should be present on the same line (once all comments are removed)
 +
# for example [ ["def", "main("], ["def", "min(" ] ] requires that the student's program contain
 +
# 2 function definitions, main, and min.
 +
# Theire should be as many feedback string as there are groups of patterns.  For example, for the
 +
# example of main and min, you could have
 +
# [ "Your program should define a main function", Your program should contain a min function" ]
 +
# as feedback given for the group of patterns that would not be satisfied.
 +
patternsMustBePresent = [] # [  "def\s?main(\s?):" ] ]
 +
                          #[  "^main(\s?)" ] ]
 +
 +
 +
# FEEDBACK_PATTERNMUSTBEPRESENT: feedback given for each pattern in the list above that is not
 +
# found in the student program.                     
 +
FEEDBACK_PATTERNMUSTBEPRESENT = [ "You seem to have forgotten to define the function main()" ]
 +
                                #  "Your program does not call the main function." ]
 +
 +
 +
# patternsMustNotBePresent:  similar to patternsMustBePresent, but in reverse. 
 +
# The patterns in a group should not appear on the same line.
 +
# for assignments where min() or max() shouldn't be used, use [ [ "=min(" ], ["=max(" ] ]
 +
patternsMustNotBePresent = [  ] # [ "=min(" ], ["=max(" ] ]
 +
 +
 +
# FEEDBACK_PATTERNMUSTNOTBEPRESENT: message printed if the program uses one of the forbidden
 +
# patterns.
 +
FEEDBACK_PATTERNMUSTNOTBEPRESENT = [ "You cannot use the min() or max() function in your program." ,
 +
                                    "You cannot use the min() or max() function in your program."  ]
 +
 +
 +
# FEEDBACK_EXECUTABLESDIFFERENTSIZE: message for times where executables of student and solution must
 +
# match.
 +
FEEDBACK_EXECUTABLESDIFFERENTSIZE = "Your executable does not match the solution executable."
 +
FEEDBACK_DATASECTIONSDIFFERENTSIZE = "Your data section is not the same as the original program."
 +
 +
</source>
 +
<br />
 +
==hw2sol.asm==
 +
::<source lang="asm">
 +
<br />
 +
;;; ; ; hw2sol.asm
 +
;;; ; ; D. Thiebaut
 +
;;; ; ;
 +
;;; ; ;
 +
 +
 +
        extern  _printDec
 +
        extern  _printString
 +
        extern  _println
 +
        extern  _getInput
 +
 +
section .data
 +
prompt db "> "
 +
promptLen equ $-prompt
 +
ansStr          db      "ans = "
 +
ansStrLen equ $-ansStr
 +
 +
a dd 0
 +
b dd 0
 +
c dd 0
 +
ans dd 0
 +
 +
section .text
 +
global _start
 +
_start:
 +
;; display prompt
 +
mov ecx, prompt
 +
mov edx, promptLen
 +
call _printString
 +
;; get a
 +
call _getInput
 +
mov dword[a], eax
 +
 +
;; display prompt
 +
mov ecx, prompt
 +
mov edx, promptLen
 +
call _printString
 +
;; get b
 +
call _getInput
 +
mov dword[b], eax
 +
 +
;; display prompt
 +
mov ecx, prompt
 +
mov edx, promptLen
 +
call _printString
 +
;; get c
 +
call _getInput
 +
mov dword[c], eax
 +
 +
;; -----------------------------------
 +
;; computation: ans = 2*(a-b) + 3*c
 +
;; -----------------------------------
 +
 +
mov eax, dword[a] ;eax <- a
 +
sub eax, dword[b] ;eax <- a-b
 +
add eax, eax      ;eax <- a-b + a-b
 +
 +
add eax, dword[c]
 +
add eax, dword[c]
 +
add eax, dword[c] ;eax <- 2*(a-b) +3*c
 +
mov dword[ans], eax
 +
;ans <- eax
 +
;; -----------------------------------
 +
;; display "ans ="
 +
;; -----------------------------------
 +
mov ecx, ansStr
 +
mov edx, ansStrLen
 +
call _printString
 +
 +
;; -----------------------------------
 +
;; display ans variable
 +
;; -----------------------------------
 +
mov eax, dword[ans]
 +
call _printDec
 +
call _println
 +
call _println
 +
 +
;;; exit
 +
mov ebx, 0
 +
mov eax, 1
 +
int80
 +
</source>
 +
<br />
 +
 +
 
[[Category:CSC231]][[Category:Homework]]
 
[[Category:CSC231]][[Category:Homework]]

Latest revision as of 10:30, 30 September 2017

--D. Thiebaut (talk) 15:47, 25 September 2017 (EDT)




This assignment is due on 10/02/17 at 11:55 p.m.




Problem #1

Answer the quizzes in the Moodle Homework 2 section. When you are asked whether an instruction or piece of assembly language code is syntactically correct, the question is not whether it logically makes sense, but rather, will nasm accept to assemble it without reporting a warning or an error.

Problem #2


Connect to your 231b-xx account on aurora, and run the following commands:

getcopy 231Lib.asm
getcopy hw2_skel.asm
cp hw2_skel.asm hw2.asm

You should now have a new file called hw2.asm in your directory, and another file called 231Lib.asm. 231Lib.asm is a library we will use to print and input decimal numbers. The library file will help us get input from the keyboard, and print strings. Friendly hint: you do not need to understand the code in 231Lib.asm in order to use it!

The hw2.asm file is just a skeleton and you will need to add code to it to solve this problem. For right now, just assemble and link it to the new library, and run it:

nasm -f elf  hw2.asm  
nasm -f elf  231Lib.asm
ld -melf_i386 -o hw2 hw2.o 231Lib.o


The program will prompt you for 3 integer numbers, which it will put into three 32-bit integers called a, b, and c in the data segment. It then takes the integer stored in a fourth variable called ans (for answer), and prints it. Since ans is initialized with 0, that's the number that gets printed.

Here's an example of what happens if I run the program and feed it 3 numbers: 1, 2, and 3 (user input underlined):

./hw2
> 10
> 11
> 20
ans = 0


Your Assignment


Modify hw2.asm and make it compute: ans = 3*(a+b-c) + 2*(c-1).

Note 1: The sub instruction works similarly to add:

      sub       dest, source         ; dest <-- dest - source

Note 2: Do not use the mul instruction. It is too complicated for us to use now. Instead, if you want to multiply something by two, just add that quantity twice. In other words, if you want to compute 2*x, then simply compute x + x. Similarly, when you need to compute 3*z, simply compute z + z + z, or 2*z + z.

You should add code only between the two box comments shown below:

	;; -----------------------------------
	;; computation: ans =3*(a+b-c) + 2*(c-1)
	;; -----------------------------------
	
	; your code will go here...
	

	;; -----------------------------------
	;; display "ans ="
	;; -----------------------------------


Examples


I ran my solution program a few times with different numbers. Your program should behave exactly the same!

cs231a@aurora ~ $ ./hw2
> 0
> 1
> 10
ans = -9

cs231a@aurora ~ $ ./hw2 
> 1
> 2
> 3
ans = 4

cs231a@aurora ~ $ ./hw2
> 10
> 1
> 3
ans = 28

cs231a@aurora ~ $ ./hw2
> 100
> 1000
> 6
ans = 3292

Submission


Submit your program on Moodle, in the Problem 2 section.





Assembly Files


Below are the two files used in Problem 2, for reference.

hw2.asm


;;; hw2.asm
;;; put your name here
;;; describe what the program does
;;; explain how to assemble and run it.


	        extern  _printDec
	        extern  _printString
	        extern  _println
	        extern  _getInput
	
	section	.data
prompt		db	"> "
promptLen	equ	$-prompt	
ansStr          db      "ans = "
ansStrLen	equ	$-ansStr	

a		dd	0
b		dd	0
c		dd	0
ans		dd	0
	
		section	.text
		global	_start
_start:
	;; display prompt
		mov	ecx, prompt
		mov	edx, promptLen
		call	_printString
	;; get a
		call	_getInput
		mov	dword[a], eax

	;; display prompt
		mov	ecx, prompt
		mov	edx, promptLen
		call	_printString
	;; get b
		call	_getInput
		mov	dword[b], eax
	
	;; display prompt
		mov	ecx, prompt
		mov	edx, promptLen
		call	_printString
	;; get c
		call	_getInput
		mov	dword[c], eax
	
	;; -----------------------------------
	;; computation: ans = 2*(a-b) + 3*c
	;; -----------------------------------
	
	; your code will go here...
	

	;; -----------------------------------
	;; display "ans ="
	;; -----------------------------------
		mov	ecx, ansStr
		mov	edx, ansStrLen
		call	_printString

	;; -----------------------------------
	;; display ans variable
	;; -----------------------------------
		mov	eax, dword[ans]
		call	_printDec
		call	_println
		call	_println
	
;;; exit
		mov	ebx, 0
		mov	eax, 1
		int     0x80


231Lib.asm


 
;;; 231Lib.asm
;;; A simple I/O library for CSC231.
;;; will be expanded as needed.
;;;
;;; D. Thiebaut
;;; Adapted from Swarnali Ahmed's 2002 program: mytools.inc
;;; http://cs.smith.edu/dftwiki
;;;
;;; Contains several functions for performing simple I/O of
;;; data.
;;; _printDec: function that prints an integer on the screen
;;; _printString: function that prints a string on the screen
;;; _println: moves the cursor to the next line on the screen
;;; _getInput: gets a possibly signed integer from the keyboard
;;;
;;; Version 2.  Sept 22, 2014. 
;;;      - updated _getInput to get only 1 char at at time.
;;;      - now works with pipes       
;;; Version 1.  Sept 21, 2014.
;;; 
%assign SYS_EXIT        1
%assign SYS_WRITE       4
%assign STDOUT          1

global	_atoi	
global  _printDec
global  _printInt	
global  _printString
global  _printCString	
global  _println
global  _getInput
global  _printRegs
global  _printHex

        

        section         .text
        
     
;;; ;------------------------------------------------------
;;; ;------------------------------------------------------
;;; ; _atoi:    gets a numerical input in a 0-terminated string
;;; ;	        pointed to by eax.
;;; ;           returns the resulting number in eax (32 bits).
;;; ;           recognizes - as the first character of
;;; ;           negative numbers.  Does not skip whitespace
;;; ;           at the beginning.  Stops on first not decimal
;;; ;           character encountered.
;;; ;
;;; ; NO REGISTERS MODIFIED, except eax
;;; ;
;;; ; Example of call:
;;; ;
;;; ;          call  getInput
;;; ;          mov   dword[x], eax ; put integer in x
;;; ;
;;; ;------------------------------------------------------
;;; ;------------------------------------------------------
_atoi:	
                section .bss
intg2           resd    1
isneg2          resb    1
        
                section .text
                pushad                  ; save all registers

                mov     esi, eax        ; eci --> buffer
                mov     ecx, 0          ; edi = counter of chars
.count:		cmp	byte[esi], 0	; '\0'?
		je	.parse
		inc	esi
		inc	ecx
		jmp	.count
.parse:
                mov     esi, eax        ; esi --> buffer
                mov     ecx, edi        ; loop for all chars received
                mov     dword[intg2], 0
                mov     byte[isneg2], 0

.negativ:      
                cmp     byte[esi], '-'
                jne     .loop
                inc     byte[isneg2]
        
.loop:          mov     ebx, 0
                mov     bl, byte[esi]

                ;; stop on line feed
                cmp     bl, 0		; '\0'?
                je      .done

                ;; stop on non-digit characters
                cmp     bl, '0'
                jb      .done
                cmp     bl, '9'
                ja      .done
        
                ;; bl is a digit...  multiply .int by 10 first
                mov     edx, 10
                mov     eax, dword[intg2]
                mul     edx             ; edx:eax <-- 10 * .int
                
                ;; add int version of char
                sub     bl, '0'
                add     eax, ebx
                mov     dword[intg2], eax
                inc     esi
                loop    .loop
.done:
                ;; if negative, make eax neg
                cmp     byte[isneg2], 0
                je      .return
                neg     eax
                mov     dword [intg2], eax
        
                ;; restore registers and return result in eax
.return:

                popad
                mov     eax, [intg2]
                ret

;;; ;------------------------------------------------------
;;; ;------------------------------------------------------
;;; ; getInput: gets a numerical input from the keyboard.
;;; ;           returns the resulting number in eax (32 bits).
;;; ;           recognizes - as the first character of
;;; ;           negative numbers.  Does not skip whitespace
;;; ;           at the beginning.  Stops on first not decimal
;;; ;           character encountered.
;;; ;
;;; ; NO REGISTERS MODIFIED, except eax
;;; ;
;;; ; Example of call:
;;; ;
;;; ;          call  getInput
;;; ;          mov   dword[x], eax ; put integer in x
;;; ;
;;; ;------------------------------------------------------
;;; ;------------------------------------------------------
_getInput:
                section .bss
buffer          resb    120
intg            resd    1
isneg           resb    1
        
                section .text
                pushad                  ; save all registers

                mov     esi, buffer     ; eci --> buffer
                mov     edi, 0          ; edi = counter of chars
.loop1:        
                mov     eax, 03         ; input
                mov     ebx, 0          ; stdin
                mov     ecx, esi        ; where to put the next char
                mov     edx, 1          ; one char at a time
                int     0x80            ; get the input into buffer

                cmp     byte[esi], 0    ; EOF?
                je      .parse
                cmp     byte[esi], 10   ; line feed?
                je      .parse
                inc     esi             ; point to next cell 
                inc     edi             ; increment char counter
                jmp     .loop1

.parse:
                mov     esi, buffer     ; esi --> buffer
                mov     ecx, edi        ; loop for all chars received
                mov     dword[intg], 0
                mov     byte[isneg], 0

.negativ:      
                cmp     byte[esi], '-'
                jne     .loop
                inc     byte[isneg]
        
.loop:          mov     ebx, 0
                mov     bl, byte[esi]

                ;; stop on line feed
                cmp     bl, 10          ; line feed?
                je      .done

                ;; stop on non-digit characters
                cmp     bl, '0'
                jb      .done
                cmp     bl, '9'
                ja      .done
        
                ;; bl is a digit...  multiply .int by 10 first
                mov     edx, 10
                mov     eax, dword[intg]
                mul     edx             ; edx:eax <-- 10 * .int
                
                ;; add int version of char
                sub     bl, '0'
                add     eax, ebx
                mov     dword[intg], eax
                inc     esi
                loop    .loop
.done:
                ;; if negative, make eax neg
                cmp     byte[isneg], 0
                je      .return
                neg     eax
                mov     dword [intg], eax
        
                ;; restore registers and return result in eax
.return:

                popad
                mov     eax, [intg]
                ret     	
        
        
;;; ;------------------------------------------------------
;;; ;------------------------------------------------------
;;; ; _printDec: takes the double word in eax and prints it
;;; ; to STDOUT in decimal.
;;; ;
;;; ; Examples:
;;; ; print a byte variable
;;; ;          mov     eax, 0
;;; ;          mov     al, byte[someVar]
;;; ;          call    _printDec
;;; ;
;;; ; print a word variable
;;; ;          mov     eax
;;; ;          mov     ax, word[otherVar]
;;; ;          call    _printDec
;;; ;
;;; ; print a double-word variable
;;; ;          mov     eax, dword[thirdVar]
;;; ;          call    _printDec
;;; ;
;;; ; print register edx in decimal
;;; ;
;;; ;          mov     eax, edx
;;; ;          call    _printDec
;;; ;
;;; ;REGISTERS MODIFIED: NONE
;;; ;------------------------------------------------------
;;; ;------------------------------------------------------

_printDec:
;;; saves all the registers so that they are not changed by the function

                section         .bss
.decstr         resb            10
.ct1            resd            1  ; to keep track of the size of the string

                section .text
                pushad                          ; save all registers

                mov             dword[.ct1],0   ; assume initially 0
                mov             edi,.decstr     ; edi points to decstring
                add             edi,9           ; moved to the last element of string
                xor             edx,edx         ; clear edx for 64-bit division
.whileNotZero:
                mov             ebx,10          ; get ready to divide by 10
                div             ebx             ; divide by 10
                add             edx,'0'         ; converts to ascii char
                mov             byte[edi],dl    ; put it in sring
                dec             edi             ; mov to next char in string
                inc             dword[.ct1]     ; increment char counter
                xor             edx,edx         ; clear edx
                cmp             eax,0           ; is remainder of division 0?
                jne             .whileNotZero   ; no, keep on looping

                inc             edi             ; conversion, finish, bring edi
                mov             ecx, edi        ; back to beg of string. make ecx
                mov             edx, [.ct1]     ; point to it, and edx gets # chars
                mov             eax, SYS_WRITE  ; and print!
                mov             ebx, STDOUT
                int             0x80

                popad                           ; restore all registers

                ret
        
;;; ; ------------------------------------------------------------
;;; ; _printString:        prints a string whose address is in
;;; ;                      ecx, and whose total number of chars
;;; ;                      is in edx.
;;; ; Examples:
;;; ; Assume a string labeled msg, containing "Hello World!",
;;; ; and a constant MSGLEN equal to 12.  To print this string:
;;; ;
;;; ;           mov        ecx, msg
;;; ;           mov        edx, MSGLEN
;;; ;           call       _printSTring
;;; ;
;;; ; REGISTERS MODIFIED:  NONE
;;; ; ------------------------------------------------------------

;;; ;save eax and ebx so that it is not modified by the function

_printString:
                push            eax
                push            ebx
        
                mov             eax,SYS_WRITE
                mov             ebx,STDOUT
                int             0x80

                pop             ebx
                pop             eax
                ret

;;; ; ------------------------------------------------------------
;;; ; _printCString:       prints a string whose address is in
;;; ;                      ecx, and which is 0-terminated, like
;;; ;			   C strings.
;;; ; Examples:
;;; ; Assume a string labeled msg, containing "Hello World!",
;;; ; and a constant MSGLEN equal to 12.  To print this string:
;;; ;
;;; ;           mov        ecx, msg     ;there's a \0 somewher in msg
;;; ;           call       _printCString
;;; ;
;;; ; REGISTERS MODIFIED:  NONE
;;; ; ------------------------------------------------------------

;;; ;save eax and ebx so that it is not modified by the function
_printCString:	push		ebx
		push		edx	 	; save regs 
		mov		ebx, ecx 	; make ebx point to string
		mov		edx, 0		; count in edx
.for:		cmp		byte[ebx], 0 	; '\0' found yet?
		je		.done
		inc		ebx 		; no, point to next char 
		inc		edx		; add 1 to counter
		jmp		.for

.done:		call		_printString
		pop		edx
		pop		ebx
		ret
		
;;; ; ------------------------------------------------------------
;;; ; _println             put the cursor on the next line.
;;; ;
;;; ; Example:
;;; ;           call      _println
;;; ;
;;; ; REGISTERS MODIFIED:  NONE
;;; ; ------------------------------------------------------------
_println:  
                section .data
.nl             db              10
        
                section .text
                push            ecx
                push            edx
        
                mov             ecx, .nl
                mov             edx, 1
                call            _printString

                pop             edx
                pop             ecx
                ret

;;; ; ------------------------------------------------------------
;;; ; _printHex: prints contents of eax in hexadecimal, uppercase.
;;; ;            using 8 chars.
;;; ; ------------------------------------------------------------
                section .data
table           db      "0123456789ABCDEF"
hexString       db      "xxxxxxxx"
tempEax         dd      0
        
                section .text
_printHex:
                pushad                  ; save all registers
                mov     [tempEax], eax  ; save eax, as we are going to need it
                                        ; several times, for all its digits 
                mov     ecx, 8          ; get ready to loop 8 times

        ;; get char equivalent to lower nybble of eax
for:            and     eax, 0xF        ; isolate lower nybble of eax
                add     eax, table      ; and translate it into ascii hex char
                mov     bl, byte[eax]   ; bl <- ascii

        ;; make eax point to place where to put this digit in hexString
                mov     eax, hexString  ; beginning of table
                add     eax, ecx        ; add ecx to it, since ecx counts
                dec     eax             ; but eax 1 too big, decrement it
                mov     byte[eax], bl   ; store ascii char at right index in hexString

        ;; shift eax down by 4 bits by dividing it by 16
                mov     ebx, 16         ; ready to shift down by 4 bits
                mov     eax, [tempEax]  ; get eax back
                div     ebx             ; shift down by 4
                mov     [tempEax], eax  ; save again

                loop    for             ; and repeat, 8 times!
        
        ;; print the pattern in hexString, which contains 8 chars

                mov     ecx, hexString  ; 
                mov     edx, 8
                call    _printString

                popad
                ret
          
;;; ; ------------------------------------------------------------
;;; ; _printInt: prints the contents of eax as a 2's complement
;;; ;            number.
;;; ; ------------------------------------------------------------
_printInt:      push    eax			; save the regs we are using
                push    ecx
                push    edx
                
        ;; check if msb is set
                test    eax, 0x80000000
                jz      positive
		neg	eax
                push    eax

		;; the number is negative.  Print a minus sign and
		;; the positive equivalent of the number
                mov     ecx, minus
                mov     edx, 1
                call    _printString
                pop     eax
                
        ;; the number is positive, just print it.
positive:       call    _printDec

                pop     edx
                pop     ecx
                pop     eax
                ret

;;; ; ------------------------------------------------------------
;;; ; printMinus, _printSpace: print a minus sign, and a plus sign.
;;; ; ------------------------------------------------------------
_printMinus:    push    ecx
                push    edx
                mov     ecx, minus
                mov     edx, 1
                call    _printString
                pop     edx
                pop     ecx
                ret

_printSpace:    push    ecx
                push    edx
                mov     ecx, space
                mov     edx, 1
                call    _printString
                pop     edx
                pop     ecx
                ret

;;; ; ------------------------------------------------------------
;;; ; _printRegs: prints all the registers.
;;; ; ------------------------------------------------------------
                section .data
minus           db      '-'
space           db      ' '        
eaxStr          db      'eax '
ebxStr          db      'ebx '
ecxStr          db      'ecx '
edxStr          db      'edx '
esiStr          db      'esi '
ediStr          db      'edi '
eaxTemp         dd      0
ebxTemp         dd      0
ecxTemp         dd      0
edxTemp         dd      0
esiTemp         dd      0
ediTemp         dd      0
                              
                section .text
_printRegs:     mov     [eaxTemp], eax
                mov     [ebxTemp], ebx
                mov     [ecxTemp], ecx
                mov     [edxTemp], edx
                mov     [ediTemp], edi
                mov     [esiTemp], esi

				pushad
				
        ;; print eax
                mov     ecx, eaxStr
                mov     edx, 4
                call    _printString
                call    _printHex
                call    _printSpace
                call    _printDec
                call    _printSpace
                call    _printInt
                call    _println

        ;; print ebx
                mov     ecx, ebxStr
                mov     edx, 4
                call    _printString
                mov		eax, [ebxTemp]
                call    _printHex
                call    _printSpace
                call    _printDec
                call    _printSpace
                call    _printInt
                call    _println

        ;; print ecx
                mov     ecx, ecxStr
                mov     edx, 4
                call    _printString
                mov		eax, [ecxTemp]
                call    _printHex
                call    _printSpace
                call    _printDec
                call    _printSpace
                call    _printInt
                call    _println

        ;; print edx
                mov     ecx, edxStr
                mov     edx, 4
                call    _printString
                mov		eax, [edxTemp]
                call    _printHex
                call    _printSpace
                call    _printDec
                call    _printSpace
                call    _printInt
                call    _println

        ;; print edi
                mov     ecx, ediStr
                mov     edx, 4
                call    _printString
                mov		eax, [ediTemp]
                call    _printHex
                call    _printSpace
                call    _printDec
                call    _printSpace
                call    _printInt
                call    _println

        ;; print esi
                mov     ecx, esiStr
                mov     edx, 4
                call    _printString
                mov		eax, [esiTemp]
                call    _printHex
                call    _printSpace
                call    _printDec
                call    _printSpace
                call    _printInt
                call    _println

		popad 
				
                ret







<showafterdate after="20171003 12:00" before="20171231 00:00">

Solution

Problem 2

;;; ; ; hw2sol.asm
;;; ; ; D. Thiebaut
;;; ; ;
;;; ; ;


	        extern  _printDec
	        extern  _printString
	        extern  _println
	        extern  _getInput
	
	section	.data
prompt		db	"> "
promptLen	equ	$-prompt	
ansStr          db      "ans = "
ansStrLen	equ	$-ansStr	

a		dd	0
b		dd	0
c		dd	0
ans		dd	0
	
		section	.text
		global	_start
_start:
	;; display prompt
		mov	ecx, prompt
		mov	edx, promptLen
		call	_printString
	;; get a
		call	_getInput
		mov	dword[a], eax

	;; display prompt
		mov	ecx, prompt
		mov	edx, promptLen
		call	_printString
	;; get b
		call	_getInput
		mov	dword[b], eax
	
	;; display prompt
		mov	ecx, prompt
		mov	edx, promptLen
		call	_printString
	;; get c
		call	_getInput
		mov	dword[c], eax
	
	;; -----------------------------------
	;; computation: ans = 2*(a-b) + 3*c
	;; -----------------------------------
	
		mov	eax, dword[a] ;eax <- a
		sub	eax, dword[b] ;eax <- a-b
		add	eax, eax      ;eax <- a-b + a-b

		add	eax, dword[c]
		add	eax, dword[c]
		add	eax, dword[c] ;eax <- 2*(a-b) +3*c
		mov	dword[ans], eax
					;ans <- eax
	;; -----------------------------------
	;; display "ans ="
	;; -----------------------------------
		mov	ecx, ansStr
		mov	edx, ansStrLen
		call	_printString

	;; -----------------------------------
	;; display ans variable
	;; -----------------------------------
		mov	eax, dword[ans]
		call	_printDec
		call	_println
		call	_println
	
;;; exit
		mov	ebx, 0
		mov	eax, 1
		int80

</showafterdate>



<onlydft>

VPL

vpl_run.sh

<br />
#! /bin/bash
   
cat > vpl_execution <<EOF
#! /bin/bash
prog=hw2

nasm -f elf 231Lib.asm
nasm -f elf  \$prog.asm
ld -melf_i386 -o \$prog \$prog.o 231Lib.o
./\$prog | cat -v
EOF
  
chmod +x vpl_execution


vpl_evaluate.sh

<br />
#! /bin/bash

cat   > vpl_execution <<EOF
#! /bin/bash 

# --- Python ----
if [[ `hostname -s` = "aurora" ]]; then
   python=/usr/bin/python3.3
else
   python=/usr/local/bin/python3.4
fi


\$python evaluate2.py

EOF

chmod +x vpl_execution


evaluate2.py

<br />
'''
Created on Jun 10, 2015
Updated 9/17/15 for assembly
This version is for assembly language programs,
and will require nasm, ld as assembler and linker.

Updated  9/24/15 for nasm, added -w-all to turn off all warnings
Updated 10/1/15 for objdump, to get just the data section, and
    to remove the address field from the data section of objdump.
    because the code of the solution might be different from the 
    code of the student's program, and could result in the addresses
    of the start of the data sections to be different, although the
    sections could be identical in contents.
Updated 12/4/15.  Created new runModule function, and deprecated
    old one. 
@author: thiebaut
'''
#--- imports ---
from __future__ import print_function
import subprocess
import sys
import os
import random
import imp
import re
import difflib
import binascii   # for binary to ascii conversion

#--- GLOBALS ---
interpreter = sys.executable
userModuleAlreadyImported = False
import subprocess

#--- use a different command line for nasm and ld if this runs on 
#--- a mac, as opposed to Linux.
useNasmOnMac = subprocess.check_output( ["uname", "-s"] ).decode( 'ascii' ).find( "Darwin" ) != -1

#--- READ PARAMS ---
from params import *
if removeComments or ( 'patternsMustBePresent' in globals() and len( patternsMustBePresent ) != 0) \
    or ( 'patternsMustNotBePresent' in globals() and len( patternsMustNotBePresent ) != 0 ):
    #import pyminifier
    pass

#--- debugging ---
debug = False

#--- Grade scale ---
def letter2Number( letter ):
    """takes a letter and returns a number.  The scale is the default scale on Moodle.
    """
    dico = {'A': (93.00, 100.00),'A-': (90.00, 92.99),
            'B': (83.00, 86.99), 'B+': (87.00, 89.99),
            'B-': (80.00, 82.99),'C': (73.00, 76.99),
            'C+': (77.00, 79.99),'C-': (70.00, 72.99),
            'D': (60.00, 66.99), 'D+': (67.00, 69.99),
            'F': (0.00, 59.99)}
    return dico[letter.upper().strip()][1]

#--- UTILITY FUNCTIONS ---
def commentLong( text ):
    lines = text.split( "\n" )
    text = "\n".join( [ " " + k for k in lines ] )
    print( "<|--\n" + text  + "\n --|>" )

def commentLongStart( ):
    print( "<|--\n" )
    
def commentLongEnd( ):
    print( "\n --|>" )
    
def commentShort( text ):
    print( "Comment :=>> " + text )

def comment( text ):
    commentShort( text )

def printGrade( grade ):
    commentShort( "Your grade is " + grade + " (which Moodle will translate in " + str( letter2Number( grade ) )+")" )
    print( "Grade :=>> ", letter2Number( grade ) )

def createFileWithString( fileName, string ):
    file = open( fileName, "w" )
    file.write( string) 
    file.close()

def removeBlankLinesFrom( lines ):
    newLines = []
    for line in lines:
        if len( line.strip() )==0:
            continue
        newLines.append( line )

    return newLines

#----------------------------------------------------------------------
#                        ASSEMBLY REALTED FILES
#----------------------------------------------------------------------
def assemble( fileNameNoExt ):
    global useNasmOnMac
    command = "nasm -f elf -w-all %s.asm" % fileNameNoExt
    if useNasmOnMac:
        command = "nasm -f macho  %s.asm -DMAC" % fileNameNoExt
    command = command.split()
    #print( "command = ", command )
    p = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (output, err) = p.communicate()
    
    err =  err.decode('ascii') 
    output =  output.decode('ascii') 
    log( "assemble: output = " + output + "\nassemble: err = " + err + "\n" )

    if len( err ) >= 1:
        return False, err

    return True, output

def link( fileNameNoExt, testModuleNames ):
    global useNasmOnMac
    
    objects = " ".join( [ k +".o" for k in [fileNameNoExt] + testModuleNames ] )
    command = "ld -melf_i386 %s -o %s" % ( objects, fileNameNoExt )
    if useNasmOnMac:
        command = "ld -e _start -macosx_version_min 10.6 -o %s %s" % (fileNameNoExt, objects )
        
    log( "link command = " + command )
    command = command.split()
    
    p = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (output, err) = p.communicate()
    
    err =  err.decode('ascii') 
    output =  output.decode('ascii') 

    log( "Link: output = " + output + "\nlink: err = " + err + "\n" )
    
    if len( err ) >= 1:
        return False, err
    return True, output

def runExecutable( fileNameNoExt, input = None ):
    command = "./%s" % fileNameNoExt
    log( "run command = " + command )

    command = command.split()
    if input != None:
        log( "feeding file %s to stdin" % input )
        fileInput = open( input, "r" )
        p = subprocess.Popen( command, stdin=fileInput, 
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    else:
        p = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (output, err) = p.communicate()
    
    log( "runExecutable: ")
    err =  err.decode('ascii') 
    output =  output.decode('ascii') 
    
    if len( err ) >= 1:
        return False, err
    return True, output

def runModule( module, inputFileName, outputFileName, testModuleNames=[] ):
    global userOutSplitPattern
    global moduleContainsMain
    global programOutputInFile

    Ok, output = runExecutable( module, inputFileName )

    open( outputFileName, "w" ).write( output + "\n" )
    
    #--- if output to test is in file, open the file and print its contents
    #--- as if the program had sent it to the output
    if Ok and ('programOutputInFile' in globals()) and programOutputInFile == True:
        outText = open( outputDataFile, "r" ).read()
        log( outText )
  
    log( "runModule: ")
    index = output.find( userOutSplitPattern )
    log( "runModule: index = %d text = %s" % ( index, output ) )
    if (userOutSplitPattern != "" and index != -1) or (userOutSplitPattern == "" ):
        output = output[index + len( userOutSplitPattern ): ]
        file = open( outputFileName, "w" )
        file.write( output )
        file.close()

    return not Ok, output, 0
    
    
def assembleLinkAndRun( asmFile, testModuleNames = [], input=None ):
    # assemble
    log( "assembleLinkAndRun( %s, %s )" % ( asmFile, testModuleNames ) )
    for asmFile1 in [asmFile] + testModuleNames:
        Ok, text = assemble( asmFile1 )
        if not Ok:
            log( "**** Assembly Error: " + text )
            return False, "Nasm Error\n" + text
        log( "assembleLinkAndRun: assemble( " + asmFile1 + ") returned " + text + "\n")
    
    # link
    Ok, text = link( asmFile, testModuleNames )
    if not Ok:
        log( "**** link Error: " + text )
        return False, "Link Error\n" + text
    log( "assembleLinkAndRun: ld( " + asmFile + ") returned " + text + "\n")
    
    #print( "assembly/linking successful" )
    
    Ok, text = runExecutable( asmFile, input )
    if not Ok:
        #print( "Run-Time Error: "  + text )
        return False, "Run-time Error\n" + text
    log( "assembleLinkAndRun: run( " + asmFile + ") returned " + text + "\n")

    return True, text

def getFilesInCurrentDirectory():    
    return "\n".join(  [f for f in os.listdir('.') if os.path.isfile(f)] )

def hexdump( fileNameNoExt ):
    command = "hexdump -v -C %s" % fileNameNoExt
    command = command.split()
    #print( "command = ", command )
    p = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (output, err) = p.communicate()
    
    err =  err.decode('ascii') 
    return  output.decode('ascii') 

def objdump( fileNameNoExt, sections = [".text", ".data" ] ):
    objdumpCommand = "objdump -s -j %s " + fileNameNoExt
    if useNasmOnMac == True:
        objdumpCommand = "/opt/local/bin/gobjdump -s -j %s " + fileNameNoExt
     
    s = ""
    for part in sections: #[ ".text", ".data" ]:
        commandList = ( objdumpCommand % part ).split()
        p = subprocess.Popen( commandList, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        (output, err) = p.communicate()
    
        err =  err.decode('ascii') 
        output =  output.decode('ascii') + "\n"
        log( "objdump = " + output )
        s += output
        
    newOut = []
    for line in s.split( "\n" ):
        if line.find( "file" ) != -1: continue
        if line.find( "Contents" ) != -1: continue
        if len( line ) <= 1: continue
        # remove the address from output
        line = " ".join( line.split()[1:] )
        newOut.append( line )
        
    return "\n".join( newOut )

def compareBinaryFiles( binFile1, binFile2, sections = [".text", ".data" ] ):
    """compare the contents of two executables, and return False and a
    message if different, or True and "" if the same."""
    # get the contents 
    
    log( "compareBinaryFiles( %s, %s, sections=%s):\n objdump( %s ) = \n%s\nobjdump( %s ) =\n%s\n" 
          % ( binFile1, binFile2, str(sections), binFile1, objdump( binFile1, sections ), 
              binFile2, objdump( binFile2, sections ) ) )

    objdump1 = objdump( binFile1, sections )
    objdump2 = objdump( binFile2, sections )
                
    if objdump1 != objdump2:
        log( "CompareBinaryFiles: Mismatched between user:\n" + objdump1 + "\nExpected code and data:\n" 
                    + objdump2 + "\n" )
        return False, "Your code and data sections below:\n" \
                    + objdump1 + "\nExpected code and data:\n" \
                    + objdump2 + "\n"
    
    log( "objdump1 = " + objdump1 )
    log( "objdump2 = " + objdump2 )
    
    # all good!
    return ( True, "" )

#----------------------------------------------------------------------
# Logging 
def log( message ):
    open( "log.txt", "a" ).write( message + "\n" )
    
def resetLog( ):
    open( "log.txt", "w" )
    
#----------------------------------------------------------------------
# Functions running other modules
#----------------------------------------------------------------------
def checkModuleRunsOk( module, inputFileName, testModuleNames = [] ):
    """ just runs the module with a dummy input file or dummy
    stdin to make sure the module runs without crashing.
    
    """
    #global interpreter
    log( "checkModuleRunsOk( %s, %s, %s )" %
         ( module, inputFileName, testModuleNames ) )

    #--- create data files if needed ---
    createDataFiles()

    #--- create executable ---
    Ok, text = assembleLinkAndRun( module, testModuleNames, inputFileName )
    
    # --- run program once ---
    #p = subprocess.Popen( [ "./" +  module ],
    #                      stdout=subprocess.PIPE,
    #                      stderr=subprocess.PIPE,
    #                      stdin=subprocess.PIPE)

    #print( "inputFileName = ", inputFileName )
    #print( "open( inputFileName, r).read() = ", open( inputFileName, "r" ).read() )
    
    #p.stdin.write( bytes( open( inputFileName, "r" ).read(), 'UTF-8' ) )
    #data = p.communicate( )
    #p.stdin.close()

    #error = data[1].decode( 'UTF-8' )
    #if len( error ) > 1:
    #    return False, error
    #return True, None
    if not Ok:
        return False, text
    else:
        return True, None

def extractTextFromErrorMessage( sys_exc_info ):
    text = []
    try:
        for field in sys_exc_info:
            k = str( field )
            if k.find( "traceback" ) != -1: continue
            if k[0]=="<" and k[-1]==">": k = k[1:-1]
            text.append( k )
        text = ": ".join( text )
    except:
        text = sys_exc_info
    log( "extractTextFromErrorMessage( %s ) returns %s" % ( str( sys_exc_info ), text ) )
    return text

def runModuleOld( module, inputFileName, outputFileName, testModuleNames=[] ): # deprecated
    """
    runModule: runs the module, passes it data from the input file on its stdin
    and get its output on stdout captured in outputFileName.
    We assume the module will not crash, because we already tested
    it with checkModuleRunsOk()."""
    
    global userOutSplitPattern
    global moduleContainsMain
    global programOutputInFile
    global mustCallMain
    
    error = False

    #--- create data files if needed ---
    createDataFiles()
    
    #--- make stdin read information from the text file
    sys.stdin = open( inputFileName, "r" )

    #--- run the student program ---
    try:
        Ok, text = assembleLinkAndRun( module, testModuleNames )                   
        log( "returned from assembleLinkAndRun: text = " + text )
        #log( "After assembling and linking " + module + ", files in dir = " + getFilesInCurrentDirectory() )
    except:
        error = True
        #sys.stderr.close()
        #sys.stderr = saveStdErr
        #sys.stdout.close()
        #sys.stdout = saveStdOut
        
        try:
            text = open( outputFileName, "r" ).read() + "\n" + text
        except:
            pass
        return error, text, 0
      
    if not Ok:
        return True, text, 0
    
    #--- if output to test is in file, open the file and print its contents
    #--- as if the program had sent it to the output
    if Ok and ('programOutputInFile' in globals()) and programOutputInFile == True:
        outText = open( outputDataFile, "r" ).read()
        print( "\n<<< Contents of file %s >>>" % outputDataFile )
        print( outText )
        print( "<<< End of file %s >>>" % outputDataFile )
        log( outText )
  
    log( "runModule: ")
    index = text.find( userOutSplitPattern )
    log( "runModule: index = %d text = %s" % ( index, text ) )
    if (userOutSplitPattern != "" and index != -1) or (userOutSplitPattern == "" ):
        text = text[index + len( userOutSplitPattern ): ]
        file = open( outputFileName, "w" )
        file.write( text )
        file.close()

    
    #print( "**B: text = ", text )
    return False, text, 0


def getNumbers( s ):
    """returns 3 lists of numbers from a string.
    """
    ints = []
    floats = []
    intFloats = []
    for word in s.replace(',', ' ').split():
        try:
            ints.append( int( word ) )
            intFloats.append( int( word ) )
            continue
        except:
            pass
        try:
            floats.append( float( word ) )
            intFloats.append( float( word ) )
            continue
        except:
            pass

    return ints, floats, intFloats

def compareNumbers( line1, line2 ):
    dummy, dummy, numbers1 = getNumbers( line1 )
    dummy, dummy, numbers2 = getNumbers( line2 )
    #print( "numbers1 = ", numbers1 )
    #print( "numbers2 = ", numbers2 )
    if len( numbers1 ) != len( numbers2 ):
        return False
    
    for i in range( len( numbers1 ) ):
        x1 = numbers1[i]
        x2 = numbers2[i]
        #print( "compareNumbers(): x1 = ", x1, " x2 = ", x2, end=" " )
        if x1==0 or x1==0.0:
            if abs( (x1-x2) ) > 0.01:
                #print( "returning False <--------------------------")
                return False
            continue
        if abs( (x1-x2)/x1 ) > 0.01:
            #print( "returning False <--------------------------" )
            return False
        
    #print( "returning True" )
    return True 

def removeLinesWithNoNumbers( lines ):
    """ gets a list of lines and returns a list of lines that only contained
    numbers (ints or floats), and removes words from line."""
    newLines = []
    for line in lines:
        dummy, dummy, numbers = getNumbers( line )
        if len( numbers ) == 0: continue
        newLines.append( line )
    return newLines
    
def removeAllExceptNumbers( lines ):
    """ gets a list of lines and returns a list of lines that only contained
    numbers (ints or floats), and removes words from line."""
    newLines = []
    for line in lines:
        dummy, dummy, numbers = getNumbers( line )
        #print( "numbers = " , numbers )
        if len( numbers ) == 0: continue
        newLines.append( " ".join( [str(k) for k in numbers] ) )
    return newLines
    
"""
def compareUserExpectedOld( inputLines, userOutText, expectedOutText ):
    global stripOutputLines, compareOnlyNumbersInOutput
    global removeBlankLines, removeExtaSpaces
    print( "removeBlankLines = ", removeBlankLines )
    # split in lines
    userOutTextLines = userOutText.split( "\n" )
    expectedOutTextLines = expectedOutText.split( "\n" )
    
    if stripOutputLines:
        userOutTextLines = [k.strip() for k in userOutTextLines ]
        expectedOutTextLines = [k.strip() for k in expectedOutTextLines ]
        
    if removeBlankLines: 
        userOutTextLines = removeBlankLinesFrom( userOutTextLines )
        expectedOutTextLines = removeBlankLinesFrom( expectedOutTextLines )
        print( "removeBlankLines: ", "\n".join( expectedOutTextLines ) )
        
    if removeExtaSpaces:
        userOutTextLines = [' '.join( k.split()) for k in userOutTextLines]
        expectedOutTextLines = [' '.join( k.split()) for k in expectedOutTextLines]
        
    if sortOutputLines:
        userOutTextLines.sort()
        expectedOutTextLines.sort()

    if compareOnlyNumbersInOutput:
        userOutTextLines = removeLinesWithNoNumbers( userOutTextLines )
        expectedOutTextLines = removeLinesWithNoNumbers( expectedOutTextLines )
        
    #print( "userOutTextLines =", userOutTextLines )
    #print( "expectedOutTextLines =", expectedOutTextLines )
    report = []
    misMatchLineNumbers = []
    
    for i in range( 0, min( len(userOutTextLines), len( expectedOutTextLines))):
        if compareOnlyNumbersInOutput:
            #print( "compareNumbers( ", userOutTextLines[i], ", ", expectedOutTextLines[i], ") = ", 
            #       compareNumbers( userOutTextLines[i], expectedOutTextLines[i] ) )
            if compareNumbers( userOutTextLines[i], expectedOutTextLines[i] ) == False:
                misMatchLineNumbers.append( i )
                report.append( "**ERROR***>" +  showSpaces( userOutTextLines[i] ) )
                report.append( "*EXPECTED*>" +  showSpaces( expectedOutTextLines[i] ) )
            else: 
                report.append( "*CORRECT**>" + showSpaces( userOutTextLines[i] ) )
                
        elif userOutputMustContainExpectedOutpout:
            if userOutTextLines[i].find( expectedOutTextLines[i] ) == -1:
                misMatchLineNumbers.append( i )
                report.append( "**ERROR***>" +  showSpaces( userOutTextLines[i] ) )
                report.append( "*SHOULD HAVE CONTAINED*>" +  showSpaces( expectedOutTextLines[i] ) )
            else: 
                report.append( "*CORRECT**>" +  showSpaces( userOutTextLines[i] ) )
                           
        else:
            if userOutTextLines[i] != expectedOutTextLines[i]:
                #print( i, userOutTextLines[i], expectedOutTextLines[i], sep="\n\t")
                misMatchLineNumbers.append( i )
                report.append( "**ERROR***>" +  showSpaces( userOutTextLines[i] ) )
                report.append( "*EXPECTED*>" +  showSpaces( expectedOutTextLines[i] ))
            else: 
                report.append( "*CORRECT**>" +  showSpaces( userOutTextLines[i] ) )
    
    if  len(userOutTextLines) != len( expectedOutTextLines):
        misMatchLineNumbers.append( min( len(userOutTextLines), len( expectedOutTextLines)) )
        report.append( "\n**ERROR***> your output contains %d lines, but %d lines are expected." 
                       % ( len(userOutTextLines), len( expectedOutTextLines) ) )
        
    return misMatchLineNumbers, report
"""

def compareUserExpected( inputLines, userOutText, expectedOutText ):
    log( "compareUserExpected()")
    log( "userOutText = " + str( userOutText ) )
    log( "expectedOutText = " + str( expectedOutText ) )
    global stripOutputLines, compareOnlyNumbersInOutput
    global removeBlankLines, removeExtaSpaces
        
    # split in lines
    userOutTextLines = userOutText.split( "\n" )
    expectedOutTextLines = expectedOutText.split( "\n" )
    
    if stripOutputLines:
        userOutTextLines = [k.strip() for k in userOutTextLines ]
        expectedOutTextLines = [k.strip() for k in expectedOutTextLines ]
        
    #print( "removeBlankLines = ", removeBlankLines )
    if removeBlankLines: 
        userOutTextLines = removeBlankLinesFrom( userOutTextLines )
        expectedOutTextLines = removeBlankLinesFrom( expectedOutTextLines )
        #print( "expectedOutTextLines = " + "\n".join(expectedOutTextLines ))
        
    if removeExtaSpaces:
        userOutTextLines = [' '.join( k.split()) for k in userOutTextLines]
        expectedOutTextLines = [' '.join( k.split()) for k in expectedOutTextLines]
        
    if sortOutputLines:
        userOutTextLines.sort()
        expectedOutTextLines.sort()

    if compareOnlyNumbersInOutput:
        userOutTextLines = removeAllExceptNumbers( userOutTextLines ) 
        expectedOutTextLines = removeAllExceptNumbers( expectedOutTextLines ) 
    
    log( "compareUserExpected: userOutTextLines =" + str( userOutTextLines ) )
    log( "compareUserExpected: expectedOutTextLines =" + str( expectedOutTextLines ) )
    misMatchLineNumbers = []
    
    # create a difference report 
    d = difflib.Differ()
    diff = d.compare( userOutTextLines, expectedOutTextLines )
    reportLines  =[k.strip() for k in diff] 
    for i in range( len( reportLines ) ): 
        if reportLines[i].startswith( '+' ): 
            misMatchLineNumbers.append( i+1 )
            
    #print( "misMatchLineNumbers = ", misMatchLineNumbers )
    #print( "report = ", "\n".join( reportLines ) )
    return misMatchLineNumbers, reportLines


def testProgramWithInputFiles():
    """compare output of user program to solution program.
    One or more input files are used"""
    global moduleName
    global solutionFileName
    global noInputFiles
    global input1, input2, input3, input4, input5
    global GRADE_CORRECTOUTPUT
    global GRADE_INCORRECTOUTPUT
    global GRADE_PROGRAMCRASHES
    global FEEDBACK_INCORRECTOUTPUT
    global FEEDBACK_CORRECTOUTPUT
    global FEEDBACK_PROGRAMCRASHES
    global FEEDBACK_OUTPUTNOTFORMATTEDCORRECTLY
    global gradeLinearlyWithNumberCorrectLines
    
    log( "testProgramWithInputFiles()" )

    input = [input1, input2, input3, input4, input5]

    # test if user program crashes on its own
    createFileWithString( "input.txt", input[0] )

    log( "\nCreating file input.txt for checkModuleRunsOk(), containing: " + input[0] + "\n")
    
    if "testModuleNames" in globals() and len( testModuleNames ) != 0:
        Ok, errorMessage = checkModuleRunsOk( moduleName, "input.txt", testModuleNames )
    else:
        Ok, errorMessage = checkModuleRunsOk( moduleName, "input.txt" )

    if not Ok:
        commentLong( FEEDBACK_PROGRAMCRASHES )
        commentLong( "- The Assemble/Link/Run process stopped with this error:\n"
                    + "Error message:\n"
                    + errorMessage + "\n" )
        log( "- The Assemble/Link/Run process stopped with this error:\n"
                    + "Error message:\n"
                    + errorMessage + "\n" )
        printGrade( GRADE_PROGRAMCRASHES )
        return False


 
    numberTestsPassed = 0
    
    for testNo in range( noInputFiles ):
        comment( "- Test No. " + str( testNo+1 ) )
        log( "- Test No. " + str( testNo+1 ) )
        createFileWithString( "input.txt", input[testNo] )
        
        # get solution program's output         
        dummy, expectedOutText = assembleLinkAndRun( solutionFileName, testModuleNames, "input.txt" )


        # get user's program output   
        if "testModuleNames" in globals() and len( testModuleNames ) != 0:
            error, userOutText, score2  = runModule( moduleName, "input.txt", 
                                                     "userOut", testModuleNames )
        else:
            error, userOutText, score2  = runModule( moduleName, "input.txt", "userOut" )

    
        if error:
            commentLong( FEEDBACK_PROGRAMCRASHES )
            commentLong( "- Your program crashed with this error:\n"
                         + "Error message:\n"
                         + userOutText + "\n" )
            printGrade( GRADE_PROGRAMCRASHES )
            return False

        missMatchLineNumbers, report = \
            compareUserExpected( "", userOutText, expectedOutText )

        if len( missMatchLineNumbers ) != 0:
            commentLong( FEEDBACK_INCORRECTOUTPUT + "\nThe input was:\n" 
                         + open( "input.txt", "r").read()  )                
            commentLong( "\n".join( report ))
        else:
            commentLong( "Test passed: input was: " + open( "input.txt", "r").read() )
            numberTestsPassed += 1

    if numberTestsPassed == noInputFiles:        
        commentLong( FEEDBACK_CORRECTOUTPUT )
        printGrade( GRADE_CORRECTOUTPUT )
    else:       
        commentLong( FEEDBACK_INCORRECTOUTPUT )
        printGrade( GRADE_INCORRECTOUTPUT )
        
    return True

def showSpaces( text ):
    newText = []
    for line in text.split( "\n" ):
        newText.append( line.replace( ' ', '~') )
    return '\n'.join( newText )

def testProgramWithNoInputFiles():
    """compare output of user program to solution program.
    no input files are used"""
    global moduleName
    global testModuleNames
    global solutionFileName
    global GRADE_CORRECTOUTPUT
    global GRADE_INCORRECTOUTPUT
    global GRADE_PROGRAMCRASHES
    global FEEDBACK_INCORRECTOUTPUT
    global FEEDBACK_CORRECTOUTPUT
    global FEEDBACK_PROGRAMCRASHES
    global GRADE_OUTPUTNOTFORMATTEDCORRECTLY
    global GRADE_LINEARGRADINGMINGRADE
    global GRADE_LINEARGRADINGMAXGRADE
    global FEEDBACK_OUTPUTNOTFORMATTEDCORRECTLY
    global gradeLinearlyWithNumberCorrectLines
    
    # test if user program crashes on its own
    createFileWithString( "input.txt", "\n\n\n" )
    if "testModuleNames" in globals() and len( testModuleNames ) != 0:
        Ok, errorMessage = checkModuleRunsOk( moduleName, "input.txt", testModuleNames )
    else:
        Ok, errorMessage = checkModuleRunsOk( moduleName, "input.txt" )
         
    if not Ok:
        commentLong( FEEDBACK_PROGRAMCRASHES )
        commentLong( "- Your program crashed with this error:\n"
                    + "Error message:\n"
                    + errorMessage + "\n" )
        printGrade( GRADE_PROGRAMCRASHES )
        return False
          
    # get solution program's output
    if "testModuleNames" in globals() and len( testModuleNames ) != 0:
        dummy, expectedOutText, score2 = runModule( solutionFileName, "input.txt", "expectedOut", testModuleNames )
    else:
        dummy, expectedOutText, score2 = runModule( solutionFileName, "input.txt", "expectedOut" )
        
    # get user's program output
    if "testModuleNames" in globals() and len( testModuleNames ) != 0:
        error, userOutText, score2  = runModule( moduleName, "input.txt", "userOut", testModuleNames )
    else:
        error, userOutText, score2  = runModule( moduleName, "input.txt", "userOut" )
        
    if debug: print( "userOutText =" , userOutText )
    if error:
        commentLong( FEEDBACK_PROGRAMCRASHES )
        commentLong( "- Your program crashed with this error:\n"
                    + "Error message:\n"
                    + userOutText + "\n" )
        printGrade( GRADE_PROGRAMCRASHES )
        return False
 
    # test if executables are identical
    if 'compareExecutables' in globals() and compareExecutables == True:
        Ok, errorMessage = compareBinaryFiles( moduleName, solutionFileName, 
                                               sections=[".data", ".text" ] )
        log( "compareExecutable returns %s, %s" % (str(Ok), errorMessage))
        if not Ok:
            commentLong( FEEDBACK_EXECUTABLESDIFFERENTSIZE )
            commentLong( "- Your executable does not match the solution executable:\n"
                         "Below are the hex versions of your code and data sections,\n"
                         +"against the expected code and data sections.\n"
                         + errorMessage )
            printGrade( GRADE_EXECUTABLESDIFFERENTSIZE )
            return False

    # test if data sections are identical
    if 'compareDataSections' in globals() and compareDataSections == True:
        Ok, errorMessage = compareBinaryFiles( moduleName, solutionFileName, 
                                               sections=[ ".data" ] )
        log( "compareDataSections returns %s, %s" % (str(Ok), errorMessage))
        if not Ok:
            commentLong( FEEDBACK_DATASECTIONSDIFFERENTSIZE )
            commentLong( "- Your data section has been modified.\n"
                         +"This is not allowed for this problem.\n"
                         + errorMessage )
            printGrade( GRADE_DATASECTIONSDIFFERENTSIZE )
            return False

   
    if checkForBoxedOutput:
        Ok = testForBoxedOutpout( userOutText )
        if not Ok:
            commentLong( FEEDBACK_OUTPUTNOTFORMATTEDCORRECTLY + "\n"
                    + showSpaces( userOutText ) + "\n" )
            printGrade( GRADE_OUTPUTNOTFORMATTEDCORRECTLY )
            return False 
        else:
            comment( "Output correctly formatted.\nTesting contents.")

    #--- if linear grade required, compute degree of matching between student
    #--- program output and solution outout.
    if gradeLinearlyWithNumberCorrectLines:
            percent, report = computePercentGoodOutput( userOutText, expectedOutText )
            if percent < 1.0:
                commentLong( FEEDBACK_INCORRECTOUTPUT )
                commentLong( report )
                grade = computeLinearGrade(  percent, GRADE_LINEARGRADINGMINGRADE, GRADE_LINEARGRADINGMAXGRADE )
                printGrade( grade )
                return False
            else:
                commentLong( FEEDBACK_CORRECTOUTPUT )
                printGrade( GRADE_CORRECTOUTPUT )
                return True
                
    else:
        log( "compareUserExpected: All good! testing userOutText = " + str( userOutText ) +
                " against expectedOutText = " + str( expectedOutText ) )
        missMatchLineNumbers, report = \
            compareUserExpected( "", userOutText, expectedOutText )
                
        if len( missMatchLineNumbers ) != 0:
            commentLong( FEEDBACK_INCORRECTOUTPUT )
            commentLong( "\n".join( report ))
            printGrade( GRADE_INCORRECTOUTPUT )
            return False
 

    commentLong( FEEDBACK_CORRECTOUTPUT )
    printGrade( GRADE_CORRECTOUTPUT )
    return True

def testForBoxedOutpout( userOut ):
    """returns True if output has lines of same length (after being
    stripped.   returns False otherwise."""
    lines = userOut.split( "\n" )
    lines = [k.strip() for k in lines if len(k.strip()) != 0 ]
    if len( lines ) == 0:
        return False
    boxLength = len( lines[0] )
    for line in lines:
        if len( line ) != boxLength:
            return False
    return True

"""
def findLineNo( pattern, text ):
    import re
    for i, line in enumerate( text.split( "\n" ) ):
        #if line.find( pattern ) != -1:
        #    return i+1
        res = re.search( pattern, line )
        if res != None:
            return i
        
    return -1
"""

def createInputFiles():
    """create input data files for the program.  This should be called
    before every call to the solution or student's program, as the assignment
    may require the program to modify the input data file."""
    # create data files if necessary
    if 'noInputFiles' in globals():
        #print( "found noDataFiles in globals" )
        for i in range( noInputFiles ):
            inputName = "input%d" % (i+1) 
            fileText = globals()[ inputName ]
            log( "writing to " + inputName +  ":" + fileText + "\n" )
            open( inputName, "w" ).write( fileText )
    else:
        log( "createDataFiles(): no files found to create!" )
#      
def createDataFiles():
    """create input data files for the program.  This should be called
    before every call to the solution or student's program, as the assignment
    may require the program to modify the input data file."""
    # create data files if necessary
    if 'noDataFiles' in globals():
        #print( "found noDataFiles in globals" )
        for i in range( noDataFiles ):
            fileName = "dataFile%dName" % (i+1)
            fileName = globals()[ fileName ]
            fileText = "dataFile%d" % (i+1)
            fileText = globals()[ fileText ]
            log( "writing to " + fileName +  ":" + fileText + "\n" )
            open( fileName, "w" ).write( fileText )
    else:
        log( "createDataFiles(): no files found to create!" )
#----------------------------------------------------------------------
# functions related to finding several string patterns on the same
# line in the source code
# For example, to see if the program contains "def main():" left
# aligned, in the source, create a list [ "^def ", " main():" ]
# and pass it to patternsAppearOnSameLine().
def findLineNos( pattern, text ):
    """returns a list of line numbers where the pattern
    appears in text"""
    lineNos = []
    pattern = escapeString( pattern )
    for i, line in enumerate( text.split( "\n" ) ):
        res = re.search( pattern, line )
        if res != None:
            #print( "  re.search( %s, %s ) --> %s" % ( pattern, line, res ) )
            lineNos.append( i )

    #print( "findLineNos( '%s' ) = %s" % ( pattern, lineNos ) )       
    return lineNos

def escapeString( s ):
    s = s.replace( "(", "\(" ).replace( ")", "\)" )
    s = s.replace( "[", "\[" ).replace( "]", "\]" )
    return s

def patternsAppearOnSameLine( patterns, text ):
    if len( patterns )== 0:
        #print( "patternsAppearOnSameLine: case 0" )
        return False # trivial case

    lineNos = set( findLineNos( patterns[0], text ) )
    #print( "lineNos = ", lineNos )
    if len( patterns )==1:
        if lineNos != set():
            #print( "patternsAppearOnSameLine: case 1a" )
            return True
        else:
            #print( "patternsAppearOnSameLine: case 1b" )
            return False
    
    for pattern in patterns[1:]:
        lineNos2 = set( findLineNos( pattern, text ) )
        if lineNos2.intersection( lineNos ) == set():
            #print( "patternsAppearOnSameLine: case 2" )
            #print( "lineNos2 = ", list(lineNos2), " lineNos = ", list(lineNos) )
            return False
        
    #print( "patternsAppearOnSameLine: case 3" )
    return True

def addIfNameEqMainFunc( fileName ):
    """ add if __name__=="__main__": to the program
    """
    file = open( fileName, "r" )
    lines = file.read()
    file.close()

    newLines = ""
    for line in lines.split( "\n" ):
        if line.find( "main()" )==0:
            line = 'if __name__ == "__main__":\n\tmain()' 
        newLines += line + "\n"

    # write it back                                                                                                    
    file = open( fileName, "w" )
    file.write( newLines )
    file.close()

def computeLinearGrade( percent, minGrade, maxGrade ):
    """computes the grade as a linear expression of the number of correct
    lines output t0 to the expected lines, relative to the min and maxGrades
    passed.  Percent should be a float between 0 and 1.0.  minGrade and maxGrade
    should be a string between 'F' and 'A' ('C-', 'B+' type grades are allowed).  
    """
    # create list of grades, from 'F' to 'A'.
    #print( "computeLinearGrade( %1.2f, %s, %s )" % ( percent, minGrade, maxGrade) )
    grades = ['F']
    for g in ['D','C','B','A']:
        for x in ['-', '', '+' ]:
            grades.append( g+x )
    grades = grades[0:-1]
    
    # compute grade based on linear fit.  if percent is 0.0, then the grade
    # will be minGrade.  If percent is 1.0, then the grade will be maxGrade.
    indexLow = grades.index( minGrade.strip() )
    indexHigh= grades.index( maxGrade.strip() )
    index = int( round( indexLow + (indexHigh-indexLow)*percent ) )
    grade = grades[index]
    return grade

def computePercentGoodOutput( studentOut, expectedOut ):
    """ computePercentGoodOutput: given two textual outputs
    that should be mostly identical, computes the percentage
    of matching lines between the two.   This percentage,
    between 0 and 1.0 can be used to generate a grade.
    """
    global stripOutputLines, compareOnlyNumbersInOutput
    global removeBlankLines, removeExtaSpaces
        
    # split in lines
    userOutTextLines = studentOut.splitlines()
    expectedOutTextLines = expectedOut.splitlines()
    
    if stripOutputLines:
        userOutTextLines = [k.strip() for k in userOutTextLines ]
        expectedOutTextLines = [k.strip() for k in expectedOutTextLines ]
        
    if removeBlankLines: 
        userOutTextLines = removeBlankLinesFrom( userOutTextLines )
        expectedOutTextLines = removeBlankLinesFrom( expectedOutTextLines )
        
    if removeExtaSpaces:
        userOutTextLines = [' '.join( k.split()) for k in userOutTextLines]
        expectedOutTextLines = [' '.join( k.split()) for k in expectedOutTextLines]
        
    if sortOutputLines:
        userOutTextLines.sort()
        expectedOutTextLines.sort()

    if compareOnlyNumbersInOutput:
        userOutTextLines = removeAllExceptNumbers( userOutTextLines ) 
        expectedOutTextLines = removeAllExceptNumbers( expectedOutTextLines ) 
    
        
    seq1lines = expectedOutTextLines
    seq2lines = userOutTextLines
    
    d = difflib.Differ()
    diff = d.compare( seq1lines, seq2lines )
    unified_diffOutputLines = [k.strip() for k in diff]

    countTotal = 0 # counts matching lines
    countPlus  = 0 # counts lines that start with '+'
    countMinus = 0 # counts lines that start with '-'

    # go through each line from the comparison and perform
    # some accounting.
    for line in unified_diffOutputLines:
        if len( line.strip() )==0: continue
        if line.find( "?" )==0: continue
        if line.find( "+++" )==0 or line.find( "---" )==0 \
           or line.find( "@@@" )==0: continue
        #print( "line: ", line )
        if line[0]=="+": countPlus += 1
        elif line[0]=="-": countMinus += 1
        else: countTotal += 1
    #print( countPlus, countMinus, countTotal )
    return countTotal * 1.0 / ( countTotal + countMinus ), "\n".join( unified_diffOutputLines )
    
def displayInputFilesForUser():
    """display the contents of the input files for the user."""
    input = [input1, input2, input3, input4, input5]
    
    createDataFiles()
    createInputFiles()

    commentLongStart()
    print( "Your program was tested with %d input data or files:" % (noInputFiles) )
    for i in range( noInputFiles ):
        for file in input[i].splitlines():
            print( "Input #%d: %s" % ((i+1), file) )
            try:
                print( open( file, "r" ).read() )
            except:
                pass
                #print( "(invalid file name)" )
        print()
    
    commentLongEnd()
#----------------------------------------------------------------------
#                                   MAIN
#----------------------------------------------------------------------
def main():
    global noInputFiles
    global moduleName
    global solutionFileName
    global checkForBoxedOutput
    global testModuleNames
            
    # reset log
    resetLog()
    
            
    # test code for various patterns
    """
    if ('patternsMustBePresent' in globals()) \
        and len( patternsMustBePresent ) != 0 or len( patternsMustNotBePresent ) != 0 or removeComments:
        
        # remove all comments first
        #bareSource = pyminifier.removeComments( open( moduleName + ".py", "r" ).read() )
        bareSource = open( moduleName + ".asm", "r" ).read()
        
        # deal with patterns that must be present, if any        
        for i, patternGroup in enumerate( patternsMustBePresent ):
            if patternsAppearOnSameLine( patternGroup, bareSource ) == False:
                commentLong( FEEDBACK_PATTERNMUSTBEPRESENT[i] + "\n" )
                printGrade( GRADE_ERRORPATTERNMUSTBEPRESENT )
                return
            
        # deal with patterns that must NOT be present, if any        
        for i, patternGroup in enumerate( patternsMustNotBePresent ):
            if patternsAppearOnSameLine( patternGroup, bareSource ) == True:
                commentLong( FEEDBACK_PATTERNMUSTNOTBEPRESENT[i] + "\n" )
                printGrade( GRADE_ERRORPATTERNMUSTNOTBEPRESENT )
                return
    """
                    
    # test program runs
    Ok = False
    if noInputFiles == 0:
        log( "calling testProgramWithNoInputFiles()")
        testProgramWithNoInputFiles()
        return
    else:   
        if 'displayContentsOfInputFiles' in globals() and displayContentsOfInputFiles==True:
            displayInputFilesForUser()
        log( "calling testProgramWithInputFiles()")
        testProgramWithInputFiles()
        
main()


params.py

<br />
"""
params.py
D. Thiebaut

This module contains only constants used by evaluate.py to run a student program
and a solution program for a given assignment.  The outputs of the two programs
are compared and used to generate a grade.

Edit the different constants according to how the student program is supposed
to operate, and what is important in the output.   Options allow to test only
the numbers in the output, skipping words, to remove all blank lines from
the output, to remove all extra whitespace, or to sort the output lines 
alphabetically.  

06/21/15 Version 1: first release 
"""


# moduleName: this is the name of the student program.  Do not include
# the .py at the end.
moduleName = "hw2"
testModuleNames = [ "231Lib" ]

# solutionFileName: the name of the solution program. Do not include the
# .py extension at the end
solutionFileName = "hw2sol"


# removeBlankLines: set to True if blank lines should be removed from
# the student program and from the solution program output.
removeBlankLines = True


# removeComments: set to False if comments should no be removed.  This
# option is important if special text patterns will be tested in the
# source code.  For example, ensuring that students use a main() function
# or that the source code does not use min() or max() for some 
# assignment.
removeComments = False


# removeExtraSpaces: always useful when the output format is not important
# and student program's output is not format-specific.
removeExtaSpaces = True


# addIfNameEqMain: if True, evaluate.py will search if the student
# or solution program calls main() directly, left-aligned, and if so
# will add "if __name__ == "__main__": in front of it.  This insures that
# the student program can be run only by calling main().
addIfNameEqMain = False


# stripIfNameEqualMain: if the student program uses "if __name__ == "__main__":
# before calling main(), then remove the if statement and unindent the call
# to main()
stripIfNameEqualMain = False


# sortOutputLines: if the order in which the output lines are generated 
# is unimportant, and could vary depending on the student's approach, then
# set this boolean to False.  This is done before the student and solutions
# outputs are compared. 
sortOutputLines = False


# stripOutputLines: if True, then all lines output by the student or solution
# program will be stripped of leading & trailing whitespace before comparison.
stripOutputLines = True


# compareOnlyNumbersInOutput: if True, all words that are not numbers will
# be removed from the outputs of the student and solution programs.
compareOnlyNumbersInOutput = False


# userOutputMustContainExpectedOutpout: if True the output of the student program
# is valid as long as it contains the output of the solution program.  All other 
# lines in the student program are discarded.
userOutputMustContainExpectedOutpout = True


# checkForBoxedOutput: Set this to False if the output of the programs must fit in
# a +-----+
#   | box |
#   +-----+
#
checkForBoxedOutput = False

# compareExecutables: if True, the size of the executable generated by nasm and ld
# is compared to the size of the solution executable.  As well, the byte to byte
# comparison is performed, except for the last 100 bytes, where the name of the 
# program might reside.
compareExecutables = False
compareDataSections = False

# -----------------------------------------------------------------------------
# gradeLinearlyWithNumberCorrectLines: if True, the grade will be linearly
# determined by the number of output lines that do not match the output of the
# solution program, over the total number of lines.
gradeLinearlyWithNumberCorrectLines = False


# GRADE_LINEARGRADINGMINGRADE: the lowest possible grade when using linear
# grading (see above flag)
GRADE_LINEARGRADINGMINGRADE = "F"     # Min grade for linear grading


# GRADE_LINEARGRADINGMAXGRADE: the highest possible grade when using linear
# grading (see above flag).  Should normally be 'A'
GRADE_LINEARGRADINGMAXGRADE = "A"    # Max grade for linear grading


# userOutSplitPattern: the pattern used to separate the input section of
# the programs, when the programs use input() statements, and the output
# resulting from the computation.  The simplest is to make all programs
# print a blank line after the input section and before displaying the
# results.  This way, separating the input from the output is simply
# done by looking for a \n in the output and splitting there. 
userOutSplitPattern = ""


# mustCallMain: makes sure the program calls the main() function somewhere
# in the code.
mustCallMain = False


# displayContentsOfInputFiles: if True the contents of the input files is
# displayed in the response to the student.   
displayContentsOfInputFiles = True


# programOutputInFile: if True, the output of the student and solution 
# programs are in a text file.  The name of the text file should be 
# constant and not depend on any input data, and is specified in the 
# outputDataFile variable (see below).
programOutputInFile = False


# outputDataFile: the name of the file containing the output of the program.
# controlled by the programOutputInFile boolean (above)
outputDataFile = ""


# -----------------------------------------------------------------------------
# noDataFiles: defines the number of auxiliary files the student and solution
# programs will need.   The file names and their contents will be defined in
# the dataFile?Name and dataFile? variables (see below).  Up to 4 data files
# are allowed.
noDataFiles = 0


# dataFile1Name: name of Data file 1
dataFile1Name = "data1.txt"


# dataFile1: contents of Data File 1
dataFile1 = """
dummy
"""


# dataFile2Name: name of Data file 2
dataFile2Name = "data2.txt"


# dataFile2: contents of Data File 2
dataFile2 = """ """


# dataFile3Name: name of Data file 3
dataFile3Name = " "


# dataFile3: contents of Data File 3
dataFile3 = """ """


# dataFile4Name: name of Data file 4
dataFile4Name = ""


# dataFile4: contents of Data File 4
dataFile4 = """ """


# -----------------------------------------------------------------------------
# noInputFiles: the number of files containing the contents of stdin, as it will
# be fed to the student and solution programs.   Maximum value 5.
noInputFiles = 1


# input1: contents of Input File 1
input1 = """10
2
3


"""



# input2: contents of Input File 2
input2 = """
"""


# input3: contents of Input File 3
input3 = """
"""


# input4: contents of Input File 4
input4 = """
"""


# input5: contents of Input File 5
input5 = """
"""


# -----------------------------------------------------------------------------
# GRADE_CORRECTOUTPUT: the grade for a correct output: normally 'A'
GRADE_CORRECTOUTPUT = "A"


# GRADE_INCORRECTOUTPUT: the grade for an  incorrect output.
GRADE_INCORRECTOUTPUT = "C+"


# GRADE_PROGRAMCRASHES: grade if program crashes
GRADE_PROGRAMCRASHES = "C-"


# GRADE_OUTPUTNOTFORMATTEDCORRECTLY: grade if output format of output is not correct
# This is used for boxed output.
GRADE_OUTPUTNOTFORMATTEDCORRECTLY = "C"


# GRADE_ERRORPATTERNMUSTBEPRESENT: grade received if the program must contain a patter
# but that pattern is not found.  For example, declaring a main function, or calling a 
# main function (see pattern lists below).
GRADE_ERRORPATTERNMUSTBEPRESENT = "C"


# GRADE_ERRORPATTERNMUSTNOTBEPRESENT: grade received if the program contains a pattern
# that is not allowed.  For example, if the program uses the min() or max() function 
# (see pattern lists below) when it shouldn't.
GRADE_ERRORPATTERNMUSTNOTBEPRESENT = "C"

# GRADE_EXECUTABLESDIFFERENTSIZE: grade for executable not matching the executable of
# the solution.  To be used in assignment where students have to reverse engineer an
# executable into an assembly program.
GRADE_EXECUTABLESDIFFERENTSIZE = "C"
GRADE_DATASECTIONSDIFFERENTSIZE = "C"

# -----------------------------------------------------------------------------
# FEEDBACK_CORRECTOUTPUT: message printed when the output of the student and solution
# programs match.  Adapt to the assignment in question.
FEEDBACK_CORRECTOUTPUT = """Congrats!  the output of your program is correct."""


# FEEDBACK_PROGRAMCRASHES: message printed when student program crashes.  The error
# produced by the interpreter will also be printed.
FEEDBACK_PROGRAMCRASHES = """Your program generated an error when assembled, linked, or when it was run.
This might be because of a syntax error in your program.  Make
sure your program assembles, links, and runs correctly before submitting it.
"""


# FEEDBACK_INCORRECTOUTPUT: message printed when the output is not correct. Adapt this
# message to the assignment.
FEEDBACK_INCORRECTOUTPUT = """Your output is not correct.
A line output by your program does not match the expected output.
"""


# FEEDBACK_OUTPUTNOTFORMATTEDCORRECTLY: message printed if the output is not formatted in a box.
FEEDBACK_OUTPUTNOTFORMATTEDCORRECTLY = """The output of your program is not correctly formatted in a box.\n"""


# FEEDBACK_OUTPUTFORMATTEDCORRECTLY: message printed if the output is nicely formatted as a box.
FEEDBACK_OUTPUTFORMATTEDCORRECTLY = """Output correctly formatted in a box.\n"""


# -----------------------------------------------------------------------------
# patternsMustBePresent:
# these are list of patterns that should be present on the same line (once all comments are removed)
# for example [ ["def", "main("], ["def", "min(" ] ] requires that the student's program contain 
# 2 function definitions, main, and min.
# Theire should be as many feedback string as there are groups of patterns.  For example, for the
# example of main and min, you could have
# [ "Your program should define a main function", Your program should contain a min function" ]
# as feedback given for the group of patterns that would not be satisfied.
patternsMustBePresent = [] # [  "def\s?main(\s?):" ] ]
                          #[  "^main(\s?)" ] ]


# FEEDBACK_PATTERNMUSTBEPRESENT: feedback given for each pattern in the list above that is not
# found in the student program.                       
FEEDBACK_PATTERNMUSTBEPRESENT = [ "You seem to have forgotten to define the function main()" ]
                                #  "Your program does not call the main function." ]


# patternsMustNotBePresent:  similar to patternsMustBePresent, but in reverse.  
# The patterns in a group should not appear on the same line.
# for assignments where min() or max() shouldn't be used, use [ [ "=min(" ], ["=max(" ] ]
patternsMustNotBePresent = [  ] # [ "=min(" ], ["=max(" ] ]


# FEEDBACK_PATTERNMUSTNOTBEPRESENT: message printed if the program uses one of the forbidden 
# patterns.
FEEDBACK_PATTERNMUSTNOTBEPRESENT = [ "You cannot use the min() or max() function in your program." , 
                                    "You cannot use the min() or max() function in your program."  ]


# FEEDBACK_EXECUTABLESDIFFERENTSIZE: message for times where executables of student and solution must
# match.
FEEDBACK_EXECUTABLESDIFFERENTSIZE = "Your executable does not match the solution executable."
FEEDBACK_DATASECTIONSDIFFERENTSIZE = "Your data section is not the same as the original program."


hw2sol.asm

<br />
;;; ; ; hw2sol.asm
;;; ; ; D. Thiebaut
;;; ; ;
;;; ; ;


	        extern  _printDec
	        extern  _printString
	        extern  _println
	        extern  _getInput
	
	section	.data
prompt		db	"> "
promptLen	equ	$-prompt	
ansStr          db      "ans = "
ansStrLen	equ	$-ansStr	

a		dd	0
b		dd	0
c		dd	0
ans		dd	0
	
		section	.text
		global	_start
_start:
	;; display prompt
		mov	ecx, prompt
		mov	edx, promptLen
		call	_printString
	;; get a
		call	_getInput
		mov	dword[a], eax

	;; display prompt
		mov	ecx, prompt
		mov	edx, promptLen
		call	_printString
	;; get b
		call	_getInput
		mov	dword[b], eax
	
	;; display prompt
		mov	ecx, prompt
		mov	edx, promptLen
		call	_printString
	;; get c
		call	_getInput
		mov	dword[c], eax
	
	;; -----------------------------------
	;; computation: ans = 2*(a-b) + 3*c
	;; -----------------------------------
	
		mov	eax, dword[a] ;eax <- a
		sub	eax, dword[b] ;eax <- a-b
		add	eax, eax      ;eax <- a-b + a-b

		add	eax, dword[c]
		add	eax, dword[c]
		add	eax, dword[c] ;eax <- 2*(a-b) +3*c
		mov	dword[ans], eax
					;ans <- eax
	;; -----------------------------------
	;; display "ans ="
	;; -----------------------------------
		mov	ecx, ansStr
		mov	edx, ansStrLen
		call	_printString

	;; -----------------------------------
	;; display ans variable
	;; -----------------------------------
		mov	eax, dword[ans]
		call	_printDec
		call	_println
		call	_println
	
;;; exit
		mov	ebx, 0
		mov	eax, 1
		int80