Yet another (mostly) functional C interpreter.
I wrote this to use as the central component of a game centered around programming low-level devices, similar to "Shenzen I/O" and "TIS-100", but realized after beginning on the actual "game part" of the game that I don't really like Unity or C#.
Now I don't know what to do with it.
¯\_(ツ)_/¯
Cish only works with one file at a time. Like with an actual C program, there
must be a main
function defined. The standard library can be included and
used, although it's mostly unimplemented.
Antlr and the Antlr Runtime must be installed on your system. Cish is currently only tested using Antlr v 4.11.1.
brew install antlr antlr4-cpp-runtime
mkdir build
cd build
cmake ..
make
(optionally)
make install
The make-script builds two artifacts - libcish.a
and (unless excluded via)
CMake options) cish_cli
. The library can be used to add C interpretation to
your favorite application, and the command line client can be used to combine a
subset of C with the speed of a turtle.
All of Cish is under heavy unit-test barrage from gtest
, but there are also
multiple cish vs gcc test-programs in the gcc_compare
directory. The latter
executes a simple program and compares stdout
and $?
. I've found that if I
ever break something, no matter how trivial, it breaks at least one of the many
tests.
make check
runs the unit-tests, and gcc_compare/compare.sh
runs the gcc
comparison tests. Note that cish_cli
must be on the system path for the
compare.sh
script to run properly. Most easily achieved by make install
.
- Most of the standard library
- Switch-statements
- Literal arrays
- Break & continue
enum
typedef
- Preprocessor macro
- Library-defined global variables (
EOF
,stdout
,errno
, etc) - Ternary expressions
- Performance 🐢
Antlr is used for parsing the AST, and is converted (nearly) one-to-one to an "execution-tree".
Antlr is used to (a) make sure that the program is syntactically valid, and (b)
express the program in a manageable structure. Each node in the AST is then
visited and (often) converted into either an Expression
or a Statement
, the
sum of which makes up the "execution tree". During this conversion, it is
verified that the program makes logical and functional sense. Referred
variables must have been declared, expected parameters must be passed,
dereferencing an int
makes no sense, etc.
Note that because I have no idea what I'm really doing, the "execution tree" I'm
referring to is called cish::AST
in the code :)
Cish attempts to emulate the real workings of C as closely as possible, and with it unsafe pointers, wild-west style casting & other neat tricks. The Cish VM does however not have the concept of "stack" or "heap". Instead, everything is allocated in a "heap-like" fashion.
The execution tree consists of Statement
and Expression
nodes. The nodes
are intrinsically (recursively?) evaluated, so the execution process really
only involves finding the node that defines the main()
-function and
evaluating it. It will in turn evaluate all of its child-nodes, and so on until
the program returns.