├── .htaccess ├── README ├── TODO ├── alias-screenshot.png ├── cgi-bin ├── .htaccess ├── create_db.py ├── db_common.py ├── demjson.py ├── index.html ├── load_question.py ├── parse_questions.py ├── pg_encoder.py ├── pg_logger.py ├── run_tests.py ├── web_exec.py └── web_run_test.py ├── edu-python-questions.js ├── edu-python-title.css ├── edu-python-tutor.js ├── edu-python.css ├── edu-python.js ├── example-code ├── aliasing.txt ├── fact.txt ├── fib.txt ├── filter.txt ├── ins_sort.txt ├── map.txt ├── memo_fib.txt ├── oop_1.txt ├── oop_2.txt ├── oop_inherit.txt ├── oop_small.txt ├── py_tutorial.txt ├── sqrt.txt ├── strtok.txt ├── sum.txt ├── towers_of_hanoi.txt ├── wentworth_gcd.txt ├── wentworth_sumList.txt └── wentworth_try_finally.txt ├── grading-375.png ├── hunt-mcilroy.js ├── index.html ├── jquery-1.3.2.min.js ├── jquery.autogrow.js ├── jquery.ba-bbq.min.js ├── jquery.corner.js ├── jquery.jsPlumb-1.3.3-all-min.js ├── jquery.min.js ├── jquery.textarea.js ├── jsplumb-test.js ├── mock-data.js ├── question.html ├── questions ├── debug-bsearch.txt ├── debug-ireverse.txt ├── debug-mergesort.txt ├── optimize-find-dups.txt ├── optimize-search.txt ├── optimize-sum.txt ├── remove-dups.txt ├── reverse.txt └── two-sum.txt ├── red-sad-face.jpg ├── test-programs ├── caught_exception_1.golden ├── caught_exception_1.py ├── caught_exception_2.golden ├── caught_exception_2.py ├── circ_ref.golden ├── circ_ref.py ├── circ_ref_2.golden ├── circ_ref_2.py ├── circ_ref_fake.golden ├── circ_ref_fake.py ├── class_test.golden ├── class_test.py ├── class_test_2.golden ├── class_test_2.py ├── class_test_3.golden ├── class_test_3.py ├── data_test.golden ├── data_test.py ├── dict_error.golden ├── dict_error.py ├── dict_test.golden ├── dict_test.py ├── exec_test.golden ├── exec_test.py ├── func_exception.golden ├── func_exception.py ├── generator_test.golden ├── generator_test.py ├── import_error.golden ├── import_error.py ├── infinite_loop.golden ├── infinite_loop.py ├── infinite_loop_one_liner.golden ├── infinite_loop_one_liner.py ├── lambda_1.golden ├── lambda_1.py ├── list_dict_test.golden ├── list_dict_test.py ├── list_test.golden ├── list_test.py ├── newstyle_class.golden ├── newstyle_class.py ├── one_func.golden ├── one_func.py ├── open_error.golden ├── open_error.py ├── parse_error.golden ├── parse_error.py ├── parse_error_2.golden ├── parse_error_2.py ├── parse_error_3.golden ├── parse_error_3.py ├── print_builtins_error.golden ├── print_builtins_error.py ├── runtime_error.golden ├── runtime_error.py ├── set_test.golden ├── set_test.py ├── simple.golden ├── simple.py ├── three_lists.golden ├── three_lists.py ├── tuple_test.golden ├── tuple_test.py ├── two_funcs.golden └── two_funcs.py ├── tutor.html ├── tutorials ├── MIT-6.01 │ ├── README │ ├── map.py │ ├── oop_1.py │ ├── oop_2.py │ ├── oop_3.py │ └── summation.py ├── README ├── advanced │ └── map.py ├── math │ └── newton.py ├── oop │ └── oop_demo.py └── personal-finance │ └── compound_interest.py └── yellow-happy-face.png /.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Set a ~2MB limit for response headers (bigger than default 512K limit) 4 | SecResponseBodyLimit 2000000 5 | 6 | 7 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Online Python Tutor 2 | Copyright (C) 2010 Philip J. Guo (philip@pgbovine.net) 3 | https://github.com/pgbovine/OnlinePythonTutor/ 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | 18 | ====== 19 | Introduction: 20 | 21 | The Online Python Tutor is a web application where you can type Python 22 | scripts directly into your web browser, execute those scripts, and 23 | single-step FORWARDS AND BACKWARDS through execution in order to view 24 | the run-time state of all data structures. 25 | 26 | Using this tool, teachers and students can write small Python code 27 | snippets together and see what happens to the data structures when the 28 | code gets executed. 29 | 30 | Try it out live at: http://www.onlinepythontutor.com/ 31 | 32 | ====== 33 | System architecture overview: 34 | 35 | The Online Python Tutor is implemented as a web application, with a 36 | JavaScript front-end making AJAX calls to a pure-Python back-end. 37 | 38 | The back-end has been tested on an Apache server running Python 2.5 39 | through CGI. Note that it will probably fail in subtle ways on other 40 | Python 2.X (and will DEFINITELY fail on Python 3.X). Peter Wentworth 41 | has create a port to Python 3.X, and hopefully we can eventually 42 | integrate his code into my repository. 43 | 44 | 45 | The front-end is HTML/JavaScript (using the jQuery library). It's 46 | responsible for the input text box, submitting the Python code (as 47 | plaintext) to the back-end, receiving an execution trace from the 48 | back-end, and then rendering that trace as data structure 49 | visualizations. The front-end code resides in these files in the 50 | current directory: 51 | 52 | tutor.html 53 | question.html 54 | edu-python.js 55 | edu-python-tutor.js 56 | edu-python-questions.js 57 | edu-python.css 58 | jquery.textarea.js 59 | .htaccess - to increase the size of allowed Apache HTTP responses 60 | 61 | (there are also other 3rd-party JavaScript library files) 62 | 63 | Note on .htaccess: If your server limits the size of responses received 64 | from HTTP requests, then you might need to use the following .htaccess 65 | file included in your top-level (front-end) directory, to allow the 66 | Online Python Tutor to receive traces from the back-end: 67 | 68 | 69 | 70 | # Set a ~2MB limit for response headers (bigger than default 512K limit) 71 | SecResponseBodyLimit 2000000 72 | 73 | 74 | 75 | 76 | The back-end is a server-side CGI application that takes Python script 77 | source code as input, executes the entire script (up to 200 executed 78 | lines, to prevent infinite loops), and collects a full trace of all 79 | variable values (i.e., data structures) after each line has been 80 | executed. It then sends that full trace to the front-end in a 81 | specially-encoded JSON format. The front-end then parses and visualizes 82 | that trace and allows the user to single-step forwards AND backwards 83 | through execution. 84 | 85 | The back-end resides in the cgi-bin/ sub-directory in this repository: 86 | 87 | cgi-bin/web_exec.py - the CGI entrance point to the back-end 88 | cgi-bin/web_run_test.py - the CGI entrance point to the question 89 | grading back-end 90 | cgi-bin/pg_logger.py - the 'meat' of the back-end 91 | cgi-bin/pg_encoder.py - encodes Python data into JSON 92 | cgi-bin/demjson.py - 3rd-party JSON module, since Python 2.5 93 | doesn't have the built-in 'import json' 94 | cgi-bin/create_db.py - for optional sqlite query logging 95 | cgi-bin/db_common.py - for optional sqlite query logging 96 | cgi-bin/.htaccess - for Apache CGI execute permissions 97 | 98 | 99 | Due to the AJAX same-origin policy, the front-end and back-end must be 100 | deployed on the same server (unless you do some fancy proxy magic). 101 | 102 | 103 | ====== 104 | Original founding vision (from January 2010): 105 | 106 | I want to create a web-based interactive learning platform for students 107 | to explore programming. I envision an HTML UI where a student can enter 108 | in code and then single-step through it and see how the data structures 109 | change during execution. 110 | 111 | Key insight: I realized that for the small programs that teachers and 112 | students write for educational purposes, it's possible to simply LOG 113 | everything that happens to data structures during execution. Then we 114 | can simply play back that log in the front-end, which allows 115 | single-stepping forwards and also BACKWARDS. 116 | 117 | After all, we don't need students to be able to interactive probe and 118 | make changes in the middle of execution, which is the only value-added 119 | of a REAL debugger. 120 | 121 | What kinds of things do we want to log? 122 | 123 | On the execution of each line, log: 124 | - the line number just executed 125 | - all data created by the program 126 | 127 | Also log calls and returns of a student's function 128 | (but NOT library functions) 129 | 130 | We can use the Python JSON module to encode data structures in JSON and 131 | send it to the client's web browser 132 | 133 | The PDB debugger (Lib/pdb.py) is written in pure Python: 134 | http://docs.python.org/library/pdb.html 135 | - the bdb debugger framework is the C module that pdb calls 136 | http://docs.python.org/library/bdb.html 137 | 138 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | A disorganized collection of TODOs, bug reports, and friends' suggestions 2 | --- 3 | 4 | January/Feb 2010 (notes from initial release) 5 | 6 | 7 | Maybe don't display boilerplate code when classes are being initialized? 8 | 9 | 10 | Hubert's suggestions for permalinks: 11 | ''' 12 | Hubert: 1) It would be cool to have the ability to link directly to an 13 | execution point, so that when I send you a link, you could see the code 14 | I'd written and the point in execution that I want to discuss. 15 | 16 | me: good suggestion. that also requires me to save your code in 17 | something other than a regular POST variable, which i'm not doing right 18 | now. then i could send links of the following form: 19 | 20 | http://python.pgrind.com/code_id=20#4 (for line 4 of code snippet 20) 21 | 22 | Hubert: yup, yeah the problem lies in storing the code, which brings in 23 | a whole can of worms. but suppose you did, consider a RESTful approach 24 | (which I am drilling into deeply, hence it's on my mind): 25 | 26 | http://python.pgrind.com/pg/code/20#4 27 | 28 | ...quite easy to do and manage in a framework like Django. 29 | ''' 30 | 31 | 32 | Cory McLean: 'Might be nice to be able to automatically jump to particular step 33 | # rather than just inc/decrement and jump to ends, particular if the 34 | number of steps executed is significant.' 35 | 36 | 37 | Related work: 38 | - PyWhip: http://pywhip.appspot.com/ 39 | - also powered by Google App Engine 40 | - Crunchy: http://code.google.com/p/crunchy/ 41 | 42 | --- 43 | 2010-09-05 44 | 45 | From Hacker News feedback: 46 | 47 | - support for raw_input() sounds like something reasonable to include, 48 | though, since it could simply pop up a text box and ask the user to 49 | input a string e.g., javascript:alert("hello, " + prompt("what is your 50 | name?")) 51 | 52 | --- 53 | 2010-09-29 54 | 55 | Chas Leichner (chaoslichen@gmail.com) emailed me to show me his related 56 | project: 57 | 58 | http://www.youtube.com/watch?v=ZiS4MZF5eDQ 59 | http://code.google.com/p/idlecarpentry/ 60 | 61 | He had an interesting idea of annotating lines of code with special 62 | comments, and those comments would display when those lines were being 63 | executed. That way, you can have sorta like 'literate programming 64 | tutorials' that intermix code and comments. I could think about 65 | implementing something similar within my Online Python Tutor. 66 | 67 | e.g., 68 | 69 | #> declare a variable 70 | x = 5 71 | 72 | #> an if statement tests a condition 73 | if x < 5: 74 | y = x + 5 75 | else: 76 | y = x - 5 77 | 78 | the appropriate annotations would display when you stepped through the 79 | line of code immediately below each annotation. 80 | 81 | --- 82 | 2011-09-15 83 | 84 | - Integrate Peter's Python 3 fork into one unified application, and 85 | perhaps deploy online on WebFaction, since they offer both Python 2.X 86 | and Python 3.X on their servers. 87 | 88 | 89 | --- 90 | 2011-09-23 91 | 92 | - The BACKEND can easily print out a nice list of variables in each 93 | stack frame (and globals) by order of their APPEARANCE in the original 94 | program execution ... that way, the front-end doesn't have to "guess" 95 | an optimal order and can simply use the order that the backend has 96 | pre-computed. e.g., list local stack variables by their order of 97 | definition in the original program execution. 98 | 99 | 100 | --- 101 | 2011-09-24 102 | 103 | Ron's suggestion: 104 | 105 | Ron Yeh - Feedback: It'd be nice if (for longer programs) the text 106 | window would auto scroll to the current line. For your "Philip's 107 | 10-minute intro to Python" example, I was stepping through your code but 108 | then the current line went off screen. I had to manually scroll down a 109 | few times. 110 | 111 | Philip Guo - great idea! do you know if there's any jQuery or other js 112 | tricks to do this sort of scrolling? 113 | 114 | Ron Yeh - Pretty sure I've done this before, a while back. Basicaly you 115 | just need a named div or anchor in your document, and then you can 116 | scroll to it. This page might help: 117 | http://css-tricks.com/snippets/jquery/smooth-scrolling/ 118 | 119 | (also auto-scroll the program's stdout output as well as the code 120 | display ... that one is easy --- always auto-scroll to BOTTOM!) 121 | 122 | --- 123 | 2011-09-26 124 | 125 | A subset of the suggestions from MIT folks (see more in my private notes 126 | file): 127 | 128 | - what if the user accidentally (or purposefully) creates a HUGE list or 129 | other data structure? how will the visualization handle this? right 130 | now it might croak. 131 | - one idea is to LIMIT the size of the returned JSON data from the 132 | back-end??? 133 | 134 | - to make it easier to jump to certain points in execution rather than 135 | tediously single-step through execution. e.g., could imagine adding a 136 | scrubber (like for video playback) combined with pointing at line of 137 | code to jump to the NEXT or PREV time that line of code as executed. 138 | 139 | --- 140 | 2011-09-29 141 | 142 | Upgrade to Python 2.7 after moving over to Webfaction, so that I can use 143 | the default standard library "import json" 144 | 145 | 146 | --- 147 | 2011-10-05 feedback from Imran: 148 | 149 | ''' 150 | ok, here's a super artificial example that actually is really confusing 151 | in the standard Python REPL as well: 152 | 153 | cA = type("A",(object,),{}) 154 | cA2 = type("A",(object,),{}) 155 | 156 | a=cA() 157 | a2=cA2() 158 | 159 | If you visualize this program, it's actually better than CPython because 160 | it shows that the two class objects have different ids even though they 161 | have the same __name__, but then a and a2 are just shown as instances of 162 | "A", which is now ambiguous. 163 | 164 | you're well within your rights to call this example bad code (because 165 | holy hell, it is) and out of the scope of your project, but one way to 166 | handle it might be to keep a separate column for type objects, or to 167 | include the class id along with the class __name__ when identifying the 168 | type of an object. 169 | 170 | also, if you modify the OOP example to make A and B new-style classes 171 | inheriting from object, the class visualization shows the __dict__ and 172 | __weakref__ fields in the A and B type objects, with no useful repr for 173 | them. Do you really want those shown? 174 | 175 | can you tell i've been doing crazy python oop stuff recently? 176 | 177 | ps here's an even more painful instance of the bug report: 178 | 179 | x=[1,2,3] 180 | wtfpwn = type('list',(list,),{}) 181 | INCEPTION = [wtfpwn()] 182 | print x 183 | 184 | which "list" is which??? 185 | ''' 186 | 187 | --- 188 | 2011-10-12 189 | 190 | Code snippets from Adam Hartz for time-limiting a Python child process: 191 | 192 | def setlimits(): 193 | """ 194 | Helper to set CPU time limit for check_code, so that infinite loops 195 | in submitted code get caught instead of running forever. 196 | """ 197 | resource.setrlimit(resource.RLIMIT_CPU, (10, 10)) 198 | 199 | def check_python_code(code): 200 | #TODO: Investigate pypy for sandboxing 201 | """ 202 | Run the given code; outputs a tuple containing: 203 | the program's output (stdout), error messages (stderr) 204 | """ 205 | python = subprocess.Popen(["python"],stdin=subprocess.PIPE,\ 206 | stdout=subprocess.PIPE,\ 207 | stderr=subprocess.PIPE,\ 208 | preexec_fn=setlimits) 209 | PY_PROCESS_LOCK.acquire() 210 | output = python.communicate(code) 211 | PY_PROCESS_LOCK.release() 212 | return output 213 | 214 | --- 215 | 2011-10-17 216 | 217 | Bug report from user ... problems with utf-8 in non-commented code: 218 | 219 | # coding = utf-8 220 | x = [1, 2, 3] # čęė - OK 221 | y = x 222 | y = u"asdęė" # problem 223 | 224 | -------------------------------------------------------------------------------- /alias-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcientist/OnlinePythonTutor/78e0f4060f6d913e7651c64779befb3529bccb63/alias-screenshot.png -------------------------------------------------------------------------------- /cgi-bin/.htaccess: -------------------------------------------------------------------------------- 1 | Options +ExecCGI 2 | AddHandler cgi-script .py 3 | -------------------------------------------------------------------------------- /cgi-bin/create_db.py: -------------------------------------------------------------------------------- 1 | # Setup sqlite database for optional query logging 2 | 3 | from db_common import * 4 | import os 5 | 6 | # the 'post_date' field is stored as an int representing GMT time since 7 | # the UNIX epoch; to convert to localtime in sqlite, use: 8 | # SELECT datetime(1092941466, 'unixepoch', 'localtime'); 9 | 10 | def create_db(): 11 | assert not os.path.exists(DB_FILE) 12 | (con, cur) = db_connect() 13 | cur.execute('''CREATE TABLE query_log 14 | (id INTEGER PRIMARY KEY, 15 | post_date TEXT, 16 | ip_addr TEXT, 17 | user_agent TEXT, 18 | input_script TEXT, 19 | had_error INTEGER)''') 20 | con.commit() 21 | cur.close() 22 | 23 | if __name__ == "__main__": 24 | create_db() 25 | print 'Created', DB_FILE 26 | 27 | -------------------------------------------------------------------------------- /cgi-bin/db_common.py: -------------------------------------------------------------------------------- 1 | # Setup sqlite database for optional query logging 2 | 3 | import sqlite3 4 | 5 | DB_FILE = 'edu-python-log.sqlite3' 6 | 7 | def db_connect(): 8 | con = sqlite3.connect(DB_FILE) 9 | cur = con.cursor() 10 | return (con, cur) 11 | 12 | -------------------------------------------------------------------------------- /cgi-bin/index.html: -------------------------------------------------------------------------------- 1 | bye 2 | -------------------------------------------------------------------------------- /cgi-bin/load_question.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.5 2 | 3 | # Online Python Tutor 4 | # Copyright (C) 2010-2011 Philip J. Guo (philip@pgbovine.net) 5 | # https://github.com/pgbovine/OnlinePythonTutor/ 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | 21 | # Load a question file in the 'questions/' sub-directory, parse it, 22 | # and return it to the caller in JSON format 23 | QUESTIONS_DIR = '../questions/' 24 | 25 | from parse_questions import parseQuestionsFile 26 | 27 | import cgi, os, demjson 28 | 29 | form = cgi.FieldStorage() 30 | question_file = form['question_file'].value 31 | 32 | fn = QUESTIONS_DIR + question_file + '.txt' 33 | assert os.path.isfile(fn) 34 | 35 | 36 | # Crucial first line to make sure that Apache serves this data 37 | # correctly - DON'T FORGET THE EXTRA NEWLINES!!!: 38 | print "Content-type: text/plain; charset=iso-8859-1\n\n" 39 | print demjson.encode(parseQuestionsFile(fn)) 40 | -------------------------------------------------------------------------------- /cgi-bin/parse_questions.py: -------------------------------------------------------------------------------- 1 | # Online Python Tutor 2 | # Copyright (C) 2010-2011 Philip J. Guo (philip@pgbovine.net) 3 | # https://github.com/pgbovine/OnlinePythonTutor/ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | # Defines a function that parses an Online Python Tutor 'questions file' 19 | # into a dict, which can easily be converted into JSON 20 | 21 | import os, sys 22 | 23 | delimiters = set(['Name:', 'Question:', 'Hint:', 'Solution:', 24 | 'Skeleton:', 'Test:', 'Expect:']) 25 | 26 | def parseQuestionsFile(filename): 27 | ret = {} 28 | ret['tests'] = [] 29 | ret['expects'] = [] 30 | 31 | curParts = [] 32 | curDelimiter = None 33 | 34 | def processRecord(): 35 | if curDelimiter == 'Name:': 36 | ret['name'] = '\n'.join(curParts).strip() 37 | elif curDelimiter == 'Question:': 38 | ret['question'] = ' '.join(curParts).strip() 39 | elif curDelimiter == 'Hint:': 40 | ret['hint'] = ' '.join(curParts).strip() 41 | elif curDelimiter == 'Solution:': 42 | ret['solution'] = ' '.join(curParts).strip() 43 | elif curDelimiter == 'Skeleton:': 44 | ret['skeleton'] = '\n'.join(curParts).strip() 45 | elif curDelimiter == 'Test:': 46 | ret['tests'].append('\n'.join(curParts).strip()) 47 | elif curDelimiter == 'Expect:': 48 | ret['expects'].append('\n'.join(curParts).strip()) 49 | 50 | 51 | for line in open(filename): 52 | # only strip TRAILING spaces and not leading spaces 53 | line = line.rstrip() 54 | 55 | # comments are denoted by a leading '//', so ignore those lines. 56 | # Note that I don't use '#' as the comment token since sometimes I 57 | # want to include Python comments in the skeleton code. 58 | if line.startswith('//'): 59 | continue 60 | 61 | # special-case one-liners: 62 | if line.startswith('MaxLineDelta:'): 63 | ret['max_line_delta'] = int(line.split(':')[1]) 64 | continue # move to next line 65 | 66 | if line.startswith('MaxInstructions:'): 67 | ret['max_instructions'] = int(line.split(':')[1]) 68 | continue # move to next line 69 | 70 | 71 | if line in delimiters: 72 | processRecord() 73 | curDelimiter = line 74 | curParts = [] 75 | else: 76 | curParts.append(line) 77 | 78 | # don't forget to process the FINAL record 79 | processRecord() 80 | 81 | assert len(ret['tests']) == len(ret['expects']) 82 | 83 | return ret 84 | 85 | 86 | if __name__ == '__main__': 87 | import pprint 88 | pp = pprint.PrettyPrinter(indent=2) 89 | pp.pprint(parseQuestionsFile(sys.argv[1])) 90 | -------------------------------------------------------------------------------- /cgi-bin/pg_encoder.py: -------------------------------------------------------------------------------- 1 | # Online Python Tutor 2 | # Copyright (C) 2010-2011 Philip J. Guo (philip@pgbovine.net) 3 | # https://github.com/pgbovine/OnlinePythonTutor/ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | # Given an arbitrary piece of Python data, encode it in such a manner 20 | # that it can be later encoded into JSON. 21 | # http://json.org/ 22 | # 23 | # We use this function to encode run-time traces of data structures 24 | # to send to the front-end. 25 | # 26 | # Format: 27 | # * None, int, long, float, str, bool - unchanged 28 | # (json.dumps encodes these fine verbatim) 29 | # * list - ['LIST', unique_id, elt1, elt2, elt3, ..., eltN] 30 | # * tuple - ['TUPLE', unique_id, elt1, elt2, elt3, ..., eltN] 31 | # * set - ['SET', unique_id, elt1, elt2, elt3, ..., eltN] 32 | # * dict - ['DICT', unique_id, [key1, value1], [key2, value2], ..., [keyN, valueN]] 33 | # * instance - ['INSTANCE', class name, unique_id, [attr1, value1], [attr2, value2], ..., [attrN, valueN]] 34 | # * class - ['CLASS', class name, unique_id, [list of superclass names], [attr1, value1], [attr2, value2], ..., [attrN, valueN]] 35 | # * circular reference - ['CIRCULAR_REF', unique_id] 36 | # * other - [, unique_id, string representation of object] 37 | # 38 | # the unique_id is derived from id(), which allows us to explicitly 39 | # capture aliasing of compound values 40 | 41 | # Key: real ID from id() 42 | # Value: a small integer for greater readability, set by cur_small_id 43 | real_to_small_IDs = {} 44 | cur_small_id = 1 45 | 46 | import re, types 47 | typeRE = re.compile("") 48 | classRE = re.compile("") 49 | 50 | def encode(dat, ignore_id=False): 51 | def encode_helper(dat, compound_obj_ids): 52 | # primitive type 53 | if dat is None or \ 54 | type(dat) in (int, long, float, str, bool): 55 | return dat 56 | # compound type 57 | else: 58 | my_id = id(dat) 59 | 60 | global cur_small_id 61 | if my_id not in real_to_small_IDs: 62 | if ignore_id: 63 | real_to_small_IDs[my_id] = 99999 64 | else: 65 | real_to_small_IDs[my_id] = cur_small_id 66 | cur_small_id += 1 67 | 68 | if my_id in compound_obj_ids: 69 | return ['CIRCULAR_REF', real_to_small_IDs[my_id]] 70 | 71 | new_compound_obj_ids = compound_obj_ids.union([my_id]) 72 | 73 | typ = type(dat) 74 | 75 | my_small_id = real_to_small_IDs[my_id] 76 | 77 | if typ == list: 78 | ret = ['LIST', my_small_id] 79 | for e in dat: ret.append(encode_helper(e, new_compound_obj_ids)) 80 | elif typ == tuple: 81 | ret = ['TUPLE', my_small_id] 82 | for e in dat: ret.append(encode_helper(e, new_compound_obj_ids)) 83 | elif typ == set: 84 | ret = ['SET', my_small_id] 85 | for e in dat: ret.append(encode_helper(e, new_compound_obj_ids)) 86 | elif typ == dict: 87 | ret = ['DICT', my_small_id] 88 | for (k,v) in dat.iteritems(): 89 | # don't display some built-in locals ... 90 | if k not in ('__module__', '__return__'): 91 | ret.append([encode_helper(k, new_compound_obj_ids), encode_helper(v, new_compound_obj_ids)]) 92 | elif typ in (types.InstanceType, types.ClassType, types.TypeType) or \ 93 | classRE.match(str(typ)): 94 | # ugh, classRE match is a bit of a hack :( 95 | if typ == types.InstanceType or classRE.match(str(typ)): 96 | ret = ['INSTANCE', dat.__class__.__name__, my_small_id] 97 | else: 98 | superclass_names = [e.__name__ for e in dat.__bases__] 99 | ret = ['CLASS', dat.__name__, my_small_id, superclass_names] 100 | 101 | # traverse inside of its __dict__ to grab attributes 102 | # (filter out useless-seeming ones): 103 | user_attrs = sorted([e for e in dat.__dict__.keys() 104 | if e not in ('__doc__', '__module__', '__return__')]) 105 | 106 | for attr in user_attrs: 107 | ret.append([encode_helper(attr, new_compound_obj_ids), encode_helper(dat.__dict__[attr], new_compound_obj_ids)]) 108 | else: 109 | typeStr = str(typ) 110 | m = typeRE.match(typeStr) 111 | assert m, typ 112 | ret = [m.group(1), my_small_id, str(dat)] 113 | 114 | return ret 115 | 116 | return encode_helper(dat, set()) 117 | 118 | -------------------------------------------------------------------------------- /cgi-bin/run_tests.py: -------------------------------------------------------------------------------- 1 | # Online Python Tutor 2 | # Copyright (C) 2010-2011 Philip J. Guo (philip@pgbovine.net) 3 | # https://github.com/pgbovine/OnlinePythonTutor/ 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | # Regression tests for Online Python Tutor back-end 20 | # 21 | # Run using: 22 | # python run_tests.py --all 23 | 24 | import os, sys, re, shutil, filecmp, optparse, difflib 25 | import pg_logger 26 | import demjson 27 | 28 | 29 | # all tests are found in this directory: 30 | REGTEST_DIR = '../test-programs/' 31 | 32 | ALL_TESTS = [e for e in os.listdir(REGTEST_DIR) if e.endswith('.py')] 33 | 34 | # return True if there seemed to be an error in execution 35 | def execute(test_script): 36 | def my_finalizer(output_lst): 37 | outfile = open(test_script[:-3] + '.out', 'w') 38 | output_json = demjson.encode(output_lst, compactly=False) 39 | print >> outfile, output_json 40 | 41 | pg_logger.exec_script_str(open(test_script).read(), my_finalizer, True) 42 | 43 | 44 | def clobber_golden_file(golden_file): 45 | print ' Overriding golden file' 46 | outfile = golden_file.replace('.golden', '.out') 47 | assert os.path.isfile(outfile) 48 | shutil.copy(outfile, golden_file) 49 | 50 | 51 | # returns True if there is a diff, False otherwise 52 | def diff_test_golden_data(golden_file): 53 | outfile = golden_file.replace('.golden', '.out') 54 | assert os.path.isfile(outfile) 55 | assert os.path.isfile(golden_file) 56 | 57 | # filter out machine-specific memory addresses: 58 | outfile_filtered = \ 59 | [re.sub(' 0x.+?>', ' 0xADDR>', e) for e in open(outfile).readlines()] 60 | golden_file_filtered = \ 61 | [re.sub(' 0x.+?>', ' 0xADDR>', e) for e in open(golden_file).readlines()] 62 | 63 | return outfile_filtered != golden_file_filtered 64 | 65 | 66 | def diff_test_output(test_name): 67 | golden_file = test_name[:-3] + '.golden' 68 | assert os.path.isfile(golden_file) 69 | 70 | outfile = golden_file.replace('.golden', '.out') 71 | assert os.path.isfile(outfile) 72 | 73 | golden_s = open(golden_file).readlines() 74 | out_s = open(outfile).readlines() 75 | 76 | golden_s_filtered = [re.sub(' 0x.+?>', ' 0xADDR>', e) for e in golden_s] 77 | out_s_filtered = [re.sub(' 0x.+?>', ' 0xADDR>', e) for e in out_s] 78 | 79 | for line in difflib.unified_diff(golden_s_filtered, out_s_filtered, \ 80 | fromfile=golden_file, tofile=outfile): 81 | print line, 82 | 83 | 84 | def run_test(test_name, clobber_golden=False): 85 | print 'Testing', test_name 86 | assert test_name.endswith('.py') 87 | outfile = test_name[:-3] + '.out' 88 | if os.path.isfile(outfile): 89 | os.remove(outfile) 90 | 91 | try: 92 | execute(test_name) 93 | except: 94 | pass 95 | 96 | golden_file = test_name[:-3] + '.golden' 97 | if os.path.isfile(golden_file): 98 | if diff_test_golden_data(golden_file): 99 | print " FAILED" 100 | if clobber_golden: 101 | clobber_golden_file(golden_file) 102 | else: 103 | clobber_golden_file(golden_file) 104 | 105 | 106 | def run_all_tests(clobber=False): 107 | for t in ALL_TESTS: 108 | run_test(t, clobber) 109 | 110 | def diff_all_test_outputs(): 111 | for t in ALL_TESTS: 112 | print '=== diffing', t, '===' 113 | diff_test_output(t) 114 | 115 | 116 | if __name__ == "__main__": 117 | os.chdir(REGTEST_DIR) # change to this dir to make everything easier 118 | 119 | parser = optparse.OptionParser() 120 | parser.add_option("--all", action="store_true", dest="run_all", 121 | help="Run all tests") 122 | parser.add_option("--only-clobber", action="store_true", dest="only_clobber", 123 | help="Clobber ALL golden files WITHOUT re-running tests") 124 | parser.add_option("--clobber", action="store_true", dest="clobber", 125 | help="Clobber golden files when running tests") 126 | parser.add_option("--test", dest="test_name", 127 | help="Run one test") 128 | parser.add_option("--difftest", dest="diff_test_name", 129 | help="Diff against .golden for one test") 130 | parser.add_option("--diffall", action="store_true", dest="diff_all", 131 | help="Diff against .golden for all tests") 132 | (options, args) = parser.parse_args() 133 | if options.run_all: 134 | if options.clobber: 135 | print 'Running all tests and clobbering results ...' 136 | else: 137 | print 'Running all tests ...' 138 | run_all_tests(options.clobber) 139 | 140 | elif options.diff_all: 141 | diff_all_test_outputs() 142 | elif options.diff_test_name: 143 | diff_test_output(options.diff_test_name) 144 | elif options.test_name: 145 | run_test(options.test_name, options.clobber) 146 | elif options.only_clobber: 147 | for t in ALL_TESTS: 148 | golden_file = t[:-3] + '.golden' 149 | clobber_golden_file(golden_file) 150 | else: 151 | parser.print_help() 152 | 153 | -------------------------------------------------------------------------------- /cgi-bin/web_exec.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.5 2 | 3 | # Online Python Tutor 4 | # Copyright (C) 2010-2011 Philip J. Guo (philip@pgbovine.net) 5 | # https://github.com/pgbovine/OnlinePythonTutor/ 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | 21 | # Executes the Online Python Tutor back-end as a CGI script, which 22 | # accepts one POST parameter, 'user_script', containing the string 23 | # contents of the Python script that the user wants to execute. 24 | # 25 | # Returns a complete JSON execution trace to the front-end. 26 | # 27 | # This version uses Python 2.5 on the MIT CSAIL servers. 28 | # (note that Python 2.4 doesn't work on CSAIL, but Python 2.5 does) 29 | # 30 | # If you want to run this script, then you'll need to change the 31 | # shebang line at the top of this file to point to your system's Python. 32 | # 33 | # Also, check CGI execute permission in your script directory. 34 | # You might need to create an .htaccess file like the following: 35 | # 36 | # Options +ExecCGI 37 | # AddHandler cgi-script .py 38 | 39 | 40 | # set to true if you want to log queries in DB_FILE 41 | LOG_QUERIES = False # don't do logging for now 42 | 43 | import cgi 44 | import pg_logger 45 | 46 | # Python 2.5 doesn't have a built-in json module, so I'm using a 47 | # 3rd-party module. I think you can do 'import json' in Python >= 2.6 48 | import demjson 49 | 50 | if LOG_QUERIES: 51 | import os, time, db_common 52 | 53 | def web_finalizer(output_lst): 54 | # use compactly=False to produce human-readable JSON, 55 | # except at the expense of being a LARGER download 56 | output_json = demjson.encode(output_lst, compactly=True) 57 | 58 | # query logging is optional 59 | if LOG_QUERIES: 60 | # just to be paranoid, don't croak the whole program just 61 | # because there's some error in logging it to the database 62 | try: 63 | # log queries into sqlite database: 64 | had_error = False 65 | # (note that the CSAIL 'www' user needs to have write permissions in 66 | # this directory for logging to work properly) 67 | if len(output_lst): 68 | evt = output_lst[-1]['event'] 69 | if evt == 'exception' or evt == 'uncaught_exception': 70 | had_error = True 71 | 72 | (con, cur) = db_common.db_connect() 73 | cur.execute("INSERT INTO query_log VALUES (NULL, ?, ?, ?, ?, ?)", 74 | (int(time.time()), 75 | os.environ.get("REMOTE_ADDR", "N/A"), 76 | os.environ.get("HTTP_USER_AGENT", "N/A"), 77 | user_script, 78 | had_error)) 79 | con.commit() 80 | cur.close() 81 | except: 82 | # haha this is bad form, but silently fail on error :) 83 | pass 84 | 85 | # Crucial first line to make sure that Apache serves this data 86 | # correctly - DON'T FORGET THE EXTRA NEWLINES!!!: 87 | print "Content-type: text/plain; charset=iso-8859-1\n\n" 88 | print output_json 89 | 90 | 91 | form = cgi.FieldStorage() 92 | user_script = form['user_script'].value 93 | if 'max_instructions' in form: 94 | pg_logger.set_max_executed_lines(int(form['max_instructions'].value)) 95 | 96 | pg_logger.exec_script_str(user_script, web_finalizer) 97 | -------------------------------------------------------------------------------- /cgi-bin/web_run_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2.5 2 | 3 | # Online Python Tutor 4 | # Copyright (C) 2010-2011 Philip J. Guo (philip@pgbovine.net) 5 | # https://github.com/pgbovine/OnlinePythonTutor/ 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | 21 | # Runs both 'user_script' and 'expect_script' and returns whether the 22 | # test has passed or failed, along with the FULL trace if the test has 23 | # failed (so that the user can debug it) 24 | 25 | 26 | import cgi 27 | import pg_logger 28 | 29 | # Python 2.5 doesn't have a built-in json module, so I'm using a 30 | # 3rd-party module. I think you can do 'import json' in Python >= 2.6 31 | import demjson 32 | 33 | user_trace = None # the FULL user trace (without any IDs, though) 34 | expect_trace_final_entry = None 35 | 36 | def user_script_finalizer(output_lst): 37 | # very important! 38 | global user_trace, expect_trace_final_entry 39 | 40 | user_trace = output_lst 41 | 42 | # dunno which order these events come in ... 43 | if user_trace and expect_trace_final_entry: 44 | really_finalize() 45 | 46 | 47 | def expect_script_finalizer(output_lst): 48 | # very important! 49 | global user_trace, expect_trace_final_entry 50 | 51 | expect_trace_final_entry = output_lst[-1] 52 | 53 | # if there's an error here, bail NOW and return an error 54 | if (expect_trace_final_entry['event'] != 'return' or \ 55 | expect_trace_final_entry['func_name'] != ''): 56 | ret = {} 57 | ret['status'] = 'error' 58 | ret['error_msg'] = "Fatal error: expected output is malformed!" 59 | 60 | # Crucial first line to make sure that Apache serves this data 61 | # correctly - DON'T FORGET THE EXTRA NEWLINES!!!: 62 | print "Content-type: text/plain; charset=iso-8859-1\n\n" 63 | output_json = demjson.encode(ret, compactly=True) 64 | print output_json 65 | 66 | else: 67 | # dunno which order these events come in ... 68 | if user_trace and expect_trace_final_entry: 69 | really_finalize() 70 | 71 | 72 | def really_finalize(): 73 | # Procedure for grading testResults vs. expectResults: 74 | # - The final line in expectResults should be a 'return' from 75 | # '' that contains only ONE global variable. THAT'S 76 | # the variable that we're gonna compare against testResults. 77 | 78 | vars_to_compare = expect_trace_final_entry['globals'].keys() 79 | if len(vars_to_compare) != 1: 80 | ret['status'] = 'error' 81 | ret['error_msg'] = "Fatal error: expected output has more than one global var!" 82 | else: 83 | single_var_to_compare = vars_to_compare[0] 84 | 85 | user_trace_final_entry = user_trace[-1] 86 | 87 | ret = {} 88 | ret['status'] = 'ok' 89 | ret['passed_test'] = False 90 | ret['output_var_to_compare'] = single_var_to_compare 91 | 92 | # Grab the 'inputs' by finding all global vars that are in scope 93 | # prior to making the first function call. 94 | # 95 | # NB: This means that you can't call any functions to initialize 96 | # your input data, since the FIRST function call must be the function 97 | # that you're testing. 98 | for e in user_trace: 99 | if e['event'] == 'call': 100 | ret['input_globals'] = e['globals'] 101 | break 102 | 103 | # always fetch expect_val 104 | ret['expect_val'] = expect_trace_final_entry['globals'][single_var_to_compare] 105 | 106 | if user_trace_final_entry['event'] == 'return': # normal termination 107 | if single_var_to_compare not in user_trace_final_entry['globals']: 108 | ret['status'] = 'error' 109 | ret['error_msg'] = "Error: output has no global var named '%s'" % (single_var_to_compare,) 110 | else: 111 | ret['test_val'] = user_trace_final_entry['globals'][single_var_to_compare] 112 | 113 | # do the actual comparison here! 114 | if ret['expect_val'] == ret['test_val']: 115 | ret['passed_test'] = True 116 | 117 | else: 118 | ret['status'] = 'error' 119 | ret['error_msg'] = user_trace_final_entry['exception_msg'] 120 | 121 | 122 | # Crucial first line to make sure that Apache serves this data 123 | # correctly - DON'T FORGET THE EXTRA NEWLINES!!!: 124 | print "Content-type: text/plain; charset=iso-8859-1\n\n" 125 | output_json = demjson.encode(ret, compactly=True) 126 | print output_json 127 | 128 | 129 | form = cgi.FieldStorage() 130 | user_script = form['user_script'].value 131 | expect_script = form['expect_script'].value 132 | 133 | # WEIRD: always run the expect_script FIRST since it's less likely to have 134 | # errors. for some mysterious reason, if there's an error in user_script, 135 | # then it will never run expect_script 136 | # 137 | # also make sure to ignore IDs so that we can do direct object comparisons! 138 | pg_logger.exec_script_str(expect_script, expect_script_finalizer, ignore_id=True) 139 | 140 | 141 | # set a custom instruction limit only for user scripts ... 142 | if 'max_instructions' in form: 143 | pg_logger.set_max_executed_lines(int(form['max_instructions'].value)) 144 | 145 | pg_logger.exec_script_str(user_script, user_script_finalizer, ignore_id=True) 146 | -------------------------------------------------------------------------------- /edu-python-title.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Online Python Tutor 4 | Copyright (C) 2010-2011 Philip J. Guo (philip@pgbovine.net) 5 | https://github.com/pgbovine/OnlinePythonTutor/ 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | 20 | */ 21 | 22 | 23 | /* 24 | Color scheme ideas: 25 | 26 | Current scheme: pastel blue and yellow with a hint of red: 27 | http://colorschemedesigner.com/#3N32mmmuew0w0 28 | 29 | Primary Color: 30 | 3D58A2 41507A 142B69 6F89D1 899CD1 31 | Secondary Color A: 32 | EBF048 B1B456 989C17 F4F776 F5F798 33 | Secondary Color B: 34 | F15149 B55B56 9D1E18 F87D76 F89D99 35 | 36 | */ 37 | 38 | 39 | body { 40 | background-color: white; 41 | font-family: verdana, arial, helvetica, sans-serif; 42 | /*font-size: 10pt;*/ 43 | font-size: 14px; /* for better readability */ 44 | } 45 | 46 | a { 47 | color: #3D58A2; 48 | } 49 | 50 | a:visited { 51 | color: #3D58A2; 52 | } 53 | 54 | a:hover { 55 | color: #142B69; /* darken during hover */ 56 | } 57 | 58 | 59 | h1 { 60 | font-weight: normal; 61 | font-size: 20pt; 62 | font-family: georgia, serif; 63 | line-height: 1em; /* enforce single spacing so that Georgia works */ 64 | 65 | margin-top: 0px; 66 | margin-bottom: 3px; 67 | } 68 | 69 | h2 { 70 | font-size: 13pt; 71 | font-weight: normal; 72 | font-family: georgia, serif; 73 | line-height: 1.1em; /* enforce single spacing so that Georgia works */ 74 | 75 | margin-top: 2px; 76 | margin-bottom: 20px; 77 | } 78 | 79 | div.activityPane { 80 | /*background-color: #899CD1;*/ 81 | background-color: #F5F798; 82 | /* TOP RIGHT BOTTOM LEFT */ 83 | padding: 15px 20px 5px 20px; 84 | width: 450px; 85 | margin: 20px; 86 | text-align: left; 87 | } 88 | 89 | pre { 90 | font-family: Andale mono, monospace; 91 | margin-left: 0.5em; 92 | } 93 | 94 | .titlePane h2 { 95 | margin-top: 8px; 96 | } 97 | 98 | .titlePane { 99 | margin-bottom: 25px; 100 | } 101 | 102 | #footer { 103 | text-align: center; 104 | color: #444444; 105 | font-size: 9pt; 106 | max-width: 700px; 107 | border-top: 1px solid #bbbbbb; 108 | padding-top: 5px; 109 | margin-top: 5px; 110 | } 111 | 112 | h3 { 113 | font-size: 14pt; 114 | font-weight: normal; 115 | } 116 | 117 | ul { 118 | padding-left: 18px; 119 | } 120 | 121 | li { 122 | margin-bottom: 6px; 123 | font-size: 11pt; 124 | } 125 | -------------------------------------------------------------------------------- /edu-python-tutor.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Online Python Tutor 4 | Copyright (C) 2010-2011 Philip J. Guo (philip@pgbovine.net) 5 | https://github.com/pgbovine/OnlinePythonTutor/ 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | 20 | */ 21 | 22 | // The Online Python Tutor front-end, which calls the cgi-bin/web_exec.py 23 | // back-end with a string representing the user's script POST['user_script'] 24 | // and receives a complete execution trace, which it parses and displays to HTML. 25 | 26 | // Pre-req: edu-python.js and jquery.ba-bbq.min.js should be imported BEFORE this file 27 | 28 | 29 | function enterEditMode() { 30 | $.bbq.pushState({ mode: 'edit' }); 31 | } 32 | 33 | function enterVisualizeMode(traceData) { 34 | curTrace = traceData; // first assign it to the global curTrace, then 35 | // let jQuery BBQ take care of the rest 36 | $.bbq.pushState({ mode: 'visualize' }); 37 | } 38 | 39 | 40 | $(document).ready(function() { 41 | eduPythonCommonInit(); // must call this first! 42 | 43 | $("#pyInput").tabby(); // recognize TAB and SHIFT-TAB 44 | 45 | 46 | // be friendly to the browser's forward and back buttons 47 | // thanks to http://benalman.com/projects/jquery-bbq-plugin/ 48 | $(window).bind("hashchange", function(e) { 49 | appMode = $.bbq.getState("mode"); // assign this to the GLOBAL appMode 50 | 51 | // default mode is 'edit' 52 | if (appMode == undefined) { 53 | appMode = 'edit'; 54 | } 55 | 56 | // if there's no curTrace, then default to edit mode since there's 57 | // nothing to visualize: 58 | if (!curTrace) { 59 | appMode = 'edit'; 60 | $.bbq.pushState({ mode: 'edit' }); 61 | } 62 | 63 | 64 | if (appMode == 'edit') { 65 | $("#pyInputPane").show(); 66 | $("#pyOutputPane").hide(); 67 | } 68 | else if (appMode == 'visualize') { 69 | $("#pyInputPane").hide(); 70 | $("#pyOutputPane").show(); 71 | 72 | $('#executeBtn').html("Visualize execution"); 73 | $('#executeBtn').attr('disabled', false); 74 | 75 | 76 | // do this AFTER making #pyOutputPane visible, or else 77 | // jsPlumb connectors won't render properly 78 | processTrace(curTrace /* kinda dumb and redundant */, false); 79 | } 80 | else { 81 | assert(false); 82 | } 83 | }); 84 | 85 | // From: http://benalman.com/projects/jquery-bbq-plugin/ 86 | // Since the event is only triggered when the hash changes, we need 87 | // to trigger the event now, to handle the hash the page may have 88 | // loaded with. 89 | $(window).trigger( "hashchange" ); 90 | 91 | 92 | $("#executeBtn").attr('disabled', false); 93 | $("#executeBtn").click(function() { 94 | $('#executeBtn').html("Please wait ... processing your code"); 95 | $('#executeBtn').attr('disabled', true); 96 | $("#pyOutputPane").hide(); 97 | 98 | $.post("cgi-bin/web_exec.py", 99 | {user_script : $("#pyInput").val()}, 100 | function(traceData) { 101 | renderPyCodeOutput($("#pyInput").val()); 102 | enterVisualizeMode(traceData); 103 | }, 104 | "json"); 105 | }); 106 | 107 | 108 | $("#editBtn").click(function() { 109 | enterEditMode(); 110 | }); 111 | 112 | 113 | // canned examples 114 | 115 | $("#tutorialExampleLink").click(function() { 116 | $.get("example-code/py_tutorial.txt", function(dat) {$("#pyInput").val(dat);}); 117 | return false; 118 | }); 119 | 120 | $("#strtokExampleLink").click(function() { 121 | $.get("example-code/strtok.txt", function(dat) {$("#pyInput").val(dat);}); 122 | return false; 123 | }); 124 | 125 | $("#fibonacciExampleLink").click(function() { 126 | $.get("example-code/fib.txt", function(dat) {$("#pyInput").val(dat);}); 127 | return false; 128 | }); 129 | 130 | $("#memoFibExampleLink").click(function() { 131 | $.get("example-code/memo_fib.txt", function(dat) {$("#pyInput").val(dat);}); 132 | return false; 133 | }); 134 | 135 | $("#factExampleLink").click(function() { 136 | $.get("example-code/fact.txt", function(dat) {$("#pyInput").val(dat);}); 137 | return false; 138 | }); 139 | 140 | $("#filterExampleLink").click(function() { 141 | $.get("example-code/filter.txt", function(dat) {$("#pyInput").val(dat);}); 142 | return false; 143 | }); 144 | 145 | $("#insSortExampleLink").click(function() { 146 | $.get("example-code/ins_sort.txt", function(dat) {$("#pyInput").val(dat);}); 147 | return false; 148 | }); 149 | 150 | $("#aliasExampleLink").click(function() { 151 | $.get("example-code/aliasing.txt", function(dat) {$("#pyInput").val(dat);}); 152 | return false; 153 | }); 154 | 155 | $("#newtonExampleLink").click(function() { 156 | $.get("example-code/sqrt.txt", function(dat) {$("#pyInput").val(dat);}); 157 | return false; 158 | }); 159 | 160 | $("#oopSmallExampleLink").click(function() { 161 | $.get("example-code/oop_small.txt", function(dat) {$("#pyInput").val(dat);}); 162 | return false; 163 | }); 164 | 165 | $("#mapExampleLink").click(function() { 166 | $.get("example-code/map.txt", function(dat) {$("#pyInput").val(dat);}); 167 | return false; 168 | }); 169 | 170 | $("#oop1ExampleLink").click(function() { 171 | $.get("example-code/oop_1.txt", function(dat) {$("#pyInput").val(dat);}); 172 | return false; 173 | }); 174 | 175 | $("#oop2ExampleLink").click(function() { 176 | $.get("example-code/oop_2.txt", function(dat) {$("#pyInput").val(dat);}); 177 | return false; 178 | }); 179 | 180 | $("#inheritanceExampleLink").click(function() { 181 | $.get("example-code/oop_inherit.txt", function(dat) {$("#pyInput").val(dat);}); 182 | return false; 183 | }); 184 | 185 | $("#sumExampleLink").click(function() { 186 | $.get("example-code/sum.txt", function(dat) {$("#pyInput").val(dat);}); 187 | return false; 188 | }); 189 | 190 | $("#pwGcdLink").click(function() { 191 | $.get("example-code/wentworth_gcd.txt", function(dat) {$("#pyInput").val(dat);}); 192 | return false; 193 | }); 194 | 195 | $("#pwSumListLink").click(function() { 196 | $.get("example-code/wentworth_sumList.txt", function(dat) {$("#pyInput").val(dat);}); 197 | return false; 198 | }); 199 | 200 | $("#towersOfHanoiLink").click(function() { 201 | $.get("example-code/towers_of_hanoi.txt", function(dat) {$("#pyInput").val(dat);}); 202 | return false; 203 | }); 204 | 205 | $("#pwTryFinallyLink").click(function() { 206 | $.get("example-code/wentworth_try_finally.txt", function(dat) {$("#pyInput").val(dat);}); 207 | return false; 208 | }); 209 | 210 | 211 | // select an example on start-up: 212 | $("#aliasExampleLink").trigger('click'); 213 | }); 214 | 215 | -------------------------------------------------------------------------------- /example-code/aliasing.txt: -------------------------------------------------------------------------------- 1 | # Example of aliasing 2 | x = [1, 2, 3] 3 | y = x 4 | x.append(4) 5 | y.append(5) 6 | z = [1, 2, 3, 4, 5] 7 | x.append(6) 8 | y.append(7) 9 | y = "hello" 10 | 11 | def foo(lst): 12 | lst.append("hello") 13 | bar(lst) 14 | 15 | def bar(myLst): 16 | print myLst 17 | 18 | foo(x) 19 | foo(z) 20 | -------------------------------------------------------------------------------- /example-code/fact.txt: -------------------------------------------------------------------------------- 1 | # dumb recursive factorial 2 | def fact(n): 3 | if (n <= 1): 4 | return 1 5 | else: 6 | return n * fact(n - 1) 7 | 8 | print fact(6) 9 | -------------------------------------------------------------------------------- /example-code/fib.txt: -------------------------------------------------------------------------------- 1 | # Infinite Fibonacci!!! 2 | 3 | arr = [1, 1] 4 | 5 | print arr[0] 6 | 7 | while True: 8 | print arr[-1] 9 | tmp = sum(arr) 10 | arr.append(tmp) 11 | del arr[0] 12 | -------------------------------------------------------------------------------- /example-code/filter.txt: -------------------------------------------------------------------------------- 1 | input = [("Mary", 27), ("Joe", 30), ("Ruth", 43), ("Bob", 17), ("Jenny", 22)] 2 | 3 | youngPeople = [] 4 | 5 | for (person, age) in input: 6 | if age < 30: 7 | youngPeople.append(person) 8 | else: 9 | print "HAHA", person, "is too old!" 10 | 11 | print "There are", len(youngPeople), "young people" 12 | -------------------------------------------------------------------------------- /example-code/ins_sort.txt: -------------------------------------------------------------------------------- 1 | # from: http://www.ece.uci.edu/~chou/py02/python.html 2 | def InsertionSort(A): 3 | for j in range(1, len(A)): 4 | key = A[j] 5 | i = j - 1 6 | while (i >= 0) and (A[i] > key): 7 | A[i+1] = A[i] 8 | i = i - 1 9 | A[i+1] = key 10 | 11 | input = [8, 3, 9, 15, 29, 7, 10] 12 | InsertionSort(input) 13 | print input 14 | -------------------------------------------------------------------------------- /example-code/map.txt: -------------------------------------------------------------------------------- 1 | # Functional programming with map 2 | # Adapted from MIT 6.01 course notes (Section A.2.3) 3 | # http://mit.edu/6.01/mercurial/spring10/www/handouts/readings.pdf 4 | 5 | def map(func, lst): 6 | if lst == []: 7 | return [] 8 | else: 9 | return [func(lst[0])] + map(func, lst[1:]) 10 | 11 | def halveElements(lst): 12 | return map(lambda x: x / 2.0, lst) 13 | 14 | input = [2, 4, 6, 8, 10] 15 | output = halveElements(input) 16 | -------------------------------------------------------------------------------- /example-code/memo_fib.txt: -------------------------------------------------------------------------------- 1 | # use memoization to make the recursive Fibonacci 2 | # implementation only take O(n) time and space 3 | 4 | MemoTable = {} 5 | 6 | def MemoizedFib(n): 7 | if n <= 2: 8 | return 1 9 | 10 | if n in MemoTable: 11 | return MemoTable[n] 12 | 13 | MemoTable[n] = MemoizedFib(n-1) + MemoizedFib(n-2) 14 | return MemoTable[n] 15 | 16 | 17 | res = MemoizedFib(10) 18 | -------------------------------------------------------------------------------- /example-code/oop_1.txt: -------------------------------------------------------------------------------- 1 | # Object-oriented programming intro 2 | # Adapted from MIT 6.01 course notes (Section 3.5) 3 | # http://mit.edu/6.01/mercurial/spring10/www/handouts/readings.pdf 4 | 5 | class Staff601: 6 | course = '6.01' 7 | building = 34 8 | room = 501 9 | 10 | def salutation(self): 11 | return self.role + ' ' + self.name 12 | 13 | pat = Staff601() 14 | print pat.course 15 | 16 | pat.name = 'Pat' 17 | pat.age = 60 18 | pat.role = 'Professor' 19 | 20 | print pat.building 21 | pat.building = 32 22 | print pat.building 23 | 24 | print pat.salutation() 25 | print Staff601.salutation(pat) 26 | -------------------------------------------------------------------------------- /example-code/oop_2.txt: -------------------------------------------------------------------------------- 1 | # The __init__ 'constructor' - object-oriented programming intro 2 | # Adapted from MIT 6.01 course notes (Section 3.5) 3 | # http://mit.edu/6.01/mercurial/spring10/www/handouts/readings.pdf 4 | 5 | class Staff601: 6 | course = '6.01' 7 | building = 34 8 | room = 501 9 | 10 | def __init__(self, name, role, years, salary): 11 | self.name = name 12 | self.role = role 13 | self.age = years 14 | self.salary = salary 15 | 16 | def salutation(self): 17 | return self.role + ' ' + self.name 18 | 19 | pat = Staff601('Pat', 'Professor', 60, 100000) 20 | print pat.salutation() 21 | 22 | -------------------------------------------------------------------------------- /example-code/oop_inherit.txt: -------------------------------------------------------------------------------- 1 | # Inheritance - object-oriented programming intro 2 | # Adapted from MIT 6.01 course notes (Section 3.5) 3 | # http://mit.edu/6.01/mercurial/spring10/www/handouts/readings.pdf 4 | 5 | class Staff601: 6 | course = '6.01' 7 | building = 34 8 | room = 501 9 | 10 | def giveRaise(self, percentage): 11 | self.salary = self.salary + self.salary * percentage 12 | 13 | class Prof601(Staff601): 14 | salary = 100000 15 | 16 | def __init__(self, name, age): 17 | self.name = name 18 | self.giveRaise((age - 18) * 0.03) 19 | 20 | def salutation(self): 21 | return self.role + ' ' + self.name 22 | 23 | pat = Prof601('Pat', 60) 24 | 25 | -------------------------------------------------------------------------------- /example-code/oop_small.txt: -------------------------------------------------------------------------------- 1 | class A: 2 | x = 1 3 | y = 'hello' 4 | 5 | class B: 6 | z = 'bye' 7 | 8 | class C(A,B): 9 | def salutation(self): 10 | return '%d %s %s' % (self.x, self.y, self.z) 11 | 12 | inst = C() 13 | print inst.salutation() 14 | inst.x = 100 15 | print inst.salutation() 16 | -------------------------------------------------------------------------------- /example-code/py_tutorial.txt: -------------------------------------------------------------------------------- 1 | # Philip's 10-minute intro to Python 2 | 3 | # numbers! 4 | age = 26 5 | pi = 3.14159 6 | 7 | # strings! 8 | s = 'Rutherford Birchard Hayes' 9 | tokens = s.split() 10 | firstName = tokens[0] 11 | middleName = tokens[1] 12 | lastName = tokens[2] 13 | s2 = firstName + ' ' + middleName + ' ' + lastName 14 | 15 | # 'if' statement - indentation matters! 16 | if (s == s2): 17 | print 'yes!!!' 18 | else: 19 | print 'nooooooo' 20 | 21 | # list (mutable sequence) 22 | beatles = ['John', 'Paul', 'George'] 23 | beatles.append('Ringo') 24 | 25 | # 'for' loop - indentation matters! 26 | for b in beatles: 27 | print 'Hello', b 28 | 29 | # tuple (immutable sequence) 30 | ages = (18, 21, 28, 21, 22, 18, 19, 34, 9) 31 | 32 | # set (no order, no duplicates) 33 | uniqueAges = set(ages) 34 | uniqueAges.add(18) # already in set, no effect 35 | uniqueAges.remove(21) 36 | 37 | # no guaranteed order when iterating over a set 38 | for thisAge in uniqueAges: 39 | print thisAge 40 | 41 | # testing set membership 42 | if 18 in uniqueAges: 43 | print 'There is an 18-year-old present!' 44 | 45 | # sorting 46 | beatles.sort() # in-place 47 | orderedUniqueAges = sorted(uniqueAges) # new list 48 | 49 | # dict - mapping unique keys to values 50 | netWorth = {} 51 | netWorth['Donald Trump'] = 3000000000 52 | netWorth['Bill Gates'] = 58000000000 53 | netWorth['Tom Cruise'] = 40000000 54 | netWorth['Joe Postdoc'] = 20000 55 | 56 | # iterating over key-value pairs: 57 | for (person, worth) in netWorth.iteritems(): 58 | if worth < 1000000: 59 | print 'haha', person, 'is not a millionaire' 60 | 61 | # testing dict membership 62 | if 'Tom Cruise' in netWorth: 63 | print 'show me the money!' 64 | -------------------------------------------------------------------------------- /example-code/sqrt.txt: -------------------------------------------------------------------------------- 1 | # Calculating square roots by Newton's method, inspired by SICP 2 | # http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-10.html#%_sec_1.1.7 3 | 4 | def sqrt(x): 5 | def average(a, b): 6 | return (a + b) / 2.0 7 | 8 | def is_good_enough(guess): 9 | return (abs((guess * guess) - x) < 0.001) 10 | 11 | def improve(guess): 12 | return average(guess, x / guess) 13 | 14 | def sqrt_iter(guess): 15 | if is_good_enough(guess): 16 | return guess 17 | else: 18 | return sqrt_iter(improve(guess)) 19 | 20 | return sqrt_iter(1.0) 21 | 22 | 23 | ans = sqrt(9) 24 | -------------------------------------------------------------------------------- /example-code/strtok.txt: -------------------------------------------------------------------------------- 1 | input = 'John,Doe,1984,4,1,male' 2 | 3 | tokens = input.split(',') 4 | firstName = tokens[0] 5 | lastName = tokens[1] 6 | birthdate = (int(tokens[2]), int(tokens[3]), int(tokens[4])) 7 | isMale = (tokens[5] == 'male') 8 | 9 | print 'Hi', firstName, lastName 10 | -------------------------------------------------------------------------------- /example-code/sum.txt: -------------------------------------------------------------------------------- 1 | # Higher-order functions 2 | # Adapted from MIT 6.01 course notes (Section A.2.2) 3 | # http://mit.edu/6.01/mercurial/spring10/www/handouts/readings.pdf 4 | 5 | def summation(low, high, f, next): 6 | s = 0 7 | x = low 8 | while x <= high: 9 | s = s + f(x) 10 | x = next(x) 11 | return s 12 | 13 | def sumsquares(low, high): 14 | return summation(low, high, lambda x: x**2, lambda x: x+1) 15 | 16 | print sumsquares(1, 10) 17 | -------------------------------------------------------------------------------- /example-code/towers_of_hanoi.txt: -------------------------------------------------------------------------------- 1 | # move a stack of n disks from stack a to stack b, 2 | # using tmp as a temporary stack 3 | def TowerOfHanoi(n, a, b, tmp): 4 | if n == 1: 5 | b.append(a.pop()) 6 | else: 7 | TowerOfHanoi(n-1, a, tmp, b) 8 | b.append(a.pop()) 9 | TowerOfHanoi(n-1, tmp, b, a) 10 | 11 | stack1 = [4,3,2,1] 12 | stack2 = [] 13 | stack3 = [] 14 | 15 | # transfer stack1 to stack3 using Tower of Hanoi rules 16 | TowerOfHanoi(len(stack1), stack1, stack3, stack2) 17 | -------------------------------------------------------------------------------- /example-code/wentworth_gcd.txt: -------------------------------------------------------------------------------- 1 | # Tutorial code from Prof. Peter Wentworth 2 | # Rhodes University, South Africa (http://www.ru.ac.za/) 3 | 4 | def gcd(x, y, depth=1): 5 | ''' 6 | Find the greatest common divisor of x, y 7 | Pre: x >= y, y >= 0, both x and y are int 8 | ''' 9 | result = x # set provisional return value 10 | if y != 0: 11 | indent = "**" * depth 12 | print("%s About to recursively call gcd(%d, %d)" % (indent, y, x%y)) 13 | result = gcd(y, x % y, depth+1) 14 | print("%s result is %d" % (indent, result)) 15 | return result 16 | 17 | def main(): 18 | m = 77 19 | n = 28 20 | print("Finding gcd(%d, %d)" % (m,n)) 21 | g = gcd(m, n) 22 | print('Greatest common divisor of %d, %d = %d' 23 | % (m, n, g)) 24 | 25 | main() 26 | 27 | -------------------------------------------------------------------------------- /example-code/wentworth_sumList.txt: -------------------------------------------------------------------------------- 1 | # Tutorial code from Prof. Peter Wentworth 2 | # Rhodes University, South Africa (http://www.ru.ac.za/) 3 | 4 | def sumList(xs): 5 | ''' 6 | Sum a list that can contain nested lists. 7 | Precondition: All leaf elements are numbers. 8 | ''' 9 | sum = 0 10 | for e in xs: 11 | if type(e) is list: 12 | print("Calling sumList(%s) recursively" % e) 13 | v = sumList(e) 14 | print("sumList(%s) returned %s" % (e, v)) 15 | sum += v 16 | else: 17 | sum += e 18 | return sum 19 | 20 | 21 | testData = [10, [20, 30, [40], 50], 60] 22 | print("Calling sumList(%s)" % testData) 23 | result = sumList(testData) 24 | print("Final sum of all numbers in initial list is %s" % result) 25 | 26 | -------------------------------------------------------------------------------- /example-code/wentworth_try_finally.txt: -------------------------------------------------------------------------------- 1 | # Tutorial code from Prof. Peter Wentworth 2 | # Rhodes University, South Africa (http://www.ru.ac.za/) 3 | 4 | # Demonstrate recursion that throws an exception 5 | # at its base case, and the tracing of try ... finally. 6 | # 7 | # How many "survived!" messages will be printed??? 8 | 9 | def f(n): 10 | try: 11 | x = 10 / n 12 | print "x is", x 13 | f(n-1) 14 | print "survived!" 15 | finally: 16 | print "Bye from f where n =", n 17 | 18 | f(4) 19 | -------------------------------------------------------------------------------- /grading-375.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcientist/OnlinePythonTutor/78e0f4060f6d913e7651c64779befb3529bccb63/grading-375.png -------------------------------------------------------------------------------- /hunt-mcilroy.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2006 Tony Garnock-Jones 2 | * Copyright (c) 2006 LShift Ltd. 3 | * 4 | * Permission is hereby granted, free of charge, to any person 5 | * obtaining a copy of this software and associated documentation files 6 | * (the "Software"), to deal in the Software without restriction, 7 | * including without limitation the rights to use, copy, modify, merge, 8 | * publish, distribute, sublicense, and/or sell copies of the Software, 9 | * and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 19 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | function diff(file1, file2) { 26 | /* Text diff algorithm following Hunt and McIlroy 1976. 27 | * J. W. Hunt and M. D. McIlroy, An algorithm for differential file 28 | * comparison, Bell Telephone Laboratories CSTR #41 (1976) 29 | * http://www.cs.dartmouth.edu/~doug/ 30 | * 31 | * Expects two arrays of strings. 32 | */ 33 | 34 | var equivalenceClasses = {}; 35 | for (var j = 0; j < file2.length; j++) { 36 | var line = file2[j]; 37 | if (equivalenceClasses[line]) { 38 | equivalenceClasses[line].push(j); 39 | } else { 40 | equivalenceClasses[line] = [j]; 41 | } 42 | } 43 | 44 | var candidates = [{file1index: -1, 45 | file2index: -1, 46 | chain: null}]; 47 | 48 | for (var i = 0; i < file1.length; i++) { 49 | var line = file1[i]; 50 | var file2indices = equivalenceClasses[line] || []; 51 | 52 | var r = 0; 53 | var c = candidates[0]; 54 | 55 | for (var jX = 0; jX < file2indices.length; jX++) { 56 | var j = file2indices[jX]; 57 | 58 | for (var s = 0; s < candidates.length; s++) { 59 | if ((candidates[s].file2index < j) && 60 | ((s == candidates.length - 1) || 61 | (candidates[s + 1].file2index > j))) 62 | break; 63 | } 64 | 65 | if (s < candidates.length) { 66 | var newCandidate = {file1index: i, 67 | file2index: j, 68 | chain: candidates[s]}; 69 | if (r == candidates.length) { 70 | candidates.push(c); 71 | } else { 72 | candidates[r] = c; 73 | } 74 | r = s + 1; 75 | c = newCandidate; 76 | if (r == candidates.length) { 77 | break; // no point in examining further (j)s 78 | } 79 | } 80 | } 81 | 82 | candidates[r] = c; 83 | } 84 | 85 | // At this point, we know the LCS: it's in the reverse of the 86 | // linked-list through .chain of 87 | // candidates[candidates.length - 1]. 88 | 89 | // We now apply the LCS to build a "comm"-style picture of the 90 | // differences between file1 and file2. 91 | 92 | var result = []; 93 | var tail1 = file1.length; 94 | var tail2 = file2.length; 95 | var common = {common: []}; 96 | 97 | function processCommon() { 98 | if (common.common.length) { 99 | common.common.reverse(); 100 | result.push(common); 101 | common = {common: []}; 102 | } 103 | } 104 | 105 | for (var candidate = candidates[candidates.length - 1]; 106 | candidate != null; 107 | candidate = candidate.chain) { 108 | var different = {file1: [], file2: []}; 109 | 110 | while (--tail1 > candidate.file1index) { 111 | different.file1.push(file1[tail1]); 112 | } 113 | 114 | while (--tail2 > candidate.file2index) { 115 | different.file2.push(file2[tail2]); 116 | } 117 | 118 | if (different.file1.length || different.file2.length) { 119 | processCommon(); 120 | different.file1.reverse(); 121 | different.file2.reverse(); 122 | result.push(different); 123 | } 124 | 125 | if (tail1 >= 0) { 126 | common.common.push(file1[tail1]); 127 | } 128 | } 129 | 130 | processCommon(); 131 | 132 | result.reverse(); 133 | return result; 134 | } 135 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | 26 | 27 | Online Python Tutor: Learn and practice Python programming in your web browser 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
55 | 56 |
57 | 58 |

