Skip to content

Instantly share code, notes, and snippets.

@fperez
Last active August 23, 2016 23:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fperez/60bb699660d6909d41c6ba4e04f3a2de to your computer and use it in GitHub Desktop.
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
Display the source blob
Display the rendered blob
Raw
{
"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