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 <a href=Samples/hello.c>Samples/hello.c
or peruse the other Samples.
Major Features Include:
This project uses a "nobuild" build system. Bootstrap the build system by doing something like:
$ cc build.c -o build && ./build -b builddir && ./build tests
From 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.
If you like using make, the makefile will bootstrap the build system for you.
You can bootstrap the build system on windows using build.bat. This is purely convenience.
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.c
Run 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
Fixes "C's biggest mistake".
int sum(int vals[:]){ // slice is {count, data}
int result = 0;
for(size_t i = 0; i < vals.count; i++){
result += vals[i];
}
return result;
}
int a[] = {1,2,3,4};
printf("sum(a) = %d\n", sum(a)); // arrays implicitly convert to slice, preserving bounds info
int oob(int vals[:]){
return vals[99]; // length is known, so implementation can emit array-bounds-check
}
oob(a[:2]); // error: slice subscript out of bounds
This can also be used for typed varargs, which are just sugar over a compound array literal that gets sliced.
int sum(int vals...){
int result = 0;
for(size_t i = 0; i < vals.count; i++){
result += vals[i];
}
return result;
}
printf("sum(1,2,3,4,) = %d\n", sum(1,2,3,4));
int a[] = {1,2,3,4};
// You can use named arguments to call with an array or slice
printf("sum(a) = %d\n", sum(.vals=a));
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 wednesday
Register 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"
#endif
Also, 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"
#endif
Define 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] 144
Same 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 order
Numbered 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
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 <a href=Samples/hello.c>Samples/hello.c
or peruse the other Samples.
Major Features Include:
This project uses a "nobuild" build system. Bootstrap the build system by doing something like:
$ cc build.c -o build && ./build -b builddir && ./build tests
From 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.
If you like using make, the makefile will bootstrap the build system for you.
You can bootstrap the build system on windows using build.bat. This is purely convenience.
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.c
Run 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
Fixes "C's biggest mistake".
int sum(int vals[:]){ // slice is {count, data}
int result = 0;
for(size_t i = 0; i < vals.count; i++){
result += vals[i];
}
return result;
}
int a[] = {1,2,3,4};
printf("sum(a) = %d\n", sum(a)); // arrays implicitly convert to slice, preserving bounds info
int oob(int vals[:]){
return vals[99]; // length is known, so implementation can emit array-bounds-check
}
oob(a[:2]); // error: slice subscript out of bounds
This can also be used for typed varargs, which are just sugar over a compound array literal that gets sliced.
int sum(int vals...){
int result = 0;
for(size_t i = 0; i < vals.count; i++){
result += vals[i];
}
return result;
}
printf("sum(1,2,3,4,) = %d\n", sum(1,2,3,4));
int a[] = {1,2,3,4};
// You can use named arguments to call with an array or slice
printf("sum(a) = %d\n", sum(.vals=a));
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 wednesday
Register 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"
#endif
Also, 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"
#endif
Define 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] 144
Same 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 order
Numbered 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