Try it
Start c-shell and type C directly at the prompt:
c-shell 0.2.5 · 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
Highlights
- Your compiler, your answers. GCC, Clang, MSVC, and tcc determine the language rules, ABI behavior, warnings, and errors.
- A stateful C session. Declarations, statements, functions, and types remain available to later inputs without replacing C's normal scope rules.
- Interactive editing. Syntax highlighting, completion, history, smart continuation indentation, closing-brace dedent, and editable prior inputs make multi-line C comfortable at a terminal.
- Useful values, not just exit codes. Scalar values, strings, arrays, and
supported structs are printed automatically;
%typeinspects expressions without evaluating them. - Interactive or scriptable. Use the REPL,
-e, script files, or piped input with deterministic exit status and diagnostics. - Cross-platform compiler drivers. GNU-style and MSVC-style command lines are detected by capability probes rather than executable names.
Why a real compiler?
c-shell assembles your input into a complete C program and hands it to the real compiler on your machine. That is deliberate: integer promotions, implementation-defined details, ABI choices and compiler diagnostics are most useful when they come from the toolchain you actually use. An interpreter's answer speaks only for the interpreter.
A result is still an observation of one compiler invocation, not a guarantee from the C standard. In particular, undefined or unspecified behavior may change with compiler version, flags, surrounding code or another execution.
Installation
Requires a Rust toolchain to build and, at runtime, a C compiler compatible with one of the supported GNU/Clang or MSVC command-line dialects. GCC, Clang, MSVC and tcc are the built-in detection candidates.
Install the published crate from crates.io:
Or install directly from a source checkout:
With --cc, only that compiler is tried. Otherwise startup checks $CC, then
PATH (cc, gcc, clang, tcc; on Windows gcc, clang, cc,
clang-cl, cl). --cc and $CC must name one executable or path, not a
shell command containing flags. The first candidate that passes every check
wins. Capabilities are probed by trial compilation, never derived from version
strings — executable names can be aliases, and distros backport features. For
example, Apple Command Line Tools may provide /usr/bin/gcc as an Apple Clang
driver, while GNU GCC installed separately through Homebrew or MacPorts is
genuine GCC.
# 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).
This matches the compiler's language mode, not its entire command line:
c-shell also adds diagnostics, output and platform link flags plus generated
REPL scaffolding.
Select a supported mode with --std c17, or switch mid-session with %std c17 / %std default. One exception: when the compiler's default mode cannot
compile _Generic (MSVC without /std: is the common case), c-shell tries
c17 and then c11, and the banner reports an automatic raise. _Generic
support is the actual capability floor: a candidate that cannot compile a
representative value-printer probe in the selected mode is skipped. An
unsupported explicit --std is an error both at startup and in %std.
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 |
int main() { ... } |
rejected with guidance; c-shell supplies main |
#include <time.h> |
likewise |
A completed interactive if uses blank-line confirmation: press Enter on the
empty continuation line to submit it, or type else / else if there to
continue the same statement. Functions and loops submit directly when their
required closing syntax is entered. A braced struct, union or enum
definition remains open after } until its mandatory declaration semicolon is
entered. Control headers with their body on the next line, mandatory
do ... while, and conditional preprocessor groups through #endif are
accumulated automatically. In scripts
and pipes, one-line lookahead attaches else without requiring a blank line.
c-shell generates its own main to host session locals and the output
protocol. Pasting a complete program such as int main() { puts("hi"); }
therefore produces a direct explanation instead of a compiler redefinition or
GCC nested-function warning: enter the statements from the body directly and
omit the final return.
Common headers (stdio, stdlib, string, math, stdbool, stdint,
inttypes, stddef, limits, ctype, stdarg, time) are pre-included, so
no #include is needed for the everyday library; %header lists them. Unix
builds link -lm; Windows math functions come from the C runtime. GNU-style
GCC/Clang drivers use -Wall -Wextra; MSVC-style cl/clang-cl drivers use
/W3. The warning is often exactly the thing you came to check.
Rebinding declarations and definitions
When a block-scope declaration fails with a compiler redeclaration diagnostic, c-shell retries it inside a new nested block. If the complete program then compiles, later inputs remain inside that block and see the shadowing binding:
In [1]: int x = 1;
In [2]: x = 5;
In [3]: int x = 2;
(opened a nested scope to shadow an earlier declaration)
In [4]: x
Out[4]: 2
%src shows the actual braces; no C declaration is rewritten into an
assignment. Consequently C's own scope rules still apply—for example, in
int x = x + 1; the initializer refers to the newly declared x, not the
outer one.
A rejected file-scope redefinition is handled differently: c-shell tries the new function or type in place of each previous non-preprocessor file-scope input, and commits only the substitution for which the real compiler accepts the entire session:
In [1]: int f(int n) { return n * 2; }
(added at file scope)
In [2]: f(3)
Out[2]: 6
In [3]: int f(int n) { return n * 3; }
(replaced previous file-scope definition)
In [4]: f(3)
Out[4]: 9
The old item is replaced at its original position to preserve declaration
order. A function-shaped input is never retried inside main; this prevents
GCC nested-function extensions from creating a session that Clang or MSVC
interprets differently.
Both forms are recorded in the session journal. %undo reverses the most
recent retained state change, including an appended statement, a newly opened
shadowing scope, or a file-scope replacement. Failed inputs and pure value
queries do not change retained state and therefore create nothing to undo.
%reset clears the complete session.
Commands
%-prefixed, in the spirit of IPython:
%help %quit %clear %reset %edit [n]
%src %header %type %time %timeit
%undo %cc %std
%help lists the commands and nothing else, so it stays a one-screen
reference. %help --verbose appends the usage notes: which inputs are
retained, how continuation and rebinding behave, what the value printer and
%type cover, and how the scanf tape replays.
%clear erases the terminal display and returns the cursor to the top without
changing variables, retained C code or the input counter.
%undo reverses the most recent retained state change. It does not rewind the
visible In[n] counter or remove failed and forgotten pure inputs from the
numbered %edit archive.
%src defaults to the user-facing program: current file-scope definitions and
retained statements inside a clean main, including any rebinding braces, but
without printers or protocol markers. %src --raw prints the complete
compiler input with CS_PRINT, _Generic and marker machinery. Either view is
formatted with clang-format when available; formatting is presentation only,
has a three-second deadline, and evaluation still compiles the unformatted
generated source.
%edit copies the most recent C input into the terminal prompt; %edit 12
retrieves In[12]. Numbered C inputs—including failed compilations and
forgotten pure queries—remain addressable until %reset. The command itself
does not compile, submit or consume an input number: it returns to the same
In[n] prompt with the selected text pre-filled and the cursor at its end.
Modify it with the normal line-editor keys and press Enter when ready; even
unchanged text is submitted only at that point as a fresh In[n], while the
original numbered input remains unchanged. Ctrl-C or clearing the buffer
cancels naturally. The eventual submitted block enters process-local Up/Down
recall and follows the normal shadowing or file-scope replacement rules.
%edit is available only in the interactive REPL.
%type <expression> reports the expression type without evaluating that
expression, committing code or consuming an In[n] number:
In [1]: const char *message = "hello";
In [2]: %type message
const char *
In [2]: %type 1 + 0.5
double
Aggregate categories retain an available name:
In [2]: struct Point { int x, y; } point;
(added at file scope)
In [3]: %type point
Struct Point
In [3]: typedef union { int i; double d; } Value;
(added at file scope)
In [4]: Value value = { 1 };
In [5]: %type value
Union Value
The implementation uses C11 _Generic, not compiler-specific typeof. It
names scalar types and pointers to scalar types. Generic matching sees
compatible expression types rather than source spelling: scalar typedefs and
aliases of named tags are canonicalized, top-level qualifiers are removed by
lvalue conversion, and arrays/functions decay to pointers. Complete named
aggregate definitions visible in the session, plus simple typedef struct { ... } Name and typedef union { ... } Name forms, are added to each query
dynamically. A typedef of a named tag uses the tag's canonical name; an
anonymous aggregate typedef uses its typedef name. Truly anonymous aggregates
and other types outside the table report <unrecognized type>.
Tab completes % commands, C keywords, stdlib staples and retained session
identifiers of at least two characters. When a prefix is ambiguous, Tab opens a
dropdown menu under the cursor listing every candidate, IPython-style; Tab and
the arrow keys move through it and Enter accepts. Up/Down recalls up to 1000
inputs from the current process so multi-line blocks can be recovered, but
nothing is loaded or saved across launches. There is no %history or history
file. The separate current-session input archive exists only for direct
%edit n lookup and is cleared by %reset.
How it works
c-shell works by reassembling your session and passing complete C files to your
real host compiler (gcc, clang, cl, or tcc).
For full technical details on accumulation and replay, the scanf stdin tape,
_Generic value printers, diagnostic remapping, and capability caching, see
HOW_IT_WORKS.md.
Known limitations
For a complete breakdown of design boundaries, non-sandboxed execution, stdin replay scope, and platform behavior, see LIMITATIONS.md.
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