Online Python Tutor

59 |

Learn and practice Python programming in your web browser

60 | 61 |
62 | 63 |
64 | 65 |
66 | 67 |

Learn

68 |

Python by writing code and visualizing execution

69 | 70 |

This free educational application allows teachers and students to 71 | write Python scripts directly in the web browser, execute those scripts, 72 | single-step forwards and backwards through execution, and view 73 | the run-time state of all data structures.

74 | 75 |

Rather than displaying a bland text-based console, the Online Python 76 | Tutor provides a rich visualization of variables, heap objects, and 77 | stack frames. For example, the following code:

78 | 79 |
 80 | x = ["Alice", "Bob", "Charlie"]
 81 | y = x
 82 | z = ["Alice", "Bob", "Charlie"]
 83 | 
84 | 85 |

will be visualized as the following HTML diagram, which properly shows 86 | aliasing relationships:

87 | 88 |
89 | 90 | 91 |

Go play with the Online Python Tutor!

92 |
93 | 94 |
95 | 96 |
97 | 98 |

Solve

99 |

programming problems by writing Python code

100 | 101 |

The Online Python Tutor also allows students to practice solving 102 | programming problems like those they would receive for class assignments 103 | or technical job interviews.

104 | 105 |

It provides web-based interfaces for writing solution and test code, 106 | executing on a series of graded test inputs, and showing what tests 107 | passed and failed.

