forked from orangeduck/Cello
-
Notifications
You must be signed in to change notification settings - Fork 0
/
README
440 lines (315 loc) · 13.2 KB
/
README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
libCello
========
Example
-------
```c
/*
** Example libCello Program
*/
#include "Cello.h"
int main(int argc, char** argv) {
/* Stack objects are created using "$" */
var int_item = $(Int, 5);
var float_item = $(Real, 2.4);
var string_item = $(String, "Hello");
/* Heap objects are created using "new" */
var items = new(List, 3, int_item, float_item, string_item);
/* Collections can be looped over */
foreach(item in items) {
/* Types are also objects */
var type = type_of(item);
print("Type: '%$'\n", type);
}
/* Heap objects destroyed with "delete" */
delete(items);
/* Tables require "Eq" and "Hash" on key type */
var prices = new(Table, String, Int);
put(prices, $(String, "Apple"), $(Int, 12));
put(prices, $(String, "Banana"), $(Int, 6));
put(prices, $(String, "Pear"), $(Int, 55));
var pear_price = get(prices, $(String, "Pear"));
print("Price of a 'Pear' is %$\n", pear_price);
/* Tables also supports iteration */
foreach(key in prices) {
var price = get(prices, key);
print("Price of %$ is %$\n", key, price);
}
/* "with" automatically closes file at end of scope. */
with(file in open($(File, NULL), "prices.bin", "wb"))) {
/* First class function object */
lambda(write_pair, args) {
/* Run time type-checking with "cast" */
var key = cast(at(args, 0), String);
var val = cast(get(prices, key), Int);
/* Format specifier for objects is %$ */
print_to(file, 0, "%$ :: %$\n", key, val);
return None;
};
/* Higher order functions */
map(prices, write_pair);
}
delete(prices);
}
```
About
-----
libCello is a GNU99 C __library__ which brings higher level programming to C. It takes inspiration from Obj-C, Haskell and Python. Most closely libCello resembles a C dialect with interfaces, dynamic/duck typing, exceptions, and some syntactic sugar. There are a selection of new keywords, and many generically named functions in the namespace, but other than that it should be fully compatible with normal C code.
Although I've made the syntax pleasant, libCello isn't a library for beginners. It is for C power users, as manual memory management doesn't play nicely with many higher-order concepts.
The high level stucture of libCello projects is inspired by Haskell and C, while the syntax and semantics are inspired by Python and Obj-C. Object Orientation in C was not the goal of this project, but I hope that with libCello I've turned C into something of a dynamic and powerful functional language which it may have once been.
libCello was created *just for fun* as an experiment to see how far we could push the C language. Therefore __libCello should NOT be used for production code!__ Many of the data structures are still inefficient in their implementation and using this library sacrifices much of the compile-time safety of the C language. Please understand the risks before you choose to use libCello.
More Examples
-------------
Some functional tools using `lambda` statements.
```c
#include "Cello.h"
int main(int argc, char** argv) {
/*
** Basic method of lambda construction .
** Must be at the statement level.
** Cannot be used in/as expression.
*/
lambda(hello_name, args) {
/* Input is a list of arguments */
var name = cast(at(args, 0), String);
print("Hello %s!\n", name);
/* Always must return */
return None;
}
/* Functions called with "call" */
call(hello_name, $(String, "Bob"));
/* Higher order functions supported */
var names = new(List, 3, $(String, "Dan"), $(String, "Robert"), $(String, "Chris"));
map(names, hello_name);
delete(names);
/* Here is an example with two arguments */
lambda(add_print, args) {
int fst = as_long(cast(at(args, 0), Int));
int snd = as_long(cast(at(args, 1), Int));
printf("%i + %i = %i\n", fst, snd, fst+snd);
return None;
}
/*
** Notice arguments to "call" in curried form.
** Use "call_with" for uncurried calling.
*/
call(add_print, $(Int, 10), $(Int, 21));
/* Partially applied Function, neat! */
lambda_partial(add_partial, add_print, $(Int, 5));
call(add_partial, $(Int, 1));
/*
** We can use normal c-functions too.
** If they have all argument types as "var".
** Then they can be uncurried.
*/
var Welcome_Pair(var fst, var snd) {
print("Hello %s and %s!\n", fst, snd);
return None;
}
lambda_uncurry(welcome_uncurried, Welcome_Pair, 2);
call(welcome_uncurried, $(String, "John"), $(String, "David"));
return 0;
}
```
More More Examples
------------------
__Defining a new Class.__
A Class is a TypeClass, also known as an Interface. These are defined to allow overloaded or generic functions.
They are defined as follows.
In the header...
```c
/** Vector - vector operations */
class {
float (*dot)(var, var);
float (*length)(var);
} Vector;
float dot(var self, var obj);
float length(var self);
```
And in the source file...
```c
float dot(var self, var obj) {
Vector* ivector = type_class(type_of(self), Vector);
assert(ivector->dot);
return ivector->dot(self, obj);
}
float length(var self) {
Vector* ivector = type_class(type_of(self), Vector);
assert(ivector->length);
return ivector->length(self);
}
```
__Defining a new Type.__
A Type is an structure which implemenents a number of Classes.
They typically have an associated data object. They can be defined as follows.
In the header...
```c
global var Vec2;
data {
var type;
float x, y;
} Vec2Data;
/** Vec2_New(var self, float x, float y); */
var Vec2_New(var self, va_list* args);
var Vec2_Delete(var self);
void Vec2_Assign(var self, var obj);
var Vec2_Copy(var self);
var Vec2_Eq(var self, var obj);
float Vec2_Dot(var self, var obj);
float Vec2_Length(var self);
instance(Vec2, New) = { sizeof(Vec2Data), Vec2_New, Vec2_Delete };
instance(Vec2, Assign) = { Vec2_Assign };
instance(Vec2, Copy) = { Vec2_Copy };
instance(Vec2, Eq) = { Vec2_Eq };
instance(Vec2, Vector) = { Vec2_Dot, Vec2_Length };
```
And in the source file...
```c
var Vec2 = methods {
methods_begin(Vec2),
method(Vec2, New),
method(Vec2, Assign),
method(Vec2, Copy),
method(Vec2, Eq),
method(Vec2, Vector),
methods_end(Vec2)
};
var Vec2_New(var self, va_list* args) {
Vec2Data* v = cast(self, Vec2);
v->x = va_arg(*args, double);
v->y = va_arg(*args, double);
return self;
}
var Vec2_Delete(var self) {
return self;
}
void Vec2_Assign(var self, var obj) {
Vec2Data* v1 = cast(self, Vec2);
Vec2Data* v2 = cast(obj, Vec2);
v1->x = v2->x;
v1->y = v2->y;
}
var Vec2_Copy(var self) {
Vec2Data* v = cast(self, Vec2);
return new(Vec2, v->x, v->y);
}
var Vec2_Eq(var self, var obj) {
Vec2Data* v1 = cast(self, Vec2);
Vec2Data* v2 = cast(obj, Vec2);
return (var)(v1->x is v2->x and v1->y is v2->y);
}
float Vec2_Dot(var self, var obj) {
Vec2Data* v1 = cast(self, Vec2);
Vec2Data* v2 = cast(obj, Vec2);
return (v1->x * v2->x + v2->y * v2->y);
}
float Vec2_Length(var self) {
Vec2Data* v = cast(self, Vec2);
return sqrt(v->x * v->x + v->y * v->y);
}
```
More More More Examples
-----------------------
Memory management is hard. Very hard when combined with a lack of rich stack types. Very very hard when combined with a whole load of high level concepts. libCello gives you a few options, which where possible, the standard library is agnostic too. You can use what you think is best.
* __Destructive Operations__ - Most of the standard library uses destructive operations and expects the user to make a copy if they exlicity want one.
* __Output Parameters__ - In some places it is more appropriate to use output parameters and in which case `assign` is used to move the data around.
* __Reference Objects__ - References wrap standard objects, where `with` can be used to declare their lifetime. And `at` can be used to dereference.
```c
local void object_lifetime_example(void) {
with(liferef in $(Reference, new(String, "Life is long"))) {
print("This string is alive: %$\n", at(liferef,0));
}
print("Now it has been cleared up!\n");
}
/* They can also be stacked */
local void many_object_lifetimes(void) {
with(liferef0 in $(Reference, new(String, "Life is long")))
with(liferef1 in $(Reference, new(String, "Life is Beautiful")))
with(liferef2 in $(Reference, new(String, "Life is Grand"))) {
print("%s :: %s :: %s\n", at(liferef0,0), at(liferef1,0), at(liferef2,0));
}
}
```
* __Reference Pools__ - Reference pools are also avaliable which use `retain` and `release` to providing a reference counting mechanism.
```c
#include "Cello.h"
local var g_pool;
local void table_fill(var x) {
put(x, $(String, "First"), $(Real, 0.0));
put(x, $(String, "Second"), $(Real, 0.1));
put(x, $(String, "Third"), $(Real, 5.7));
release(g_pool, x);
}
local void table_process(var x) {
put(x, $(String, "First"), $(Real, -0.65));
release(g_pool, x);
}
int main(int argc, char** argv) {
g_pool = new(Pool);
var x = retain(g_pool, new(Table, String, Real));
table_fill(retain(g_pool, x));
table_process(retain(g_pool, x));
release(g_pool, x);
delete(g_pool);
}
```
While useless in such a trivial example, because the pool always cleans up references on delete, it can be very useful to avoid memory leaks in things such as event loops or with general pooled memory. It can be used as a poor mans sweep garbage collector.
More More More More Examples
----------------------------
libCello provides a kind of exception handling to deal with errors.
```c
local var DivideByZeroError = Singleton(DivideByZeroError);
local int try_divide(int x, int y) {
if (y == 0) {
throw(DivideByZeroError, "Division By Zero (%i / %i)", x, y);
} else {
return x / y;
}
}
int main(int argc, char** argv) {
try {
int result = try_divide(2, 0);
} catch (e in DivideByZeroError) {
// Panic!
return 1;
}
return 0;
}
```
One can also catch multiple objects and then write conditional code based on each. Or one can catch all exceptions or any thown object by leaving the specifer list empty.
```c
try {
do_some_work();
} catch (e in TypeError, ClassError) {
if (e is TypeError) { print("Got TypeError!\n"); }
if (e is ClassError) { print("Got ClassError!\n"); }
}
try {
do_some_other_word();
} catch (e) {
print("Got Exception: %$\n", e);
}
```
Throwing an exception will jump the program control to the innermost `catch` block. If it is not handled here it is passed on to an outer block. To catch an exception one must put a reference to the thrown object. Any object can be thrown and caught as an Exception in libCello so users can create their own Exception types or find other applications for the semantics. The thrown message will be preserved internally, but be careful of throwing stack memory which may become invalidated when jumping to the new location.
More More More More More Examples
---------------------------------
There are some more examples in the demos folder as well as a large amount of reference material under tests and via the source code. Native class definitions (such as for `New`, `Eq`, `Ord`) can be found in `Prelude.h`
Explanation
------------
The first thing that probably comes into your head viewing the above code is `var`. This is a typedef of `void*` and is used via convention in libCello code to allow for overloaded functions. As you can see in the example code type checking/hinting can be done at runtime via the `cast` function.
This allows for a form of poor-man's duck-typing. If an object looks (or sounds) like it has a length, then you are more than free to use `len` upon it. One can test if a type implements a certain class with the function `type_implements(type, Class)`. Calling a function on an object which does not implement the appropriate classes will throw a `ClassError`.
Another thing that may have jumped to your mind in the examples is `new`, `delete` and the `$` symbol. These are ways to allocate memory for objects. `new` and `delete` are used for heap objects and call constructors/destructors. `$` (which I like to think of as a clef) is used to allocate objects on the stack. It __doesn't__ call a constructor or destructor, but will initialize the corresponding types data structure with the arguments provided. This makes it useful for basic or boxed types.
The idea of `with` is stolen from Python and will execute `enter_with` and `exit_with` on an object on entrance and exit to the block. The behaviour of these can be defined by implementing the `With` class. Currently it does not handle exceptions inside the block.
Other than these things there is not much surprising in the code which cannot be explained via syntactic sugar.
* `is`, `not`, `and`, `or` -> `==`, `!`, `&&`, `||`
* `elif` -> `else if`
* `local` -> `static`
* `global` -> `extern`
* `class` -> `typedef struct`
* `data` -> `typedef struct`
finally the `instance` and `methods` macros are helpers for defining Type objects statically. Because Type objects are somewhat complicated the syntax is very awkward otherwise.
Questions & Contributions
-------------------------
Questions, Contributions and Feedback are more than welcome.
You can e-mail me at:
Or get in contact via the IRC channel at #libCello on Freenode