c-shell
An interactive shell for the C language. To check how a piece of syntax
behaves, skip the new-file / write-main / compile / run loop — just type it
at the prompt.
c-shell 0.1.0 · cc (GCC) 16.1.1 (default std gnu23)
In [1]: int x = 41;
In [2]: x + 1
Out[2]: 42
In [3]: 3 / 2
Out[3]: 1
In [4]: 3.0 / 2
Out[4]: 1.5
In [5]: -1 > 0u
<input>:1:4: warning: comparison of integer expressions of different signedness
1 | -1 > 0u
| ^
Out[5]: 1
Not an interpreter
c-shell assembles your input into a complete C program and hands it to the real compiler on your machine. That is deliberate: integer promotion, bit-field layout, evaluation order, undefined behavior — the things most worth testing interactively — only have trustworthy answers from the compiler you actually use. An interpreter's answer speaks only for the interpreter.
Installation
Requires a Rust toolchain to build, and any C compiler (gcc / clang / MSVC) at runtime.
On startup the compiler is resolved in the order --cc flag → $CC → PATH
(cc, gcc, clang, tcc; on Windows gcc, clang, cc, clang-cl,
cl). The first candidate that passes every check wins. Capabilities are
probed by trial compilation, never derived from version strings — version
numbers lie (gcc on macOS is actually clang, and distros backport
features).
# diagnostics on stderr, exit code on failure
|
Terminal handling is automatic: when stdout is not a terminal (or
TERM=dumb, or NO_COLOR is set) colors are off; when stdin is not a
terminal the banner, prompts and farewell are suppressed and input is read
in batch mode — which also accumulates multi-line definitions, so scripts
and pipes can contain full function bodies.
The language standard defaults to whatever your compiler defaults to
(gnu23 for gcc 16, gnu17 for clang 22; the banner shows what was detected) —
the behavior you see here is the behavior a plain gcc foo.c would give you.
Pin a standard with --std c17, or switch mid-session with %std c17 /
%std default. One exception: when the compiler's default mode is too old to
compile _Generic (MSVC without /std: is C89), the standard is auto-raised
to c17/c11 and the banner says so. C11 is a hard floor: a compiler that
cannot reach it in any mode is skipped, and if no candidate qualifies,
c-shell reports an error and exits.
Usage
| Input | Behavior |
|---|---|
x + 1 |
evaluated, value printed as Out[n] |
x + 1; |
trailing ; runs it silently (as in IPython) |
int x = 41; |
declaration, visible to later inputs |
int f(int a) { ... } |
function definition, hoisted to file scope |
#include <time.h> |
likewise |
Common headers (stdio/stdlib/string/math/stdbool/stdint/limits/ctype) are
pre-included and -lm is linked by default. -Wall -Wextra is on by
default: the warning is often exactly the thing you came to check.
Commands
%-prefixed, in the spirit of IPython:
%help %quit %reset %history
%src %undo %cc %std
%src prints the complete C program the session assembles — when you want to
know exactly what the tool is doing on your behalf, look there.
Tab completes % commands, C keywords, stdlib staples and every name your
session has mentioned. Input history persists across sessions in
~/.local/share/c-shell/history (%APPDATA%\c-shell\history on Windows).
How it works
Accumulate and replay. Every evaluation reassembles and reruns the whole
session. Session variables are therefore ordinary locals in main — no
global symbol table, no splitting declarations from initializers.
Classification by trial compilation. Whether a line is an expression, a statement or a file-scope item is not decided by parsing C; it is wrapped each way and compiled, and whichever form the compiler accepts is the answer. The only judge that always agrees with the compiler is the compiler.
Value printing. _Generic selects a print function, which is then
called — selecting a call expression instead would type-check every
unselected branch against the wrong argument type and fail to compile.
Only state-changing inputs are kept. An expression at the prompt is
usually a question (x + 1, sizeof(int)); questions are answered and
forgotten, or the session would grow slower with every one. Only inputs that
may change state — assignment, ++/--, function calls — are replayed.
%src shows what was actually kept.
Diagnostics are remapped. The compiler sees a generated file with a prelude and all earlier statements above your input, so its line numbers are meaningless at the prompt. Every location — including the line-number gutter of gcc's source excerpts — is rewritten to input-relative lines. Locations pointing into generated scaffolding are never presented as your code.
Known limitations
- Side effects replay. Every evaluation reruns the session, so
scanf, file writes andtime()execute again each time. Pure syntax exploration never notices; be aware of it otherwise. - No redeclaration in the same scope. Declaring
int xtwice is an error, because it is one in C. Use%undoto drop the previous one. - The purity heuristic is conservative. Any expression containing a function call is kept, even if the function happens to be pure.
- Crashing or timed-out inputs are not committed — otherwise every later replay would crash again.
- No session save/load.
- MSVC must be started from a Developer Command Prompt, or
INCLUDE/LIBare missing.
Development
After cloning, enable the pre-commit gate (fmt + clippy + tests — the same checks CI runs):
The toolchain is pinned by rust-toolchain.toml so local clippy and CI
clippy are the same version; new lints arrive when the pin is bumped, never
by surprise on push.
Before working on the code, read DESIGN.md: it records the architecture decisions, the open state-model question, and several traps you need to know about before changing things.
License
MIT