108 | 109 |
110 | 111 |
112 | 113 |

The above screenshot shows passed and failed tests. The user can 114 | click on the "Debug me" button besides one of the sad faces to debug why 115 | the program failed on a particular test.

116 | 117 |

Here are some sample practice problems:

118 | 119 | 126 | 127 |
128 | 129 |
130 | 131 |

Debug

132 |

existing programs that almost work properly

133 | 134 |

Using the Online Python Tutor's bidirectional single-stepping and 135 | data structure visualization capabilities, students can practice 136 | debugging, an important skill which is rarely covered in web-based 137 | programming problems.

138 | 139 |

They can work on problems like, "Change at most 2 lines of code to 140 | make this almost-correct Python program work properly." Here are 141 | some sample debugging problems:

142 | 143 | 150 | 151 |
152 | 153 |
154 | 155 |

Optimize

156 |

existing programs to run using fewer instructions

157 | 158 |

Students can practice refactoring already-correct programs to run 159 | faster and execute fewer instructions.

160 | 161 |

They can work on problems like, "Optimize this program so that it 162 | terminates correctly after running less than 50 lines of Python 163 | code." Here are some sample optimization problems:

164 | 165 | 172 | 173 |
174 | 175 |
176 | 177 |

Create

178 |

new practice problems in plain text format

179 | 180 |

Teachers can easily create new practice problems by writing them in a 181 | lightweight plain text format. For example, reverse.txt provides the specification 183 | for the Reverse list problem.

184 | 185 |

The problem specification format allows constraints like "code 186 | diffs must be less than N lines" (used for debugging problems) and 187 | "tests must terminate in at most M executed Python lines" (used 188 | for optimization problems).

189 | 190 |

I plan to add support for semantic constraints like "don't allow 191 | the program to create any auxiliary data structures", which could be 192 | used for problems like "merge these two lists in-place without 193 | creating any new temporary lists".

194 | 195 |
196 | 197 | 198 |
199 | 200 | 201 | 219 | 220 |
221 | 222 | 223 | 224 | -------------------------------------------------------------------------------- /jquery.autogrow.js: -------------------------------------------------------------------------------- 1 | // include this file AFTER including jQuery 2 | 3 | // auto-growing text area code taken from: 4 | // http://onehackoranother.com/projects/jquery/jquery-grab-bag/autogrow-textarea.html 5 | // http://github.com/jaz303/jquery-grab-bag/blob/master/javascripts/jquery.autogrow-textarea.js 6 | (function($) { 7 | 8 | /* 9 | * Auto-growing textareas; technique ripped from Facebook 10 | */ 11 | $.fn.autogrow = function(options) { 12 | 13 | this.filter('textarea').each(function() { 14 | 15 | var $this = $(this), 16 | minHeight = $this.height(), 17 | lineHeight = $this.css('lineHeight'); 18 | 19 | var shadow = $('
').css({ 20 | position: 'absolute', 21 | top: -10000, 22 | left: -10000, 23 | width: $(this).width() - parseInt($this.css('paddingLeft')) - parseInt($this.css('paddingRight')), 24 | fontSize: $this.css('fontSize'), 25 | fontFamily: $this.css('fontFamily'), 26 | lineHeight: $this.css('lineHeight'), 27 | resize: 'none' 28 | }).appendTo(document.body); 29 | 30 | var update = function() { 31 | 32 | var times = function(string, number) { 33 | for (var i = 0, r = ''; i < number; i ++) r += string; 34 | return r; 35 | }; 36 | 37 | var val = this.value.replace(//g, '>') 39 | .replace(/&/g, '&') 40 | .replace(/\n$/, '
 ') 41 | .replace(/\n/g, '
