Last active
August 23, 2016 23:06
-
-
Save fperez/60bb699660d6909d41c6ba4e04f3a2de to your computer and use it in GitHub Desktop.
A quick and dirty implementation of a %%C magic to compile C code in IPython
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# A quick and dirty implementation of a `%%C` magic to compile C code in IPython\n", | |
"\n", | |
"License: BSD, © [Fernando Perez](http://github.com/fperez).\n", | |
"\n", | |
"From a discussion on gitter with @D3f0, here's a simple magic that calls a C compiler \n", | |
"to compile C sources and execute them directly, making it easy to interactively explore \n", | |
"small snippets of self-contained C code.\n", | |
"\n", | |
"Much more could be done here, including:\n", | |
"\n", | |
"* smart caching of sources\n", | |
"* better configuration of the C compiler, including linker/include flags\n", | |
"* enabiling not only calling of external subprocesses but also perhaps providing a `%%CExtension` magic that would link the code as an extension in the current\n", | |
"Python process (similar to how the `%%cython` magic works)\n", | |
"* changing the CodeMirror mode to C syntax\n", | |
"* etc.\n", | |
"\n", | |
"But this is a reasonable starting point, written in a hurry.\n", | |
"\n", | |
"**Note:** this is *not* a full-blown standalone C kernel; that [already exists](https://github.com/brendan-rius/jupyter-c-kernel).\n", | |
"I was focusing here on a simple magic for easily interleaving C into a normal IPython\n", | |
"session." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"metadata": { | |
"collapsed": true | |
}, | |
"outputs": [], | |
"source": [ | |
"# Stdlib imports\n", | |
"import atexit\n", | |
"import os\n", | |
"import shutil\n", | |
"import subprocess as sp\n", | |
"import sys\n", | |
"import tempfile\n", | |
"\n", | |
"# Tools to build a stateful magic in IPython\n", | |
"from IPython.core.magic import Magics, magics_class, cell_magic\n", | |
"\n", | |
"\n", | |
"@magics_class\n", | |
"class CTools(Magics):\n", | |
" def __init__(self, shell=None, **kwargs):\n", | |
" super().__init__(shell, **kwargs)\n", | |
" self.CC = kwargs.get('CC', 'gcc')\n", | |
" self.tmpdir = tempfile.mkdtemp()\n", | |
" atexit.register(shutil.rmtree, self.tmpdir, ignore_errors=True)\n", | |
" \n", | |
" @cell_magic\n", | |
" def C(self, line, cell):\n", | |
" \"\"\"Compile and execute the cell assuming it's valid, self-contained C code.\n", | |
" \n", | |
" Any extra arguments on the first line are passed to the compiler (e.g. `-lm` to\n", | |
" link the math library). The body of the cell should compile as a standalone C\n", | |
" file.\n", | |
" \n", | |
" Returns the return code of the compilation process if it fails, or of the\n", | |
" executed C code if non-zero (if both succeed, returns None).\n", | |
" \"\"\"\n", | |
" # We treat all extra cmd line args on the input line as CC flags\n", | |
" ccflags = line.split() if line else []\n", | |
"\n", | |
" # Write the source \n", | |
" fd, fname = tempfile.mkstemp(dir=self.tmpdir, text=True, suffix='.c')\n", | |
" fobj = os.fdopen(fd, 'w')\n", | |
" fobj.write(cell)\n", | |
" fobj.close()\n", | |
" \n", | |
" # Compile into a binary\n", | |
" binname = fname + '.o'\n", | |
" comp_cmd = [self.CC] + ccflags + [fname, '-o', binname]\n", | |
" comp = sp.run(comp_cmd, stdout=sp.PIPE, stderr=sp.PIPE)\n", | |
" \n", | |
" # Call binary if compilation succeeded, otherwise show user compiler errors\n", | |
" if comp.returncode:\n", | |
" print(\"*** Error in compilation ***\", file=sys.stderr)\n", | |
" if comp.stdout:\n", | |
" print(comp.stdout.decode(), end='')\n", | |
" if comp.stderr:\n", | |
" print(comp.stderr.decode(), file=sys.stderr, end='')\n", | |
" return comp.returncode\n", | |
" else:\n", | |
" bin = sp.run([binname], stdout=sp.PIPE, stderr=sp.PIPE)\n", | |
" if bin.stdout:\n", | |
" print(bin.stdout.decode(), end='')\n", | |
" if bin.stderr:\n", | |
" print(bin.stderr.decode(), file=sys.stderr, end='')\n", | |
" if bin.returncode:\n", | |
" return bin.returncode\n", | |
"\n", | |
"\n", | |
"# Register the magics for live use\n", | |
"get_ipython().register_magics(CTools)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"A simple example that prints to stdout and stderr:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"metadata": { | |
"collapsed": false | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Hello world.\n" | |
] | |
}, | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"HELP!" | |
] | |
} | |
], | |
"source": [ | |
"%%C\n", | |
"#include <stdio.h>\n", | |
"int main(void) {\n", | |
" printf(\"Hello world.\\n\");\n", | |
" fprintf( stderr, \"HELP!\" );\n", | |
" return 0;\n", | |
"}" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"An example with a syntax error in the sources. Note the return value is the actual \n", | |
"return code of the compiler process:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"metadata": { | |
"collapsed": false | |
}, | |
"outputs": [ | |
{ | |
"name": "stderr", | |
"output_type": "stream", | |
"text": [ | |
"*** Error in compilation ***\n", | |
"/var/folders/j1/n8kn9ftd7257n2rvkkzlj3mc0010dw/T/tmptclob0mg/tmpxy5yb85j.c:4:5: warning: implicit declaration of function 'pritf' is invalid in C99 [-Wimplicit-function-declaration]\n", | |
" pritf(\"Hello world\\n\"a);\n", | |
" ^\n", | |
"/var/folders/j1/n8kn9ftd7257n2rvkkzlj3mc0010dw/T/tmptclob0mg/tmpxy5yb85j.c:4:26: error: expected ')'\n", | |
" pritf(\"Hello world\\n\"a);\n", | |
" ^\n", | |
"/var/folders/j1/n8kn9ftd7257n2rvkkzlj3mc0010dw/T/tmptclob0mg/tmpxy5yb85j.c:4:10: note: to match this '('\n", | |
" pritf(\"Hello world\\n\"a);\n", | |
" ^\n", | |
"1 warning and 1 error generated.\n" | |
] | |
}, | |
{ | |
"data": { | |
"text/plain": [ | |
"1" | |
] | |
}, | |
"execution_count": 3, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"%%C\n", | |
"#include <stdio.h>\n", | |
"int main(void) {\n", | |
" // This code has an error:\n", | |
" pritf(\"Hello world\\n\"a);\n", | |
" return 0;\n", | |
"}" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"The return value of a successful call is the return of the C process, if non-zero \n", | |
"(we basically replace 0 retcode with None to avoid littering normall calls with extra\n", | |
"zeros):" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"metadata": { | |
"collapsed": false | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Non-zero return.\n" | |
] | |
}, | |
{ | |
"data": { | |
"text/plain": [ | |
"128" | |
] | |
}, | |
"execution_count": 4, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"%%C\n", | |
"#include <stdio.h>\n", | |
"int main(void) {\n", | |
" printf(\"Non-zero return.\\n\");\n", | |
" return 128;\n", | |
"}" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"An example that passes extra flags to the compiler (in this case, linker flags for the math\n", | |
"library):" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": { | |
"collapsed": false | |
}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"pi = 3.141593\n", | |
"sin(pi) = 1.000000" | |
] | |
} | |
], | |
"source": [ | |
"%%C -lm\n", | |
"#include <stdio.h>\n", | |
"#include <math.h>\n", | |
"int main(void) {\n", | |
" printf(\"pi = %f\\n\", M_PI);\n", | |
" printf(\"sin(pi) = %f\", sin(M_PI/2.0));\n", | |
" return 0;\n", | |
"}" | |
] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.5.2" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 1 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment