Skip to content

gaswelder/che

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Che – modified C language

This is a modified variant of C. The main goal is to get rid of the manual hassle the standard C has: forward declarations, headers, build dependencies and other annoyances.

It's a translator that converts a dialect into standard C99 and calls the c99 command installed in the system to produce the executable. c99 is a command specified by POSIX, and GCC or Clang packages typically have this binding installed. If not, you'd have to create a c99 wrapper yourself for whatever c99-compliant compiler you have.

Language differences

No include headers. The C languange standard library is well defined, so there is no point in writing its headers manually every time, so hello world program can be just this, without any #includes:

int main() {
	puts("Howdy, Globe!");
}

No function prototypes. Forward declarations and prototypes are necessary for a single-pass C compiler. This one is a multi-pass translator, so it can as well generate all needed prototypes for the underlying C compiler. So this example will work:

int main() {
	func2();
}

void func2() {
	puts("Howdy, Globe!");
}

No need in struct. Instead of this:

struct foo_s {
	int a;
	int b;
};
typedef struct foo_s foo_t;

we have this:

typedef {
	int a;
	int b;
} foo_t;

Declaration lists. In C it is possible to declare multiple variables with a common type like this:

int a, b, c;
struct vec {
	int x, y, x;
};

Here it is also possible to do that with function parameters:

int sum(int a, b, c) {
	...
}

Switch syntax. Switch cases don't need the break keyword and require brackes instead. It's also possible to enumerate switch cases using comma. So the following:

switch (val) {
	case OPT_INT, OPT_UINT, OPT_SIZE: {
		handle_int();
	}
	case OPT_STR: {
		handle_str();
	}
	default: {
		handle_unknown();
	}
}

which is equivalent to c:

switch (val) {
	case OPT_INT:
	case OPT_UINT:
	case OPT_SIZE: { handle_int(); break; }
	case OPT_STR: { handle_str(); break; }
	default: { handle_unknown(); }
}

The nelem macro. One common idiom to get a static array's length is:

for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
	...
}

It's often defined as macro in various places, so one of those is built in:

for (int i = 0; i < nelem(a); i++) {
	...
}

The panic statement. panic is basically fprintf(stderr, <panic arguments>); exit(1), but it also generates a message with the panic's position in the source code.

Modules

A single C source file is called a "module". It's compiled independently and linked with other compiled modules by the linker. Here this approach was taken furtner to an import system:

// main.c:
----------------
#import module2

int main() {
	module2.foo();
}
----------------

// module2.c:
----------------
pub void foo() {
	puts("Howdy, Globe!");
}
----------------

// command line:
$ che main.c
----------------

In C, in order to make functions private to the module, they are declared as static. Here, static is gone, and pub is introduced:

// f1 will be accessible from importing modules, but f2 will not and will be
// compiled as static.
pub int f1() {...}
int f2() {...}

As an exception, main doesn't have to be marked public, so “hello world” still is:

int main() {
	puts("Howdy, Globe!");
}

Note also that instead of foo the importing module is calling module2.foo. The translator will convert those namespaced calles into global names suitable for a regular C compiler, taking care of possible name conflicts.

All the modules are automatically compiled and linked in the right order. The build command is che main.c, as opposed to c99 module1.c module2.c main.c.

No static variables. In C variables can be marked static in two cases. One is function-local, the other is module-local. Both are essentially module globals:

// *** C ***
// module-local variable
static long seed = 42;

int count() {
	// function-local variable
	static int n;
	n++;
	return n;
}

long foo() {
	return seed++ * count();
}

Here static variable are gone. Module variables are always assumed static, and a module can't export variables, so there is no pub for them.

// these variables can be accessed only inside this module.
long seed = 42;
int current_count = 0;

int count() {
	current_count++;
	return current_count;
}

pub long foo() {
	return seed++ * count();
}

Enums and typedefs may be marked pub to become importable. By default all enum and typedef declarations are private.

Testing

che has a built-in test runner. Invoking che test <dir> will compile and run every *.test.c file in the given directory. A *.test.c file is a regular program with main:

// md5.test.c
#import crypt/md5

int main() {
	md5.md5sum_t s = {0};
	md5.md5str_t buf = "";

	char *expected = "0cc175b9c0f1b6a831c399e269772661";
	char *input = "a";
	md5.md5_str(input, s);
	md5.md5_sprint(s, buf);
	if (!strcmp(buf, want) == 0) {
		printf("* FAIL (%s != %s)\n", buf, want);
		return 1;
	}
	return 0;
}

If it returns a non-zero exit status, the runner will display the test as failed and will add the test's output.

OS interoperability

Some libraries (for example, net) depend on POSIX, so not everything can be compiled on Windows or Mac. There was multi-platform support initially, but that's not a goal just yet.

If a module needs to call an os function, it will have to include the C headers, and add hints for types and functions. To avoid name conflicts with the OS functions, the special OS namespace can be used:

// this is a wrapper around the OS's unlink.
pub bool unlink(const char *path) {
    return OS.unlink(path) == 0;
}

About

Modified C language

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published