') 42 | .replace(/ {2,}/g, function(space) { return times(' ', space.length -1) + ' ' }); 43 | 44 | shadow.html(val); 45 | // pgbovine - tweak the value below to adjust the 'slack' 46 | $(this).css('height', Math.max(shadow.height() + 40, minHeight)); 47 | 48 | } 49 | 50 | $(this).change(update).keyup(update).keydown(update); 51 | 52 | update.apply(this); 53 | 54 | }); 55 | 56 | return this; 57 | 58 | } 59 | 60 | })(jQuery); 61 | 62 | -------------------------------------------------------------------------------- /jquery.ba-bbq.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010 3 | * http://benalman.com/projects/jquery-bbq-plugin/ 4 | * 5 | * Copyright (c) 2010 "Cowboy" Ben Alman 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://benalman.com/about/license/ 8 | */ 9 | (function($,p){var i,m=Array.prototype.slice,r=decodeURIComponent,a=$.param,c,l,v,b=$.bbq=$.bbq||{},q,u,j,e=$.event.special,d="hashchange",A="querystring",D="fragment",y="elemUrlAttr",g="location",k="href",t="src",x=/^.*\?|#.*$/g,w=/^.*\#/,h,C={};function E(F){return typeof F==="string"}function B(G){var F=m.call(arguments,1);return function(){return G.apply(this,F.concat(m.call(arguments)))}}function n(F){return F.replace(/^[^#]*#?(.*)$/,"$1")}function o(F){return F.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(H,M,F,I,G){var O,L,K,N,J;if(I!==i){K=F.match(H?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);J=K[3]||"";if(G===2&&E(I)){L=I.replace(H?w:x,"")}else{N=l(K[2]);I=E(I)?l[H?D:A](I):I;L=G===2?I:G===1?$.extend({},I,N):$.extend({},N,I);L=a(L);if(H){L=L.replace(h,r)}}O=K[1]+(H?"#":L||!K[1]?"?":"")+L+J}else{O=M(F!==i?F:p[g][k])}return O}a[A]=B(f,0,o);a[D]=c=B(f,1,n);c.noEscape=function(G){G=G||"";var F=$.map(G.split(""),encodeURIComponent);h=new RegExp(F.join("|"),"g")};c.noEscape(",/");$.deparam=l=function(I,F){var H={},G={"true":!0,"false":!1,"null":null};$.each(I.replace(/\+/g," ").split("&"),function(L,Q){var K=Q.split("="),P=r(K[0]),J,O=H,M=0,R=P.split("]["),N=R.length-1;if(/\[/.test(R[0])&&/\]$/.test(R[N])){R[N]=R[N].replace(/\]$/,"");R=R.shift().split("[").concat(R);N=R.length-1}else{N=0}if(K.length===2){J=r(K[1]);if(F){J=J&&!isNaN(J)?+J:J==="undefined"?i:G[J]!==i?G[J]:J}if(N){for(;M<=N;M++){P=R[M]===""?O.length:R[M];O=O[P]=M').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this); -------------------------------------------------------------------------------- /jquery.textarea.js: -------------------------------------------------------------------------------- 1 | // include this file AFTER including jQuery 2 | 3 | /* 4 | * Tabby jQuery plugin version 0.12 5 | * 6 | * Ted Devito - http://teddevito.com/demos/textarea.html 7 | * 8 | * You should have received a copy of the GNU General Public License 9 | * along with Easy Widgets. If not, see 10 | * 11 | * Plugin development pattern based on: http://www.learningjquery.com/2007/10/a-plugin-development-pattern 12 | * 13 | */ 14 | 15 | // create closure 16 | 17 | (function($) { 18 | 19 | // plugin definition 20 | 21 | $.fn.tabby = function(options) { 22 | //debug(this); 23 | // build main options before element iteration 24 | var opts = $.extend({}, $.fn.tabby.defaults, options); 25 | var pressed = $.fn.tabby.pressed; 26 | 27 | // iterate and reformat each matched element 28 | return this.each(function() { 29 | $this = $(this); 30 | 31 | // build element specific options 32 | var options = $.meta ? $.extend({}, opts, $this.data()) : opts; 33 | 34 | $this.bind('keydown',function (e) { 35 | var kc = $.fn.tabby.catch_kc(e); 36 | if (16 == kc) pressed.shft = true; 37 | /* 38 | because both CTRL+TAB and ALT+TAB default to an event (changing tab/window) that 39 | will prevent js from capturing the keyup event, we'll set a timer on releasing them. 40 | */ 41 | if (17 == kc) {pressed.ctrl = true; setTimeout("$.fn.tabby.pressed.ctrl = false;",1000);} 42 | if (18 == kc) {pressed.alt = true; setTimeout("$.fn.tabby.pressed.alt = false;",1000);} 43 | 44 | if (9 == kc && !pressed.ctrl && !pressed.alt) { 45 | e.preventDefault; // does not work in O9.63 ?? 46 | pressed.last = kc; setTimeout("$.fn.tabby.pressed.last = null;",0); 47 | process_keypress ($(e.target).get(0), pressed.shft, options); 48 | return false; 49 | } 50 | 51 | }).bind('keyup',function (e) { 52 | if (16 == $.fn.tabby.catch_kc(e)) pressed.shft = false; 53 | }).bind('blur',function (e) { // workaround for Opera -- http://www.webdeveloper.com/forum/showthread.php?p=806588 54 | if (9 == pressed.last) $(e.target).one('focus',function (e) {pressed.last = null;}).get(0).focus(); 55 | }); 56 | 57 | }); 58 | }; 59 | 60 | // define and expose any extra methods 61 | $.fn.tabby.catch_kc = function(e) { return e.keyCode ? e.keyCode : e.charCode ? e.charCode : e.which; }; 62 | $.fn.tabby.pressed = {shft : false, ctrl : false, alt : false, last: null}; 63 | 64 | // private function for debugging 65 | function debug($obj) { 66 | if (window.console && window.console.log) 67 | window.console.log('textarea count: ' + $obj.size()); 68 | }; 69 | 70 | function process_keypress (o,shft,options) { 71 | var scrollTo = o.scrollTop; 72 | //var tabString = String.fromCharCode(9); 73 | 74 | // gecko; o.setSelectionRange is only available when the text box has focus 75 | if (o.setSelectionRange) gecko_tab (o, shft, options); 76 | 77 | // ie; document.selection is always available 78 | else if (document.selection) ie_tab (o, shft, options); 79 | 80 | o.scrollTop = scrollTo; 81 | } 82 | 83 | // plugin defaults 84 | //$.fn.tabby.defaults = {tabString : String.fromCharCode(9)}; 85 | // modified by pgbovine: 86 | $.fn.tabby.defaults = {tabString : ' '}; 87 | 88 | function gecko_tab (o, shft, options) { 89 | var ss = o.selectionStart; 90 | var es = o.selectionEnd; 91 | 92 | // when there's no selection and we're just working with the caret, we'll add/remove the tabs at the caret, providing more control 93 | if(ss == es) { 94 | // SHIFT+TAB 95 | if (shft) { 96 | // check to the left of the caret first 97 | //if ("\t" == o.value.substring(ss-options.tabString.length, ss)) { 98 | // modified by pgbovine: 99 | if (" " == o.value.substring(ss-options.tabString.length, ss)) { 100 | o.value = o.value.substring(0, ss-options.tabString.length) + o.value.substring(ss); // put it back together omitting one character to the left 101 | o.focus(); 102 | o.setSelectionRange(ss - options.tabString.length, ss - options.tabString.length); 103 | } 104 | // then check to the right of the caret 105 | else if ("\t" == o.value.substring(ss, ss + options.tabString.length)) { 106 | o.value = o.value.substring(0, ss) + o.value.substring(ss + options.tabString.length); // put it back together omitting one character to the right 107 | o.focus(); 108 | o.setSelectionRange(ss,ss); 109 | } 110 | } 111 | // TAB 112 | else { 113 | o.value = o.value.substring(0, ss) + options.tabString + o.value.substring(ss); 114 | o.focus(); 115 | o.setSelectionRange(ss + options.tabString.length, ss + options.tabString.length); 116 | } 117 | } 118 | // selections will always add/remove tabs from the start of the line 119 | else { 120 | // split the textarea up into lines and figure out which lines are included in the selection 121 | var lines = o.value.split("\n"); 122 | var indices = new Array(); 123 | var sl = 0; // start of the line 124 | var el = 0; // end of the line 125 | var sel = false; 126 | for (var i in lines) { 127 | el = sl + lines[i].length; 128 | indices.push({start: sl, end: el, selected: (sl <= ss && el > ss) || (el >= es && sl < es) || (sl > ss && el < es)}); 129 | sl = el + 1;// for "\n" 130 | } 131 | 132 | // walk through the array of lines (indices) and add tabs where appropriate 133 | var modifier = 0; 134 | for (var i in indices) { 135 | if (indices[i].selected) { 136 | var pos = indices[i].start + modifier; // adjust for tabs already inserted/removed 137 | // SHIFT+TAB 138 | if (shft && options.tabString == o.value.substring(pos,pos+options.tabString.length)) { // only SHIFT+TAB if there's a tab at the start of the line 139 | o.value = o.value.substring(0,pos) + o.value.substring(pos + options.tabString.length); // omit the tabstring to the right 140 | modifier -= options.tabString.length; 141 | } 142 | // TAB 143 | else if (!shft) { 144 | o.value = o.value.substring(0,pos) + options.tabString + o.value.substring(pos); // insert the tabstring 145 | modifier += options.tabString.length; 146 | } 147 | } 148 | } 149 | o.focus(); 150 | var ns = ss + ((modifier > 0) ? options.tabString.length : (modifier < 0) ? -options.tabString.length : 0); 151 | var ne = es + modifier; 152 | o.setSelectionRange(ns,ne); 153 | } 154 | } 155 | 156 | function ie_tab (o, shft, options) { 157 | var range = document.selection.createRange(); 158 | 159 | if (o == range.parentElement()) { 160 | // when there's no selection and we're just working with the caret, we'll add/remove the tabs at the caret, providing more control 161 | if ('' == range.text) { 162 | // SHIFT+TAB 163 | if (shft) { 164 | var bookmark = range.getBookmark(); 165 | //first try to the left by moving opening up our empty range to the left 166 | range.moveStart('character', -options.tabString.length); 167 | if (options.tabString == range.text) { 168 | range.text = ''; 169 | } else { 170 | // if that didn't work then reset the range and try opening it to the right 171 | range.moveToBookmark(bookmark); 172 | range.moveEnd('character', options.tabString.length); 173 | if (options.tabString == range.text) 174 | range.text = ''; 175 | } 176 | // move the pointer to the start of them empty range and select it 177 | range.collapse(true); 178 | range.select(); 179 | } 180 | 181 | else { 182 | // very simple here. just insert the tab into the range and put the pointer at the end 183 | range.text = options.tabString; 184 | range.collapse(false); 185 | range.select(); 186 | } 187 | } 188 | // selections will always add/remove tabs from the start of the line 189 | else { 190 | 191 | var selection_text = range.text; 192 | var selection_len = selection_text.length; 193 | var selection_arr = selection_text.split("\r\n"); 194 | 195 | var before_range = document.body.createTextRange(); 196 | before_range.moveToElementText(o); 197 | before_range.setEndPoint("EndToStart", range); 198 | var before_text = before_range.text; 199 | var before_arr = before_text.split("\r\n"); 200 | var before_len = before_text.length; // - before_arr.length + 1; 201 | 202 | var after_range = document.body.createTextRange(); 203 | after_range.moveToElementText(o); 204 | after_range.setEndPoint("StartToEnd", range); 205 | var after_text = after_range.text; // we can accurately calculate distance to the end because we're not worried about MSIE trimming a \r\n 206 | 207 | var end_range = document.body.createTextRange(); 208 | end_range.moveToElementText(o); 209 | end_range.setEndPoint("StartToEnd", before_range); 210 | var end_text = end_range.text; // we can accurately calculate distance to the end because we're not worried about MSIE trimming a \r\n 211 | 212 | var check_html = $(o).html(); 213 | $("#r3").text(before_len + " + " + selection_len + " + " + after_text.length + " = " + check_html.length); 214 | if((before_len + end_text.length) < check_html.length) { 215 | before_arr.push(""); 216 | before_len += 2; // for the \r\n that was trimmed 217 | if (shft && options.tabString == selection_arr[0].substring(0,options.tabString.length)) 218 | selection_arr[0] = selection_arr[0].substring(options.tabString.length); 219 | else if (!shft) selection_arr[0] = options.tabString + selection_arr[0]; 220 | } else { 221 | if (shft && options.tabString == before_arr[before_arr.length-1].substring(0,options.tabString.length)) 222 | before_arr[before_arr.length-1] = before_arr[before_arr.length-1].substring(options.tabString.length); 223 | else if (!shft) before_arr[before_arr.length-1] = options.tabString + before_arr[before_arr.length-1]; 224 | } 225 | 226 | for (var i = 1; i < selection_arr.length; i++) { 227 | if (shft && options.tabString == selection_arr[i].substring(0,options.tabString.length)) 228 | selection_arr[i] = selection_arr[i].substring(options.tabString.length); 229 | else if (!shft) selection_arr[i] = options.tabString + selection_arr[i]; 230 | } 231 | 232 | if (1 == before_arr.length && 0 == before_len) { 233 | if (shft && options.tabString == selection_arr[0].substring(0,options.tabString.length)) 234 | selection_arr[0] = selection_arr[0].substring(options.tabString.length); 235 | else if (!shft) selection_arr[0] = options.tabString + selection_arr[0]; 236 | } 237 | 238 | if ((before_len + selection_len + after_text.length) < check_html.length) { 239 | selection_arr.push(""); 240 | selection_len += 2; // for the \r\n that was trimmed 241 | } 242 | 243 | before_range.text = before_arr.join("\r\n"); 244 | range.text = selection_arr.join("\r\n"); 245 | 246 | var new_range = document.body.createTextRange(); 247 | new_range.moveToElementText(o); 248 | 249 | if (0 < before_len) new_range.setEndPoint("StartToEnd", before_range); 250 | else new_range.setEndPoint("StartToStart", before_range); 251 | new_range.setEndPoint("EndToEnd", range); 252 | 253 | new_range.select(); 254 | 255 | } 256 | } 257 | } 258 | 259 | // end of closure 260 | })(jQuery); 261 | -------------------------------------------------------------------------------- /jsplumb-test.js: -------------------------------------------------------------------------------- 1 | // jsPlumb demo for Online Python Tutor 2.0 2 | 3 | var lightGray = "#dddddd"; 4 | var darkBlue = "#3D58A2"; 5 | var pinkish = "#F15149"; 6 | 7 | function initJsPlumb() { 8 | //console.log("initJsPlumb()"); 9 | 10 | // set some sensible defaults 11 | jsPlumb.Defaults.Endpoint = ["Dot", {radius:3}]; 12 | //jsPlumb.Defaults.Endpoint = ["Rectangle", {width:3, height:3}]; 13 | jsPlumb.Defaults.EndpointStyle = {fillStyle: lightGray}; 14 | jsPlumb.Defaults.Anchors = ["RightMiddle", "LeftMiddle"]; 15 | jsPlumb.Defaults.Connector = [ "Bezier", { curviness:15 }]; /* too much 'curviness' causes lines to run together */ 16 | jsPlumb.Defaults.PaintStyle = {lineWidth:1, strokeStyle: lightGray}; 17 | 18 | jsPlumb.Defaults.EndpointHoverStyle = {fillStyle: pinkish}; 19 | jsPlumb.Defaults.HoverPaintStyle = {lineWidth:2, strokeStyle: pinkish}; 20 | 21 | // make some example connections 22 | jsPlumb.connect({source:"global_TowerOfHanoi", target:"heap_func1"}); 23 | jsPlumb.connect({source:"global_stack1", target:"heap_list2"}); 24 | jsPlumb.connect({source:"global_stack2", target:"heap_list3"}); 25 | jsPlumb.connect({source:"global_stack3", target:"heap_list4"}); 26 | 27 | jsPlumb.connect({source:"TowerOfHanoi5_a", target:"heap_list2"}); 28 | jsPlumb.connect({source:"TowerOfHanoi5_b", target:"heap_list4"}); 29 | jsPlumb.connect({source:"TowerOfHanoi5_tmp", target:"heap_list3"}); 30 | 31 | jsPlumb.connect({source:"TowerOfHanoi4_a", target:"heap_list2"}); 32 | jsPlumb.connect({source:"TowerOfHanoi4_b", target:"heap_list3"}); 33 | jsPlumb.connect({source:"TowerOfHanoi4_tmp", target:"heap_list4"}); 34 | 35 | jsPlumb.connect({source:"TowerOfHanoi3_a", target:"heap_list4"}); 36 | jsPlumb.connect({source:"TowerOfHanoi3_b", target:"heap_list3"}); 37 | jsPlumb.connect({source:"TowerOfHanoi3_tmp", target:"heap_list2"}); 38 | 39 | jsPlumb.connect({source:"TowerOfHanoi2_a", target:"heap_list4"}); 40 | jsPlumb.connect({source:"TowerOfHanoi2_b", target:"heap_list2"}); 41 | jsPlumb.connect({source:"TowerOfHanoi2_tmp", target:"heap_list3"}); 42 | 43 | jsPlumb.connect({source:"TowerOfHanoi1_a", target:"heap_list3"}); 44 | jsPlumb.connect({source:"TowerOfHanoi1_b", target:"heap_list2"}); 45 | jsPlumb.connect({source:"TowerOfHanoi1_tmp", target:"heap_list4"}); 46 | 47 | 48 | $(".stackFrameHeader").click(function() { 49 | var enclosingStackFrame = $(this).parent(); 50 | var enclosingStackFrameID = enclosingStackFrame.attr('id'); 51 | 52 | var allConnections = jsPlumb.getConnections(); 53 | for (var i = 0; i < allConnections.length; i++) { 54 | var c = allConnections[i]; 55 | 56 | // this is VERY VERY fragile code, since it assumes that going up 57 | // five layers of parent() calls will get you from the source end 58 | // of the connector to the enclosing stack frame 59 | var stackFrameDiv = c.source.parent().parent().parent().parent().parent(); 60 | 61 | // if this connector starts in the selected stack frame ... 62 | if (stackFrameDiv.attr('id') == enclosingStackFrameID) { 63 | // then HIGHLIGHT IT! 64 | c.setPaintStyle({lineWidth:2, strokeStyle: darkBlue}); 65 | c.endpoints[0].setPaintStyle({fillStyle: darkBlue}); 66 | //c.endpoints[1].setPaintStyle({fillStyle: darkBlue}); 67 | c.endpoints[1].setVisible(false, true, true); // JUST set right endpoint to be invisible 68 | 69 | // ... and move it to the VERY FRONT 70 | $(c.canvas).css("z-index", 1000); 71 | } 72 | else { 73 | // else unhighlight it 74 | c.setPaintStyle({lineWidth:1, strokeStyle: lightGray}); 75 | c.endpoints[0].setPaintStyle({fillStyle: lightGray}); 76 | //c.endpoints[1].setPaintStyle({fillStyle: lightGray}); 77 | c.endpoints[1].setVisible(false, true, true); // JUST set right endpoint to be invisible 78 | 79 | $(c.canvas).css("z-index", 0); 80 | } 81 | } 82 | 83 | // clear everything, then just activate $(this) one ... 84 | $(".stackFrame").removeClass("selectedStackFrame"); 85 | $(".stackFrameHeader").addClass("inactiveStackFrameHeader"); 86 | 87 | enclosingStackFrame.addClass("selectedStackFrame"); 88 | $(this).removeClass("inactiveStackFrameHeader"); 89 | }); 90 | 91 | // 'click' on the top-most stack frame 92 | $('#toh1_header').trigger('click'); 93 | 94 | } 95 | 96 | $(document).ready(function() { 97 | // HACK:add a delay so that plumbs render properly AFTER all elements are loaded 98 | window.setTimeout(initJsPlumb, 150); 99 | }); 100 | 101 | -------------------------------------------------------------------------------- /question.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | 26 | 27 | Online Python Tutor: Practice Questions 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
60 | 61 |
62 | 63 |
Please load a question ...
64 | 65 |
66 | 67 | 68 | 69 | 70 |
71 | 72 |
73 | 74 | 75 |
76 | 77 |

Write your solution code here:
78 | 80 | 81 |

Write your test code here:
82 | 84 | 85 |

86 | 87 | 88 | 89 |

90 | 91 | 92 | 93 | 94 | 132 | 137 | 138 |
95 | 96 |
97 | 98 |
99 | 100 | Use left and right arrow keys to step through this code: 101 |
102 | 103 |
104 | 105 |
106 | 107 | 108 | 109 | 110 | 111 |
112 | 113 |
114 | 115 | 116 | Step ? of ? 117 | 118 | 119 |
120 | 121 |
122 | 123 | 124 |
125 | 126 | Program output: 127 |
128 | 129 | 130 | 131 |
133 | 134 |
135 | 136 |
139 | 140 | 141 |
142 | 143 | 144 |
145 | 146 |
147 |

148 |   
149 |
150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 |
InputResultStatus
163 | 164 |
165 | 166 |
167 | 168 | 169 |
170 | 171 | 211 | 212 |
213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /questions/debug-bsearch.txt: -------------------------------------------------------------------------------- 1 | Name: 2 | Debug: Binary search 3 | 4 | Question: 5 | 6 | Change ONE line of code to make the given binary search function work 7 | properly on all tests. 8 | 9 | Hint: 10 | What happens when you search for an element that is not in the list? 11 | 'bsearch' should return -1. 12 | 13 | Solution: 14 | Change the first line of 'helper' to "if low >= hi:" 15 | 16 | MaxLineDelta: 1 17 | MaxInstructions: 100 18 | 19 | Skeleton: 20 | 21 | def bsearch(lst, elt): 22 | return helper(lst, elt, 0, len(lst)) 23 | 24 | def helper(lst, elt, lo, hi): 25 | if lo > hi: 26 | return -1 27 | mid = (lo+hi)//2 28 | midval = lst[mid] 29 | if midval < elt: 30 | return helper(lst, elt, mid+1, hi) 31 | elif midval > elt: 32 | return helper(lst, elt, lo, mid) 33 | else: 34 | return mid 35 | 36 | 37 | Test: 38 | haystack = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] 39 | needle = 'f' 40 | result = bsearch(haystack, needle) 41 | 42 | Expect: 43 | result = 5 44 | 45 | Test: 46 | haystack = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] 47 | needle = 'a' 48 | result = bsearch(haystack, needle) 49 | 50 | Expect: 51 | result = 0 52 | 53 | Test: 54 | haystack = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] 55 | needle = 'd' 56 | result = bsearch(haystack, needle) 57 | 58 | Expect: 59 | result = 3 60 | 61 | Test: 62 | haystack = ['b', 'c', 'd', 'e', 'f', 'g', 'h'] 63 | needle = 'a' 64 | result = bsearch(haystack, needle) 65 | 66 | Expect: 67 | result = -1 68 | 69 | Test: 70 | haystack = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] 71 | needle = 'x' 72 | result = bsearch(haystack, needle) 73 | 74 | Expect: 75 | result = -1 76 | 77 | Test: 78 | haystack = ['a', 'b', 'd', 'e', 'f', 'g', 'h'] 79 | needle = 'c' 80 | result = bsearch(haystack, needle) 81 | 82 | Expect: 83 | result = -1 84 | 85 | -------------------------------------------------------------------------------- /questions/debug-ireverse.txt: -------------------------------------------------------------------------------- 1 | Name: 2 | Debug: In-place reverse 3 | 4 | Question: 5 | 6 | The given function reverses a list in-place, but it doesn't work for all 7 | test inputs. Change only ONE line of code to make this function pass 8 | all tests. 9 | 10 | MaxLineDelta: 1 11 | 12 | Hint: 13 | Focus on the 'indices' variable. 14 | 15 | Solution: 16 | Change line 3 to "indices = range((maxIndex + 1)/2)" 17 | 18 | Skeleton: 19 | 20 | def reverse(lst): 21 | maxIndex = len(lst) - 1 22 | indices = range(maxIndex/2) 23 | for i in indices: 24 | tmp = lst[i] 25 | lst[i] = lst[maxIndex - i] 26 | lst[maxIndex - i] = tmp 27 | 28 | 29 | Test: 30 | input = ['a', 'b', 'c', 'd', 'e'] 31 | reverse(input) 32 | 33 | Expect: 34 | input = ['e', 'd', 'c', 'b', 'a'] 35 | 36 | 37 | Test: 38 | input = ['a', 'b', 'c', 'd'] 39 | reverse(input) 40 | 41 | Expect: 42 | input = ['d', 'c', 'b', 'a'] 43 | 44 | 45 | Test: 46 | input = ['a', 'b', 'c'] 47 | reverse(input) 48 | 49 | Expect: 50 | input = ['c', 'b', 'a'] 51 | 52 | 53 | Test: 54 | input = ['a', 'b'] 55 | reverse(input) 56 | 57 | Expect: 58 | input = ['b', 'a'] 59 | 60 | 61 | Test: 62 | input = ['a'] 63 | reverse(input) 64 | 65 | Expect: 66 | input = ['a'] 67 | -------------------------------------------------------------------------------- /questions/debug-mergesort.txt: -------------------------------------------------------------------------------- 1 | Name: 2 | Debug: Mergesort 3 | 4 | Question: 5 | 6 | Add at most TWO lines of code to make the given buggy mergesort function 7 | work properly. 8 | 9 | Hint: 10 | Focus on the merge function. 11 | 12 | Solution: 13 | The merge function must append all of the leftover entries from 'left' 14 | and 'right' onto 'result'. 15 | 16 | MaxLineDelta: 2 17 | 18 | Skeleton: 19 | 20 | # Adapted from: 21 | # http://en.literateprograms.org/Merge_sort_(Python) 22 | 23 | def merge(left, right): 24 | result = [] 25 | i ,j = 0, 0 26 | while i < len(left) and j < len(right): 27 | if left[i] <= right[j]: 28 | result.append(left[i]) 29 | i += 1 30 | else: 31 | result.append(right[j]) 32 | j += 1 33 | return result 34 | 35 | def mergesort(list): 36 | if len(list) < 2: 37 | return list 38 | else: 39 | middle = len(list) / 2 40 | left = mergesort(list[:middle]) 41 | right = mergesort(list[middle:]) 42 | return merge(left, right) 43 | 44 | 45 | Test: 46 | input = [4,3,2,1] 47 | result = mergesort(input) 48 | 49 | Expect: 50 | result = [1,2,3,4] 51 | 52 | Test: 53 | input = [3,4,6,5,8,7,2,1] 54 | result = mergesort(input) 55 | 56 | Expect: 57 | result = [1,2,3,4,5,6,7,8] 58 | 59 | Test: 60 | input = [2,1,3] 61 | result = mergesort(input) 62 | 63 | Expect: 64 | result = [1,2,3] 65 | 66 | 67 | Test: 68 | input = [1] 69 | result = mergesort(input) 70 | 71 | Expect: 72 | result = [1] 73 | 74 | -------------------------------------------------------------------------------- /questions/optimize-find-dups.txt: -------------------------------------------------------------------------------- 1 | Name: 2 | Optimize: Find duplicates 3 | 4 | Question: 5 | 6 | The given function returns a list of duplicate elements in the input 7 | list. Optimize this function so that it works on any 10-element sorted 8 | list by executing less than 50 instructions. 9 | 10 | Hint: 11 | Think about iterating through the list only once. 12 | 13 | Solution: 14 | Use a set to check for duplicates. 15 | 16 | MaxInstructions: 50 17 | 18 | Skeleton: 19 | 20 | def findDups(lst): 21 | dups = set() 22 | for i in range(len(lst)): 23 | for j in range(i + 1, len(lst)): 24 | if lst[i] == lst[j]: 25 | dups.add(lst[i]) 26 | return dups 27 | 28 | 29 | // sample solution: 30 | // def findDups(lst): 31 | // seen = set() 32 | // dups = set() 33 | // for e in lst: 34 | // if e in seen: 35 | // dups.add(e) 36 | // seen.add(e) 37 | // return dups 38 | 39 | 40 | Test: 41 | input = ['a', 'b', 'c', 'a', 'd', 'c', 'e', 'f', 'g', 'f'] 42 | result = findDups(input) 43 | 44 | Expect: 45 | result = set(['a', 'c', 'f']) 46 | 47 | Test: 48 | input = ['a', 'b', 'xxx', 'c', 'd', 'e', 'f', 'g', 'h', 'xxx'] 49 | result = findDups(input) 50 | 51 | Expect: 52 | result = set(['xxx']) 53 | 54 | Test: 55 | input = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'] 56 | result = findDups(input) 57 | 58 | Expect: 59 | result = set() 60 | 61 | -------------------------------------------------------------------------------- /questions/optimize-search.txt: -------------------------------------------------------------------------------- 1 | Name: 2 | Optimize: List search 3 | 4 | Question: 5 | 6 | The following search function finds the index of the first occurrence of 7 | elt in a sorted list lst (or -1 if elt not found). Optimize this 8 | function so that it works on any 64-element sorted list by executing 9 | less than 50 instructions. 10 | 11 | Hint: 12 | Linear search will never work. 13 | 14 | Solution: 15 | Implement binary search instead of linear search. A recursive solution 16 | might not be efficient enough, though! 17 | 18 | MaxInstructions: 50 19 | 20 | Skeleton: 21 | 22 | def search(lst, elt): 23 | ind = -1; 24 | for i in range(len(lst)): 25 | if lst[i] == elt: 26 | return i 27 | return -1 28 | 29 | 30 | // # Iterative solution from stackoverflow 31 | // # http://stackoverflow.com/questions/212358/binary-search-in-python 32 | // def search(lst, elt): 33 | // lo, hi = 0, len(lst) 34 | // while lo < hi: 35 | // mid = (lo+hi)//2 36 | // midval = lst[mid] 37 | // if midval < elt: 38 | // lo = mid+1 39 | // elif midval > elt: 40 | // hi = mid 41 | // else: 42 | // return mid 43 | // return -1 44 | // 45 | // # recursive implementation, which is too slow 46 | // def search(lst, elt): 47 | // return bsearch(lst, elt, 0, len(lst)) 48 | // 49 | // def bsearch(lst, elt, lo, hi): 50 | // if lo > hi: 51 | // return -1 52 | // mid = (lo+hi)//2 53 | // midval = lst[mid] 54 | // if midval < elt: 55 | // return bsearch(lst, elt, mid+1, hi) 56 | // elif midval > elt: 57 | // return bsearch(lst, elt, lo, mid) 58 | // else: 59 | // return mid 60 | 61 | 62 | Test: 63 | haystack = range(64) 64 | needle = 63 65 | result = search(haystack, needle) 66 | 67 | Expect: 68 | result = 63 69 | 70 | Test: 71 | haystack = range(64) 72 | needle = 60 73 | result = search(haystack, needle) 74 | 75 | Expect: 76 | result = 60 77 | 78 | Test: 79 | haystack = range(64) 80 | needle = 32 81 | result = search(haystack, needle) 82 | 83 | Expect: 84 | result = 32 85 | 86 | Test: 87 | haystack = range(64) 88 | needle = 3 89 | result = search(haystack, needle) 90 | 91 | Expect: 92 | result = 3 93 | 94 | Test: 95 | haystack = range(64) 96 | needle = 1000 97 | result = search(haystack, needle) 98 | 99 | Expect: 100 | result = -1 101 | 102 | -------------------------------------------------------------------------------- /questions/optimize-sum.txt: -------------------------------------------------------------------------------- 1 | Name: 2 | Optimize: Greatest sum 3 | 4 | Question: 5 | 6 | The given function finds the greatest sum amongst all pairs of elements 7 | in the array. Optimize this function so that it works on any 5-element 8 | list by executing less than 20 instructions. 9 | 10 | Hint: 11 | Think about sorting. 12 | 13 | Solution: 14 | Sort the list and return the sum of the two largest elements. 15 | 16 | MaxInstructions: 20 17 | 18 | Skeleton: 19 | 20 | def maxPairSum(lst): 21 | maxSum = -99999 22 | for i in range(len(lst)): 23 | for j in range(i + 1, len(lst)): 24 | maxSum = max(lst[i] + lst[j], maxSum) 25 | return maxSum 26 | 27 | 28 | Test: 29 | input = [10, 2, 1, 7, 3, 4, 5, 6, 8, 9] 30 | result = maxPairSum(input) 31 | 32 | Expect: 33 | result = 19 34 | 35 | Test: 36 | input = [10, 2, 1, 7, 3, 4, 5, 6, 80, 9] 37 | result = maxPairSum(input) 38 | 39 | Expect: 40 | result = 90 41 | 42 | Test: 43 | input = [1,1,1,1,2,1,1,1,1,1] 44 | result = maxPairSum(input) 45 | 46 | Expect: 47 | result = 3 48 | 49 | -------------------------------------------------------------------------------- /questions/remove-dups.txt: -------------------------------------------------------------------------------- 1 | Name: 2 | Remove duplicate characters 3 | 4 | Question: 5 | 6 | Write a function to return a new string that contains the contents of 7 | the input string (in the original order) with all duplicate characters 8 | removed. 9 | 10 | Hint: 11 | Think about using a set to keep track of already-seen characters. 12 | 13 | Solution: 14 | Iterate through the input string and keep already-seen characters in a 15 | set. If a character hasn't been seen yet, then append it to an output 16 | list. Finally convert the list into a string using "''.join()" and then 17 | return it. 18 | 19 | Skeleton: 20 | 21 | def removeDups(s): 22 | # write your solution code here 23 | 24 | // # Example solution: 25 | // def removeDups(s): 26 | // seen = set() 27 | // out = [] 28 | // for c in s: 29 | // if c not in seen: 30 | // out.append(c) 31 | // seen.add(c) 32 | // return ''.join(out) 33 | 34 | Test: 35 | input = "AAAABBBBB" 36 | result = removeDups(input) 37 | 38 | Expect: 39 | result = "AB" 40 | 41 | Test: 42 | input = "Hello World" 43 | result = removeDups(input) 44 | 45 | Expect: 46 | result = "Helo Wrd" 47 | 48 | Test: 49 | input = "Hello World" 50 | result = removeDups(input) 51 | 52 | Expect: 53 | result = "Helo Wrd" 54 | 55 | Test: 56 | input = "alibaba" 57 | result = removeDups(input) 58 | 59 | Expect: 60 | result = "alib" 61 | 62 | Test: 63 | input = "abcdefg" 64 | result = removeDups(input) 65 | 66 | Expect: 67 | result = "abcdefg" 68 | 69 | 70 | Test: 71 | input = "" 72 | result = removeDups(input) 73 | 74 | Expect: 75 | result = "" 76 | 77 | -------------------------------------------------------------------------------- /questions/reverse.txt: -------------------------------------------------------------------------------- 1 | Name: 2 | Reverse list 3 | 4 | Question: 5 | 6 | Write a function to return the reverse of the input list. 7 | 8 | Hint: 9 | Think about iterating backwards. 10 | 11 | Solution: 12 | Either iterate backwards to build a new list, or use the Python extended 13 | indexing syntax: "return lst[::-1]" 14 | 15 | Skeleton: 16 | 17 | def reverse(lst): 18 | # write your solution code here 19 | 20 | Test: 21 | input = ['a', 'b', 'c', 'd', 'e'] 22 | result = reverse(input) 23 | 24 | Expect: 25 | result = ['e', 'd', 'c', 'b', 'a'] 26 | 27 | 28 | Test: 29 | input = ['a', 'b', 'c', 'd'] 30 | result = reverse(input) 31 | 32 | Expect: 33 | result = ['d', 'c', 'b', 'a'] 34 | 35 | 36 | Test: 37 | input = ['a', 'b', 'c'] 38 | result = reverse(input) 39 | 40 | Expect: 41 | result = ['c', 'b', 'a'] 42 | 43 | 44 | Test: 45 | input = ['a', 'b'] 46 | result = reverse(input) 47 | 48 | Expect: 49 | result = ['b', 'a'] 50 | 51 | 52 | Test: 53 | input = ['a'] 54 | result = reverse(input) 55 | 56 | Expect: 57 | result = ['a'] 58 | 59 | 60 | Test: 61 | input = [] 62 | result = reverse(input) 63 | 64 | Expect: 65 | result = [] 66 | 67 | -------------------------------------------------------------------------------- /questions/two-sum.txt: -------------------------------------------------------------------------------- 1 | Name: 2 | Two-sum 3 | 4 | Question: 5 | 6 | Write a function to return the sum of the two largest elements in the 7 | given list (assuming you're given a list with at least two elements). 8 | 9 | Hint: 10 | Can you find the largest element? How about the second largest? Or 11 | think about sorting. 12 | 13 | Solution: 14 | Scan through the list twice to find the two largest elements, then sum 15 | them (or sort and sum the two largest elements). 16 | 17 | Skeleton: 18 | 19 | def sumTwoLargest(lst): 20 | # write your solution code here 21 | 22 | Test: 23 | input = [5,3,4,7,10] 24 | result = sumTwoLargest(input) 25 | 26 | Expect: 27 | result = 17 28 | 29 | Test: 30 | input = [5,4,3,2,1] 31 | result = sumTwoLargest(input) 32 | 33 | Expect: 34 | result = 9 35 | 36 | Test: 37 | input = [3,7] 38 | result = sumTwoLargest(input) 39 | 40 | Expect: 41 | result = 10 42 | 43 | Test: 44 | input = [1,1,1,1,1,1,1,1,1,1] 45 | result = sumTwoLargest(input) 46 | 47 | Expect: 48 | result = 2 49 | 50 | Test: 51 | input = [1,9,5,7,3] 52 | result = sumTwoLargest(input) 53 | 54 | Expect: 55 | result = 16 56 | 57 | 58 | Test: 59 | input = [1,2,5,3,5,4] 60 | result = sumTwoLargest(input) 61 | 62 | Expect: 63 | result = 10 64 | 65 | -------------------------------------------------------------------------------- /red-sad-face.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcientist/OnlinePythonTutor/78e0f4060f6d913e7651c64779befb3529bccb63/red-sad-face.jpg -------------------------------------------------------------------------------- /test-programs/caught_exception_1.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "step_line", 9 | "func_name" : "", 10 | "globals" : { }, 11 | "line" : 2, 12 | "stack_locals" : [ ], 13 | "stdout" : "" 14 | }, 15 | { "event" : "exception", 16 | "exception_msg" : "ZeroDivisionError: integer division or modulo by zero", 17 | "func_name" : "", 18 | "globals" : { }, 19 | "line" : 2, 20 | "stack_locals" : [ ], 21 | "stdout" : "" 22 | }, 23 | { "event" : "step_line", 24 | "func_name" : "", 25 | "globals" : { }, 26 | "line" : 3, 27 | "stack_locals" : [ ], 28 | "stdout" : "" 29 | }, 30 | { "event" : "step_line", 31 | "func_name" : "", 32 | "globals" : { }, 33 | "line" : 4, 34 | "stack_locals" : [ ], 35 | "stdout" : "" 36 | }, 37 | { "event" : "return", 38 | "func_name" : "", 39 | "globals" : { }, 40 | "line" : 4, 41 | "stack_locals" : [ ], 42 | "stdout" : "DIVIDE BY ZERO\n" 43 | } 44 | ] 45 | -------------------------------------------------------------------------------- /test-programs/caught_exception_1.py: -------------------------------------------------------------------------------- 1 | try: 2 | x = 1 / 0 3 | except: 4 | print "DIVIDE BY ZERO" 5 | 6 | -------------------------------------------------------------------------------- /test-programs/caught_exception_2.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 2, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "step_line", 9 | "func_name" : "", 10 | "globals" : { }, 11 | "line" : 3, 12 | "stack_locals" : [ ], 13 | "stdout" : "" 14 | }, 15 | { "event" : "exception", 16 | "exception_msg" : "ZeroDivisionError: integer division or modulo by zero", 17 | "func_name" : "", 18 | "globals" : { }, 19 | "line" : 3, 20 | "stack_locals" : [ ], 21 | "stdout" : "" 22 | }, 23 | { "event" : "step_line", 24 | "func_name" : "", 25 | "globals" : { }, 26 | "line" : 4, 27 | "stack_locals" : [ ], 28 | "stdout" : "" 29 | }, 30 | { "event" : "step_line", 31 | "func_name" : "", 32 | "globals" : { }, 33 | "line" : 5, 34 | "stack_locals" : [ ], 35 | "stdout" : "" 36 | }, 37 | { "event" : "step_line", 38 | "func_name" : "", 39 | "globals" : { }, 40 | "line" : 9, 41 | "stack_locals" : [ ], 42 | "stdout" : "DIVIDE BY ZERO\n" 43 | }, 44 | { "event" : "exception", 45 | "exception_msg" : "NameError: name 'y' is not defined", 46 | "func_name" : "", 47 | "globals" : { }, 48 | "line" : 9, 49 | "stack_locals" : [ ], 50 | "stdout" : "DIVIDE BY ZERO\n" 51 | } 52 | ] 53 | -------------------------------------------------------------------------------- /test-programs/caught_exception_2.py: -------------------------------------------------------------------------------- 1 | # caught exception: 2 | try: 3 | x = 1 / 0 4 | except: 5 | print "DIVIDE BY ZERO" 6 | 7 | 8 | # uncaught: 9 | print y 10 | 11 | print "should not reach here" 12 | 13 | -------------------------------------------------------------------------------- /test-programs/circ_ref.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 3, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "step_line", 9 | "func_name" : "", 10 | "globals" : { "x" : [ "LIST", 11 | 99999, 12 | 1, 13 | 2 14 | ] }, 15 | "line" : 4, 16 | "stack_locals" : [ ], 17 | "stdout" : "" 18 | }, 19 | { "event" : "return", 20 | "func_name" : "", 21 | "globals" : { "x" : [ "LIST", 22 | 99999, 23 | 1, 24 | 2, 25 | [ "CIRCULAR_REF", 26 | 99999 27 | ] 28 | ] }, 29 | "line" : 4, 30 | "stack_locals" : [ ], 31 | "stdout" : "" 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /test-programs/circ_ref.py: -------------------------------------------------------------------------------- 1 | # true circular reference 2 | 3 | x = [1, 2] 4 | x.append(x) 5 | 6 | -------------------------------------------------------------------------------- /test-programs/circ_ref_2.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 3, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "step_line", 9 | "func_name" : "", 10 | "globals" : { "x" : [ "LIST", 11 | 99999, 12 | 1, 13 | 2 14 | ] }, 15 | "line" : 4, 16 | "stack_locals" : [ ], 17 | "stdout" : "" 18 | }, 19 | { "event" : "step_line", 20 | "func_name" : "", 21 | "globals" : { "x" : [ "LIST", 22 | 99999, 23 | 1, 24 | 2 25 | ], 26 | "y" : [ "LIST", 27 | 99999, 28 | 3, 29 | 4, 30 | [ "LIST", 31 | 99999, 32 | 1, 33 | 2 34 | ] 35 | ] 36 | }, 37 | "line" : 5, 38 | "stack_locals" : [ ], 39 | "stdout" : "" 40 | }, 41 | { "event" : "return", 42 | "func_name" : "", 43 | "globals" : { "x" : [ "LIST", 44 | 99999, 45 | 1, 46 | 2, 47 | [ "LIST", 48 | 99999, 49 | 3, 50 | 4, 51 | [ "CIRCULAR_REF", 52 | 99999 53 | ] 54 | ] 55 | ], 56 | "y" : [ "LIST", 57 | 99999, 58 | 3, 59 | 4, 60 | [ "LIST", 61 | 99999, 62 | 1, 63 | 2, 64 | [ "CIRCULAR_REF", 65 | 99999 66 | ] 67 | ] 68 | ] 69 | }, 70 | "line" : 5, 71 | "stack_locals" : [ ], 72 | "stdout" : "" 73 | } 74 | ] 75 | -------------------------------------------------------------------------------- /test-programs/circ_ref_2.py: -------------------------------------------------------------------------------- 1 | # true indirect circular reference 2 | 3 | x = [1, 2] 4 | y = [3, 4, x] 5 | x.append(y) 6 | 7 | -------------------------------------------------------------------------------- /test-programs/circ_ref_fake.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 3, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "step_line", 9 | "func_name" : "", 10 | "globals" : { "a" : [ "LIST", 11 | 99999, 12 | 10, 13 | 20, 14 | 30 15 | ] }, 16 | "line" : 4, 17 | "stack_locals" : [ ], 18 | "stdout" : "" 19 | }, 20 | { "event" : "step_line", 21 | "func_name" : "", 22 | "globals" : { "a" : [ "LIST", 23 | 99999, 24 | 10, 25 | 20, 26 | 30 27 | ], 28 | "b" : [ "LIST", 29 | 99999, 30 | 10, 31 | 20, 32 | 30 33 | ] 34 | }, 35 | "line" : 5, 36 | "stack_locals" : [ ], 37 | "stdout" : "" 38 | }, 39 | { "event" : "step_line", 40 | "func_name" : "", 41 | "globals" : { "a" : [ "LIST", 42 | 99999, 43 | 10, 44 | 20, 45 | 30 46 | ], 47 | "b" : [ "LIST", 48 | 99999, 49 | 10, 50 | 20, 51 | 30 52 | ], 53 | "c" : [ "LIST", 54 | 99999, 55 | 10, 56 | 20, 57 | 30 58 | ] 59 | }, 60 | "line" : 6, 61 | "stack_locals" : [ ], 62 | "stdout" : "" 63 | }, 64 | { "event" : "return", 65 | "func_name" : "", 66 | "globals" : { "a" : [ "LIST", 67 | 99999, 68 | 10, 69 | 20, 70 | 30 71 | ], 72 | "b" : [ "LIST", 73 | 99999, 74 | 10, 75 | 20, 76 | 30 77 | ], 78 | "c" : [ "LIST", 79 | 99999, 80 | 10, 81 | 20, 82 | 30 83 | ], 84 | "d" : [ "TUPLE", 85 | 99999, 86 | [ "LIST", 87 | 99999, 88 | 10, 89 | 20, 90 | 30 91 | ], 92 | [ "LIST", 93 | 99999, 94 | 10, 95 | 20, 96 | 30 97 | ], 98 | [ "LIST", 99 | 99999, 100 | 10, 101 | 20, 102 | 30 103 | ] 104 | ] 105 | }, 106 | "line" : 6, 107 | "stack_locals" : [ ], 108 | "stdout" : "" 109 | } 110 | ] 111 | -------------------------------------------------------------------------------- /test-programs/circ_ref_fake.py: -------------------------------------------------------------------------------- 1 | # not a true circular reference 2 | 3 | a = [10, 20, 30] 4 | b = a 5 | c = [10, 20, 30] 6 | d = (a, b, c) 7 | 8 | -------------------------------------------------------------------------------- /test-programs/class_test.py: -------------------------------------------------------------------------------- 1 | class Point: 2 | def __init__(self, x, y): 3 | self.x = x 4 | self.y = y 5 | 6 | def __str__(self): 7 | return "(%d, %d)" % (self.x, self.y) 8 | 9 | p = Point(1, 2) 10 | print p 11 | p2 = Point(3, -4) 12 | print p2 13 | 14 | -------------------------------------------------------------------------------- /test-programs/class_test_2.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "call", 9 | "func_name" : "Outer", 10 | "globals" : { }, 11 | "line" : 1, 12 | "stack_locals" : [ [ "Outer", 13 | { } 14 | ] ], 15 | "stdout" : "" 16 | }, 17 | { "event" : "step_line", 18 | "func_name" : "Outer", 19 | "globals" : { }, 20 | "line" : 1, 21 | "stack_locals" : [ [ "Outer", 22 | { } 23 | ] ], 24 | "stdout" : "" 25 | }, 26 | { "event" : "step_line", 27 | "func_name" : "Outer", 28 | "globals" : { }, 29 | "line" : 2, 30 | "stack_locals" : [ [ "Outer", 31 | { } 32 | ] ], 33 | "stdout" : "" 34 | }, 35 | { "event" : "return", 36 | "func_name" : "Outer", 37 | "globals" : { }, 38 | "line" : 2, 39 | "stack_locals" : [ [ "Outer", 40 | { "__return__" : [ "DICT", 41 | 99999 42 | ] } 43 | ] ], 44 | "stdout" : "" 45 | }, 46 | { "event" : "step_line", 47 | "func_name" : "", 48 | "globals" : { "Outer" : [ "CLASS", 49 | "Outer", 50 | 99999, 51 | [ ] 52 | ] }, 53 | "line" : 4, 54 | "stack_locals" : [ ], 55 | "stdout" : "" 56 | }, 57 | { "event" : "step_line", 58 | "func_name" : "", 59 | "globals" : { "Outer" : [ "CLASS", 60 | "Outer", 61 | 99999, 62 | [ ] 63 | ], 64 | "o" : [ "INSTANCE", 65 | "Outer", 66 | 99999 67 | ] 68 | }, 69 | "line" : 5, 70 | "stack_locals" : [ ], 71 | "stdout" : "" 72 | }, 73 | { "event" : "step_line", 74 | "func_name" : "", 75 | "globals" : { "Outer" : [ "CLASS", 76 | "Outer", 77 | 99999, 78 | [ ] 79 | ], 80 | "o" : [ "INSTANCE", 81 | "Outer", 82 | 99999, 83 | [ "a", 84 | 5 85 | ] 86 | ] 87 | }, 88 | "line" : 6, 89 | "stack_locals" : [ ], 90 | "stdout" : "" 91 | }, 92 | { "event" : "step_line", 93 | "func_name" : "", 94 | "globals" : { "Outer" : [ "CLASS", 95 | "Outer", 96 | 99999, 97 | [ ] 98 | ], 99 | "o" : [ "INSTANCE", 100 | "Outer", 101 | 99999, 102 | [ "a", 103 | 5 104 | ], 105 | [ "b", 106 | "Hi" 107 | ] 108 | ] 109 | }, 110 | "line" : 7, 111 | "stack_locals" : [ ], 112 | "stdout" : "" 113 | }, 114 | { "event" : "return", 115 | "func_name" : "", 116 | "globals" : { "Outer" : [ "CLASS", 117 | "Outer", 118 | 99999, 119 | [ ] 120 | ], 121 | "o" : [ "INSTANCE", 122 | "Outer", 123 | 99999, 124 | [ "a", 125 | 5 126 | ], 127 | [ "b", 128 | "Hi" 129 | ] 130 | ] 131 | }, 132 | "line" : 7, 133 | "stack_locals" : [ ], 134 | "stdout" : "<__main__.Outer instance at 0x4c26c0>\n" 135 | } 136 | ] 137 | -------------------------------------------------------------------------------- /test-programs/class_test_2.py: -------------------------------------------------------------------------------- 1 | class Outer(): 2 | pass 3 | 4 | o = Outer() 5 | o.a = 5 6 | o.b = "Hi" 7 | print o 8 | 9 | -------------------------------------------------------------------------------- /test-programs/class_test_3.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "call", 9 | "func_name" : "Staff601", 10 | "globals" : { }, 11 | "line" : 1, 12 | "stack_locals" : [ [ "Staff601", 13 | { } 14 | ] ], 15 | "stdout" : "" 16 | }, 17 | { "event" : "step_line", 18 | "func_name" : "Staff601", 19 | "globals" : { }, 20 | "line" : 1, 21 | "stack_locals" : [ [ "Staff601", 22 | { } 23 | ] ], 24 | "stdout" : "" 25 | }, 26 | { "event" : "step_line", 27 | "func_name" : "Staff601", 28 | "globals" : { }, 29 | "line" : 2, 30 | "stack_locals" : [ [ "Staff601", 31 | { } 32 | ] ], 33 | "stdout" : "" 34 | }, 35 | { "event" : "step_line", 36 | "func_name" : "Staff601", 37 | "globals" : { }, 38 | "line" : 3, 39 | "stack_locals" : [ [ "Staff601", 40 | { "course" : "6.01" } 41 | ] ], 42 | "stdout" : "" 43 | }, 44 | { "event" : "step_line", 45 | "func_name" : "Staff601", 46 | "globals" : { }, 47 | "line" : 4, 48 | "stack_locals" : [ [ "Staff601", 49 | { "building" : 34, 50 | "course" : "6.01" 51 | } 52 | ] ], 53 | "stdout" : "" 54 | }, 55 | { "event" : "return", 56 | "func_name" : "Staff601", 57 | "globals" : { }, 58 | "line" : 4, 59 | "stack_locals" : [ [ "Staff601", 60 | { "__return__" : [ "DICT", 61 | 99999, 62 | [ "building", 63 | 34 64 | ], 65 | [ "course", 66 | "6.01" 67 | ], 68 | [ "room", 69 | 501 70 | ] 71 | ], 72 | "building" : 34, 73 | "course" : "6.01", 74 | "room" : 501 75 | } 76 | ] ], 77 | "stdout" : "" 78 | }, 79 | { "event" : "step_line", 80 | "func_name" : "", 81 | "globals" : { "Staff601" : [ "CLASS", 82 | "Staff601", 83 | 99999, 84 | [ ], 85 | [ "building", 86 | 34 87 | ], 88 | [ "course", 89 | "6.01" 90 | ], 91 | [ "room", 92 | 501 93 | ] 94 | ] }, 95 | "line" : 6, 96 | "stack_locals" : [ ], 97 | "stdout" : "" 98 | }, 99 | { "event" : "step_line", 100 | "func_name" : "", 101 | "globals" : { "Staff601" : [ "CLASS", 102 | "Staff601", 103 | 99999, 104 | [ ], 105 | [ "building", 106 | 34 107 | ], 108 | [ "course", 109 | "6.01" 110 | ], 111 | [ "room", 112 | 501 113 | ] 114 | ], 115 | "pat" : [ "INSTANCE", 116 | "Staff601", 117 | 99999 118 | ] 119 | }, 120 | "line" : 7, 121 | "stack_locals" : [ ], 122 | "stdout" : "" 123 | }, 124 | { "event" : "step_line", 125 | "func_name" : "", 126 | "globals" : { "Staff601" : [ "CLASS", 127 | "Staff601", 128 | 99999, 129 | [ ], 130 | [ "building", 131 | 34 132 | ], 133 | [ "course", 134 | "6.01" 135 | ], 136 | [ "room", 137 | 501 138 | ] 139 | ], 140 | "pat" : [ "INSTANCE", 141 | "Staff601", 142 | 99999 143 | ] 144 | }, 145 | "line" : 9, 146 | "stack_locals" : [ ], 147 | "stdout" : "6.01\n" 148 | }, 149 | { "event" : "step_line", 150 | "func_name" : "", 151 | "globals" : { "Staff601" : [ "CLASS", 152 | "Staff601", 153 | 99999, 154 | [ ], 155 | [ "building", 156 | 34 157 | ], 158 | [ "course", 159 | "6.01" 160 | ], 161 | [ "room", 162 | 501 163 | ] 164 | ], 165 | "pat" : [ "INSTANCE", 166 | "Staff601", 167 | 99999, 168 | [ "name", 169 | "Pat" 170 | ] 171 | ] 172 | }, 173 | "line" : 10, 174 | "stack_locals" : [ ], 175 | "stdout" : "6.01\n" 176 | }, 177 | { "event" : "step_line", 178 | "func_name" : "", 179 | "globals" : { "Staff601" : [ "CLASS", 180 | "Staff601", 181 | 99999, 182 | [ ], 183 | [ "building", 184 | 34 185 | ], 186 | [ "course", 187 | "6.01" 188 | ], 189 | [ "room", 190 | 501 191 | ] 192 | ], 193 | "pat" : [ "INSTANCE", 194 | "Staff601", 195 | 99999, 196 | [ "age", 197 | 60 198 | ], 199 | [ "name", 200 | "Pat" 201 | ] 202 | ] 203 | }, 204 | "line" : 11, 205 | "stack_locals" : [ ], 206 | "stdout" : "6.01\n" 207 | }, 208 | { "event" : "step_line", 209 | "func_name" : "", 210 | "globals" : { "Staff601" : [ "CLASS", 211 | "Staff601", 212 | 99999, 213 | [ ], 214 | [ "building", 215 | 34 216 | ], 217 | [ "course", 218 | "6.01" 219 | ], 220 | [ "room", 221 | 501 222 | ] 223 | ], 224 | "pat" : [ "INSTANCE", 225 | "Staff601", 226 | 99999, 227 | [ "age", 228 | 60 229 | ], 230 | [ "name", 231 | "Pat" 232 | ], 233 | [ "role", 234 | "Professor" 235 | ] 236 | ] 237 | }, 238 | "line" : 13, 239 | "stack_locals" : [ ], 240 | "stdout" : "6.01\n" 241 | }, 242 | { "event" : "step_line", 243 | "func_name" : "", 244 | "globals" : { "Staff601" : [ "CLASS", 245 | "Staff601", 246 | 99999, 247 | [ ], 248 | [ "building", 249 | 34 250 | ], 251 | [ "course", 252 | "6.01" 253 | ], 254 | [ "room", 255 | 501 256 | ] 257 | ], 258 | "pat" : [ "INSTANCE", 259 | "Staff601", 260 | 99999, 261 | [ "age", 262 | 60 263 | ], 264 | [ "name", 265 | "Pat" 266 | ], 267 | [ "role", 268 | "Professor" 269 | ] 270 | ] 271 | }, 272 | "line" : 14, 273 | "stack_locals" : [ ], 274 | "stdout" : "6.01\n34\n" 275 | }, 276 | { "event" : "step_line", 277 | "func_name" : "", 278 | "globals" : { "Staff601" : [ "CLASS", 279 | "Staff601", 280 | 99999, 281 | [ ], 282 | [ "building", 283 | 34 284 | ], 285 | [ "course", 286 | "6.01" 287 | ], 288 | [ "room", 289 | 501 290 | ] 291 | ], 292 | "pat" : [ "INSTANCE", 293 | "Staff601", 294 | 99999, 295 | [ "age", 296 | 60 297 | ], 298 | [ "building", 299 | 32 300 | ], 301 | [ "name", 302 | "Pat" 303 | ], 304 | [ "role", 305 | "Professor" 306 | ] 307 | ] 308 | }, 309 | "line" : 15, 310 | "stack_locals" : [ ], 311 | "stdout" : "6.01\n34\n" 312 | }, 313 | { "event" : "return", 314 | "func_name" : "", 315 | "globals" : { "Staff601" : [ "CLASS", 316 | "Staff601", 317 | 99999, 318 | [ ], 319 | [ "building", 320 | 34 321 | ], 322 | [ "course", 323 | "6.01" 324 | ], 325 | [ "room", 326 | 501 327 | ] 328 | ], 329 | "pat" : [ "INSTANCE", 330 | "Staff601", 331 | 99999, 332 | [ "age", 333 | 60 334 | ], 335 | [ "building", 336 | 32 337 | ], 338 | [ "name", 339 | "Pat" 340 | ], 341 | [ "role", 342 | "Professor" 343 | ] 344 | ] 345 | }, 346 | "line" : 15, 347 | "stack_locals" : [ ], 348 | "stdout" : "6.01\n34\n32\n" 349 | } 350 | ] 351 | -------------------------------------------------------------------------------- /test-programs/class_test_3.py: -------------------------------------------------------------------------------- 1 | class Staff601: 2 | course = '6.01' 3 | building = 34 4 | room = 501 5 | 6 | pat = Staff601() 7 | print pat.course 8 | 9 | pat.name = 'Pat' 10 | pat.age = 60 11 | pat.role = 'Professor' 12 | 13 | print pat.building 14 | pat.building = 32 15 | print pat.building 16 | 17 | -------------------------------------------------------------------------------- /test-programs/data_test.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "step_line", 9 | "func_name" : "", 10 | "globals" : { "x" : [ "TUPLE", 11 | 99999, 12 | "hello", 13 | "world", 14 | 1, 15 | 2, 16 | 3, 17 | "goodbye" 18 | ] }, 19 | "line" : 2, 20 | "stack_locals" : [ ], 21 | "stdout" : "" 22 | }, 23 | { "event" : "step_line", 24 | "func_name" : "", 25 | "globals" : { "x" : [ "TUPLE", 26 | 99999, 27 | "hello", 28 | "world", 29 | 1, 30 | 2, 31 | 3, 32 | "goodbye" 33 | ], 34 | "y" : [ "LIST", 35 | 99999, 36 | "hello", 37 | "world", 38 | 1, 39 | 2, 40 | 3, 41 | "goodbye" 42 | ] 43 | }, 44 | "line" : 3, 45 | "stack_locals" : [ ], 46 | "stdout" : "" 47 | }, 48 | { "event" : "step_line", 49 | "func_name" : "", 50 | "globals" : { "x" : [ "TUPLE", 51 | 99999, 52 | "hello", 53 | "world", 54 | 1, 55 | 2, 56 | 3, 57 | "goodbye" 58 | ], 59 | "y" : [ "LIST", 60 | 99999, 61 | "hello", 62 | "world", 63 | 1, 64 | 2, 65 | 3, 66 | "goodbye" 67 | ], 68 | "z" : [ "SET", 69 | 99999, 70 | 1, 71 | 2, 72 | 3, 73 | "goodbye", 74 | "world", 75 | "hello" 76 | ] 77 | }, 78 | "line" : 4, 79 | "stack_locals" : [ ], 80 | "stdout" : "" 81 | }, 82 | { "event" : "return", 83 | "func_name" : "", 84 | "globals" : { "w" : [ "DICT", 85 | 99999, 86 | [ "mindy", 87 | 6 88 | ], 89 | [ "joe", 90 | 5 91 | ], 92 | [ "jack", 93 | 7 94 | ] 95 | ], 96 | "x" : [ "TUPLE", 97 | 99999, 98 | "hello", 99 | "world", 100 | 1, 101 | 2, 102 | 3, 103 | "goodbye" 104 | ], 105 | "y" : [ "LIST", 106 | 99999, 107 | "hello", 108 | "world", 109 | 1, 110 | 2, 111 | 3, 112 | "goodbye" 113 | ], 114 | "z" : [ "SET", 115 | 99999, 116 | 1, 117 | 2, 118 | 3, 119 | "goodbye", 120 | "world", 121 | "hello" 122 | ] 123 | }, 124 | "line" : 4, 125 | "stack_locals" : [ ], 126 | "stdout" : "" 127 | } 128 | ] 129 | -------------------------------------------------------------------------------- /test-programs/data_test.py: -------------------------------------------------------------------------------- 1 | x = ('hello', 'world', 1, 2, 3, 'goodbye') 2 | y = list(x) 3 | z = set(x) 4 | w = {"joe" : 5, "mindy" : 6, "jack" : 7} 5 | -------------------------------------------------------------------------------- /test-programs/dict_error.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "step_line", 9 | "func_name" : "", 10 | "globals" : { "foo" : [ "function", 11 | 99999, 12 | "" 13 | ] }, 14 | "line" : 4, 15 | "stack_locals" : [ ], 16 | "stdout" : "" 17 | }, 18 | { "event" : "call", 19 | "func_name" : "foo", 20 | "globals" : { "foo" : [ "function", 21 | 99999, 22 | "" 23 | ] }, 24 | "line" : 1, 25 | "stack_locals" : [ [ "foo", 26 | { } 27 | ] ], 28 | "stdout" : "" 29 | }, 30 | { "event" : "step_line", 31 | "func_name" : "foo", 32 | "globals" : { "foo" : [ "function", 33 | 99999, 34 | "" 35 | ] }, 36 | "line" : 2, 37 | "stack_locals" : [ [ "foo", 38 | { } 39 | ] ], 40 | "stdout" : "" 41 | }, 42 | { "event" : "exception", 43 | "exception_msg" : "NameError: global name 'local_y' is not defined", 44 | "func_name" : "foo", 45 | "globals" : { "foo" : [ "function", 46 | 99999, 47 | "" 48 | ] }, 49 | "line" : 2, 50 | "stack_locals" : [ [ "foo", 51 | { } 52 | ] ], 53 | "stdout" : "" 54 | }, 55 | { "event" : "return", 56 | "func_name" : "foo", 57 | "globals" : { "foo" : [ "function", 58 | 99999, 59 | "" 60 | ] }, 61 | "line" : 2, 62 | "stack_locals" : [ [ "foo", 63 | { "__return__" : null } 64 | ] ], 65 | "stdout" : "" 66 | }, 67 | { "event" : "exception", 68 | "exception_msg" : "NameError: global name 'local_y' is not defined", 69 | "func_name" : "", 70 | "globals" : { "foo" : [ "function", 71 | 99999, 72 | "" 73 | ] }, 74 | "line" : 4, 75 | "stack_locals" : [ ], 76 | "stdout" : "" 77 | } 78 | ] 79 | -------------------------------------------------------------------------------- /test-programs/dict_error.py: -------------------------------------------------------------------------------- 1 | def foo(): 2 | local_y[('tup', 'le')] = set([1, 2, 3]) 3 | 4 | foo() 5 | -------------------------------------------------------------------------------- /test-programs/dict_test.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "step_line", 9 | "func_name" : "", 10 | "globals" : { "x" : [ "DICT", 11 | 99999, 12 | [ 1, 13 | 2 14 | ] 15 | ] }, 16 | "line" : 2, 17 | "stack_locals" : [ ], 18 | "stdout" : "" 19 | }, 20 | { "event" : "step_line", 21 | "func_name" : "", 22 | "globals" : { "x" : [ "DICT", 23 | 99999, 24 | [ 1, 25 | 2 26 | ], 27 | [ [ "TUPLE", 28 | 99999, 29 | "tup", 30 | "le" 31 | ], 32 | [ "SET", 33 | 99999, 34 | 1, 35 | 2, 36 | 3 37 | ] 38 | ] 39 | ] }, 40 | "line" : 4, 41 | "stack_locals" : [ ], 42 | "stdout" : "" 43 | }, 44 | { "event" : "step_line", 45 | "func_name" : "", 46 | "globals" : { "foo" : [ "function", 47 | 99999, 48 | "" 49 | ], 50 | "x" : [ "DICT", 51 | 99999, 52 | [ 1, 53 | 2 54 | ], 55 | [ [ "TUPLE", 56 | 99999, 57 | "tup", 58 | "le" 59 | ], 60 | [ "SET", 61 | 99999, 62 | 1, 63 | 2, 64 | 3 65 | ] 66 | ] 67 | ] 68 | }, 69 | "line" : 10, 70 | "stack_locals" : [ ], 71 | "stdout" : "" 72 | }, 73 | { "event" : "call", 74 | "func_name" : "foo", 75 | "globals" : { "foo" : [ "function", 76 | 99999, 77 | "" 78 | ], 79 | "x" : [ "DICT", 80 | 99999, 81 | [ 1, 82 | 2 83 | ], 84 | [ [ "TUPLE", 85 | 99999, 86 | "tup", 87 | "le" 88 | ], 89 | [ "SET", 90 | 99999, 91 | 1, 92 | 2, 93 | 3 94 | ] 95 | ] 96 | ] 97 | }, 98 | "line" : 4, 99 | "stack_locals" : [ [ "foo", 100 | { } 101 | ] ], 102 | "stdout" : "" 103 | }, 104 | { "event" : "step_line", 105 | "func_name" : "foo", 106 | "globals" : { "foo" : [ "function", 107 | 99999, 108 | "" 109 | ], 110 | "x" : [ "DICT", 111 | 99999, 112 | [ 1, 113 | 2 114 | ], 115 | [ [ "TUPLE", 116 | 99999, 117 | "tup", 118 | "le" 119 | ], 120 | [ "SET", 121 | 99999, 122 | 1, 123 | 2, 124 | 3 125 | ] 126 | ] 127 | ] 128 | }, 129 | "line" : 5, 130 | "stack_locals" : [ [ "foo", 131 | { } 132 | ] ], 133 | "stdout" : "" 134 | }, 135 | { "event" : "step_line", 136 | "func_name" : "foo", 137 | "globals" : { "foo" : [ "function", 138 | 99999, 139 | "" 140 | ], 141 | "x" : [ "DICT", 142 | 99999, 143 | [ 1, 144 | 2 145 | ], 146 | [ [ "TUPLE", 147 | 99999, 148 | "tup", 149 | "le" 150 | ], 151 | [ "SET", 152 | 99999, 153 | 1, 154 | 2, 155 | 3 156 | ] 157 | ] 158 | ] 159 | }, 160 | "line" : 6, 161 | "stack_locals" : [ [ "foo", 162 | { "local_x" : [ "DICT", 163 | 99999, 164 | [ 1, 165 | 2 166 | ] 167 | ] } 168 | ] ], 169 | "stdout" : "" 170 | }, 171 | { "event" : "step_line", 172 | "func_name" : "foo", 173 | "globals" : { "foo" : [ "function", 174 | 99999, 175 | "" 176 | ], 177 | "x" : [ "DICT", 178 | 99999, 179 | [ 1, 180 | 2 181 | ], 182 | [ [ "TUPLE", 183 | 99999, 184 | "tup", 185 | "le" 186 | ], 187 | [ "SET", 188 | 99999, 189 | 1, 190 | 2, 191 | 3 192 | ] 193 | ] 194 | ] 195 | }, 196 | "line" : 7, 197 | "stack_locals" : [ [ "foo", 198 | { "local_x" : [ "DICT", 199 | 99999, 200 | [ 1, 201 | 2 202 | ] 203 | ], 204 | "local_y" : [ "DICT", 205 | 99999 206 | ] 207 | } 208 | ] ], 209 | "stdout" : "" 210 | }, 211 | { "event" : "step_line", 212 | "func_name" : "foo", 213 | "globals" : { "foo" : [ "function", 214 | 99999, 215 | "" 216 | ], 217 | "x" : [ "DICT", 218 | 99999, 219 | [ 1, 220 | 2 221 | ], 222 | [ [ "TUPLE", 223 | 99999, 224 | "tup", 225 | "le" 226 | ], 227 | [ "SET", 228 | 99999, 229 | 1, 230 | 2, 231 | 3 232 | ] 233 | ] 234 | ] 235 | }, 236 | "line" : 8, 237 | "stack_locals" : [ [ "foo", 238 | { "local_x" : [ "DICT", 239 | 99999, 240 | [ 1, 241 | 2 242 | ] 243 | ], 244 | "local_y" : [ "DICT", 245 | 99999, 246 | [ [ "TUPLE", 247 | 99999, 248 | "tup", 249 | "le" 250 | ], 251 | [ "SET", 252 | 99999, 253 | 1, 254 | 2, 255 | 3 256 | ] 257 | ] 258 | ] 259 | } 260 | ] ], 261 | "stdout" : "" 262 | }, 263 | { "event" : "return", 264 | "func_name" : "foo", 265 | "globals" : { "foo" : [ "function", 266 | 99999, 267 | "" 268 | ], 269 | "x" : [ "DICT", 270 | 99999, 271 | [ 1, 272 | 2 273 | ], 274 | [ [ "TUPLE", 275 | 99999, 276 | "tup", 277 | "le" 278 | ], 279 | [ "SET", 280 | 99999, 281 | 1, 282 | 2, 283 | 3 284 | ] 285 | ] 286 | ] 287 | }, 288 | "line" : 8, 289 | "stack_locals" : [ [ "foo", 290 | { "__return__" : null, 291 | "local_x" : [ "DICT", 292 | 99999, 293 | [ 1, 294 | 2 295 | ] 296 | ], 297 | "local_y" : [ "DICT", 298 | 99999, 299 | [ [ "TUPLE", 300 | 99999, 301 | "tup", 302 | "le" 303 | ], 304 | [ "SET", 305 | 99999, 306 | 1, 307 | 2, 308 | 3 309 | ] 310 | ] 311 | ] 312 | } 313 | ] ], 314 | "stdout" : "hello [set([1, 2, 3])]\n" 315 | }, 316 | { "event" : "return", 317 | "func_name" : "", 318 | "globals" : { "foo" : [ "function", 319 | 99999, 320 | "" 321 | ], 322 | "x" : [ "DICT", 323 | 99999, 324 | [ 1, 325 | 2 326 | ], 327 | [ [ "TUPLE", 328 | 99999, 329 | "tup", 330 | "le" 331 | ], 332 | [ "SET", 333 | 99999, 334 | 1, 335 | 2, 336 | 3 337 | ] 338 | ] 339 | ] 340 | }, 341 | "line" : 10, 342 | "stack_locals" : [ ], 343 | "stdout" : "hello [set([1, 2, 3])]\n" 344 | } 345 | ] 346 | -------------------------------------------------------------------------------- /test-programs/dict_test.py: -------------------------------------------------------------------------------- 1 | x = {1 : 2} 2 | x[('tup', 'le')] = set([1, 2, 3]) 3 | 4 | def foo(): 5 | local_x = {1 : 2} 6 | local_y = {} 7 | local_y[('tup', 'le')] = set([1, 2, 3]) 8 | print "hello", local_y.values() 9 | 10 | foo() 11 | -------------------------------------------------------------------------------- /test-programs/exec_test.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "call", 9 | "func_name" : "", 10 | "globals" : { }, 11 | "line" : 1, 12 | "stack_locals" : [ ], 13 | "stdout" : "" 14 | }, 15 | { "event" : "step_line", 16 | "func_name" : "", 17 | "globals" : { }, 18 | "line" : 1, 19 | "stack_locals" : [ ], 20 | "stdout" : "" 21 | }, 22 | { "event" : "exception", 23 | "exception_msg" : "ImportError: __import__ not found", 24 | "func_name" : "", 25 | "globals" : { }, 26 | "line" : 1, 27 | "stack_locals" : [ ], 28 | "stdout" : "" 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /test-programs/exec_test.py: -------------------------------------------------------------------------------- 1 | exec "import os; os.system('echo security breach')" 2 | -------------------------------------------------------------------------------- /test-programs/func_exception.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "step_line", 9 | "func_name" : "", 10 | "globals" : { "g" : [ "function", 11 | 99999, 12 | "" 13 | ] }, 14 | "line" : 6, 15 | "stack_locals" : [ ], 16 | "stdout" : "" 17 | }, 18 | { "event" : "call", 19 | "func_name" : "g", 20 | "globals" : { "g" : [ "function", 21 | 99999, 22 | "" 23 | ] }, 24 | "line" : 1, 25 | "stack_locals" : [ [ "g", 26 | { "x" : 5, 27 | "y" : 0 28 | } 29 | ] ], 30 | "stdout" : "" 31 | }, 32 | { "event" : "step_line", 33 | "func_name" : "g", 34 | "globals" : { "g" : [ "function", 35 | 99999, 36 | "" 37 | ] }, 38 | "line" : 2, 39 | "stack_locals" : [ [ "g", 40 | { "x" : 5, 41 | "y" : 0 42 | } 43 | ] ], 44 | "stdout" : "" 45 | }, 46 | { "event" : "step_line", 47 | "func_name" : "g", 48 | "globals" : { "g" : [ "function", 49 | 99999, 50 | "" 51 | ] }, 52 | "line" : 3, 53 | "stack_locals" : [ [ "g", 54 | { "x" : 5, 55 | "y" : 0 56 | } 57 | ] ], 58 | "stdout" : "In g\n" 59 | }, 60 | { "event" : "exception", 61 | "exception_msg" : "ZeroDivisionError: integer division or modulo by zero", 62 | "func_name" : "g", 63 | "globals" : { "g" : [ "function", 64 | 99999, 65 | "" 66 | ] }, 67 | "line" : 3, 68 | "stack_locals" : [ [ "g", 69 | { "x" : 5, 70 | "y" : 0 71 | } 72 | ] ], 73 | "stdout" : "In g\n" 74 | }, 75 | { "event" : "return", 76 | "func_name" : "g", 77 | "globals" : { "g" : [ "function", 78 | 99999, 79 | "" 80 | ] }, 81 | "line" : 3, 82 | "stack_locals" : [ [ "g", 83 | { "__return__" : null, 84 | "x" : 5, 85 | "y" : 0 86 | } 87 | ] ], 88 | "stdout" : "In g\n" 89 | }, 90 | { "event" : "exception", 91 | "exception_msg" : "ZeroDivisionError: integer division or modulo by zero", 92 | "func_name" : "", 93 | "globals" : { "g" : [ "function", 94 | 99999, 95 | "" 96 | ] }, 97 | "line" : 6, 98 | "stack_locals" : [ ], 99 | "stdout" : "In g\n" 100 | } 101 | ] 102 | -------------------------------------------------------------------------------- /test-programs/func_exception.py: -------------------------------------------------------------------------------- 1 | def g(x,y): 2 | print("In g") 3 | ans = x/y 4 | return ans 5 | 6 | g(5, 0) 7 | 8 | -------------------------------------------------------------------------------- /test-programs/generator_test.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "step_line", 9 | "func_name" : "", 10 | "globals" : { "x" : [ "generator", 11 | 99999, 12 | " at 0x4ba580>" 13 | ] }, 14 | "line" : 2, 15 | "stack_locals" : [ ], 16 | "stdout" : "" 17 | }, 18 | { "event" : "step_line", 19 | "func_name" : "", 20 | "globals" : { "x" : [ "generator", 21 | 99999, 22 | " at 0x4ba580>" 23 | ], 24 | "y" : [ "generator", 25 | 99999, 26 | " at 0x4ba580>" 27 | ] 28 | }, 29 | "line" : 3, 30 | "stack_locals" : [ ], 31 | "stdout" : "" 32 | }, 33 | { "event" : "return", 34 | "func_name" : "", 35 | "globals" : { "x" : [ "generator", 36 | 99999, 37 | " at 0x4ba580>" 38 | ], 39 | "y" : [ "generator", 40 | 99999, 41 | " at 0x4ba580>" 42 | ], 43 | "z" : [ "generator", 44 | 99999, 45 | " at 0x4baaa8>" 46 | ] 47 | }, 48 | "line" : 3, 49 | "stack_locals" : [ ], 50 | "stdout" : "" 51 | } 52 | ] 53 | -------------------------------------------------------------------------------- /test-programs/generator_test.py: -------------------------------------------------------------------------------- 1 | x = (e for e in range(10)) 2 | y = x 3 | z = (e for e in range(10)) 4 | -------------------------------------------------------------------------------- /test-programs/import_error.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 2, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "exception", 9 | "exception_msg" : "ImportError: __import__ not found", 10 | "func_name" : "", 11 | "globals" : { }, 12 | "line" : 2, 13 | "stack_locals" : [ ], 14 | "stdout" : "" 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /test-programs/import_error.py: -------------------------------------------------------------------------------- 1 | # should NOT allow for any imports 2 | import os 3 | 4 | os.system("echo security breach") 5 | -------------------------------------------------------------------------------- /test-programs/infinite_loop.py: -------------------------------------------------------------------------------- 1 | # Fibonacci!!! 2 | 3 | arr = [1, 1] 4 | 5 | print arr[0] 6 | 7 | while True: 8 | print arr[-1] 9 | tmp = sum(arr) 10 | arr.append(tmp) 11 | del arr[0] 12 | 13 | -------------------------------------------------------------------------------- /test-programs/infinite_loop_one_liner.py: -------------------------------------------------------------------------------- 1 | while 1: print "hahahaha" 2 | -------------------------------------------------------------------------------- /test-programs/lambda_1.py: -------------------------------------------------------------------------------- 1 | def summation(low, high, f, next): 2 | s = 0 3 | x = low 4 | while x <= high: 5 | s = s + f(x) 6 | x = next(x) 7 | return s 8 | 9 | def sumsquares(low, high): 10 | return summation(low, high, lambda x: x**2, lambda x: x+1) 11 | 12 | print sumsquares(1, 5) 13 | -------------------------------------------------------------------------------- /test-programs/list_dict_test.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "step_line", 9 | "func_name" : "", 10 | "globals" : { "x" : [ "DICT", 11 | 99999 12 | ] }, 13 | "line" : 2, 14 | "stack_locals" : [ ], 15 | "stdout" : "" 16 | }, 17 | { "event" : "step_line", 18 | "func_name" : "", 19 | "globals" : { "l" : [ "LIST", 20 | 99999, 21 | "hello", 22 | "world", 23 | "goodbye" 24 | ], 25 | "x" : [ "DICT", 26 | 99999 27 | ] 28 | }, 29 | "line" : 3, 30 | "stack_locals" : [ ], 31 | "stdout" : "" 32 | }, 33 | { "event" : "step_line", 34 | "func_name" : "", 35 | "globals" : { "e" : "hello", 36 | "i" : 0, 37 | "l" : [ "LIST", 38 | 99999, 39 | "hello", 40 | "world", 41 | "goodbye" 42 | ], 43 | "x" : [ "DICT", 44 | 99999 45 | ] 46 | }, 47 | "line" : 4, 48 | "stack_locals" : [ ], 49 | "stdout" : "" 50 | }, 51 | { "event" : "step_line", 52 | "func_name" : "", 53 | "globals" : { "e" : "hello", 54 | "i" : 0, 55 | "l" : [ "LIST", 56 | 99999, 57 | "hello", 58 | "world", 59 | "goodbye" 60 | ], 61 | "x" : [ "DICT", 62 | 99999, 63 | [ "hello", 64 | 0 65 | ] 66 | ] 67 | }, 68 | "line" : 3, 69 | "stack_locals" : [ ], 70 | "stdout" : "" 71 | }, 72 | { "event" : "step_line", 73 | "func_name" : "", 74 | "globals" : { "e" : "world", 75 | "i" : 1, 76 | "l" : [ "LIST", 77 | 99999, 78 | "hello", 79 | "world", 80 | "goodbye" 81 | ], 82 | "x" : [ "DICT", 83 | 99999, 84 | [ "hello", 85 | 0 86 | ] 87 | ] 88 | }, 89 | "line" : 4, 90 | "stack_locals" : [ ], 91 | "stdout" : "" 92 | }, 93 | { "event" : "step_line", 94 | "func_name" : "", 95 | "globals" : { "e" : "world", 96 | "i" : 1, 97 | "l" : [ "LIST", 98 | 99999, 99 | "hello", 100 | "world", 101 | "goodbye" 102 | ], 103 | "x" : [ "DICT", 104 | 99999, 105 | [ "world", 106 | 1 107 | ], 108 | [ "hello", 109 | 0 110 | ] 111 | ] 112 | }, 113 | "line" : 3, 114 | "stack_locals" : [ ], 115 | "stdout" : "" 116 | }, 117 | { "event" : "step_line", 118 | "func_name" : "", 119 | "globals" : { "e" : "goodbye", 120 | "i" : 2, 121 | "l" : [ "LIST", 122 | 99999, 123 | "hello", 124 | "world", 125 | "goodbye" 126 | ], 127 | "x" : [ "DICT", 128 | 99999, 129 | [ "world", 130 | 1 131 | ], 132 | [ "hello", 133 | 0 134 | ] 135 | ] 136 | }, 137 | "line" : 4, 138 | "stack_locals" : [ ], 139 | "stdout" : "" 140 | }, 141 | { "event" : "step_line", 142 | "func_name" : "", 143 | "globals" : { "e" : "goodbye", 144 | "i" : 2, 145 | "l" : [ "LIST", 146 | 99999, 147 | "hello", 148 | "world", 149 | "goodbye" 150 | ], 151 | "x" : [ "DICT", 152 | 99999, 153 | [ "world", 154 | 1 155 | ], 156 | [ "hello", 157 | 0 158 | ], 159 | [ "goodbye", 160 | 2 161 | ] 162 | ] 163 | }, 164 | "line" : 3, 165 | "stack_locals" : [ ], 166 | "stdout" : "" 167 | }, 168 | { "event" : "step_line", 169 | "func_name" : "", 170 | "globals" : { "e" : "goodbye", 171 | "i" : 2, 172 | "l" : [ "LIST", 173 | 99999, 174 | "hello", 175 | "world", 176 | "goodbye" 177 | ], 178 | "x" : [ "DICT", 179 | 99999, 180 | [ "world", 181 | 1 182 | ], 183 | [ "hello", 184 | 0 185 | ], 186 | [ "goodbye", 187 | 2 188 | ] 189 | ] 190 | }, 191 | "line" : 5, 192 | "stack_locals" : [ ], 193 | "stdout" : "" 194 | }, 195 | { "event" : "return", 196 | "func_name" : "", 197 | "globals" : { "e" : "goodbye", 198 | "i" : 2, 199 | "l" : [ "LIST", 200 | 99999, 201 | "hello", 202 | "world", 203 | "goodbye" 204 | ], 205 | "x" : [ "DICT", 206 | 99999, 207 | [ "world", 208 | 1 209 | ], 210 | [ "hello", 211 | 0 212 | ], 213 | [ "goodbye", 214 | 2 215 | ] 216 | ] 217 | }, 218 | "line" : 5, 219 | "stack_locals" : [ ], 220 | "stdout" : "{'world': 1, 'hello': 0, 'goodbye': 2}\n" 221 | } 222 | ] 223 | -------------------------------------------------------------------------------- /test-programs/list_dict_test.py: -------------------------------------------------------------------------------- 1 | x = {} 2 | l = ['hello', "world", 'goodbye'] 3 | for (i, e) in enumerate(l): 4 | x[e] = i 5 | print x 6 | 7 | -------------------------------------------------------------------------------- /test-programs/list_test.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "return", 9 | "func_name" : "", 10 | "globals" : { "x" : [ "LIST", 11 | 99999, 12 | 1, 13 | 2, 14 | "hello", 15 | [ "TUPLE", 16 | 99999, 17 | 3, 18 | 4 19 | ] 20 | ] }, 21 | "line" : 1, 22 | "stack_locals" : [ ], 23 | "stdout" : "" 24 | } 25 | ] 26 | -------------------------------------------------------------------------------- /test-programs/list_test.py: -------------------------------------------------------------------------------- 1 | x = [1, 2, "hello", (3, 4)] 2 | -------------------------------------------------------------------------------- /test-programs/newstyle_class.py: -------------------------------------------------------------------------------- 1 | class A(object): 2 | bla = "A" 3 | def __init__(self): 4 | self.blb = "B" 5 | 6 | def x(self): 7 | self.bla = self.blb 8 | 9 | a = A() 10 | 11 | a.x() 12 | 13 | print a.bla 14 | print A.bla 15 | 16 | -------------------------------------------------------------------------------- /test-programs/one_func.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "step_line", 9 | "func_name" : "", 10 | "globals" : { "add" : [ "function", 11 | 99999, 12 | "" 13 | ] }, 14 | "line" : 5, 15 | "stack_locals" : [ ], 16 | "stdout" : "" 17 | }, 18 | { "event" : "step_line", 19 | "func_name" : "", 20 | "globals" : { "add" : [ "function", 21 | 99999, 22 | "" 23 | ], 24 | "x" : 5 25 | }, 26 | "line" : 6, 27 | "stack_locals" : [ ], 28 | "stdout" : "" 29 | }, 30 | { "event" : "step_line", 31 | "func_name" : "", 32 | "globals" : { "add" : [ "function", 33 | 99999, 34 | "" 35 | ], 36 | "x" : 5, 37 | "y" : 10 38 | }, 39 | "line" : 7, 40 | "stack_locals" : [ ], 41 | "stdout" : "" 42 | }, 43 | { "event" : "step_line", 44 | "func_name" : "", 45 | "globals" : { "add" : [ "function", 46 | 99999, 47 | "" 48 | ], 49 | "x" : 5, 50 | "y" : 10, 51 | "z" : 50 52 | }, 53 | "line" : 8, 54 | "stack_locals" : [ ], 55 | "stdout" : "" 56 | }, 57 | { "event" : "call", 58 | "func_name" : "add", 59 | "globals" : { "add" : [ "function", 60 | 99999, 61 | "" 62 | ], 63 | "x" : 5, 64 | "y" : 10, 65 | "z" : 50 66 | }, 67 | "line" : 1, 68 | "stack_locals" : [ [ "add", 69 | { "a" : 5, 70 | "b" : 10, 71 | "c" : 50 72 | } 73 | ] ], 74 | "stdout" : "" 75 | }, 76 | { "event" : "step_line", 77 | "func_name" : "add", 78 | "globals" : { "add" : [ "function", 79 | 99999, 80 | "" 81 | ], 82 | "x" : 5, 83 | "y" : 10, 84 | "z" : 50 85 | }, 86 | "line" : 2, 87 | "stack_locals" : [ [ "add", 88 | { "a" : 5, 89 | "b" : 10, 90 | "c" : 50 91 | } 92 | ] ], 93 | "stdout" : "" 94 | }, 95 | { "event" : "step_line", 96 | "func_name" : "add", 97 | "globals" : { "add" : [ "function", 98 | 99999, 99 | "" 100 | ], 101 | "x" : 5, 102 | "y" : 10, 103 | "z" : 50 104 | }, 105 | "line" : 3, 106 | "stack_locals" : [ [ "add", 107 | { "a" : 5, 108 | "b" : 10, 109 | "c" : 50, 110 | "d" : 15 111 | } 112 | ] ], 113 | "stdout" : "" 114 | }, 115 | { "event" : "return", 116 | "func_name" : "add", 117 | "globals" : { "add" : [ "function", 118 | 99999, 119 | "" 120 | ], 121 | "x" : 5, 122 | "y" : 10, 123 | "z" : 50 124 | }, 125 | "line" : 3, 126 | "stack_locals" : [ [ "add", 127 | { "__return__" : 65, 128 | "a" : 5, 129 | "b" : 10, 130 | "c" : 50, 131 | "d" : 15 132 | } 133 | ] ], 134 | "stdout" : "" 135 | }, 136 | { "event" : "return", 137 | "func_name" : "", 138 | "globals" : { "add" : [ "function", 139 | 99999, 140 | "" 141 | ], 142 | "x" : 5, 143 | "y" : 10, 144 | "z" : 50 145 | }, 146 | "line" : 8, 147 | "stack_locals" : [ ], 148 | "stdout" : "65\n" 149 | } 150 | ] 151 | -------------------------------------------------------------------------------- /test-programs/one_func.py: -------------------------------------------------------------------------------- 1 | def add(a, b, c): 2 | d = a + b 3 | return c + d 4 | 5 | x = 5 6 | y = 10 7 | z = x * y 8 | print add(x, y, z) 9 | -------------------------------------------------------------------------------- /test-programs/open_error.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "exception", 9 | "exception_msg" : "NameError: name 'open' is not defined", 10 | "func_name" : "", 11 | "globals" : { }, 12 | "line" : 1, 13 | "stack_locals" : [ ], 14 | "stdout" : "" 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /test-programs/open_error.py: -------------------------------------------------------------------------------- 1 | for line in open("/etc/passwd"): 2 | print line 3 | 4 | -------------------------------------------------------------------------------- /test-programs/parse_error.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "uncaught_exception", 2 | "exception_msg" : "Error: unexpected indent", 3 | "line" : 4, 4 | "offset" : 3 5 | } ] 6 | -------------------------------------------------------------------------------- /test-programs/parse_error.py: -------------------------------------------------------------------------------- 1 | x = 0 2 | for i in range(10): 3 | x += 1 4 | print x 5 | x += 1 6 | 7 | -------------------------------------------------------------------------------- /test-programs/parse_error_2.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "uncaught_exception", 2 | "exception_msg" : "Error: invalid syntax", 3 | "line" : 5, 4 | "offset" : 11 5 | } ] 6 | -------------------------------------------------------------------------------- /test-programs/parse_error_2.py: -------------------------------------------------------------------------------- 1 | x = 5 2 | y = x 3 | z = x + y 4 | 5 | for x haslk;fjlasfhlkjl;sa 6 | -------------------------------------------------------------------------------- /test-programs/parse_error_3.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "uncaught_exception", 2 | "exception_msg" : "Error: unindent does not match any outer indentation level", 3 | "line" : 6, 4 | "offset" : 11 5 | } ] 6 | -------------------------------------------------------------------------------- /test-programs/parse_error_3.py: -------------------------------------------------------------------------------- 1 | x = [] 2 | for i in range(10): 3 | x.append(i) 4 | if i == 24: 5 | pass 6 | print y 7 | -------------------------------------------------------------------------------- /test-programs/print_builtins_error.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "uncaught_exception", 9 | "exception_msg" : "Unknown error" 10 | } 11 | ] 12 | -------------------------------------------------------------------------------- /test-programs/print_builtins_error.py: -------------------------------------------------------------------------------- 1 | print __builtins__ 2 | -------------------------------------------------------------------------------- /test-programs/runtime_error.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "step_line", 9 | "func_name" : "", 10 | "globals" : { "x" : 5 }, 11 | "line" : 2, 12 | "stack_locals" : [ ], 13 | "stdout" : "" 14 | }, 15 | { "event" : "step_line", 16 | "func_name" : "", 17 | "globals" : { "i" : 0, 18 | "x" : 5 19 | }, 20 | "line" : 3, 21 | "stack_locals" : [ ], 22 | "stdout" : "" 23 | }, 24 | { "event" : "step_line", 25 | "func_name" : "", 26 | "globals" : { "i" : 0, 27 | "x" : 5 28 | }, 29 | "line" : 6, 30 | "stack_locals" : [ ], 31 | "stdout" : "" 32 | }, 33 | { "event" : "step_line", 34 | "func_name" : "", 35 | "globals" : { "i" : 0, 36 | "x" : 5, 37 | "z" : 0 38 | }, 39 | "line" : 7, 40 | "stack_locals" : [ ], 41 | "stdout" : "" 42 | }, 43 | { "event" : "step_line", 44 | "func_name" : "", 45 | "globals" : { "i" : 0, 46 | "x" : 5, 47 | "z" : 0 48 | }, 49 | "line" : 2, 50 | "stack_locals" : [ ], 51 | "stdout" : "0\n" 52 | }, 53 | { "event" : "step_line", 54 | "func_name" : "", 55 | "globals" : { "i" : 1, 56 | "x" : 5, 57 | "z" : 0 58 | }, 59 | "line" : 3, 60 | "stack_locals" : [ ], 61 | "stdout" : "0\n" 62 | }, 63 | { "event" : "step_line", 64 | "func_name" : "", 65 | "globals" : { "i" : 1, 66 | "x" : 5, 67 | "z" : 0 68 | }, 69 | "line" : 6, 70 | "stack_locals" : [ ], 71 | "stdout" : "0\n" 72 | }, 73 | { "event" : "step_line", 74 | "func_name" : "", 75 | "globals" : { "i" : 1, 76 | "x" : 5, 77 | "z" : 1 78 | }, 79 | "line" : 7, 80 | "stack_locals" : [ ], 81 | "stdout" : "0\n" 82 | }, 83 | { "event" : "step_line", 84 | "func_name" : "", 85 | "globals" : { "i" : 1, 86 | "x" : 5, 87 | "z" : 1 88 | }, 89 | "line" : 2, 90 | "stack_locals" : [ ], 91 | "stdout" : "0\n1\n" 92 | }, 93 | { "event" : "step_line", 94 | "func_name" : "", 95 | "globals" : { "i" : 2, 96 | "x" : 5, 97 | "z" : 1 98 | }, 99 | "line" : 3, 100 | "stack_locals" : [ ], 101 | "stdout" : "0\n1\n" 102 | }, 103 | { "event" : "step_line", 104 | "func_name" : "", 105 | "globals" : { "i" : 2, 106 | "x" : 5, 107 | "z" : 1 108 | }, 109 | "line" : 6, 110 | "stack_locals" : [ ], 111 | "stdout" : "0\n1\n" 112 | }, 113 | { "event" : "step_line", 114 | "func_name" : "", 115 | "globals" : { "i" : 2, 116 | "x" : 5, 117 | "z" : 2 118 | }, 119 | "line" : 7, 120 | "stack_locals" : [ ], 121 | "stdout" : "0\n1\n" 122 | }, 123 | { "event" : "step_line", 124 | "func_name" : "", 125 | "globals" : { "i" : 2, 126 | "x" : 5, 127 | "z" : 2 128 | }, 129 | "line" : 2, 130 | "stack_locals" : [ ], 131 | "stdout" : "0\n1\n2\n" 132 | }, 133 | { "event" : "step_line", 134 | "func_name" : "", 135 | "globals" : { "i" : 3, 136 | "x" : 5, 137 | "z" : 2 138 | }, 139 | "line" : 3, 140 | "stack_locals" : [ ], 141 | "stdout" : "0\n1\n2\n" 142 | }, 143 | { "event" : "step_line", 144 | "func_name" : "", 145 | "globals" : { "i" : 3, 146 | "x" : 5, 147 | "z" : 2 148 | }, 149 | "line" : 6, 150 | "stack_locals" : [ ], 151 | "stdout" : "0\n1\n2\n" 152 | }, 153 | { "event" : "step_line", 154 | "func_name" : "", 155 | "globals" : { "i" : 3, 156 | "x" : 5, 157 | "z" : 3 158 | }, 159 | "line" : 7, 160 | "stack_locals" : [ ], 161 | "stdout" : "0\n1\n2\n" 162 | }, 163 | { "event" : "step_line", 164 | "func_name" : "", 165 | "globals" : { "i" : 3, 166 | "x" : 5, 167 | "z" : 3 168 | }, 169 | "line" : 2, 170 | "stack_locals" : [ ], 171 | "stdout" : "0\n1\n2\n3\n" 172 | }, 173 | { "event" : "step_line", 174 | "func_name" : "", 175 | "globals" : { "i" : 4, 176 | "x" : 5, 177 | "z" : 3 178 | }, 179 | "line" : 3, 180 | "stack_locals" : [ ], 181 | "stdout" : "0\n1\n2\n3\n" 182 | }, 183 | { "event" : "step_line", 184 | "func_name" : "", 185 | "globals" : { "i" : 4, 186 | "x" : 5, 187 | "z" : 3 188 | }, 189 | "line" : 6, 190 | "stack_locals" : [ ], 191 | "stdout" : "0\n1\n2\n3\n" 192 | }, 193 | { "event" : "step_line", 194 | "func_name" : "", 195 | "globals" : { "i" : 4, 196 | "x" : 5, 197 | "z" : 4 198 | }, 199 | "line" : 7, 200 | "stack_locals" : [ ], 201 | "stdout" : "0\n1\n2\n3\n" 202 | }, 203 | { "event" : "step_line", 204 | "func_name" : "", 205 | "globals" : { "i" : 4, 206 | "x" : 5, 207 | "z" : 4 208 | }, 209 | "line" : 2, 210 | "stack_locals" : [ ], 211 | "stdout" : "0\n1\n2\n3\n4\n" 212 | }, 213 | { "event" : "step_line", 214 | "func_name" : "", 215 | "globals" : { "i" : 5, 216 | "x" : 5, 217 | "z" : 4 218 | }, 219 | "line" : 3, 220 | "stack_locals" : [ ], 221 | "stdout" : "0\n1\n2\n3\n4\n" 222 | }, 223 | { "event" : "step_line", 224 | "func_name" : "", 225 | "globals" : { "i" : 5, 226 | "x" : 5, 227 | "z" : 4 228 | }, 229 | "line" : 4, 230 | "stack_locals" : [ ], 231 | "stdout" : "0\n1\n2\n3\n4\n" 232 | }, 233 | { "event" : "exception", 234 | "exception_msg" : "NameError: name 'y' is not defined", 235 | "func_name" : "", 236 | "globals" : { "i" : 5, 237 | "x" : 5, 238 | "z" : 4 239 | }, 240 | "line" : 4, 241 | "stack_locals" : [ ], 242 | "stdout" : "0\n1\n2\n3\n4\n" 243 | } 244 | ] 245 | -------------------------------------------------------------------------------- /test-programs/runtime_error.py: -------------------------------------------------------------------------------- 1 | x = 5 2 | for i in range(10): 3 | if i == x: 4 | z = x + y # ERROR! 5 | else: 6 | z = i 7 | print z 8 | 9 | -------------------------------------------------------------------------------- /test-programs/set_test.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "step_line", 9 | "func_name" : "", 10 | "globals" : { "x" : [ "SET", 11 | 99999 12 | ] }, 13 | "line" : 2, 14 | "stack_locals" : [ ], 15 | "stdout" : "" 16 | }, 17 | { "event" : "step_line", 18 | "func_name" : "", 19 | "globals" : { "x" : [ "SET", 20 | 99999, 21 | "a" 22 | ] }, 23 | "line" : 3, 24 | "stack_locals" : [ ], 25 | "stdout" : "" 26 | }, 27 | { "event" : "step_line", 28 | "func_name" : "", 29 | "globals" : { "x" : [ "SET", 30 | 99999, 31 | "a" 32 | ] }, 33 | "line" : 4, 34 | "stack_locals" : [ ], 35 | "stdout" : "" 36 | }, 37 | { "event" : "step_line", 38 | "func_name" : "", 39 | "globals" : { "x" : [ "SET", 40 | 99999, 41 | "a", 42 | "b" 43 | ] }, 44 | "line" : 5, 45 | "stack_locals" : [ ], 46 | "stdout" : "" 47 | }, 48 | { "event" : "step_line", 49 | "func_name" : "", 50 | "globals" : { "x" : [ "SET", 51 | 99999, 52 | "a", 53 | "c", 54 | "b" 55 | ] }, 56 | "line" : 6, 57 | "stack_locals" : [ ], 58 | "stdout" : "" 59 | }, 60 | { "event" : "return", 61 | "func_name" : "", 62 | "globals" : { "x" : [ "SET", 63 | 99999, 64 | "a", 65 | "c", 66 | "b" 67 | ] }, 68 | "line" : 6, 69 | "stack_locals" : [ ], 70 | "stdout" : "" 71 | } 72 | ] 73 | -------------------------------------------------------------------------------- /test-programs/set_test.py: -------------------------------------------------------------------------------- 1 | x = set() 2 | x.add('a') 3 | x.add('a') 4 | x.add('b') 5 | x.add('c') 6 | x.add('b') 7 | 8 | -------------------------------------------------------------------------------- /test-programs/simple.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "step_line", 9 | "func_name" : "", 10 | "globals" : { "x" : 5 }, 11 | "line" : 2, 12 | "stack_locals" : [ ], 13 | "stdout" : "" 14 | }, 15 | { "event" : "step_line", 16 | "func_name" : "", 17 | "globals" : { "x" : 5, 18 | "y" : 10 19 | }, 20 | "line" : 3, 21 | "stack_locals" : [ ], 22 | "stdout" : "" 23 | }, 24 | { "event" : "step_line", 25 | "func_name" : "", 26 | "globals" : { "x" : 5, 27 | "y" : 10, 28 | "z" : 50 29 | }, 30 | "line" : 4, 31 | "stack_locals" : [ ], 32 | "stdout" : "" 33 | }, 34 | { "event" : "step_line", 35 | "func_name" : "", 36 | "globals" : { "x" : 5, 37 | "y" : 10, 38 | "z" : 50 39 | }, 40 | "line" : 5, 41 | "stack_locals" : [ ], 42 | "stdout" : "HELLO WORLD\n" 43 | }, 44 | { "event" : "step_line", 45 | "func_name" : "", 46 | "globals" : { "i" : 0, 47 | "x" : 5, 48 | "y" : 10, 49 | "z" : 50 50 | }, 51 | "line" : 6, 52 | "stack_locals" : [ ], 53 | "stdout" : "HELLO WORLD\n" 54 | }, 55 | { "event" : "step_line", 56 | "func_name" : "", 57 | "globals" : { "i" : 0, 58 | "x" : 5, 59 | "y" : 10, 60 | "z" : 50 61 | }, 62 | "line" : 5, 63 | "stack_locals" : [ ], 64 | "stdout" : "HELLO WORLD\n0\n" 65 | }, 66 | { "event" : "step_line", 67 | "func_name" : "", 68 | "globals" : { "i" : 1, 69 | "x" : 5, 70 | "y" : 10, 71 | "z" : 50 72 | }, 73 | "line" : 6, 74 | "stack_locals" : [ ], 75 | "stdout" : "HELLO WORLD\n0\n" 76 | }, 77 | { "event" : "step_line", 78 | "func_name" : "", 79 | "globals" : { "i" : 1, 80 | "x" : 5, 81 | "y" : 10, 82 | "z" : 50 83 | }, 84 | "line" : 5, 85 | "stack_locals" : [ ], 86 | "stdout" : "HELLO WORLD\n0\n50\n" 87 | }, 88 | { "event" : "step_line", 89 | "func_name" : "", 90 | "globals" : { "i" : 2, 91 | "x" : 5, 92 | "y" : 10, 93 | "z" : 50 94 | }, 95 | "line" : 6, 96 | "stack_locals" : [ ], 97 | "stdout" : "HELLO WORLD\n0\n50\n" 98 | }, 99 | { "event" : "step_line", 100 | "func_name" : "", 101 | "globals" : { "i" : 2, 102 | "x" : 5, 103 | "y" : 10, 104 | "z" : 50 105 | }, 106 | "line" : 5, 107 | "stack_locals" : [ ], 108 | "stdout" : "HELLO WORLD\n0\n50\n100\n" 109 | }, 110 | { "event" : "step_line", 111 | "func_name" : "", 112 | "globals" : { "i" : 3, 113 | "x" : 5, 114 | "y" : 10, 115 | "z" : 50 116 | }, 117 | "line" : 6, 118 | "stack_locals" : [ ], 119 | "stdout" : "HELLO WORLD\n0\n50\n100\n" 120 | }, 121 | { "event" : "step_line", 122 | "func_name" : "", 123 | "globals" : { "i" : 3, 124 | "x" : 5, 125 | "y" : 10, 126 | "z" : 50 127 | }, 128 | "line" : 5, 129 | "stack_locals" : [ ], 130 | "stdout" : "HELLO WORLD\n0\n50\n100\n150\n" 131 | }, 132 | { "event" : "step_line", 133 | "func_name" : "", 134 | "globals" : { "i" : 4, 135 | "x" : 5, 136 | "y" : 10, 137 | "z" : 50 138 | }, 139 | "line" : 6, 140 | "stack_locals" : [ ], 141 | "stdout" : "HELLO WORLD\n0\n50\n100\n150\n" 142 | }, 143 | { "event" : "step_line", 144 | "func_name" : "", 145 | "globals" : { "i" : 4, 146 | "x" : 5, 147 | "y" : 10, 148 | "z" : 50 149 | }, 150 | "line" : 5, 151 | "stack_locals" : [ ], 152 | "stdout" : "HELLO WORLD\n0\n50\n100\n150\n200\n" 153 | }, 154 | { "event" : "step_line", 155 | "func_name" : "", 156 | "globals" : { "i" : 5, 157 | "x" : 5, 158 | "y" : 10, 159 | "z" : 50 160 | }, 161 | "line" : 6, 162 | "stack_locals" : [ ], 163 | "stdout" : "HELLO WORLD\n0\n50\n100\n150\n200\n" 164 | }, 165 | { "event" : "step_line", 166 | "func_name" : "", 167 | "globals" : { "i" : 5, 168 | "x" : 5, 169 | "y" : 10, 170 | "z" : 50 171 | }, 172 | "line" : 5, 173 | "stack_locals" : [ ], 174 | "stdout" : "HELLO WORLD\n0\n50\n100\n150\n200\n250\n" 175 | }, 176 | { "event" : "step_line", 177 | "func_name" : "", 178 | "globals" : { "i" : 6, 179 | "x" : 5, 180 | "y" : 10, 181 | "z" : 50 182 | }, 183 | "line" : 6, 184 | "stack_locals" : [ ], 185 | "stdout" : "HELLO WORLD\n0\n50\n100\n150\n200\n250\n" 186 | }, 187 | { "event" : "step_line", 188 | "func_name" : "", 189 | "globals" : { "i" : 6, 190 | "x" : 5, 191 | "y" : 10, 192 | "z" : 50 193 | }, 194 | "line" : 5, 195 | "stack_locals" : [ ], 196 | "stdout" : "HELLO WORLD\n0\n50\n100\n150\n200\n250\n300\n" 197 | }, 198 | { "event" : "step_line", 199 | "func_name" : "", 200 | "globals" : { "i" : 7, 201 | "x" : 5, 202 | "y" : 10, 203 | "z" : 50 204 | }, 205 | "line" : 6, 206 | "stack_locals" : [ ], 207 | "stdout" : "HELLO WORLD\n0\n50\n100\n150\n200\n250\n300\n" 208 | }, 209 | { "event" : "step_line", 210 | "func_name" : "", 211 | "globals" : { "i" : 7, 212 | "x" : 5, 213 | "y" : 10, 214 | "z" : 50 215 | }, 216 | "line" : 5, 217 | "stack_locals" : [ ], 218 | "stdout" : "HELLO WORLD\n0\n50\n100\n150\n200\n250\n300\n350\n" 219 | }, 220 | { "event" : "step_line", 221 | "func_name" : "", 222 | "globals" : { "i" : 8, 223 | "x" : 5, 224 | "y" : 10, 225 | "z" : 50 226 | }, 227 | "line" : 6, 228 | "stack_locals" : [ ], 229 | "stdout" : "HELLO WORLD\n0\n50\n100\n150\n200\n250\n300\n350\n" 230 | }, 231 | { "event" : "step_line", 232 | "func_name" : "", 233 | "globals" : { "i" : 8, 234 | "x" : 5, 235 | "y" : 10, 236 | "z" : 50 237 | }, 238 | "line" : 5, 239 | "stack_locals" : [ ], 240 | "stdout" : "HELLO WORLD\n0\n50\n100\n150\n200\n250\n300\n350\n400\n" 241 | }, 242 | { "event" : "step_line", 243 | "func_name" : "", 244 | "globals" : { "i" : 9, 245 | "x" : 5, 246 | "y" : 10, 247 | "z" : 50 248 | }, 249 | "line" : 6, 250 | "stack_locals" : [ ], 251 | "stdout" : "HELLO WORLD\n0\n50\n100\n150\n200\n250\n300\n350\n400\n" 252 | }, 253 | { "event" : "step_line", 254 | "func_name" : "", 255 | "globals" : { "i" : 9, 256 | "x" : 5, 257 | "y" : 10, 258 | "z" : 50 259 | }, 260 | "line" : 5, 261 | "stack_locals" : [ ], 262 | "stdout" : "HELLO WORLD\n0\n50\n100\n150\n200\n250\n300\n350\n400\n450\n" 263 | }, 264 | { "event" : "return", 265 | "func_name" : "", 266 | "globals" : { "i" : 9, 267 | "x" : 5, 268 | "y" : 10, 269 | "z" : 50 270 | }, 271 | "line" : 5, 272 | "stack_locals" : [ ], 273 | "stdout" : "HELLO WORLD\n0\n50\n100\n150\n200\n250\n300\n350\n400\n450\n" 274 | } 275 | ] 276 | -------------------------------------------------------------------------------- /test-programs/simple.py: -------------------------------------------------------------------------------- 1 | x = 5 2 | y = 10 3 | z = x * y 4 | print "HELLO WORLD" 5 | for i in range(10): 6 | print z * i 7 | 8 | -------------------------------------------------------------------------------- /test-programs/three_lists.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 3, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "step_line", 9 | "func_name" : "", 10 | "globals" : { "f" : [ "function", 11 | 99999, 12 | "" 13 | ] }, 14 | "line" : 6, 15 | "stack_locals" : [ ], 16 | "stdout" : "" 17 | }, 18 | { "event" : "step_line", 19 | "func_name" : "", 20 | "globals" : { "a" : [ "LIST", 21 | 99999, 22 | 10, 23 | 20, 24 | 30 25 | ], 26 | "f" : [ "function", 27 | 99999, 28 | "" 29 | ] 30 | }, 31 | "line" : 7, 32 | "stack_locals" : [ ], 33 | "stdout" : "" 34 | }, 35 | { "event" : "step_line", 36 | "func_name" : "", 37 | "globals" : { "a" : [ "LIST", 38 | 99999, 39 | 10, 40 | 20, 41 | 30 42 | ], 43 | "b" : [ "LIST", 44 | 99999, 45 | 10, 46 | 20, 47 | 30 48 | ], 49 | "f" : [ "function", 50 | 99999, 51 | "" 52 | ] 53 | }, 54 | "line" : 8, 55 | "stack_locals" : [ ], 56 | "stdout" : "" 57 | }, 58 | { "event" : "step_line", 59 | "func_name" : "", 60 | "globals" : { "a" : [ "LIST", 61 | 99999, 62 | 10, 63 | 20, 64 | 30 65 | ], 66 | "b" : [ "LIST", 67 | 99999, 68 | 10, 69 | 20, 70 | 30 71 | ], 72 | "c" : [ "LIST", 73 | 99999, 74 | 10, 75 | 20, 76 | 30 77 | ], 78 | "f" : [ "function", 79 | 99999, 80 | "" 81 | ] 82 | }, 83 | "line" : 9, 84 | "stack_locals" : [ ], 85 | "stdout" : "" 86 | }, 87 | { "event" : "step_line", 88 | "func_name" : "", 89 | "globals" : { "a" : [ "LIST", 90 | 99999, 91 | 10, 92 | 20, 93 | 30 94 | ], 95 | "b" : [ "LIST", 96 | 99999, 97 | 10, 98 | 20, 99 | 30 100 | ], 101 | "c" : [ "LIST", 102 | 99999, 103 | 10, 104 | 20, 105 | 30 106 | ], 107 | "d" : 24, 108 | "f" : [ "function", 109 | 99999, 110 | "" 111 | ] 112 | }, 113 | "line" : 10, 114 | "stack_locals" : [ ], 115 | "stdout" : "" 116 | }, 117 | { "event" : "step_line", 118 | "func_name" : "", 119 | "globals" : { "a" : [ "LIST", 120 | 99999, 121 | 10, 122 | 20, 123 | 30 124 | ], 125 | "b" : [ "LIST", 126 | 99999, 127 | 10, 128 | 20, 129 | 30 130 | ], 131 | "c" : [ "LIST", 132 | 99999, 133 | 10, 134 | 20, 135 | 30 136 | ], 137 | "d" : 24, 138 | "e" : [ "TUPLE", 139 | 99999, 140 | [ "LIST", 141 | 99999, 142 | 10, 143 | 20, 144 | 30 145 | ], 146 | [ "LIST", 147 | 99999, 148 | 10, 149 | 20, 150 | 30 151 | ], 152 | [ "LIST", 153 | 99999, 154 | 10, 155 | 20, 156 | 30 157 | ] 158 | ], 159 | "f" : [ "function", 160 | 99999, 161 | "" 162 | ] 163 | }, 164 | "line" : 12, 165 | "stack_locals" : [ ], 166 | "stdout" : "" 167 | }, 168 | { "event" : "call", 169 | "func_name" : "f", 170 | "globals" : { "a" : [ "LIST", 171 | 99999, 172 | 10, 173 | 20, 174 | 30 175 | ], 176 | "b" : [ "LIST", 177 | 99999, 178 | 10, 179 | 20, 180 | 30 181 | ], 182 | "c" : [ "LIST", 183 | 99999, 184 | 10, 185 | 20, 186 | 30 187 | ], 188 | "d" : 24, 189 | "e" : [ "TUPLE", 190 | 99999, 191 | [ "LIST", 192 | 99999, 193 | 10, 194 | 20, 195 | 30 196 | ], 197 | [ "LIST", 198 | 99999, 199 | 10, 200 | 20, 201 | 30 202 | ], 203 | [ "LIST", 204 | 99999, 205 | 10, 206 | 20, 207 | 30 208 | ] 209 | ], 210 | "f" : [ "function", 211 | 99999, 212 | "" 213 | ] 214 | }, 215 | "line" : 3, 216 | "stack_locals" : [ [ "f", 217 | { "xs" : [ "LIST", 218 | 99999, 219 | 10, 220 | 20, 221 | 30 222 | ] } 223 | ] ], 224 | "stdout" : "" 225 | }, 226 | { "event" : "step_line", 227 | "func_name" : "f", 228 | "globals" : { "a" : [ "LIST", 229 | 99999, 230 | 10, 231 | 20, 232 | 30 233 | ], 234 | "b" : [ "LIST", 235 | 99999, 236 | 10, 237 | 20, 238 | 30 239 | ], 240 | "c" : [ "LIST", 241 | 99999, 242 | 10, 243 | 20, 244 | 30 245 | ], 246 | "d" : 24, 247 | "e" : [ "TUPLE", 248 | 99999, 249 | [ "LIST", 250 | 99999, 251 | 10, 252 | 20, 253 | 30 254 | ], 255 | [ "LIST", 256 | 99999, 257 | 10, 258 | 20, 259 | 30 260 | ], 261 | [ "LIST", 262 | 99999, 263 | 10, 264 | 20, 265 | 30 266 | ] 267 | ], 268 | "f" : [ "function", 269 | 99999, 270 | "" 271 | ] 272 | }, 273 | "line" : 4, 274 | "stack_locals" : [ [ "f", 275 | { "xs" : [ "LIST", 276 | 99999, 277 | 10, 278 | 20, 279 | 30 280 | ] } 281 | ] ], 282 | "stdout" : "" 283 | }, 284 | { "event" : "return", 285 | "func_name" : "f", 286 | "globals" : { "a" : [ "LIST", 287 | 99999, 288 | 10, 289 | 20, 290 | 30 291 | ], 292 | "b" : [ "LIST", 293 | 99999, 294 | 10, 295 | 20, 296 | 30 297 | ], 298 | "c" : [ "LIST", 299 | 99999, 300 | 10, 301 | 20, 302 | 30 303 | ], 304 | "d" : 24, 305 | "e" : [ "TUPLE", 306 | 99999, 307 | [ "LIST", 308 | 99999, 309 | 10, 310 | 20, 311 | 30 312 | ], 313 | [ "LIST", 314 | 99999, 315 | 10, 316 | 20, 317 | 30 318 | ], 319 | [ "LIST", 320 | 99999, 321 | 10, 322 | 20, 323 | 30 324 | ] 325 | ], 326 | "f" : [ "function", 327 | 99999, 328 | "" 329 | ] 330 | }, 331 | "line" : 4, 332 | "stack_locals" : [ [ "f", 333 | { "__return__" : null, 334 | "xs" : [ "LIST", 335 | 99999, 336 | 10, 337 | 20, 338 | 30 339 | ] 340 | } 341 | ] ], 342 | "stdout" : "[10, 20, 30]\n" 343 | }, 344 | { "event" : "return", 345 | "func_name" : "", 346 | "globals" : { "a" : [ "LIST", 347 | 99999, 348 | 10, 349 | 20, 350 | 30 351 | ], 352 | "b" : [ "LIST", 353 | 99999, 354 | 10, 355 | 20, 356 | 30 357 | ], 358 | "c" : [ "LIST", 359 | 99999, 360 | 10, 361 | 20, 362 | 30 363 | ], 364 | "d" : 24, 365 | "e" : [ "TUPLE", 366 | 99999, 367 | [ "LIST", 368 | 99999, 369 | 10, 370 | 20, 371 | 30 372 | ], 373 | [ "LIST", 374 | 99999, 375 | 10, 376 | 20, 377 | 30 378 | ], 379 | [ "LIST", 380 | 99999, 381 | 10, 382 | 20, 383 | 30 384 | ] 385 | ], 386 | "f" : [ "function", 387 | 99999, 388 | "" 389 | ] 390 | }, 391 | "line" : 12, 392 | "stack_locals" : [ ], 393 | "stdout" : "[10, 20, 30]\n" 394 | } 395 | ] 396 | -------------------------------------------------------------------------------- /test-programs/three_lists.py: -------------------------------------------------------------------------------- 1 | # test case submitted by Peter Wentworth (p.wentworth@ru.ac.za) 2 | 3 | def f(xs): 4 | print(xs) 5 | 6 | a = [10, 20, 30] 7 | b = a 8 | c = [10, 20, 30] 9 | d = 24 10 | e = (a, b, c) 11 | 12 | f(b) 13 | 14 | -------------------------------------------------------------------------------- /test-programs/tuple_test.golden: -------------------------------------------------------------------------------- 1 | [ { "event" : "step_line", 2 | "func_name" : "", 3 | "globals" : { }, 4 | "line" : 1, 5 | "stack_locals" : [ ], 6 | "stdout" : "" 7 | }, 8 | { "event" : "step_line", 9 | "func_name" : "", 10 | "globals" : { "x" : [ "TUPLE", 11 | 99999, 12 | 1, 13 | 2, 14 | 3 15 | ] }, 16 | "line" : 2, 17 | "stack_locals" : [ ], 18 | "stdout" : "" 19 | }, 20 | { "event" : "return", 21 | "func_name" : "", 22 | "globals" : { "x" : [ "TUPLE", 23 | 99999, 24 | 1, 25 | 2, 26 | 3 27 | ], 28 | "y" : [ "TUPLE", 29 | 99999, 30 | 4 31 | ] 32 | }, 33 | "line" : 2, 34 | "stack_locals" : [ ], 35 | "stdout" : "" 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /test-programs/tuple_test.py: -------------------------------------------------------------------------------- 1 | x = (1, 2, 3) 2 | y = (4,) 3 | 4 | -------------------------------------------------------------------------------- /test-programs/two_funcs.py: -------------------------------------------------------------------------------- 1 | def add(a, b, c): 2 | d = a + b 3 | return c + d 4 | 5 | def double_add(a, b, c): 6 | x = add(a, b, c) 7 | y = add(a, b, c) 8 | return x + y 9 | 10 | x = 5 11 | y = 10 12 | z = x * y 13 | print add(x, y, z) 14 | print double_add(x, y, z) 15 | -------------------------------------------------------------------------------- /tutor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | 26 | 27 | Online Python Tutor 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 45 | 46 | 47 | 48 | 49 | 50 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 |
68 | 69 | Write your Python code here: 70 |
71 | 72 | 73 | 74 |

