Difference between revisions of "CSC231 Homework 2 Fall 2017"
(→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 = | + | 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 /> | ||
− | + | cs231a@aurora ~ $ ./hw2 | |
− | > | + | > 0 |
> 1 | > 1 | ||
> 10 | > 10 | ||
− | ans = | + | ans = -9 |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | cs231a@aurora ~ $ ./hw2 | |
> 1 | > 1 | ||
> 2 | > 2 | ||
> 3 | > 3 | ||
− | ans = | + | ans = 4 |
− | + | cs231a@aurora ~ $ ./hw2 | |
> 10 | > 10 | ||
− | > | + | > 1 |
− | > | + | > 3 |
− | ans = | + | ans = 28 |
+ | cs231a@aurora ~ $ ./hw2 | ||
+ | > 100 | ||
+ | > 1000 | ||
+ | > 6 | ||
+ | ans = 3292 | ||
+ | |||
==Submission== | ==Submission== | ||
<br /> | <br /> | ||
Line 108: | Line 108: | ||
<br /> | <br /> | ||
− | =Assembly | + | =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"> | ||
− | + | ||
;;; 231Lib.asm | ;;; 231Lib.asm | ||
;;; A simple I/O library for CSC231. | ;;; A simple I/O library for CSC231. | ||
Line 758: | Line 758: | ||
ret | ret | ||
− | |||
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)
Contents
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