📜 ⬆️ ⬇️

High Level C or a couple of words about Cello

image Cello is the library that made the high-level C possible! Generics, parametric polymorphism, interfaces, constructors / destructors, garbage collector (optional), exceptions and reflection. Yes, yes, you heard right, all these flops in one bottle. Since Cello is built within standard C, the bottom line is that you get everything a living person needs on earth: high performance, powerful tools and flexible libraries.

Talk is cheap, show me the code!

#include "Cello.h" int main(int argc, char** argv) { /* Stack objects are created using "$" */ var i0 = $(Int, 5); var i2 = $(Int, 3); var i2 = $(Int, 4); /* Heap objects are created using "new" */ var items = new(Array, Int, i0, i1, i2); /* Collections can be looped over */ foreach (item in items) { print("Object %$ is of type %$\n", item, type_of(item)); } /* Heap objects destructed via Garbage Collection */ return 0; } 

SHOCK! Why should I now have all these your Go / D / Nim / <enter>, if C on steroids solves all the problems of the human race ?! Want to learn about Cello's readiness for production and see even more code ? Welcome rolled up.

Introductory


Cello adds an extra runtime layer over C. This is an absolute necessity, because otherwise it would be possible to expand the language only by changing the compiler, and we cannot afford this luxury. The user defines type variable variables (runtime type variables) that contain all the necessary information for the new functionality, associating them with the usual legitimate types.
')
Of course, there is an overhead in GC in Cello. Pointers in Cello are accompanied by additional meta-information, which is stored right in front of the data. This suggests that the pointers in Cello are fully compatible with the workers and peasants pointers from the Standard and can easily cooperate.

All information about types to a greater extent is just a list of instances of types and interfaces . These guys have proven themselves very well, so they are used everywhere: from the GC to the documentation. They allow you to use objects in the context of their behavior . This is cool, because you can write algorithms in a generalized manner, relying only on input data, but not on the actual implementation of a particular structure.

Cello, in principle, is pretty smart and can automatically output behaviors (behaviors) in most cases. Objects in Cello can be printed, compared, hashed, serialized, sorted, copied and that's it. In short, heavenly delight.

Objects


Objects in Cello begin with ordinary worker-peasant structures that you know so well from C. A little later, Cello will add some meta-information to them. There is one requirement: you need to define structures without typedef . For example, let's write a structure for storing some kind of picture, yes on steroids! To do this, you need to define an ordinary sishny structure and register the newly acquired type using the Cello macro:

 struct Image { uint64_t width; uint64_t height; unsigned char *data; }; var Image = Cello(Image); 

Pay attention, we have two pieces. Original type Image and a variable that represents the type in runtime. By chance, we also called it Image . You probably paid attention to this suspicious comrade named var . In fact, var is just void* , that is, a generic pointer , but you should use the first option for convenience.

In the context of basic types, that's all. You don’t need to write anything else, the compiler will do everything for you. Now you can create variables of type Image : what's on the stack, what's on the heap. In addition, they can be printed, compared, thrown into the collection and that’s all:

 /* Allocate on Stack or Heap */ struct Image* x = $(Image, 0, 0, NULL); struct Image* y = new(Image); /* Print */ print("This is an image: %$\n", x); /* Compare */ print("Images %$ and %$ are equal? %s\n", x, y, eq(x, y) ? $S("Yes") : $S("No")); /* Put in an Array */ struct Array* a = new(Array, Image, x, y); print("Array of Images: %$\n", a); 

In fact, almost all the basic types in Cello, by default, with the implementation. But the real power of Cello is manifested when we begin to expand the implementation of these tipocals.

Constructors and destructors


Our sishnaya structure, Image , contains a pointer to some area of ​​memory that can be allocated by some other function. If you want to avoid leaks, you need to make sure that we free this memory in time. Now use Cello to define a destructor for Image :

 void Image_Del(var self) { struct Image* i = self; free(i->data); } 

You can easily bring the self argument to a generic Image* type. This is possible because the Cello pointers (those that we create with var ) are fully compatible with the worker-peasant pointers in C. Since you have a var pointer from Cello, you know that there is a specific type hanging on it (just like here, in the destructor), which means that it is absolutely safe to bring it to this type and, of course, get access to the fields of this type. In this particular case, we call free for a pointer to the data from the Image .

To register a destructor in Cello, you will want to pass it to the Cello macro, as a copy of the Instance new New class. Since we don’t want to define a constructor yet, we simply need to pass NULL to acc. field:

 var Image = Cello(Image, Instance(New, NULL, Image_Del)); 

Now, when GC in Cello comes to deal with the Image object, it will call our destructor. And what, in my opinion, cool!

Sugar, sugar, sugar


Daniel Holden wrote Cello to simplify his work in some places, so there’s enough of a variety of sugar. For example, the abbreviated syntax for creating variables or even a table (sic!):

 #include "Cello.h" int main(int argc, char** argv) { /* Shorthand $ can be used for basic types */ var prices = new(Table, String, Int); set(prices, $S("Apple"), $I(12)); set(prices, $S("Banana"), $I( 6)); set(prices, $S("Pear"), $I(55)); /* Tables also support iteration */ foreach (key in prices) { var val = get(prices, key); print("Price of %$ is %$\n", key, val); } return 0; } 

Or intricate range-cycles and other slices:

 #include "Cello.h" int main(int argc, char** argv) { var items = new(Array, Int, $I( 8), $I( 5), $I(20), $I(15), $I(16), $I(98)); /* Iterate over indices using "range" */ foreach (i in range($I(len(items)))) { print("Item Range %i is %i\n", i, get(items, i)); } /* Iterate over every other item with "slice" */ foreach (item in slice(items, _, _, $I(2))) { print("Item Slice %i\n", item); } return 0; } 


And that's not all ...


In fact, the possibilities of Cello do not end with the functionality I mentioned in this article, but it doesn’t matter, because you can familiarize yourself with the rest of the pieces with the help of the documentation . By the way, Cello has a cool Quickstart in which the author will show how to write a program that glitches .tga images in an interesting way. I strongly recommend that you read!

Answering the question whether Cello is ready for production ... there is no definite answer. C is mainly used where maxxx performance is needed, for example, in embedded systems. Whether the developers of this software want to pull GC behind them is a very controversial question, and I tend to a negative answer. On the other hand, they have experimented on Cello for quite a long time, so in principle, this is a working thing. I think that full-time C-programmers definitely need to check out.

For those who are interested in how this shnyashka is arranged inside, a reference to the githab . Among other things, I would also like to do a small survey on the topic of the post. I ask only the developers who really work with the C language to answer it, the rest I want to ask to refrain.

Source: https://habr.com/ru/post/262471/


All Articles