Difference between revisions of "CSC231 Homework 3 Fall 2017"

From dftwiki3
Jump to: navigation, search
(Additional Information)
 
(6 intermediate revisions by the same user not shown)
Line 2: Line 2:
 
----
 
----
 
<bluebox>
 
<bluebox>
This assignment is due on Monday 10/9/17 at 11:55 p.m.  You can work on this assignment in pairs, in which case you must include both your names in the header of the assembly language program you submit.  '''Both''' members of a pair '''must''' submit a program and answer the quiz.
+
This assignment is due on Monday 10/11/17 at 11:55 p.m.  You can work on this assignment in pairs, in which case you must include both your names in the header of the assembly language program you submit.  '''Both''' members of a pair '''must''' submit a program and answer the quiz.
 
</bluebox>
 
</bluebox>
 
<br />
 
<br />
Line 41: Line 41:
 
==Additional Information==
 
==Additional Information==
 
<br />
 
<br />
* Use the DIV instruction
+
* Use the '''DIV''' instruction
 
* Use ints (declared with '''dd''') to store the different quantities your program will compute, including the original number of seconds provided by the user.
 
* Use ints (declared with '''dd''') to store the different quantities your program will compute, including the original number of seconds provided by the user.
 
* Do not worry about whether words should be singular or plural.  We do not know how to test quantities yet.  Make your program output all the words in their plural form.
 
* Do not worry about whether words should be singular or plural.  We do not know how to test quantities yet.  Make your program output all the words in their plural form.
Line 52: Line 52:
 
* Document your program well, and submit it on Moodle, in the Homework 3, Problem 1 section.  Be careful as Moodle gives only 5-minute increments for the closing time, which means you have until 11:55 p.m. on Monday to submit the program.
 
* Document your program well, and submit it on Moodle, in the Homework 3, Problem 1 section.  Be careful as Moodle gives only 5-minute increments for the closing time, which means you have until 11:55 p.m. on Monday to submit the program.
 
<br />
 
<br />
 +
 
=Problem 2=
 
=Problem 2=
 
<br />
 
<br />
* Answer the quiz on Moodle, in the Homework 3 Problem 2 section.
+
* Answer the quiz on Moodle, in the Homework 3 Problem 2 section.   This quiz refers to the program you wrote in Problem 1.
 +
<br />
 
<br />
 
<br />
 +
<showafterdate after="20171012 12:00" before="20171231 00:00">
 +
=Solution Program=
 +
::<source lang="asm">
 +
;;; ; program_name.asm
 +
;;; ; your name
 +
;;; ;
 +
;;; ; a description of the program
 +
;;; ;
 +
;;; ; to assemble and run:
 +
;;; ;
 +
;;; ;    nasm -f elf -F  stabs program.asm
 +
;;; ;    ld -melf_i386 -o program program.o
 +
;;; ;    ./program
 +
;;; ; -------------------------------------------------------------------
 +
 +
;; %include files here...
 +
extern _printInt
 +
extern _getInput
 +
extern _printString
 +
extern _println
 +
 +
;;;  ------------------------------------------------------------
 +
;;;  data areas
 +
;;;  ------------------------------------------------------------
 +
 +
        section .data
 +
seconds dd 0
 +
days dd 0
 +
hours dd 0
 +
min dd 0
 +
sec dd 0
 +
 +
prompt db "> "
 +
secStrings db " secs "
 +
daysString db " days "
 +
hoursString db " hours "
 +
minutesString db " minutes "
 +
secondsString db " seconds "
 +
 +
;;;  ------------------------------------------------------------
 +
;;;  code area
 +
;;;  ------------------------------------------------------------
 +
 +
        section .text
 +
        global  _start
 +
_start:
 +
;;; get number of seconds
 +
mov ecx, prompt
 +
mov edx, 2
 +
call _printString
 +
call _getInput
 +
mov dword[seconds], eax
 +
 +
;;; compute # of minutes and left-over seconds
 +
mov edx, 0
 +
mov ebx, 60
 +
div ebx
 +
mov dword[sec],edx
 +
mov edx, 0 ; edx:eax contains # of minutes
 +
 +
;;; compute # of hours and left-over minutes
 +
div ebx
 +
mov dword[min], edx
 +
mov edx, 0 ; edx:eax contains # of hours
 +
 +
;;; compute # of days and left-over hours
 +
mov ebx, 24
 +
div ebx
 +
mov dword[hours], edx
 +
mov dword[days], eax
 +
 +
;;; print the whole day, hours, minutes, seconds string
 +
mov eax, dword[days]
 +
call _printInt
 +
mov ecx, daysString
 +
mov edx, 6
 +
call _printString
 +
 +
mov eax, dword[hours]
 +
call _printInt
 +
mov ecx, hoursString
 +
mov edx, 7
 +
call _printString
 +
 +
mov eax,dword[min]
 +
call _printInt
 +
mov ecx, minutesString
 +
mov edx, 9
 +
call _printString
 +
 +
mov eax, dword[sec]
 +
call _printInt
 +
mov ecx, secondsString
 +
mov edx, 9
 +
call _printString
 +
call _println
 +
;;;  exit()
 +
 +
        mov    eax,1
 +
        mov    ebx,0
 +
        int    0x80 ; final system call
 +
 +
 +
</source>
 +
</showafterdate>
 
<br />
 
<br />
 +
<onlydft>
 +
=VPL=
 +
==vpl_run.sh==
 +
<source lang="bash">
 +
#! /bin/bash
 +
# D. Thiebaut
 +
# Smith College
 +
# vpl_evaluate.sh script looping through several tests, each test with
 +
# its own input file and its own expected output file.  The output of the
 +
# student program is tested against the expected output file.
 +
# set -x
 +
 +
 +
cat > vpl_execution <<EEOOFF
 +
#! /bin/bash
 +
 +
# --- program tested (no extension) ---
 +
prog1=hw3
 +
prog1sol=hw3sol
 +
 +
# --- compile 231Lib
 +
nasm -f elf 231Lib.asm
 +
 +
# --- compile solution program
 +
nasm -f elf \${prog1sol}.asm
 +
ld -melf_i386 \${prog1sol}.o 231Lib.o -o \$prog1sol
 +
 +
# --- compile student program ---
 +
if  nasm -f elf \${prog1}.asm ; then
 +
    echo "nasm pass: Ok!"
 +
else
 +
    #echo "Syntax error found in asm file"
 +
    grade=50
 +
    #echo "Grade :=>> \$grade"
 +
    exit 0
 +
fi
 +
 +
if  ld -melf_i386 231Lib.o \${prog1}.o -o \$prog1 ; then
 +
    echo "ld pass: Ok!"
 +
else
 +
    #echo "Comment :=>> Linker error found in object file(s)"
 +
    grade=50
 +
    #echo "Grade :=>> \$grade"
 +
    exit 0
 +
fi
 +
 +
./\${prog1}
 +
 +
EEOOFF
 +
 +
 +
chmod +x vpl_execution
 +
</source>
 +
==vpl_evaluate.sh==
 +
<source lang="bash">
 +
#! /bin/bash
 +
 +
cat  > vpl_execution <<EOF
 +
#! /bin/bash
 +
 +
# --- Python ----
 +
if [[ `hostname -s` = "aurora" ]]; then
 +
  python=/usr/bin/python3.4
 +
else
 +
  python=/usr/local/bin/python3.4
 +
fi
 +
 +
 +
\$python evaluate2.py
 +
 +
EOF
 +
 +
chmod +x vpl_execution
 +
 +
 +
</source>
 +
==params.py==
 +
<source lang="python">
 +
"""
 +
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 = "hw3"
 +
testModuleNames = [ "231Lib" ]
 +
 +
# solutionFileName: the name of the solution program. Do not include the
 +
# .py extension at the end
 +
solutionFileName = "hw3sol"
 +
 +
 +
# removeBlankLines: set to False 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 = True
 +
 +
 +
# 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 = False
 +
 +
 +
# 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 = "C"    # 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 = False
 +
 +
 +
# 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
 +
input
 +
"""
 +
 +
 +
# 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 = 5
 +
 +
 +
# input1: contents of Input File 1
 +
input1 = """0
 +
 +
"""
 +
 +
 +
 +
# input2: contents of Input File 2
 +
input2 = """60
 +
 +
"""
 +
 +
 +
# input3: contents of Input File 3
 +
input3 = """3601
 +
 +
"""
 +
 +
 +
# input4: contents of Input File 4
 +
input4 = """10000
 +
 +
"""
 +
 +
 +
# input5: contents of Input File 5
 +
input5 = """10001
 +
 +
"""
 +
 +
 +
# -----------------------------------------------------------------------------
 +
# 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 = "D"
 +
 +
 +
# 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>
 +
==evaluate2.py==
 +
<source lang="python">
 +