75 | 76 | 77 | 78 |

79 | 80 |

Try these small examples:
81 | 82 | aliasing | 83 | intro | 84 | factorial | 85 | fibonacci | 86 | memoized fib | 87 | square root | 88 | insertion sort 89 |
90 | filter | 91 | tokenize | 92 | OOP | 93 | gcd | 94 | sumList | 95 | towers of hanoi | 96 | exceptions 97 | 98 |

99 | 100 |

Examples from the MIT 101 | 6.01 course:
102 | 103 | list map | 104 | summation | 105 | OOP 1 | 106 | OOP 2 | 107 | inheritance 108 | 109 |

110 | 111 |

112 | Then try some programming questions:
113 | Solve: 114 | Two-sum | 115 | Reverse list | 116 | Remove duplicate chars 117 | 118 |
Debug: 119 | In-place reverse | 120 | Binary search | 121 | Mergesort 122 | 123 |
Optimize: 124 | 125 | Greatest sum | 126 | Find duplicates | 127 | List search 128 | 129 |

130 | 131 | 132 | 133 | 134 |
135 | 136 | 137 | 138 | 139 | 175 | 180 | 181 |
140 | 141 |
142 | 143 |
144 | 145 | Use left and right arrow keys to step through this code: 146 |
147 | 148 |
149 | 150 |
151 | 152 | 153 | 154 |
155 | 156 |
157 | 158 | 159 | Step ? of ? 160 | 161 | 162 |
163 | 164 |
165 | 166 | 167 |
168 | 169 | Program output: 170 |
171 | 172 | 173 | 174 |
176 | 177 |
178 | 179 |
182 | 183 |
184 | 185 | 186 | 211 | 212 |
213 | 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /tutorials/MIT-6.01/README: -------------------------------------------------------------------------------- 1 | these examples are adapted from the MIT 6.01 course notes (Spring 2010): 2 | http://mit.edu/6.01/mercurial/spring10/www/handouts/readings.pdf 3 | 4 | -------------------------------------------------------------------------------- /tutorials/MIT-6.01/map.py: -------------------------------------------------------------------------------- 1 | # Functional programming with 'map' 2 | # Adapted from MIT 6.01 course notes (Section A.2.3) 3 | # http://mit.edu/6.01/mercurial/spring10/www/handouts/readings.pdf 4 | 5 | def map(func, lst): 6 | if lst == []: 7 | return [] 8 | else: 9 | return [func(lst[0])] + map(func, lst[1:]) 10 | 11 | def halveElements(lst): 12 | return map(lambda x: x / 2.0, lst) 13 | 14 | input = [2, 4, 6, 8, 10] 15 | output = halveElements(input) 16 | -------------------------------------------------------------------------------- /tutorials/MIT-6.01/oop_1.py: -------------------------------------------------------------------------------- 1 | # Object-oriented programming intro 2 | # Adapted from MIT 6.01 course notes (Section 3.5) 3 | # http://mit.edu/6.01/mercurial/spring10/www/handouts/readings.pdf 4 | 5 | class Staff601: 6 | course = '6.01' 7 | building = 34 8 | room = 501 9 | 10 | def salutation(self): 11 | return self.role + ' ' + self.name 12 | 13 | pat = Staff601() 14 | print pat.course 15 | 16 | pat.name = 'Pat' 17 | pat.age = 60 18 | pat.role = 'Professor' 19 | 20 | print pat.building 21 | pat.building = 32 22 | print pat.building 23 | 24 | print pat.salutation() 25 | print Staff601.salutation(pat) 26 | 27 | -------------------------------------------------------------------------------- /tutorials/MIT-6.01/oop_2.py: -------------------------------------------------------------------------------- 1 | # The __init__ 'constructor' - object-oriented programming intro 2 | # Adapted from MIT 6.01 course notes (Section 3.5) 3 | # http://mit.edu/6.01/mercurial/spring10/www/handouts/readings.pdf 4 | 5 | class Staff601: 6 | course = '6.01' 7 | building = 34 8 | room = 501 9 | 10 | def __init__(self, name, role, years, salary): 11 | self.name = name 12 | self.role = role 13 | self.age = years 14 | self.salary = salary 15 | 16 | def salutation(self): 17 | return self.role + ' ' + self.name 18 | 19 | pat = Staff601('Pat', 'Professor', 60, 100000) 20 | print pat.salutation() 21 | 22 | -------------------------------------------------------------------------------- /tutorials/MIT-6.01/oop_3.py: -------------------------------------------------------------------------------- 1 | # Inheritance - object-oriented programming intro 2 | # Adapted from MIT 6.01 course notes (Section 3.5) 3 | # http://mit.edu/6.01/mercurial/spring10/www/handouts/readings.pdf 4 | 5 | class Staff601: 6 | course = '6.01' 7 | building = 34 8 | room = 501 9 | 10 | def giveRaise(self, percentage): 11 | self.salary = self.salary + self.salary * percentage 12 | 13 | class Prof601(Staff601): 14 | salary = 100000 15 | 16 | def __init__(self, name, age): 17 | self.name = name 18 | self.giveRaise((age - 18) * 0.03) 19 | 20 | def salutation(self): 21 | return self.role + ' ' + self.name 22 | 23 | pat = Prof601('Pat', 60) 24 | 25 | -------------------------------------------------------------------------------- /tutorials/MIT-6.01/summation.py: -------------------------------------------------------------------------------- 1 | # Higher-order functions 2 | # Adapted from MIT 6.01 course notes (Section A.2.2) 3 | # http://mit.edu/6.01/mercurial/spring10/www/handouts/readings.pdf 4 | 5 | def summation(low, high, f, next): 6 | s = 0 7 | x = low 8 | while x <= high: 9 | s = s + f(x) 10 | x = next(x) 11 | return s 12 | 13 | def sumsquares(low, high): 14 | return summation(low, high, lambda x: x**2, lambda x: x+1) 15 | 16 | print sumsquares(1, 10) 17 | 18 | -------------------------------------------------------------------------------- /tutorials/README: -------------------------------------------------------------------------------- 1 | This directory contains the tutorial Python scripts that can be loaded 2 | by the Online Python Tutor 3 | -------------------------------------------------------------------------------- /tutorials/advanced/map.py: -------------------------------------------------------------------------------- 1 | def square(x): 2 | return x*x 3 | 4 | def map(f, lst): 5 | ret = [] 6 | for elt in lst: 7 | ret.append(f(elt)) 8 | return ret 9 | 10 | y = map(square, [1,2,3,4,5,6]) 11 | 12 | -------------------------------------------------------------------------------- /tutorials/math/newton.py: -------------------------------------------------------------------------------- 1 | # Calculating square roots by Newton's method, inspired by SICP 2 | # http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-10.html#%_sec_1.1.7 3 | # 4 | # translated into Python from this Scheme code: 5 | # 6 | #(define (sqrt x) 7 | # (define (good-enough? guess) 8 | # (< (abs (- (square guess) x)) 0.001)) 9 | # (define (improve guess) 10 | # (average guess (/ x guess))) 11 | # (define (sqrt-iter guess) 12 | # (if (good-enough? guess) 13 | # guess 14 | # (sqrt-iter (improve guess)))) 15 | # (sqrt-iter 1.0)) 16 | 17 | def sqrt(x): 18 | def average(a, b): 19 | return (a + b) / 2.0 20 | 21 | def is_good_enough(guess): 22 | return (abs((guess * guess) - x) < 0.001) 23 | 24 | def improve(guess): 25 | return average(guess, x / guess) 26 | 27 | def sqrt_iter(guess): 28 | if is_good_enough(guess): 29 | return guess 30 | else: 31 | return sqrt_iter(improve(guess)) 32 | 33 | return sqrt_iter(1.0) 34 | 35 | 36 | for e in range(1, 26): 37 | print 'sqrt(%d) =' % e, sqrt(e) 38 | 39 | -------------------------------------------------------------------------------- /tutorials/oop/oop_demo.py: -------------------------------------------------------------------------------- 1 | class A: 2 | x = 1 3 | y = 'hello' 4 | 5 | class B: 6 | z = 'bye' 7 | 8 | class C(A,B): 9 | def salutation(self): 10 | return '%d %s %s' % (self.x, self.y, self.z) 11 | 12 | inst = C() 13 | print inst.salutation() 14 | inst.x = 100 15 | print inst.salutation() 16 | 17 | -------------------------------------------------------------------------------- /tutorials/personal-finance/compound_interest.py: -------------------------------------------------------------------------------- 1 | # inputs: 2 | amountBorrowed = 1000 3 | annualRate = 0.08 4 | currentYear = 2010 5 | yearsToBorrow = 10 6 | 7 | # output: 8 | amountOwed = amountBorrowed 9 | 10 | for i in range(yearsToBorrow): 11 | currentYear = currentYear + 1 12 | amountOwed = amountOwed + (amountOwed * annualRate) 13 | print "Year:", currentYear, "| Amount owed:", round(amountOwed, 2) 14 | 15 | -------------------------------------------------------------------------------- /yellow-happy-face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hcientist/OnlinePythonTutor/78e0f4060f6d913e7651c64779befb3529bccb63/yellow-happy-face.png --------------------------------------------------------------------------------