- Building
- C Interpreter
- REPL mode
- Bindings-Free FFI
- Native Threads
- First Class Types
- Static If
- Procmacros
- Methods and FUCS
- Named Arguments
- Crazy Preprocessor
- Embeddable
- Self Hosting
DrC is a C Preprocessor/Parser/Interpreter targeting C2y with a boatload of
extensions. It can interpret real C projects (including itself)
and has seamless interfacing with native dynamic libraries. #include a
header and #pragma lib it and you can call native C functions from your
interpreted C code.
A REPL mode is also offered.
After building, try it out with drc Samples/hello.c
or peruse the other Samples.
Major Features Include:
- C Interpreter
- Bindings-Free FFI
- Native Threads
- REPL mode
- First Class Types
- Static If
- Procmacros
- Methods and FUCS
- Named Arguments
- Crazy Preprocessor
- Embeddable
- Self Hosting
Nobuild
This project uses a "nobuild" build system. Bootstrap the build system by doing something like:
$ cc build.c -o build && ./build -b Bin && ./build testsFrom then on, you can just build using the build program. If you edit it or any of its deps, it will rebuild itself.
The build system remembers the previous flags, like -O. If you
change those or switch them off, it knows to rebuild any programs built
with those flags.
See build.c for more info.
Makefile
If you like using make, the makefile will bootstrap the build system for you.
build.bat
You can bootstrap the build system on windows using build.bat. This is purely convenience.
Dependencies
Depends on libffi. On Linux/macOS, assumes you can just do -lffi and
that just works. On Windows, the build system fetches libffi binaries.
C scripts, top level statements, no separate compile and run steps, no intermediate files.
$ cat hello.c
#!/usr/bin/env drc
#include <stdio.h>
printf("hello.c\n");
$ ./hello.c
hello.cRun drc with --repl for a REPL experience, with interactive function calls etc.
$ drc --repl
cc> #include <stdio.h>
... int x = 3;
... for(int i = 0; i < 4; i++)
... printf("x++ = %d\n", x++);
...
x++ = 3
x++ = 4
x++ = 5
x++ = 6
cc> ^D#include real C headers, #pragma lib to load them. Also supports #pragma pkg_config to populate include paths, lib paths etc.
#pragma pkg_config "sdl2" // could ifdef for non-pkg-config systems
#pragma lib "SDL2" // load the library
#include <SDL2/SDL.h> <SDL.h> // extension, tries each in order
// top level statements
SDL_Init(SDL_INIT_VIDEO);
// Call SDL funcs from your scripts now!Related to above, you can call actual system APIs to create threads with interpreted code.
#include <stdio.h>
#include <pthread.h>
pthread_t t;
pthread_create(&t, NULL, void*(void* arg){
printf("Hello from a thread! %p\n", (void*)pthread_self());
return NULL;
}, NULL);
pthread_join(t, NULL);This example also shows off our captureless-lambda extension.
_Type T = int;
printf("%s\n", T.name); // int
T = float;
printf("%s\n", T.name); // float
T = struct Foo {int x, y;};
printf("%s {\n", T.name); // struct Foo
for(int i = 0; i < T.fields; i++){
auto f = T.field(i);
printf(" %s %s;\n", f.type.name, f.name); // int x; int y;
}
printf("};\n");Pass them as values, reflect over them, see the json demo
Include or exclude statements/decls based on compile time expressions.
static if(__shell("date")[0] == 'W'){
enum {IT_IS_WEDNESDAY=1};
}
printf("IT_IS_WEDNESDAY: %d\n", IT_IS_WEDNESDAY); // only compiles on wednesdayRegister a C function as a macro, call it as a macro, outputs get turned into pp-tokens, you can mix them into your source file for code generation and other things.
int add(int a, int b){return a + b;}
#pragma procmacro add
#if add(1, 3) == add(2, 2)
enum {MATH_WORKS=1};
#else
#error "math doesn't work"
#endifAlso, power up preprocessor conditionals:
_Bool IS_POINTER(_Type T){ return T.is_pointer;}
#pragma procmacro IS_POINTER
#define T void*
#if IS_POINTER(T)
#pragma message T "is a pointer"
#else
#pragma message T "is not a pointer"
#endif
#undef T
#define T int
#if IS_POINTER(T)
#pragma message T "is a pointer"
#else
#pragma message T "is not a pointer"
#endifDefine methods in struct body, auto deref, . and -> merged,
FUCS: Function Uniform Call Syntax to have .func notation with free
functions.
#include <stdlib.h>
#include <stdio.h>
typedef struct Ints Ints;
struct Ints {
int * data;
size_t count, capacity;
void push(_Self* self, int val){
if(self.count >= self.capacity){
size_t cap = self.capacity?2*self.capacity:4;
void* p = realloc(self.data, cap * sizeof *self.data);
if(!p) abort();
self.data = p;
self.capacity = cap;
}
self.data[self.count++] = val;
}
};
Ints i = {0};
i.push(1);
i.push(1);
// FUCS: regular function can be called like a method
int sum(Ints* ints){
int s = 0;
for(size_t i = 0; i < ints.count; i++){
s += ints.data[i];
}
return s;
}
printf("i.sum() = %d\n", i.sum());
// Or inject methods after the fact.
(Ints).push_method(fib, void(Ints*self){
if(self.count < 2) abort();
self.push(self.data[self.count-1] + self.data[self.count-2]);
});
for(size_t c = 0; c < 10; c++)
i.fib();
printf("i.sum() = %d\n", i.sum());
for(size_t c = 0; c < i.count; c++){
printf("%zu] %d\n", c, i.data[c]);
}$ drc methods.c
i.sum() = 2
i.sum() = 376
0] 1
1] 1
2] 2
3] 3
4] 5
5] 8
6] 13
7] 21
8] 34
9] 55
10] 89
11] 144Same syntax as designated initializers.
int func_with_lotta_bools(_Bool jump, _Bool skip, _Bool hop);
func_with_lotta_bools(.hop = 1, .jump = 0, .skip = 1); // out of orderNumbered args are also supported, but honestly aren't that useful, but whatever.
There's a bunch of cool preprocessor macros like __mixin, __map, #defblock.
There's also an API for registering your own builtin macros if you are embedding this
interpreter.
This will support being embedded. WIP. No global state.
The C interpreter is able to run itself.
$ drc cc.c Samples/hello.c
Hello world
$ drc cc.c cc.c Samples/hello.c
Hello world