%%C
magic to compile C code in IPython¶License: BSD, © Fernando Perez.
From a discussion on gitter with @D3f0, here's a simple magic that calls a C compiler to compile C sources and execute them directly, making it easy to interactively explore small snippets of self-contained C code.
Much more could be done here, including:
%%CExtension
magic that would link the code as an extension in the currentPython process (similar to how the %%cython
magic works)
But this is a reasonable starting point, written in a hurry.
Note: this is not a full-blown standalone C kernel; that already exists. I was focusing here on a simple magic for easily interleaving C into a normal IPython session.
# Stdlib imports
import atexit
import os
import shutil
import subprocess as sp
import sys
import tempfile
# Tools to build a stateful magic in IPython
from IPython.core.magic import Magics, magics_class, cell_magic
@magics_class
class CTools(Magics):
def __init__(self, shell=None, **kwargs):
super().__init__(shell, **kwargs)
self.CC = kwargs.get('CC', 'gcc')
self.tmpdir = tempfile.mkdtemp()
atexit.register(shutil.rmtree, self.tmpdir, ignore_errors=True)
@cell_magic
def C(self, line, cell):
"""Compile and execute the cell assuming it's valid, self-contained C code.
Any extra arguments on the first line are passed to the compiler (e.g. `-lm` to
link the math library). The body of the cell should compile as a standalone C
file.
Returns the return code of the compilation process if it fails, or of the
executed C code if non-zero (if both succeed, returns None).
"""
# We treat all extra cmd line args on the input line as CC flags
ccflags = line.split() if line else []
# Write the source
fd, fname = tempfile.mkstemp(dir=self.tmpdir, text=True, suffix='.c')
fobj = os.fdopen(fd, 'w')
fobj.write(cell)
fobj.close()
# Compile into a binary
binname = fname + '.o'
comp_cmd = [self.CC] + ccflags + [fname, '-o', binname]
comp = sp.run(comp_cmd, stdout=sp.PIPE, stderr=sp.PIPE)
# Call binary if compilation succeeded, otherwise show user compiler errors
if comp.returncode:
print("*** Error in compilation ***", file=sys.stderr)
if comp.stdout:
print(comp.stdout.decode(), end='')
if comp.stderr:
print(comp.stderr.decode(), file=sys.stderr, end='')
return comp.returncode
else:
bin = sp.run([binname], stdout=sp.PIPE, stderr=sp.PIPE)
if bin.stdout:
print(bin.stdout.decode(), end='')
if bin.stderr:
print(bin.stderr.decode(), file=sys.stderr, end='')
if bin.returncode:
return bin.returncode
# Register the magics for live use
get_ipython().register_magics(CTools)
A simple example that prints to stdout and stderr:
%%C
#include <stdio.h>
int main(void) {
printf("Hello world.\n");
fprintf( stderr, "HELP!" );
return 0;
}
Hello world.
HELP!
An example with a syntax error in the sources. Note the return value is the actual return code of the compiler process:
%%C
#include <stdio.h>
int main(void) {
// This code has an error:
pritf("Hello world\n"a);
return 0;
}
*** Error in compilation *** /var/folders/j1/n8kn9ftd7257n2rvkkzlj3mc0010dw/T/tmptclob0mg/tmpxy5yb85j.c:4:5: warning: implicit declaration of function 'pritf' is invalid in C99 [-Wimplicit-function-declaration] pritf("Hello world\n"a); ^ /var/folders/j1/n8kn9ftd7257n2rvkkzlj3mc0010dw/T/tmptclob0mg/tmpxy5yb85j.c:4:26: error: expected ')' pritf("Hello world\n"a); ^ /var/folders/j1/n8kn9ftd7257n2rvkkzlj3mc0010dw/T/tmptclob0mg/tmpxy5yb85j.c:4:10: note: to match this '(' pritf("Hello world\n"a); ^ 1 warning and 1 error generated.
1
The return value of a successful call is the return of the C process, if non-zero (we basically replace 0 retcode with None to avoid littering normall calls with extra zeros):
%%C
#include <stdio.h>
int main(void) {
printf("Non-zero return.\n");
return 128;
}
Non-zero return.
128
An example that passes extra flags to the compiler (in this case, linker flags for the math library):
%%C -lm
#include <stdio.h>
#include <math.h>
int main(void) {
printf("pi = %f\n", M_PI);
printf("sin(pi) = %f", sin(M_PI/2.0));
return 0;
}
pi = 3.141593 sin(pi) = 1.000000