'''
 +
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 ---
 +
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 ))
 +
            commentLong( "\nExpected output:\n" + expectedOutText )
 +
            commentLong( "\nYour output:\n" + userOutText )
 +
        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 = assembleLinkAndRun( solutionFileName, testModuleNames, "input.txt" )       
 +
        #dummy, expectedOutText, score2 = runModule( solutionFileName, "input.txt", "expectedOut", testModuleNames )
 +
    else:
 +
        dummy, expectedOutText = assembleLinkAndRun( solutionFileName,  "input.txt" )       
 +
        #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()
 +
 +
        newBareSource = []
 +
        if removeComments:
 +
            for line in bareSource.split( "\n" ):
 +
                index = line.find( ";" )
 +
                if index != -1:
 +
                    line = line[0:index]
 +
                newBareSource.append( line )
 +
        bareSource = "\n".join( newBareSource )
 +
 +
        # 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>
 +
</onlydft>
 
<br />
 
<br />
 
<br />
 
<br />

Latest revision as of 17:46, 7 October 2017

--D. Thiebaut (talk) 17:26, 1 October 2017 (EDT)


This assignment is due on Monday 10/11/17 at 11:55 p.m. You can work on this assignment in pairs, in which case you must include both your names in the header of the assembly language program you submit. Both members of a pair must submit a program and answer the quiz.


Problem 1


Assignment


  • Write an assembly language program that you will link with the 231Lib library, and that will prompt the user for an integer (positive) number of seconds, which it will then translate in a number of days, hours, minutes and seconds.
  • Call your program hw3.asm.
  • The format of your output should be similar to the format illustrated in the examples shown below.
  • The capture below shows how your program should be behaving.


cs231a@aurora ~/ $ ./hw3
> 1
0 days 0 hours 0 minutes 1 seconds 
cs231a@aurora ~/ $ ./hw3
> 0
0 days 0 hours 0 minutes 0 seconds 
cs231a@aurora ~/ $ ./hw3  
> 3601
0 days 1 hours 0 minutes 1 seconds 
cs231a@aurora ~/ $ ./hw3 
> 8641
0 days 2 hours 24 minutes 1 seconds 
cs231a@aurora ~/ $ ./hw3  
> 86401
1 days 0 hours 0 minutes 1 seconds 
cs231a@aurora ~/ $ 864001
864001: command not found
cs231a@aurora ~/ $ ./hw3  
> 864001
10 days 0 hours 0 minutes 1 seconds 
cs231a@aurora ~/ $ 


Additional Information


  • Use the DIV instruction
  • Use ints (declared with dd) to store the different quantities your program will compute, including the original number of seconds provided by the user.
  • Do not worry about whether words should be singular or plural. We do not know how to test quantities yet. Make your program output all the words in their plural form.
  • Make your program output all the quantities on one line.
  • You can use WolfranAlpha to convert seconds to day/hours/minutes/seconds, to test your program. WolframAlpha will correctly understand what to do if you enter something like "864001 seconds" in the search bar.


Submission


  • Document your program well, and submit it on Moodle, in the Homework 3, Problem 1 section. Be careful as Moodle gives only 5-minute increments for the closing time, which means you have until 11:55 p.m. on Monday to submit the program.


Problem 2


  • Answer the quiz on Moodle, in the Homework 3 Problem 2 section. This quiz refers to the program you wrote in Problem 1.



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

Solution Program

;;; ; program_name.asm
;;; ; your name
;;; ;
;;; ; a description of the program
;;; ;
;;; ; to assemble and run:
;;; ;
;;; ;     nasm -f elf -F  stabs program.asm
;;; ;     ld -melf_i386 -o program program.o
;;; ;     ./program
;;; ; -------------------------------------------------------------------

	;; %include files here...
	extern	_printInt
	extern	_getInput
	extern	_printString
	extern	_println
	
;;;  ------------------------------------------------------------
;;;  data areas
;;;  ------------------------------------------------------------

	        section .data
seconds		dd	0
days		dd	0
hours		dd	0
min		dd	0
sec		dd	0
	
prompt		db	"> "
secStrings	db	" secs "	
daysString	db	" days "
hoursString	db	" hours "
minutesString	db	" minutes "
secondsString	db	" seconds "
	
;;;  ------------------------------------------------------------
;;;  code area
;;;  ------------------------------------------------------------

	        section .text
	        global  _start
_start:
;;; get number of seconds
		mov	ecx, prompt
		mov	edx, 2
		call	_printString
		call	_getInput
		mov	dword[seconds], eax

;;; compute # of minutes and left-over seconds
		mov	edx, 0
		mov	ebx, 60
		div	ebx
		mov	dword[sec],edx
		mov	edx, 0	; edx:eax contains # of minutes
	
;;; compute # of hours and left-over minutes
		div	ebx
		mov	dword[min], edx
		mov	edx, 0	; edx:eax contains # of hours

;;; compute # of days and left-over hours
		mov	ebx, 24
		div	ebx
		mov	dword[hours], edx
		mov	dword[days], eax
	
;;; print the whole day, hours, minutes, seconds string
		mov	eax, dword[days]
		call	_printInt
		mov	ecx, daysString
		mov	edx, 6
		call	_printString

		mov	eax, dword[hours]
		call	_printInt
		mov	ecx, hoursString
		mov	edx, 7
		call	_printString

		mov	eax,dword[min]
		call	_printInt
		mov	ecx, minutesString
		mov	edx, 9
		call	_printString

		mov	eax, dword[sec]
		call	_printInt
		mov	ecx, secondsString
		mov	edx, 9
		call	_printString
		call	_println
;;;  exit()

	        mov     eax,1
	        mov     ebx,0
	        int     0x80	; final system call

</showafterdate>


...