├── README.md ├── react-frontend ├── .gitignore ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── components │ │ ├── code-editor.tsx │ │ ├── codemirror.css │ │ ├── controls.css │ │ ├── controls.tsx │ │ ├── placeholder.tsx │ │ ├── userinput.tsx │ │ ├── visualizer.css │ │ └── visualizer.js │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── react-app-env.d.ts │ ├── serviceWorker.ts │ ├── setupTests.ts │ └── util │ │ ├── graph-util.js │ │ └── utility-functions.js └── tsconfig.json └── remote-code-runner ├── app.py ├── hasImport.py ├── injectCode.py ├── judge.py ├── parseinput.py ├── remoteCodeRunner.py ├── requirements.txt └── tryget.py /README.md: -------------------------------------------------------------------------------- 1 | # This project is no longer being maintained! 2 | 3 | As Server hosting fees started racking up due to many many infinite recursive loops being submitted to what is more or less a hackathon project, 4 | I decided it was better to just pull the plug. Sorry :( 5 | 6 | # Recursion Visualizer 7 | 8 | Ever wondered what the recursive structure of your Leetcode algorithm looks like? 9 | Then you are in luck! This project allows you to see recursion in your code. 10 | Whoa! What does that mean? Basically, write a single recursive function, and this app will show you step by step how your algorithm reached it's solution. And at each stage of the recursion, you're able to navigate the call graph and see the return output value at each stage. How does that sound? 11 | 12 | ## Build it locally 13 | Ok, so what you need is Node.js v12.18.3, Python 3.8.5, and a lot of patience. Wait, no, I meant you need Flask. So, just install everything in requirements.txt in the remoteCodeRunner, run npm install in the react-frontend directory, start the Flask server, and you should be good! (also, you need to adjust your localhost flask URL inside the react directory.) 14 | 15 | ## DEMO 16 | Visit: https://www.youtube.com/watch?v=Zb0EJkHHxgo 17 | -------------------------------------------------------------------------------- /react-frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | firebase-debug.log* 8 | firebase-debug.*.log* 9 | 10 | # Firebase cache 11 | .firebase/ 12 | 13 | # Firebase config 14 | 15 | # Uncomment this if you'd like others to create their own Firebase project. 16 | # For a team working on the same Firebase project(s), it is recommended to leave 17 | # it commented so all members can deploy to the same project(s) in .firebaserc. 18 | # .firebaserc 19 | 20 | # Runtime data 21 | pids 22 | *.pid 23 | *.seed 24 | *.pid.lock 25 | 26 | # Directory for instrumented libs generated by jscoverage/JSCover 27 | lib-cov 28 | 29 | # Coverage directory used by tools like istanbul 30 | coverage 31 | 32 | # nyc test coverage 33 | .nyc_output 34 | 35 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 36 | .grunt 37 | 38 | # Bower dependency directory (https://bower.io/) 39 | bower_components 40 | 41 | # node-waf configuration 42 | .lock-wscript 43 | 44 | # Compiled binary addons (http://nodejs.org/api/addons.html) 45 | build/Release 46 | 47 | # Dependency directories 48 | node_modules/ 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Optional REPL history 57 | .node_repl_history 58 | 59 | # Output of 'npm pack' 60 | *.tgz 61 | 62 | # Yarn Integrity file 63 | .yarn-integrity 64 | 65 | # dotenv environment variables file 66 | .env 67 | -------------------------------------------------------------------------------- /react-frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-rec", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.11.0", 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.5.0", 9 | "@testing-library/user-event": "^7.2.1", 10 | "@types/jest": "^24.9.1", 11 | "@types/node": "^12.12.58", 12 | "@types/react": "^16.9.49", 13 | "@types/react-dom": "^16.9.8", 14 | "codemirror": "^5.57.0", 15 | "d3": "^5.16.0", 16 | "react": "^16.13.1", 17 | "react-codemirror2": "^7.2.1", 18 | "react-d3-graph": "^2.5.0", 19 | "react-dom": "^16.13.1", 20 | "react-scripts": "3.4.3", 21 | "react-spring": "^8.0.27", 22 | "typescript": "^3.7.5" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "react-scripts test", 28 | "eject": "react-scripts eject" 29 | }, 30 | "eslintConfig": { 31 | "extends": "react-app" 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /react-frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProjectsByJackHe/recursion-visualizer/9bb19e8722735a9f45b342b2649a4c00c09bca0a/react-frontend/public/favicon.ico -------------------------------------------------------------------------------- /react-frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Recursion Visualizer 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /react-frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProjectsByJackHe/recursion-visualizer/9bb19e8722735a9f45b342b2649a4c00c09bca0a/react-frontend/public/logo192.png -------------------------------------------------------------------------------- /react-frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProjectsByJackHe/recursion-visualizer/9bb19e8722735a9f45b342b2649a4c00c09bca0a/react-frontend/public/logo512.png -------------------------------------------------------------------------------- /react-frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /react-frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /react-frontend/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | body { 11 | background-color: #15121d; 12 | } 13 | 14 | .App-header { 15 | background-color: #282c34; 16 | min-height: 100vh; 17 | display: flex; 18 | flex-direction: column; 19 | align-items: center; 20 | justify-content: center; 21 | font-size: calc(10px + 2vmin); 22 | color: white; 23 | } 24 | 25 | .App-link { 26 | color: #61dafb; 27 | } -------------------------------------------------------------------------------- /react-frontend/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /react-frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, {Fragment, useState} from 'react'; 2 | import './App.css'; 3 | import UserInput from './components/userinput' 4 | import Visualizer from './components/visualizer' 5 | 6 | function App() { 7 | const [arrOfCalls, setArrOfCalls] = useState([]) 8 | const [funcName, setFuncName] = useState("") 9 | const [renderSpeed, setRenderSpeed] = useState(50) 10 | const [isRunning, setIsRunning] = useState(false) 11 | const [isLoading, setIsLoading] = useState(false) 12 | const [jelly, setJelly] = useState(false) 13 | 14 | return ( 15 | 16 | 26 | 34 | 35 | ); 36 | } 37 | 38 | export default App; 39 | -------------------------------------------------------------------------------- /react-frontend/src/components/code-editor.tsx: -------------------------------------------------------------------------------- 1 | import React, {Fragment} from 'react' 2 | import {Controlled as CodeMirror} from 'react-codemirror2' 3 | import './codemirror.css' 4 | 5 | require("codemirror/theme/material.css"); 6 | require("codemirror/theme/dracula.css"); 7 | require("codemirror/mode/python/python"); 8 | require("codemirror/lib/codemirror.css"); 9 | 10 | const DEFAULT_PYTHON_OPTIONS = { 11 | autoCloseBrackets: true, 12 | mode: "python", 13 | lineNumbers: true, 14 | class: "CodeMirror" 15 | }; 16 | 17 | 18 | const CodeEditor = (props: any) => { 19 | return 20 |
21 | { 30 | // Execute anything before onChange 31 | props.setCode(value) 32 | }} 33 | /> 34 |
35 |
36 | } 37 | 38 | export default CodeEditor -------------------------------------------------------------------------------- /react-frontend/src/components/codemirror.css: -------------------------------------------------------------------------------- 1 | .CodeMirror { 2 | font-size: 20px; 3 | resize: vertical; 4 | overflow: auto; 5 | border: 4px solid #b6b4b4; 6 | } -------------------------------------------------------------------------------- /react-frontend/src/components/controls.css: -------------------------------------------------------------------------------- 1 | .ControlPanel { 2 | width: 100%; 3 | background-color: #b6b4b4; 4 | height: 60px; 5 | } -------------------------------------------------------------------------------- /react-frontend/src/components/controls.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import React from "react"; 3 | import { Button, Slider } from "@material-ui/core"; 4 | import Switch from '@material-ui/core/Switch'; 5 | import FormControlLabel from '@material-ui/core/FormControlLabel'; 6 | import "./controls.css"; 7 | 8 | const Controls = (props: any) => { 9 | 10 | const handleChange = () => { 11 | props.setJelly(!props.jelly) 12 | } 13 | 14 | const onSlide = (e: any, newValue: number | number[]) => { 15 | props.setRenderSpeed(newValue) 16 | } 17 | 18 | return ( 19 |
20 | 30 | 40 | 41 | } 48 | label="Jelly" 49 | /> 50 | 51 | 59 |
60 | ); 61 | }; 62 | 63 | export default Controls; -------------------------------------------------------------------------------- /react-frontend/src/components/placeholder.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles } from "@material-ui/core/styles"; 3 | import Card from "@material-ui/core/Card"; 4 | import CardContent from "@material-ui/core/CardContent"; 5 | import Typography from "@material-ui/core/Typography"; 6 | 7 | const useStyles = makeStyles({ 8 | root: { 9 | maxWidth: 593, 10 | }, 11 | bullet: { 12 | display: "inline-block", 13 | margin: "0 2px", 14 | transform: "scale(0.8)", 15 | }, 16 | title: { 17 | fontSize: 14, 18 | }, 19 | pos: { 20 | marginBottom: 12, 21 | }, 22 | }); 23 | 24 | export default function OutlinedCard() { 25 | const classes = useStyles(); 26 | 27 | return ( 28 | 33 | 34 | 35 | Instructions 36 | 37 | 38 | 39 | Made with ❤️ by Jack He
40 | Please consider subscribing to my{" "} 41 | 42 | YouTube channel. 43 | 44 |
45 | 46 | 47 | 1. Write Code 48 | 49 | 54 | - You MUST define EXACTLY 1 recursive function 55 |
56 | - NO empty return statements. You gotta return something. 57 |
58 | - DO NOT include any 'print(...)' statements! 59 |
60 | - Be sure to call your function in the end. 61 |
62 | - Make sure the code you write follows correct syntax and indentation 63 | (Python 3.8)
64 |
65 | 66 | 2. Run Code 67 | 68 | 73 | - If all goes well, you will see a nice recursive visualization of 74 | your function
75 | - Green node == initial call. Red node == recursive call. Blue node == 76 | base case.
77 | - Turn 'Jelly' off for a static graph
- Click on any Node and 78 | view it's return value 79 |
80 |
81 |
82 | 90 |
91 |
92 | ); 93 | } 94 | -------------------------------------------------------------------------------- /react-frontend/src/components/userinput.tsx: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useState } from "react"; 2 | import Controls from "./controls"; 3 | import CodeEditor from "./code-editor"; 4 | import { 5 | getFuncName, 6 | getFuncCall, 7 | truncateFuncCall, 8 | } from "../util/utility-functions"; 9 | 10 | const UserInput = (props: any) => { 11 | const [submittedCode, setCode] = useState( 12 | "# DO NOT include any 'print(...)' statements! \n\ndef fib(x):\n if x == 1 or x == 2:\n return 1\n return fib(x - 1) + fib(x - 2)\n\nfib(10)" 13 | ); 14 | 15 | const runCode = async () => { 16 | let code = ""; 17 | for (let i = 0; i < submittedCode.length; i++) { 18 | if (submittedCode[i] === "\t") { 19 | code += " "; 20 | } else { 21 | code += submittedCode[i]; 22 | } 23 | } 24 | // extract function name 25 | const functionName = getFuncName(code); 26 | if (functionName === -1) { 27 | alert( 28 | "Make sure you have at least one function defined, and your function name is <= 8 characters." 29 | ); 30 | return; 31 | } 32 | console.log("function name: " + functionName); 33 | // extract function call. Result will be ['call', [start index, end index]] 34 | const functionCall = getFuncCall(code, functionName); 35 | if (functionCall === -1) { 36 | alert("Make sure you call your function"); 37 | return; 38 | } 39 | console.log("function call: " + functionCall); 40 | // truncate function call 41 | const codeToSend = truncateFuncCall(code, functionCall); 42 | console.log("code to send: " + codeToSend); 43 | props.setIsLoading(true) 44 | let callTrace; 45 | let noError = true; 46 | try { 47 | // send HTTP request to flask server and store 48 | // response inside callTrace 49 | const domain = "https://recursion0r94s8df984.herokuapp.com"; 50 | 51 | const options = `/execute?funcName=${functionName}&funcCall=${functionCall[0]}`; 52 | const fetchConfig = { 53 | method: "POST", 54 | body: codeToSend, 55 | }; 56 | let response = await fetch(domain + options, fetchConfig); 57 | callTrace = await response.text(); 58 | if (!response.ok) { 59 | throw new Error(callTrace); 60 | } 61 | } catch (e) { 62 | // if and when there is some error executing the python code on the server, 63 | // the flask server will send back some sort of error response. We will 64 | // catch that case here and alert the user accordingly 65 | alert("There was an error executing your code. Details: " + e); 66 | noError = false; 67 | } 68 | props.setIsLoading(false) 69 | console.log(callTrace) 70 | if (callTrace && noError) { 71 | // render callTrace 72 | props.setIsRunning(true) 73 | callTrace = callTrace.replace("'", ""); 74 | const arrOfStr = callTrace.split("|"); 75 | const arrOfCalls: string[][] = []; 76 | console.log(arrOfCalls); 77 | for (let i = arrOfStr.length - 1; i >= 0; i--) { 78 | arrOfCalls.push(arrOfStr[i].split(":")); 79 | } 80 | props.setArrOfCalls(arrOfCalls); 81 | props.setFuncName(functionName); 82 | } 83 | }; 84 | 85 | return ( 86 | 87 | 88 | 97 | 98 | ); 99 | }; 100 | 101 | export default UserInput; 102 | -------------------------------------------------------------------------------- /react-frontend/src/components/visualizer.css: -------------------------------------------------------------------------------- 1 | 2 | .circle { 3 | margin: auto; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | margin-top: 100px; 8 | } 9 | 10 | .line { 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | } 15 | 16 | .circleLine { 17 | position: relative; 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | border: 4px solid white; 22 | border-radius: 50%; 23 | } 24 | 25 | .circleBig { 26 | height: 80px; 27 | width: 80px; 28 | 29 | } 30 | 31 | .circleSmall { 32 | height: 20px; 33 | width: 20px; 34 | } 35 | 36 | .twoQuarterBig { 37 | width: 60px; 38 | height: 65px; 39 | border: 4px solid white; 40 | border-top: 4px solid transparent; 41 | border-bottom: 4px solid transparent; 42 | border-radius: 50%; 43 | animation: spin 3s infinite; 44 | 45 | } 46 | 47 | .twoQuarterSmall { 48 | width: 40px; 49 | height: 40px; 50 | border: 4px solid white; 51 | border-left: 4px solid transparent; 52 | border-right: 4px solid transparent; 53 | border-radius: 50%; 54 | animation: spin 6s infinite; 55 | 56 | } 57 | 58 | .lineSmallLeft { 59 | width: 40px; 60 | height: 0px; 61 | margin-left: 100px; 62 | border-bottom: 4px solid white; 63 | position: absolute; 64 | 65 | 66 | } 67 | 68 | .lineSmallRight{ 69 | width: 40px; 70 | height: 0px; 71 | margin-right: 100px; 72 | border-bottom: 4px solid white; 73 | position: absolute; 74 | } 75 | 76 | .lineBigUp { 77 | width: 4px; 78 | height: 210px; 79 | margin-top: 120px; 80 | border-top: 50px solid white; 81 | position: absolute; 82 | } 83 | 84 | .lineBigDown { 85 | width: 4px; 86 | height: 210px; 87 | margin-bottom: 120px; 88 | border-bottom: 50px solid white; 89 | position: absolute; 90 | } 91 | 92 | 93 | @keyframes spin { 94 | 100% { 95 | transform: rotateZ(360deg); 96 | } 97 | } -------------------------------------------------------------------------------- /react-frontend/src/components/visualizer.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, Fragment } from 'react' 2 | import { Graph } from 'react-d3-graph' 3 | import { parseNodesFromCalls, parseEdgesFromNodes } from '../util/graph-util' 4 | import OutlinedCard from './placeholder' 5 | import './visualizer.css' 6 | 7 | 8 | const Visualizer = (props) => { 9 | /* 10 | code here to always run whenever callTrace changes. 11 | incrementally add values from callTrace to renderTrace 12 | */ 13 | const funcName = props.name 14 | const callTrace = props.callTrace 15 | const ANIMATION_SPEED = 1100 - props.renderSpeed * 10; // milliseconds per interval 16 | const [graphState, setGraphState] = useState({ 17 | nodes: [], 18 | links: [] 19 | }) 20 | 21 | useEffect(() => { 22 | /** 23 | * Call utility functions here to transform callTrace into a list of nodes. 24 | * Also transform the list of nodes into a list of edges. 25 | * Set nodes to list of nodes. 26 | * Incrementally add edge by edge to links from list of edges. 27 | */ 28 | setGraphState({ 29 | nodes: [], 30 | links: [] 31 | }) 32 | const listOfNodes = parseNodesFromCalls(callTrace, funcName) 33 | const listOfEdges = parseEdgesFromNodes(listOfNodes) 34 | let interval 35 | let nodes = [] 36 | let links = [] 37 | if (listOfNodes.length > 0) { 38 | nodes.push(listOfNodes.shift()) 39 | setGraphState({ 40 | nodes: nodes, 41 | links: [] 42 | }) 43 | } else { 44 | setGraphState({ 45 | nodes: [], 46 | links: [] 47 | }) 48 | } 49 | interval = setInterval(() => { 50 | if (listOfEdges.length > 0) { 51 | // incrementally takes an item from listOfEdges and adds them to graphState.nodes 52 | nodes.push(listOfNodes.shift()) 53 | links.push(listOfEdges.shift()) 54 | setGraphState({ 55 | nodes: nodes, 56 | links: links 57 | }) 58 | } else { 59 | // eslint-disable-next-line 60 | props.setIsRunning(false) 61 | clearInterval(interval) 62 | } 63 | }, ANIMATION_SPEED); 64 | return () => clearInterval(interval); 65 | // eslint-disable-next-line 66 | }, [callTrace, funcName, ANIMATION_SPEED]); 67 | const myConfig = { 68 | nodeHighlightBehavior: true, 69 | node: { 70 | color: "red", 71 | size: 500, 72 | highlightStrokeColor: "blue", 73 | fontSize: 30, 74 | highlightFontSize: 50, 75 | labelPosition: "top", 76 | labelProperty: "label", 77 | fontColor: "white" 78 | }, 79 | d3: { 80 | gravity: -1000 81 | }, 82 | link: { 83 | highlightColor: "yellow", 84 | }, 85 | directed: true, 86 | width: window.innerWidth * 2, 87 | height: window.innerHeight, 88 | staticGraph: !props.jelly 89 | }; 90 | // include methods to manipulate the nodes 91 | const onClickNode = (nodeId) => { 92 | let nodesSoFar = graphState.nodes 93 | for (let i = 0; i < nodesSoFar.length; i++) { 94 | if (nodesSoFar[i].id === nodeId) { 95 | const originalColor = nodesSoFar[i].color 96 | nodesSoFar[i] = { 97 | id: nodesSoFar[i].id, 98 | caller: nodesSoFar[i].caller, 99 | result: nodesSoFar[i].result, 100 | label: nodesSoFar[i].result, 101 | color: "cyan", 102 | size: nodesSoFar[i].size 103 | } 104 | setGraphState({ 105 | nodes: nodesSoFar, 106 | links: graphState.links 107 | }) 108 | setTimeout(() => { 109 | nodesSoFar[i] = { 110 | id: nodesSoFar[i].id, 111 | caller: nodesSoFar[i].caller, 112 | result: nodesSoFar[i].result, 113 | label: nodesSoFar[i].id, 114 | color: originalColor, 115 | size: nodesSoFar[i].size 116 | } 117 | setGraphState({ 118 | nodes: nodesSoFar, 119 | links: graphState.links 120 | }) 121 | }, 3000); 122 | break 123 | } 124 | } 125 | } 126 | 127 | let placeHolder; 128 | 129 | if (props.isLoading) { 130 | console.log(props.isLoading) 131 | placeHolder = ( 132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 | ) 146 | } else { 147 | // put promo stuff and instructions here 148 | placeHolder = ( 149 | 150 | ) 151 | } 152 | 153 | return 154 | { 155 | graphState.nodes.length > 0 && !props.isLoading ? 156 | : 162 | placeHolder 163 | } 164 | 165 | } 166 | 167 | export default Visualizer -------------------------------------------------------------------------------- /react-frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /react-frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.unregister(); 18 | -------------------------------------------------------------------------------- /react-frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /react-frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /react-frontend/src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | type Config = { 24 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 25 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 26 | }; 27 | 28 | export function register(config?: Config) { 29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 30 | // The URL constructor is available in all browsers that support SW. 31 | const publicUrl = new URL( 32 | process.env.PUBLIC_URL, 33 | window.location.href 34 | ); 35 | if (publicUrl.origin !== window.location.origin) { 36 | // Our service worker won't work if PUBLIC_URL is on a different origin 37 | // from what our page is served on. This might happen if a CDN is used to 38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 39 | return; 40 | } 41 | 42 | window.addEventListener('load', () => { 43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 44 | 45 | if (isLocalhost) { 46 | // This is running on localhost. Let's check if a service worker still exists or not. 47 | checkValidServiceWorker(swUrl, config); 48 | 49 | // Add some additional logging to localhost, pointing developers to the 50 | // service worker/PWA documentation. 51 | navigator.serviceWorker.ready.then(() => { 52 | console.log( 53 | 'This web app is being served cache-first by a service ' + 54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 55 | ); 56 | }); 57 | } else { 58 | // Is not localhost. Just register service worker 59 | registerValidSW(swUrl, config); 60 | } 61 | }); 62 | } 63 | } 64 | 65 | function registerValidSW(swUrl: string, config?: Config) { 66 | navigator.serviceWorker 67 | .register(swUrl) 68 | .then(registration => { 69 | registration.onupdatefound = () => { 70 | const installingWorker = registration.installing; 71 | if (installingWorker == null) { 72 | return; 73 | } 74 | installingWorker.onstatechange = () => { 75 | if (installingWorker.state === 'installed') { 76 | if (navigator.serviceWorker.controller) { 77 | // At this point, the updated precached content has been fetched, 78 | // but the previous service worker will still serve the older 79 | // content until all client tabs are closed. 80 | console.log( 81 | 'New content is available and will be used when all ' + 82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 83 | ); 84 | 85 | // Execute callback 86 | if (config && config.onUpdate) { 87 | config.onUpdate(registration); 88 | } 89 | } else { 90 | // At this point, everything has been precached. 91 | // It's the perfect time to display a 92 | // "Content is cached for offline use." message. 93 | console.log('Content is cached for offline use.'); 94 | 95 | // Execute callback 96 | if (config && config.onSuccess) { 97 | config.onSuccess(registration); 98 | } 99 | } 100 | } 101 | }; 102 | }; 103 | }) 104 | .catch(error => { 105 | console.error('Error during service worker registration:', error); 106 | }); 107 | } 108 | 109 | function checkValidServiceWorker(swUrl: string, config?: Config) { 110 | // Check if the service worker can be found. If it can't reload the page. 111 | fetch(swUrl, { 112 | headers: { 'Service-Worker': 'script' } 113 | }) 114 | .then(response => { 115 | // Ensure service worker exists, and that we really are getting a JS file. 116 | const contentType = response.headers.get('content-type'); 117 | if ( 118 | response.status === 404 || 119 | (contentType != null && contentType.indexOf('javascript') === -1) 120 | ) { 121 | // No service worker found. Probably a different app. Reload the page. 122 | navigator.serviceWorker.ready.then(registration => { 123 | registration.unregister().then(() => { 124 | window.location.reload(); 125 | }); 126 | }); 127 | } else { 128 | // Service worker found. Proceed as normal. 129 | registerValidSW(swUrl, config); 130 | } 131 | }) 132 | .catch(() => { 133 | console.log( 134 | 'No internet connection found. App is running in offline mode.' 135 | ); 136 | }); 137 | } 138 | 139 | export function unregister() { 140 | if ('serviceWorker' in navigator) { 141 | navigator.serviceWorker.ready 142 | .then(registration => { 143 | registration.unregister(); 144 | }) 145 | .catch(error => { 146 | console.error(error.message); 147 | }); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /react-frontend/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /react-frontend/src/util/graph-util.js: -------------------------------------------------------------------------------- 1 | export function parseNodesFromCalls(calls, funcName) { 2 | /** 3 | * given an array of ["[parameter]", "result", "[caller]"], 4 | * return a list of nodes with a caller where each node is unique. 5 | * So if there are duplicate [parameter] or [caller], then add a space " ". 6 | * 7 | * example: [ 8 | * {id: [1], caller: 0, result: 0}, 9 | * {id: [2], caller: [1], result: 0}, 10 | * {id: [3], caller: [2], result: 0} 11 | * ] 12 | * 13 | */ 14 | let spaces = {} 15 | let heights = {} 16 | 17 | for (let call of calls) { 18 | spaces[call[0]] = "" 19 | } 20 | // "0" is the root caller 21 | spaces["0"] = "" 22 | 23 | let nodes = [] 24 | for (let i = 0; i < calls.length; i++) { 25 | console.log(calls[i]) 26 | const param = calls[i][0] 27 | const result = calls[i][1] 28 | const caller = calls[i][2] 29 | // shift a new " " with param 30 | let callerToAdd = "(" + caller.substring(1, caller.length - 1) + ")" + spaces[caller] 31 | callerToAdd = funcName + callerToAdd.substring(0, callerToAdd.length - 1) 32 | const paramToAdd = funcName + "(" + param.substring(1, param.length - 1) + ")" + spaces[param] 33 | spaces[param] += " " 34 | /** 35 | * Nodes with the same caller should have the same height. 36 | */ 37 | let heightToAdd = i * 100 38 | if (callerToAdd in heights) { 39 | heightToAdd = heights[callerToAdd] 40 | } else { 41 | heights[callerToAdd] = heightToAdd 42 | } 43 | nodes.push({ 44 | id: paramToAdd, 45 | caller: callerToAdd, 46 | result: result, 47 | label: paramToAdd, 48 | x: i * 100, 49 | y: heightToAdd, 50 | size: 500, 51 | color: "red" 52 | }) 53 | } 54 | // give the first node extra size and change its color 55 | if (nodes.length > 0) { 56 | nodes[0].size = 800 57 | nodes[0].color = "green" 58 | } 59 | // look for all base cases and set their color to blue 60 | for (let i = 0; i < nodes.length; i++) { 61 | if (checkIsBaseCase(nodes[i], nodes)) { 62 | nodes[i].color = "blue" 63 | } 64 | } 65 | return nodes 66 | } 67 | 68 | 69 | export function parseEdgesFromNodes(listOfNodes) { 70 | /** 71 | * Assume each node in listOfNodes has: 72 | * - param 73 | * - caller 74 | * - result 75 | * link source: caller, target: param. 76 | * 77 | * PROBLEM: each object in the list must be unique. 78 | */ 79 | let listOfEdges = [] 80 | for (let i = 1; i < listOfNodes.length; i++) { 81 | listOfEdges.push({ 82 | source: listOfNodes[i].caller, 83 | target: listOfNodes[i].id 84 | }) 85 | } 86 | return listOfEdges 87 | } 88 | 89 | function checkIsBaseCase(node, nodes) { 90 | /** 91 | * given a list of nodes: 92 | * - check every other node in nodes and see if their caller is that node. 93 | * - if so, then it's not a base case. 94 | */ 95 | for (let i = 0; i < nodes.length; i++) { 96 | if (node.id === nodes[i].caller) { 97 | return false 98 | } 99 | } 100 | return true 101 | } -------------------------------------------------------------------------------- /react-frontend/src/util/utility-functions.js: -------------------------------------------------------------------------------- 1 | // getFuncName helper 2 | const findDef = (s, code) => { 3 | const lookFor = "fed" 4 | for (let i = 0; i < lookFor.length; i++) { 5 | if (!(s - i >= 0 && lookFor[i] === code[s - i])) { 6 | return false 7 | } 8 | } 9 | // we know now that s is the starting point for 'f' in 'def'. 10 | let functionName = "" 11 | for (let j = s + 1; j < code.length; j++) { 12 | if (code[j] === "(") { 13 | break 14 | } 15 | if (code[j] !== " ") { 16 | functionName += code[j] 17 | } 18 | } 19 | return functionName 20 | } 21 | /** 22 | * @param {string} code 23 | */ 24 | export function getFuncName(code) { 25 | // search for 'def' and get whatever text comes after ' ' and ends at '(' 26 | // if no def was found or if functionName is > 8 characters long, then return undefined. 27 | for (let i = code.length - 1; i >= 0; i--) { 28 | // parse backwards to find the word 'def' 29 | let functionName = findDef(i, code) 30 | if (functionName !== false) { 31 | return functionName 32 | } 33 | } 34 | return -1 35 | } 36 | 37 | 38 | // getFuncCall helper 39 | const reverseString = (str) => { 40 | let ans = "" 41 | for (let c of str) { 42 | ans = c + ans 43 | } 44 | return ans 45 | } 46 | 47 | // getFuncCall helper 48 | const findCall = (s, code, functionName) => { 49 | const lookFor = functionName 50 | for (let i = 0; i < lookFor.length; i++) { 51 | if (!(s - i >= 0 && lookFor[i] === code[s - i])) { 52 | return false 53 | } 54 | } 55 | // we know that at position s is the last character for functionName 56 | // check that there is no 'def' a few characters later. 57 | let check1 = s - lookFor.length - 1 >= 0 && code[s - lookFor.length - 1] === "f" 58 | let check2 = s - lookFor.length - 2 >= 0 && code[s - lookFor.length - 2] === "e" 59 | let check3 = s - lookFor.length - 3 >= 0 && code[s - lookFor.length - 3] === "d" 60 | 61 | if (check1 && check2 && check3) { 62 | return false 63 | } 64 | let functionCall = "" 65 | for (let k = s - lookFor.length; k < code.length; k++) { 66 | if (code[k] !== ")") { 67 | functionCall += code[k] 68 | } else { 69 | functionCall += ")" 70 | return functionCall 71 | } 72 | } 73 | return false 74 | } 75 | /** 76 | * @param {string} code 77 | * @param {string} functionName 78 | */ 79 | export function getFuncCall(code, functionName) { 80 | // parse code in reverse, and actively search for functionName. 81 | // if found, remember index i, and go backwards some more to look for 'fed', 82 | // not counting spaces. If we encounter 3 foreign characters that are not 'fed', we 83 | // are good. 84 | // parse forward from i and collect the entire function call, ending at ")". 85 | // if no ")" was found, then return -1. 86 | // once entire function call is collected, store i, and store i + functionCall.length 87 | // return ['call', [i, i + functionCall.length]] 88 | const lookFor = reverseString(functionName) 89 | for (let i = code.length - 1; i >= 0; i--) { 90 | let call = findCall(i, code, lookFor) 91 | if (call) { 92 | return [call, [i - functionName.length, i + (call.length - functionName.length)]] 93 | } 94 | } 95 | return -1 96 | } 97 | 98 | 99 | 100 | /** 101 | * 102 | * @param {string} code 103 | * @param { -1 | (string | number[])[]} callLocation 104 | */ 105 | export function truncateFuncCall(code, functionCall) { 106 | // completely remove functionCall from code via it's callLocation. 107 | // split the entire code string from [s] to [s + functionCall.length] 108 | // recombine the split parts. 109 | const callLocation = functionCall[1] 110 | let left = "" 111 | let right = "" 112 | for (let i = 0; i < callLocation[0]; i++) { 113 | left += code[i] 114 | } 115 | for (let j = callLocation[1] + 1; j < code.length; j++) { 116 | right += code[j] 117 | } 118 | return left + right 119 | } -------------------------------------------------------------------------------- /react-frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /remote-code-runner/app.py: -------------------------------------------------------------------------------- 1 | import flask 2 | from flask import request 3 | from flask_cors import CORS 4 | import parseinput as pi 5 | import remoteCodeRunner as rc 6 | import hasImport as hi 7 | 8 | app = flask.Flask(__name__) 9 | CORS(app) 10 | 11 | # for each flask request, we need: 12 | # - data in the body to represent function logic 13 | # - data in the parameters to represent function name and function call 14 | # if either is missing, we send back an error message. If the remote 15 | # code execution raises an error, we send back that error message as well. 16 | 17 | @app.route('/execute', methods=['POST']) 18 | def execute(): 19 | if 'funcName' in request.args and 'funcCall' in request.args: 20 | funcName = request.args['funcName'] 21 | funcCall = request.args['funcCall'] 22 | if funcName != funcCall[:len(funcName)]: 23 | print(funcName) 24 | print(funcCall[:len(funcName)] != funcName) 25 | return "Check and make sure you defined EXACTLY one function.", 400 26 | body = str(request.data) 27 | inputCode = pi.parseInput(body) 28 | 29 | # check import statements 30 | if hi.hasImport(inputCode): 31 | return "Whatever you're trying to do, just don't.", 400 32 | 33 | functionTrace = rc.runCode(inputCode, funcName, funcCall) 34 | if functionTrace[0]: 35 | # success case 36 | # set response status to 200 37 | return functionTrace[1], 200 38 | else: 39 | # case of invalid value 40 | # set reponse status to 400 41 | return functionTrace[1], 400 42 | else: 43 | # case of an invalid request 44 | return "Please specify both the function name and the initial function call." 45 | 46 | if __name__ == "__main__": 47 | app.run(port=5000) -------------------------------------------------------------------------------- /remote-code-runner/hasImport.py: -------------------------------------------------------------------------------- 1 | def isImport(s, inputCode): 2 | check = "import" 3 | for i in range(len(check)): 4 | if check[i] != inputCode[s + i]: 5 | return False 6 | return True 7 | 8 | def hasImport(inputCode): 9 | for k in range(len(inputCode)): 10 | if isImport(k, inputCode): 11 | return True 12 | return False -------------------------------------------------------------------------------- /remote-code-runner/injectCode.py: -------------------------------------------------------------------------------- 1 | # alwaysInject = """ 2 | # class Call: 3 | # def __init__(self, params, level): 4 | # self.params = params 5 | # self.level = level 6 | # self.result = None 7 | # functionCalls_ = [] 8 | alwaysInject = """ 9 | class JQGRg8XBnB4: 10 | def __init__(self, params, caller): 11 | self.params = params 12 | self.caller = caller 13 | self.result = None 14 | 15 | bwmSjveL3Lc = [] 16 | """ 17 | 18 | # goes through our list and adds the output. 19 | # alwaysInjectLast = """ 20 | # response = "|" 21 | # for call in functionCalls_: 22 | # segment = str(call.params) + ":" + str(call.result) + ":" + str(call.level) + "|" 23 | # response += segment 24 | # print(response) 25 | # """ 26 | alwaysInjectLast = """ 27 | ePpPVE_GGJw = "|" 28 | for call in bwmSjveL3Lc: 29 | segment = str(call.params) + ":" + str(call.result) + ":" + str(call.caller) + "|" 30 | ePpPVE_GGJw += segment 31 | print(ePpPVE_GGJw) 32 | """ 33 | 34 | # injectInBeginningOfFunc1 = """ 35 | # c = Call([""" 36 | # injectInBeginningOfFunc2 = """], WZwr2a_lFWY) 37 | 38 | injectInBeginningOfFunc1 = """ 39 | tFRcEOmkDM8 = JQGRg8XBnB4([""" 40 | injectInBeginningOfFunc2 = """], WZwr2a_lFWY)""" 41 | 42 | # we need to add indentation to every line here accordingly. 43 | # constantLinesToAdd = "c.result = r; functionCalls_.append(c); return r" 44 | constantLinesToAdd = "tFRcEOmkDM8.result = fE2h3lGlOsk; bwmSjveL3Lc.append(tFRcEOmkDM8); return fE2h3lGlOsk;" 45 | 46 | 47 | 48 | # adds custom return statements 49 | def checkReturnStatement(inputCode, s): 50 | # start from s and check to see if 51 | # s + n is a return statement with 52 | # return values. returns that return 53 | # value and start bound and end bound 54 | # representing where the return statement begins 55 | # and where it ends. 56 | # checks return validity 57 | lookFor = "return" 58 | for i in range(len(lookFor)): 59 | if not (i + s < len(inputCode) and lookFor[i] == inputCode[i + s]): 60 | return None 61 | 62 | retVal = "" 63 | e = s 64 | for j in range(s + 7, len(inputCode)): 65 | if inputCode[j] != "\n": 66 | retVal += inputCode[j] 67 | else: 68 | e = j 69 | break 70 | return (retVal, s, e) 71 | def findReturnOutValues(inputCode): 72 | global constantLinesToAdd 73 | # looks for every return statement and adds their corresponding 74 | # output values to a list. We also want to store the location of 75 | # those return values as startbound and endbound (s, e). 76 | i = 0 77 | while i < len(inputCode): 78 | returnStatement = checkReturnStatement(inputCode, i) 79 | if returnStatement: 80 | # modify inputCode here 81 | # split input code. 82 | left = inputCode[:returnStatement[1]] 83 | right = inputCode[returnStatement[2]:] 84 | linesToAdd = "fE2h3lGlOsk = " + returnStatement[0] + "; " + constantLinesToAdd 85 | inputCode = left + linesToAdd + right 86 | i += len(linesToAdd) 87 | i += 1 88 | return inputCode 89 | def addCustomReturnStatements(inputCode): 90 | # finds every return statement, and replaces it with our custom 91 | # code 92 | inputCode = findReturnOutValues(inputCode) 93 | return inputCode 94 | 95 | # adds the 'Call() class and level += 1, and dynamically changes the input parameters to match the function call. 96 | def checkIsFuncName(inputFunctionName, inputCode, s): 97 | for i in range(len(inputFunctionName)): 98 | if not (i + s < len(inputCode) and inputFunctionName[i] == inputCode[i + s]): 99 | return False 100 | return True 101 | def untilFuncEnd(inputCode, startbound): 102 | # get list of parameters here. 103 | # returns distance until function ends AND a string of parameters. 104 | parameters = "" 105 | dtc = 0 106 | for i in range(startbound, len(inputCode)): 107 | if inputCode[i] == ":": 108 | return (dtc + 1, parameters) 109 | else: 110 | if inputCode[i] != "(" and inputCode[i] != ")": 111 | parameters += inputCode[i] 112 | dtc += 1 113 | def injectCallFunction(inputCode, inputFunctionName): 114 | global injectInBeginningOfFunc 115 | # find first function name: 116 | for s in range(len(inputCode)): 117 | if checkIsFuncName(inputFunctionName, inputCode, s): 118 | # insert injectInBeginningFunc here 119 | collection = untilFuncEnd(inputCode, s + len(inputFunctionName)) 120 | leftBound = s + len(inputFunctionName) + collection[0] 121 | left = inputCode[:leftBound] 122 | right = inputCode[leftBound:] 123 | inputParameters = collection[1] 124 | injectInBeginningOfFunc = injectInBeginningOfFunc1 + inputParameters + injectInBeginningOfFunc2 125 | return [left + injectInBeginningOfFunc + right, inputParameters] 126 | 127 | 128 | # adds the 'caller' parameter to every single instance of the function call 129 | def addLevelParameter(inputCode, inputFunctionName, caller): 130 | s = 0 131 | isFirstFunc = True 132 | while s < len(inputCode): 133 | if checkIsFuncName(inputFunctionName, inputCode, s): 134 | cut = s + len(inputFunctionName) + 1 135 | left = inputCode[:cut] 136 | right = inputCode[cut:] 137 | if isFirstFunc: 138 | inputCode = left + "WZwr2a_lFWY" + ", " + right 139 | isFirstFunc = False 140 | else: 141 | inputCode = left + "[" + caller + "]" + ", " + right 142 | s += 1 143 | return inputCode 144 | def addZero(inputFunctionCall): 145 | inputFunctionCall = "\n" + inputFunctionCall 146 | for i in range(len(inputFunctionCall)): 147 | if inputFunctionCall[i] == "(": 148 | left = inputFunctionCall[:i+1] 149 | right = inputFunctionCall[i+1:] 150 | inputFunctionCall = left + "0, " + right 151 | return inputFunctionCall 152 | 153 | def injectCode(inputCode, inputFunctionName, inputFunctionCall): 154 | finalOutput = "" 155 | finalOutput += alwaysInject 156 | paramsInjection = injectCallFunction(inputCode, inputFunctionName) 157 | inputCode = paramsInjection[0] 158 | inputCode = addLevelParameter(inputCode, inputFunctionName, paramsInjection[1]) 159 | finalOutput += addCustomReturnStatements(inputCode) 160 | finalOutput += addZero(inputFunctionCall) 161 | finalOutput += alwaysInjectLast 162 | return finalOutput -------------------------------------------------------------------------------- /remote-code-runner/judge.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | import json 4 | import time 5 | 6 | API_KEY = os.environ.get('API_KEY') 7 | 8 | def sendCodeToJudge(code): 9 | print(code) 10 | print('________________________________________\n\n') 11 | url = "https://judge0.p.rapidapi.com/submissions" 12 | payload = { "language_id": 71, "source_code": code} 13 | headers = { 14 | 'x-rapidapi-host': "judge0.p.rapidapi.com", 15 | 'x-rapidapi-key': API_KEY, 16 | 'content-type': "application/json", 17 | 'accept': "application/json" 18 | } 19 | response = requests.request("POST", url, data=json.dumps(payload), headers=headers) 20 | responseObj = json.loads(response.text) 21 | if 'error' in responseObj: 22 | return False 23 | if 'token' not in responseObj: 24 | return False 25 | token = responseObj['token'] 26 | time.sleep(2) # wait 2 seconds before retrieving results 27 | URL = "https://judge0.p.rapidapi.com/submissions/" + token 28 | HEADERS = { 29 | 'x-rapidapi-host': "judge0.p.rapidapi.com", 30 | 'x-rapidapi-key': API_KEY 31 | } 32 | subResponse = requests.request("GET", URL, headers=HEADERS) 33 | codeSubmissionResponse = json.loads(subResponse.text) 34 | print(codeSubmissionResponse) 35 | return [codeSubmissionResponse, token] -------------------------------------------------------------------------------- /remote-code-runner/parseinput.py: -------------------------------------------------------------------------------- 1 | def parseInput(inputCode): 2 | # remove the b" in the beginning and make sure \n is actually a new line. 3 | inputCode = inputCode[2:] 4 | inputCode = inputCode[:-1] 5 | inputCode = inputCode.replace('\\n', '\n').replace('\\t', '\t') 6 | return inputCode -------------------------------------------------------------------------------- /remote-code-runner/remoteCodeRunner.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import injectCode as ij 3 | import judge as j 4 | import tryget as tg 5 | 6 | def runCode(inputCode, inputFunctionName, inputFunctionCall): 7 | # On the frontend, we will parse the input to get the 8 | # - function name 9 | # - single function call 10 | # - we will truncate this function call line and add it as a seperate parameter 11 | # All the inputs we will collect from the frontend: 12 | # inputCode here is truncated to exclude the single function call. 13 | readyToExe = ij.injectCode(inputCode, inputFunctionName, inputFunctionCall) 14 | res = j.sendCodeToJudge(readyToExe) 15 | if res == False: # couldn't even POST the user's code 16 | return (False, "Be sure to follow all rules laid out in the instructions: Make sure there are no syntax errors, logic errors, and infinite recursions... and any funky business ;)") 17 | 18 | codeSubmissionResults = res[0] # output object with stdout and stderr 19 | submissionToken = res[1] # token reference ID 20 | 21 | # do processing checks first before doing error checks 22 | if codeSubmissionResults['status']['id'] < 3: 23 | # still processing... 24 | codeSubmissionResults = tg.tryGet(submissionToken) 25 | 26 | if codeSubmissionResults['status']['id'] == 3: 27 | # success case 28 | output = codeSubmissionResults['stdout'][1:len(codeSubmissionResults['stdout']) - 2] 29 | return (True, output) 30 | else: 31 | # error case 32 | if codeSubmissionResults['stderr']: 33 | return (False, codeSubmissionResults['stderr']) 34 | else: 35 | return (False, "Unknown error.") -------------------------------------------------------------------------------- /remote-code-runner/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==1.1.2 2 | Flask-Cors==3.0.9 3 | requests==2.24.0 4 | gunicorn==20.0.4 -------------------------------------------------------------------------------- /remote-code-runner/tryget.py: -------------------------------------------------------------------------------- 1 | import time 2 | import os 3 | import requests 4 | import json 5 | 6 | API_KEY = os.environ.get('API_KEY') 7 | 8 | def tryGet(token): 9 | URL = "https://judge0.p.rapidapi.com/submissions/" + token 10 | HEADERS = { 11 | 'x-rapidapi-host': "judge0.p.rapidapi.com", 12 | 'x-rapidapi-key': API_KEY 13 | } 14 | subResponse = requests.request("GET", URL, headers=HEADERS) 15 | codeSubmissionResponse = json.loads(subResponse.text) 16 | retryNumber = 1 17 | while codeSubmissionResponse['status']['id'] < 3: 18 | time.sleep(2) # wait 2 seconds before trying again 19 | codeSubmissionResponse = requests.request("GET", URL, headers=HEADERS) 20 | print("retryNumber " + str(i)) 21 | print(codeSubmissionResponse) 22 | i += 1 23 | return codeSubmissionResponse --------------------------------------------------------------------------------