Skip to content

drpriver/DrC

Repository files navigation

Table of Contents

DrC

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:

Building

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 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.

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 Interpreter

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

REPL mode

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

Bindings-Free FFI

#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!

Native Threads

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.

First Class Types

_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

Static If

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

Procmacros

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

Methods and FUCS

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

Named Arguments

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.

Crazy Preprocessor

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.

Embeddable

This will support being embedded. WIP. No global state.

Self Hosting

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

About

C Interpreter/REPL

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages