Compare commits

...

75 Commits

Author SHA1 Message Date
marjohnsen
e88cb5f36b
Add void to empty parameter list (#9) 2025-01-12 14:04:56 +01:00
Jiho Park
5af96beec6
Fix language info name to c to get correct syntax highlighting. (#8) 2024-05-20 20:25:15 +02:00
Xaver K
ca79b349d9
Added support for changing the standard using a magic. (#5) 2022-10-26 18:04:01 +02:00
Xaver K
ddce20b4ca
Fixed crash when magic line is empty. (#4) 2022-10-26 17:29:32 +02:00
Xaver K
66cbe9b2ca Set bufferedOutput=True since that is the usual command line behaviour. Updated README. 2021-02-25 12:50:14 +01:00
Xaver K
4f8d436439 Fixed scanf for C89. Small improvements for newer standards. 2020-05-12 12:28:40 +02:00
Xaver K
dddbfe9e85 Ditched C89 support for number of chars read by scanf. 2020-05-11 19:53:02 +02:00
Xaver K
449e682b18 Introduced buffered output option to replicate terminal behaviour. 2020-05-11 19:03:22 +02:00
Xaver K
7c39e852ef Fixed delayed stderr output. 2020-05-08 15:53:37 +02:00
Xaver K
8e97b754aa
Support for other standards than c11 (#3)
* Finished C89/C90/C95/C99 support. Version can only be switched in kernel or with magics. Might be worth having separate kernels for each standard.

* Added option to link maths library. On by default.

* Fixed empty input error. Now input loops until there is input with length > 0.
2020-04-17 19:27:16 +02:00
Xaver K
f8e99ec24c Fixed combined getchar and scanf input to mimic command line. 2020-04-09 13:23:56 +02:00
Xaver K
ecbea115c7 Cleanup. 2020-04-03 14:07:04 +02:00
Xaver K
3429b362ba Added input buffer in stdio_wrap.h to mimic command line behaviour. Added wait on output to avoid race condition with program termination. 2020-04-03 13:48:32 +02:00
Xaver K
bddb7ca6b0
Static server (#2)
* Wrap input and file output functions for a read only static server.

* Added -y to docker apt-get in docker file. 

*  wError/wAll as options in Kernel, defaults only to wAll.
2020-03-27 12:27:14 +01:00
Xaver K
8b69d8354d Added stdio_wrap.h to MANIFEST to have it copied during install. 2020-03-27 11:26:35 +01:00
Xaver K
ff9d635f89
Stdin using notebook raw_input (#1)
* Added stdin input via the notebook frontend

* Added support for single line code snippets.

* Updated README. 

* Updated Dockerfile. 

* Changed file cleanup to happen right after execution, rather than before kernel shutdown.
2020-03-22 12:52:45 +01:00
Brendan Rius
c9a327f77c
Merge pull request #46 from QuLogic/sdist-info
Add some metdata into sdist
2018-06-03 22:21:48 +02:00
Brendan Rius
4233523364
Merge branch 'master' into sdist-info 2018-06-03 22:21:38 +02:00
Brendan Rius
2c2ce0126f
Merge pull request #49 from ntwuxc/patch-1
Update setup.py
2018-06-03 22:19:37 +02:00
mycode
749be19e4d
Update setup.py 2018-06-03 08:13:32 +08:00
Elliott Sales de Andrade
eb6aacf4ce Add license classifier for PyPI. 2018-03-15 02:21:13 -04:00
Elliott Sales de Andrade
1b93007021 Include readme and license in sdist. 2018-03-15 02:14:35 -04:00
Brendan Rius
ca942437f6
Merge pull request #40 from halfhorst/master
add resources/ as package data
2018-01-24 11:01:54 +01:00
Cody Horst
60cbeb8021 no editable pip installation 2018-01-23 23:04:53 -08:00
Cody Horst
d29d9e8167 alter package structure, moving resources under parent module. 2018-01-23 22:34:24 -08:00
Cody Horst
d02720cc84 update master.c path to correspond to where pip install will put it 2017-12-08 20:17:56 -08:00
Cody Horst
f0fbd41b96 add resources as package data. Probably bork current Dockerfile 2017-12-08 19:34:26 -08:00
Brendan Rius
c0d71813a5 Bump to 1.2.1 2017-08-13 17:03:43 +02:00
Brendan Rius
1b7f9d4dd6 Update README.md 2017-08-13 16:25:27 +02:00
Brendan Rius
c7a966954c Merge branch 'master' of github.com:brendan-rius/jupyter-c-kernel 2017-08-13 16:04:05 +02:00
Brendan Rius
a360b41cb6 Fix install script to use name 'c' 2017-08-13 15:56:03 +02:00
Brendan Rius
3839e6f87b Update README.md
Remove useless VCR contributing rules
2017-08-11 00:23:21 +02:00
Brendan Rius
e65c6b1a79 Merge pull request #32 from jb08/patch-1
Update README typo and clarification
2017-08-10 22:53:11 +02:00
Jason B
2ffb724f39 Update README typo and clarification
fixes typo, and provides example for "URL containing the token".
2017-08-10 14:00:51 -05:00
Brendan Rius
a90e1318c6 Updated README 2017-08-10 17:29:41 +02:00
Brendan Rius
3f584f7a22 Updated README 2017-08-10 17:22:37 +02:00
Brendan Rius
cba22be395 Fix docker build 2017-08-10 17:13:34 +02:00
Brendan Rius
9ec152dda1 Remove easy install script as installation method changed 2017-08-10 17:05:45 +02:00
Brendan Rius
28604bc230 Bump to 1.2.0 2017-08-10 17:01:40 +02:00
Brendan Rius
c08039f6ad Change install procedure 2017-08-10 17:00:32 +02:00
Brendan Rius
45616068bc Bump to 1.1.0 2017-08-08 02:00:58 +02:00
Brendan Rius
09246c16e8 Merge pull request #21 from ericjperry/master
Update install.sh to support using a different tag and repo
2017-06-09 16:32:31 +02:00
Brendan Rius
628b7767da Merge pull request #27 from spoorcc/issue-14
Fix #14: Add args magic to provide cli-args to user program
2017-04-13 11:29:48 +02:00
Ben Spoor
ace4d01ca6 Add args magic to provide cli-args to user program 2017-04-13 08:09:16 +00:00
Brendan Rius
17316fb7b2 Merge pull request #26 from spoorcc/issue-10
Fix #10: Added magics to provide LDFLAGS and CFLAGS to gcc
2017-04-12 20:59:16 +02:00
Ben Spoor
36c1f7f7a8 Added cflags and ldflags magics 2017-04-12 15:32:07 +00:00
Brendan Rius
608c737360 Merge pull request #22 from fermiumlabs/issue-c_extension
Correct c extension
2017-02-18 12:15:29 +01:00
Davide Bortolami
15e7288ef4 solve error “traitlets.traitlets.TraitError: FileExtension trait 'file_extension' does not begin with a dot: ‘c’” when exporting to script with nbconvert 2017-02-18 01:56:07 +01:00
Eric Perry
b980bf36c2 Move cd before checkout since we need to be in the repo. 2017-01-18 11:27:00 -05:00
Eric Perry
31a4ca502a Parameterized install.sh script to allow specification of tag and repository. 2017-01-18 11:18:51 -05:00
Brendan Rius
6907716831 Update README.md 2016-06-08 11:44:17 +01:00
Brendan Rius
f60d66cf75 Add instruction on how to live-edit the code 2016-06-08 11:29:29 +01:00
Brendan Rius
6a26d60a2b Fix #9 2016-06-08 11:24:21 +01:00
Brendan Rius
00d5d42e0e Ignore some more files in git 2016-06-08 10:49:11 +01:00
Brendan Rius
a7bcd78fdc Improve install.sh 2016-06-08 10:41:43 +01:00
Brendan Rius
2937ba0f6f Make step-by-step installation less error prone
The pip install is now made before we clone (so we always install from PyPi) and the Git clone is made via HTTPS not SSH in case you do not have your SSH key on your GH account
2016-06-08 10:34:02 +01:00
Brendan Rius
0915173c22 Fix typo 2016-06-06 15:52:40 +01:00
Brendan Rius
416b80c542 Merge pull request #11 from brendan-rius/issue-4
Issue 4
2016-05-16 11:14:22 +01:00
Brendan Rius
94ec844396 Make sure to empty the queue so no message should be left over in it 2016-05-16 09:43:47 +01:00
Brendan Rius
1372583b4f Rename Jupytersubprocess and comment it 2016-04-30 23:05:43 +01:00
Louis 'Kureuil' Person
e329f021d6 Fix subprocess bufsize & master compilation 2016-04-30 23:58:52 +02:00
Louis 'Kureuil' Person
843b3814ba Remove stream buffering on stdout & stderr 2016-04-30 23:48:19 +02:00
Brendan Rius
fc27956da4 Remove logging 2016-04-30 22:47:49 +01:00
Brendan Rius
ebe7239f7d wip 2016-04-30 21:45:41 +01:00
Brendan Rius
6d5ea72a74 wip 2016-04-30 21:45:41 +01:00
Brendan Rius
11fbb175ec Add contributing guidelines in README 2016-04-30 21:44:41 +01:00
Brendan Rius
110b31e505 Use Python 3 by default 2016-04-30 11:18:38 +01:00
Brendan Rius
a0f02c1de0 Fix module name in kernel.json 2016-04-30 11:18:38 +01:00
Brendan Rius
15dd0e4232 Merge pull request #6 from ryukinix/install-sh
Add alternative installation using wget and sh
2016-04-29 10:41:04 +01:00
Brendan Rius
7cf9e99b57 Use setuptools instead of distutils 2016-04-29 10:35:38 +01:00
Brendan Rius
f4dc4196be Add setup.cfg 2016-04-29 10:06:15 +01:00
Brendan Rius
dc11abae49 Add URL and download URL to setup.py 2016-04-29 10:05:16 +01:00
Manoel Vilela
59e8252f94 For compatibility reasons, use jupyter-kernelspec
At older versions the meta command `jupyter` doesn't exist.
2016-04-29 05:47:25 -03:00
Manoel Vilela
553bb94ed4 Add specials args to avoid syntax errors with wget 2016-04-29 01:12:06 -03:00
Manoel Vilela
000b87b00a Add alternative installation using wget and sh 2016-04-28 23:29:12 -03:00
14 changed files with 789 additions and 81 deletions

10
.dockerignore Normal file
View File

@ -0,0 +1,10 @@
__pycache__
*.pyc
build/
dist/
MANIFEST
.idea/
.ipynb_checkpoints/
*.egg-info/
.git
.gitignore

3
.gitignore vendored
View File

@ -4,3 +4,6 @@ build/
dist/
MANIFEST
.idea/
.ipynb_checkpoints/
*.egg-info/
venv/

View File

@ -1,8 +1,19 @@
FROM jupyter/minimal-notebook
MAINTAINER Brendan Rius <ping@brendan-rius.com>
MAINTAINER Xaver Klemenschits <klemenschits@iue.tuwien.ac.at>
USER root
COPY ./ /home/$NB_USER/.jupyter/jupyter_c_kernel/
RUN pip install /home/$NB_USER/.jupyter/jupyter_c_kernel/
RUN jupyter-kernelspec install /home/$NB_USER/.jupyter/jupyter_c_kernel/c_spec/
# Install vim and ssh
RUN apt-get update
RUN apt-get install -y vim openssh-client
WORKDIR /tmp
COPY ./ jupyter_c_kernel/
RUN pip install --no-cache-dir -e jupyter_c_kernel/ > piplog.txt
RUN cd jupyter_c_kernel && install_c_kernel --user > installlog.txt
WORKDIR /home/$NB_USER/
USER $NB_USER

3
MANIFEST.in Normal file
View File

@ -0,0 +1,3 @@
include jupyter_c_kernel/resources/master.c
include jupyter_c_kernel/resources/stdio_wrap.h
include README.md LICENSE.txt

View File

@ -1,28 +1,82 @@
# Minimal C kernel for Jupyter
# C kernel for Jupyter
This project was forked from [https://github.com/brendan-rius/jupyter-c-kernel](brendan-rius/jupyter-c-kernel) as that project seems to have been abandoned. (PR is pending)
This project includes fixes to many issues reported in [https://github.com/brendan-rius/jupyter-c-kernel](brendan-rius/jupyter-c-kernel), as well as the following additional features:
* Option for buffered output to mimic command line behaviour (useful for teaching, default is on)
* Command line input via `scanf` and `getchar`
* Support for `C89`/`ANSI C` (all newer versions were already supported and still are)
Following limitations compared to command line execution exist:
* Input is always buffered due to limitations of the jupyter interface
* When using `-ansi` or `-std=C89`, glibc still has to support at least `C99` for the interfacing with jupyter (this should not be an issue on an OS made after 2000)
## Use with Docker (recommended)
* `docker pull brendanrius/jupyter-c-kernel`
* `docker run -d -p 8888:8888 brendanrius/jupyter-c-kernel`
* Go to [http://localhost:8888](http://localhost:8888) (or your VM address if you are using Docker Machine)
* `docker pull xaverklemenschits/jupyter-c-kernel`
* `docker run -p 8888:8888 xaverklemenschits/jupyter-c-kernel`
* Copy the given URL containing the token, and browse to it. For instance:
```bash
Copy/paste this URL into your browser when you connect for the first time,
to login with a token:
http://localhost:8888/?token=66750c80bd0788f6ba15760aadz53beb9a9fb4cf8ac15ce8
```
## Manual installation
* Make sure you have the following requirements installed:
Works only on Linux and OS X. Windows is not supported yet. If you want to use this project on Windows, please use Docker.
* Make sure you have the following requirements installed:
* gcc
* jupyter
* python
* python 3
* pip
* `git clone git@github.com:brendan-rius/jupyter-c-kernel.git`
* `pip install jupyter-c-kernel`
* `cd jupyter-c-kernel`
* `jupyter-kernelspec install c_spec/`
* `jupyter-notebook`. Enjoy!
### Step-by-step
```bash
git clone https://github.com/XaverKlemenschits/jupyter-c-kernel.git
cd jupyter-c-kernel
pip install -e . # for system install: sudo install .
cd jupyter_c_kernel && install_c_kernel --user # for sys install: sudo install_c_kernel
# now you can start the notebook
jupyter notebook
```
## Example of notebook
![Example of notebook](example-notebook.png?raw=true "Example of notebook")
## Custom compilation flags
You can use custom compilation flags like so:
![Custom compulation flag](custom_flags.png?raw=true "Example of notebook using custom compilation flags")
Here, the `-lm` flag is passed so you can use the math library.
## Contributing
The docker image installs the kernel in editable mode, meaning that you can
change the code in real-time in Docker. For that, just run the docker box like
that:
```bash
git clone https://github.com/XaverKlemenschits/jupyter-c-kernel.git
cd jupyter-c-kernel
docker build -t myName/jupyter .
docker run -v $(pwd):/tmp/jupyter_c_kernel/ -p 8888:8888 myName/jupyter
```
This clones the source, run the kernel, and binds the current folder (the one
you just cloned) to the corresponding folder in Docker.
Now, if you change the source, it will be reflected in [http://localhost:8888](http://localhost:8888)
instantly. Do not forget to click "restart" the kernel on the page as it does
not auto-restart.
## License
[MIT](LICENSE.txt)

View File

@ -1,11 +0,0 @@
{
"argv": [
"python",
"-m",
"c_kernel",
"-f",
"{connection_file}"
],
"display_name": "C",
"language": "c"
}

BIN
custom_flags.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -10,9 +10,7 @@
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"name": "stderr",
@ -32,6 +30,7 @@
"\n",
"int main() {\n",
" printf(\"Hello world\\n\");\n",
" return 0;\n",
"}"
]
},
@ -52,9 +51,7 @@
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"name": "stderr",
@ -98,9 +95,7 @@
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"metadata": {},
"outputs": [
{
"name": "stderr",
@ -142,14 +137,14 @@
"kernelspec": {
"display_name": "C",
"language": "c",
"name": "c_kernel"
"name": "c"
},
"language_info": {
"file_extension": "c",
"file_extension": ".c",
"mimetype": "text/plain",
"name": "c"
"name": "text/x-c++src"
}
},
"nbformat": 4,
"nbformat_minor": 0
"nbformat_minor": 1
}

View File

@ -0,0 +1,81 @@
#!/usr/bin/env python
import json
import os
import sys
import argparse
from jupyter_client.kernelspec import KernelSpecManager
from IPython.utils.tempdir import TemporaryDirectory
kernel_json = {
"argv": [
"python3",
"-m",
"jupyter_c_kernel",
"-f",
"{connection_file}"
],
"display_name": "C",
"language": "c"
}
def install_my_kernel_spec(user=True, prefix=None):
with TemporaryDirectory() as td:
os.chmod(td, 0o755) # Starts off as 700, not user readable
with open(os.path.join(td, 'kernel.json'), 'w') as f:
json.dump(kernel_json, f, sort_keys=True)
# TODO: Copy resources once they're specified
print('Installing IPython kernel spec')
KernelSpecManager().install_kernel_spec(td, 'c', user=user, prefix=prefix)
def _is_root():
try:
return os.geteuid() == 0
except AttributeError:
return False # assume not an admin on non-Unix platforms
def main(argv=[]):
parser = argparse.ArgumentParser(
description='Install KernelSpec for C Kernel'
)
prefix_locations = parser.add_mutually_exclusive_group()
prefix_locations.add_argument(
'--user',
help='Install KernelSpec in user homedirectory',
action='store_false' if _is_root() else 'store_true'
)
prefix_locations.add_argument(
'--sys-prefix',
help='Install KernelSpec in sys.prefix. Useful in conda / virtualenv',
action='store_true',
dest='sys_prefix'
)
prefix_locations.add_argument(
'--prefix',
help='Install KernelSpec in this prefix',
default=None
)
args = parser.parse_args()
if args.sys_prefix:
prefix = sys.prefix
user = None
elif args.user:
prefix = None
user = True
else:
prefix = args.prefix
user = None
install_my_kernel_spec(user=user, prefix=prefix)
if __name__ == '__main__':
main(argv=sys.argv)

View File

@ -1,7 +1,88 @@
from queue import Queue
from threading import Thread
from ipykernel.kernelbase import Kernel
import re
import subprocess
import tempfile
import os
import os.path as path
class RealTimeSubprocess(subprocess.Popen):
"""
A subprocess that allows to read its stdout and stderr in real time
"""
inputRequest = "<inputRequest>"
def __init__(self, cmd, write_to_stdout, write_to_stderr, read_from_stdin):
"""
:param cmd: the command to execute
:param write_to_stdout: a callable that will be called with chunks of data from stdout
:param write_to_stderr: a callable that will be called with chunks of data from stderr
"""
self._write_to_stdout = write_to_stdout
self._write_to_stderr = write_to_stderr
self._read_from_stdin = read_from_stdin
super().__init__(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, bufsize=0)
self._stdout_queue = Queue()
self._stdout_thread = Thread(target=RealTimeSubprocess._enqueue_output, args=(self.stdout, self._stdout_queue))
self._stdout_thread.daemon = True
self._stdout_thread.start()
self._stderr_queue = Queue()
self._stderr_thread = Thread(target=RealTimeSubprocess._enqueue_output, args=(self.stderr, self._stderr_queue))
self._stderr_thread.daemon = True
self._stderr_thread.start()
@staticmethod
def _enqueue_output(stream, queue):
"""
Add chunks of data from a stream to a queue until the stream is empty.
"""
for line in iter(lambda: stream.read(4096), b''):
queue.put(line)
stream.close()
def write_contents(self):
"""
Write the available content from stdin and stderr where specified when the instance was created
:return:
"""
def read_all_from_queue(queue):
res = b''
size = queue.qsize()
while size != 0:
res += queue.get_nowait()
size -= 1
return res
stderr_contents = read_all_from_queue(self._stderr_queue)
if stderr_contents:
self._write_to_stderr(stderr_contents.decode())
stdout_contents = read_all_from_queue(self._stdout_queue)
if stdout_contents:
contents = stdout_contents.decode()
# if there is input request, make output and then
# ask frontend for input
start = contents.find(self.__class__.inputRequest)
if(start >= 0):
contents = contents.replace(self.__class__.inputRequest, '')
if(len(contents) > 0):
self._write_to_stdout(contents)
readLine = ""
while(len(readLine) == 0):
readLine = self._read_from_stdin()
# need to add newline since it is not captured by frontend
readLine += "\n"
self.stdin.write(readLine.encode())
else:
self._write_to_stdout(contents)
class CKernel(Kernel):
@ -10,19 +91,42 @@ class CKernel(Kernel):
language = 'c'
language_version = 'C11'
language_info = {'name': 'c',
'mimetype': 'text/plain',
'file_extension': 'c'}
'mimetype': 'text/x-csrc',
'file_extension': '.c'}
banner = "C kernel.\n" \
"Uses gcc, compiles in C11, and creates source code files and executables in temporary folder.\n"
main_head = "#include <stdio.h>\n" \
"#include <math.h>\n" \
"int main(){\n"
main_foot = "\nreturn 0;\n}"
def __init__(self, *args, **kwargs):
super(CKernel, self).__init__(*args, **kwargs)
self._allow_stdin = True
self.readOnlyFileSystem = False
self.bufferedOutput = True
self.linkMaths = True # always link math library
self.wAll = True # show all warnings by default
self.wError = False # but keep comipiling for warnings
self.standard = "c11" # default standard if none is specified
self.files = []
mastertemp = tempfile.mkstemp(suffix='.out')
os.close(mastertemp[0])
self.master_path = mastertemp[1]
self.resDir = path.join(path.dirname(path.realpath(__file__)), 'resources')
filepath = path.join(self.resDir, 'master.c')
subprocess.call(['gcc', filepath, '-std=c11', '-rdynamic', '-ldl', '-o', self.master_path])
def cleanup_files(self):
"""Remove all the temporary files created by the kernel"""
# keep the list of files create in case there is an exception
# before they can be deleted as usual
for file in self.files:
os.remove(file)
if(os.path.exists(file)):
os.remove(file)
os.remove(self.master_path)
def new_temp_file(self, **kwargs):
"""Create a new temp file to be deleted when the kernel shuts down"""
@ -33,50 +137,138 @@ class CKernel(Kernel):
self.files.append(file.name)
return file
@staticmethod
def execute_command(cmd):
"""Execute a command and returns the return code, stdout and stderr"""
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
return p.returncode, stdout.decode('utf-8'), stderr.decode('utf-8')
def _write_to_stdout(self, contents):
self.send_response(self.iopub_socket, 'stream', {'name': 'stdout', 'text': contents})
@staticmethod
def compile_with_gcc(source_filename, binary_filename):
args = ['gcc', source_filename, '-std=c11', '-o', binary_filename]
return CKernel.execute_command(args)
def _write_to_stderr(self, contents):
self.send_response(self.iopub_socket, 'stream', {'name': 'stderr', 'text': contents})
def _read_from_stdin(self):
return self.raw_input()
def create_jupyter_subprocess(self, cmd):
return RealTimeSubprocess(cmd,
self._write_to_stdout,
self._write_to_stderr,
self._read_from_stdin)
def compile_with_gcc(self, source_filename, binary_filename, cflags=None, ldflags=None):
cflags = ['-pedantic', '-fPIC', '-shared', '-rdynamic'] + cflags
if self.linkMaths:
cflags = cflags + ['-lm']
if self.wError:
cflags = cflags + ['-Werror']
if self.wAll:
cflags = cflags + ['-Wall']
if self.readOnlyFileSystem:
cflags = ['-DREAD_ONLY_FILE_SYSTEM'] + cflags
if self.bufferedOutput:
cflags = ['-DBUFFERED_OUTPUT'] + cflags
args = ['gcc', source_filename] + cflags + ['-o', binary_filename] + ldflags
return self.create_jupyter_subprocess(args)
def _filter_magics(self, code):
magics = {'cflags': [],
'ldflags': [],
'args': []}
actualCode = ''
for line in code.splitlines():
if line.startswith('//%'):
magicSplit = line[3:].split(":", 2)
if(len(magicSplit) < 2):
self._write_to_stderr("[C kernel] Magic line starting with '//%' is missing a semicolon, ignoring.")
continue
key, value = magicSplit
key = key.strip().lower()
if key in ['ldflags', 'cflags']:
for flag in value.split():
magics[key] += [flag]
elif key == "args":
# Split arguments respecting quotes
for argument in re.findall(r'(?:[^\s,"]|"(?:\\.|[^"])*")+', value):
magics['args'] += [argument.strip('"')]
# always add empty line, so line numbers don't change
actualCode += '\n'
# keep lines which did not contain magics
else:
actualCode += line + '\n'
# add default standard if cflags does not contain one
if not any(item.startswith('-std=') for item in magics["cflags"]):
magics["cflags"] += ["-std=" + self.standard]
return magics, actualCode
# check whether int main() is specified, if not add it around the code
# also add common magics like -lm
def _add_main(self, magics, code):
# remove comments
tmpCode = re.sub(r"//.*", "", code)
tmpCode = re.sub(r"/\*.*?\*/", "", tmpCode, flags=re.M|re.S)
x = re.search(r"int\s+main\s*\(", tmpCode)
if not x:
code = self.main_head + code + self.main_foot
magics['cflags'] += ['-lm']
return magics, code
def do_execute(self, code, silent, store_history=True,
user_expressions=None, allow_stdin=False):
user_expressions=None, allow_stdin=True):
magics, code = self._filter_magics(code)
magics, code = self._add_main(magics, code)
# replace stdio with wrapped version
headerDir = "\"" + self.resDir + "/stdio_wrap.h" + "\""
code = code.replace("<stdio.h>", headerDir)
code = code.replace("\"stdio.h\"", headerDir)
retcode, stdout, stderr = None, '', ''
with self.new_temp_file(suffix='.c') as source_file:
source_file.write(code)
source_file.flush()
with self.new_temp_file(suffix='.out') as binary_file:
retcode, stdout, stderr = self.compile_with_gcc(source_file.name, binary_file.name)
if retcode != 0:
stderr += "[C kernel] GCC exited with code {}, the executable will not be executed".format(retcode)
self.log.info("GCC return code: {}".format(retcode))
self.log.info("GCC stdout: {}".format(stdout))
self.log.info("GCC stderr: {}".format(stderr))
p = self.compile_with_gcc(source_file.name, binary_file.name, magics['cflags'], magics['ldflags'])
while p.poll() is None:
p.write_contents()
p.write_contents()
if p.returncode != 0: # Compilation failed
self._write_to_stderr(
"[C kernel] GCC exited with code {}, the executable will not be executed".format(
p.returncode))
if retcode == 0: # If the compilation succeeded
retcode, out, err = CKernel.execute_command([binary_file.name])
if retcode != 0:
stderr += "[C kernel] Executable exited with code {}".format(retcode)
self.log.info("Executable retcode: {}".format(retcode))
self.log.info("Executable stdout: {}".format(out))
self.log.info("Executable stderr: {}".format(err))
stdout += out
stderr += err
else:
self.log.info('Compilation failed, the program will not be executed')
# delete source files before exit
os.remove(source_file.name)
os.remove(binary_file.name)
if not silent:
stream_content = {'name': 'stderr', 'text': stderr}
self.send_response(self.iopub_socket, 'stream', stream_content)
stream_content = {'name': 'stdout', 'text': stdout}
self.send_response(self.iopub_socket, 'stream', stream_content)
return {'status': 'ok', 'execution_count': self.execution_count, 'payload': [],
'user_expressions': {}}
p = self.create_jupyter_subprocess([self.master_path, binary_file.name] + magics['args'])
while p.poll() is None:
p.write_contents()
# wait for threads to finish, so output is always shown
p._stdout_thread.join()
p._stderr_thread.join()
p.write_contents()
# now remove the files we have just created
os.remove(source_file.name)
os.remove(binary_file.name)
if p.returncode != 0:
self._write_to_stderr("[C kernel] Executable exited with code {}".format(p.returncode))
return {'status': 'ok', 'execution_count': self.execution_count, 'payload': [], 'user_expressions': {}}
def do_shutdown(self, restart):

View File

@ -0,0 +1,31 @@
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
typedef int (*main_t)(int, char **, char **);
int main(int argc, char **argv, char **envp)
{
char *error = NULL;
setbuf(stdout, NULL);
setbuf(stderr, NULL);
if (argc < 2) {
fprintf(stderr, "USAGE: %s PROGRAM\nWhere PROGRAM is the user's program to supervise\n", argv[0]);
return EXIT_FAILURE;
}
void *userhandle = dlopen(argv[1], RTLD_LAZY);
if (userhandle == NULL) {
fprintf(stderr, "%s: %s\n", argv[0], dlerror());
return EXIT_FAILURE;
}
dlerror();
main_t usermain = dlsym(userhandle, "main");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "%s: %s\n", argv[0], error);
return EXIT_FAILURE;
}
/* Call Users main, but make master.c invisible by removing first argument */
return usermain(argc-1, argv+1, envp);
}

View File

@ -0,0 +1,329 @@
#ifndef STDIO_WRAP_H
#define STDIO_WRAP_H
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
/* Figure out used C standard.
__STDC_VERSION__ is not always defined until C99.
If it is not defined, set standard to C89.
It is safest to set it by hand, to make sure */
#ifdef __STDC_VERSION__
#if __STDC_VERSION__ <= 199409L
#define C89_SUPPORT
#endif /* __STDC_VERSION__ <= 199409L */
#else /* __STDC_VERSION__ */
#define C89_SUPPORT
#endif /* __STDC_VERSION__ */
/* output functions to replicate terminal behaviour */
#ifdef BUFFERED_OUTPUT
/* buffer for all output */
/* TODO allocate this dynamically */
static char outputBuff[1<<10] = "";
static char attachedOutputFlush = 0;
void flush_all_output() {
printf("%s", outputBuff);
fflush(stdout);
outputBuff[0] = '\0';
}
/* Flush all output on exit */
void attachOutputFlush() {
if(attachedOutputFlush == 0){
int error = atexit(flush_all_output);
if(error != 0) {
fprintf(stderr, "ERROR: Could not set exit function! Error %d\n", error);
}
attachedOutputFlush = 1;
}
}
/* this function is called to check whether there
is a '\n' in the output that should be flushed */
void flush_until_newline() {
long i = 0;
long length = strlen(outputBuff);
for(; i < length; ++i) {
if(outputBuff[i] == '\n') {
char *printBuff = malloc(i+2);
strncpy(printBuff, outputBuff, i+1);
printBuff[i+1] = '\0';
printf("%s", printBuff);
free(printBuff);
/* now remove the printed string from the buffer
and start again */
{
long a = 0;
++i;
/* +1 to include \0 */
for(; i < length + 1; ++a, ++i) {
outputBuff[a] = outputBuff[i];
}
i = 0;
length = strlen(outputBuff);
}
}
}
}
/* for printf, print all to a string.
Then cycle through all chars and see if \n is
written. If there is one, flush the output, otherwise
write to buffer */
int printf_wrap(const char *format, ...) {
/* append output to buffer */
va_list arglist;
int result;
va_start( arglist, format );
result = vsprintf(outputBuff + strlen(outputBuff), format, arglist);
va_end( arglist );
/* Now flush if there is a reason to */
flush_until_newline();
attachOutputFlush();
return result;
}
int putchar_wrap(int c) {
long length = strlen(outputBuff);
outputBuff[length] = (char)c;
outputBuff[length+1] = '\0';
if(c == '\n') {
flush_until_newline();
}
attachOutputFlush();
return c;
}
int fflush_wrap(FILE* stream) {
if(stream == stdout) {
flush_all_output();
}
return fflush(stream);
}
int fclose_wrap(FILE* stream) {
if(stream == stdout) {
flush_all_output();
}
return fclose(stream);
}
#endif /* BUFFERED_OUTPUT */
/* Need input buffer to know whether we need another input request */
/* TODO allocate this dynamically */
static char inputBuff[1<<10] = "";
static long scanf_wrap_number_read = 0;
/* read remaining input into buffer so it can be used in next call */
void readIntoBuffer(void) {
long length = strlen(inputBuff);
char nextChar = 0;
while((nextChar = getchar()) != '\n' && nextChar != EOF){
inputBuff[length++] = nextChar;
}
inputBuff[length++] = '\n';
inputBuff[length] = '\0';
}
/* check whether input request is needed */
char checkInputRequest(void) {
const long length = strlen(inputBuff);
long i = 0;
for(; i < length && isspace(inputBuff[i]); ++i);
return i == length;
}
/* Define the input functions to overload the old ones */
/* Wrapping of scanf depends on standard */
#ifdef C89_SUPPORT
/* Need to define vscanf for c89.
TODO: This is a bit risky, since the underlying glibc does not
have to include this if it is old. If it does not, linking will fail.
The robust way would be readin via sscanf. */
/* Read formatted input from stdin into argument list ARG.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern int vsscanf (const char *__restrict __s,
const char *__restrict __format, _G_va_list __arg)
__THROW __attribute__ ((__format__ (__scanf__, 2, 0)));
/* replace all % with %* to suppress read in and do test run */
long find_scanf_length(const char *format) {
const long length = strlen(format);
/* allow for maximum of 50 format specifiers */
char *formatString = malloc(length + 53);
long index = 0;
long formatIndex = 0;
for(; index < length; ++index, ++formatIndex) {
formatString[formatIndex] = format[index];
if(format[index] == '%' &&
(index + 1 < length && format[index + 1] != '%')) {
formatString[++formatIndex] = '*';
}
}
/* add number readin */
formatString[formatIndex++] = '%';
formatString[formatIndex++] = 'n';
formatString[formatIndex] = '\0';
/* now run and record how many characters were read */
{
int readLength = 0;
sscanf(inputBuff, formatString, &readLength);
free(formatString);
return readLength;
}
}
#endif /* C89_SUPPORT */
int scanf_wrap(const char *format, ...) {
char doRequest = checkInputRequest();
char *formatString = 0;
if(doRequest) {
#ifdef BUFFERED_OUTPUT
flush_all_output();
#endif
printf("<inputRequest>");
fflush(stdout);
/* read everything from stdin into buffer */
readIntoBuffer();
}
/* add %n to format string to get number of written chars */
{
const long length = strlen(format);
formatString = malloc(length + 3);
strcpy(formatString, format);
#ifndef C89_SUPPORT
formatString[length] = '%';
formatString[length + 1] = 'n';
formatString[length + 2] = '\0';
#else /* C89_SUPPORT */
formatString[length] = '\0';
/* In C89 we need to find how far scanf will read, by hand */
scanf_wrap_number_read = find_scanf_length(format);
#endif /* C89_SUPPORT */
}
{
va_list arglist;
int result;
va_start(arglist, format);
result = vsscanf(inputBuff, formatString, arglist);
va_end(arglist);
/* now move inputBuff up */
{
const long length = strlen(inputBuff);
long index = scanf_wrap_number_read;
long a = 0;
/* +1 to include \0 */
for(; index < length + 1; ++a, ++index) {
inputBuff[a] = inputBuff[index];
}
}
free(formatString);
return result;
}
}
int getchar_wrap(void){
/* check if there is still something in the input buffer*/
char input = 0;
long length = strlen(inputBuff);
if(length <= 0) {
#ifdef BUFFERED_OUTPUT
flush_all_output();
#endif
printf("<inputRequest>");
fflush(stdout);
readIntoBuffer();
}
input = inputBuff[0];
{
long i = 1;
long length = strlen(inputBuff) + 1;
/* shift all chars one to the left */
for(; i < length; ++i){
inputBuff[i-1] = inputBuff[i];
if(inputBuff[i] == '\0') {
break;
}
}
}
return input;
}
/* Replace all the necessary input functions
depending on the language version used */
#ifndef C89_SUPPORT
/* Need double hashes in case there are no __VA_ARGS__*/
#define scanf(format, ...) scanf_wrap(format, ##__VA_ARGS__, &scanf_wrap_number_read)
#else /* C89_SUPPORT */
/* Since there are no variadic macros in C89, this is the only way
although it is horrible */
#define scanf scanf_wrap
#endif /* C89_SUPPORT */
#define getchar() getchar_wrap()
/* Output defines */
#ifdef BUFFERED_OUTPUT
#define printf printf_wrap
#define putchar putchar_wrap
#define fflush fflush_wrap
#define fclose fclose_wrap
#endif /* BUFFERED_OUTPUT */
/* Replace FILE write operations for read-only systems */
#ifdef READ_ONLY_FILE_SYSTEM
/* Define wrapping functions */
/* Output that the fopen succeeded and return some valid pointer */
FILE *fopen_wrap(const char *filename, const char *modes) {
static long stream = 0x1FFFF0000;
#ifdef SHOW_FILE_IO_VERBOSE
printf("\x01b[42m");
printf("\"%s\" opened in mode \"%s\"\n", filename, modes);
printf("\x01b[0m");
#endif /* SHOW_FILE_IO_VERBOSE */
return (FILE*)stream++;
}
int fprintf_wrap(FILE* stream, const char* format, ...) {
printf("\x01b[42m");
printf("%p:", stream);
printf("\x01b[0m");
va_list arglist;
va_start( arglist, format );
int result = vprintf(format, arglist);
va_end( arglist );
return result;
}
/* Replace all the necessary input functions */
#define fopen(file, mode) fopen_wrap(file, mode)
#define fprintf(stream, format, ...) fprintf_wrap(stream, format, ##__VA_ARGS__)
#endif /* READ_ONLY_FILE_SYSTEM */
#endif /* STDIO_WRAP_H */

2
setup.cfg Normal file
View File

@ -0,0 +1,2 @@
[metadata]
description-file = README.md

View File

@ -1,10 +1,18 @@
from distutils.core import setup
from setuptools import setup
setup(name='jupyter_c_kernel',
version='1.0.0',
version='1.2.1',
description='Minimalistic C kernel for Jupyter',
author='Brendan Rius',
author_email='ping@brendan-rius.com',
license='MIT',
classifiers=[
'License :: OSI Approved :: MIT License',
],
url='https://github.com/brendan-rius/jupyter-c-kernel/',
download_url='https://github.com/brendan-rius/jupyter-c-kernel/tarball/1.2.1',
packages=['jupyter_c_kernel'],
keywords=['jupyter', 'kernel', 'c']
scripts=['jupyter_c_kernel/install_c_kernel'],
keywords=['jupyter', 'notebook', 'kernel', 'c'],
include_package_data=True
)