Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Messages - lerno

Pages: 1 2 [3] 4 5 ... 17
Ideas / A brief and simple generics proposal
« on: December 28, 2018, 08:04:34 PM »
Since we want semantic macros we need to consider the usecase where C uses macros to create generic code. Here is a sketch of how that could be implemented.

1. A generic implementation is implemented in a generics module:

Code: [Select]
module vector (A, B, C); // A, B, C are generic parameters.

type Foo struct {
   A a;

func C test(B b, Foo *foo) {
   return a + b;

To use this generic code, simply import:

Code: [Select]
import vector(f64, f32, i32) as generic_tests;

func f64 test()
   generic_tests.Foo foo = { 2.0 };
   return generic_tests.test(3.0, foo);

Thanks to the namespacing in C2, we can don't actually need to create an alias if we want to. The module is all we need in order to make this work.

One issue

There is only one issue here. This solution has pretty much the same issue as C++ templates – lack of good error messages.

A solution would therefore be to set up a clause with contracts the types must pass. This is similar to the solution proposed in C++ / Go2:

Code: [Select]
module vector (A, B, C);

contract(A a, B b, C c) {
   a + b;           // Only expressions allowed
   c == a + b;   // Only expressions allowed

... code ...

A generic is not completely error checked until it is instantiated, with the contract however, the expressions are FIRST validated, and reported as errors.

Code: [Select]
import vector(struct Bar, f32, i32) as gen_test;

// This would give the error
---> Illegal arguments for generic module vector, breaks contract 'struct Bar' == 'f32' + 'i32'

This proposal is minimal, does not clutter the language with type arguments (i.e. no Foo<f32> etc), prevents misuse and requires very little extra syntax and complexity.

Implementation Details / Re: Implementation based on Clang or...?
« on: December 21, 2018, 11:17:22 AM »
Well I was not talking about "at the moment" but toward the future of a possible self-hosted C2.

Implementation Details / Implementation based on Clang or...?
« on: December 20, 2018, 08:25:20 PM »
There are two lightweight C-based compilers of C, TCC and recently 9cc.

One idea would be to implement the C2 compiler on top of either of those instead. TCC is known for it's very fast compilations speed. The fact that both are written in C allows us to potentially implement them in C2 instead.


1. Use C2-Clang/LLVM to compile a mod of 9cc/TCC rewritten in C2
2. Write C2-9cc/TCC which then is piece by piece replaced by C2 implementation.

Obviously this is something for the future and should not detract from the current work on C2-Clang/LLVM.

Ideas / Re: Extended switch statement
« on: December 17, 2018, 08:10:29 PM »
First, a minimal change would be to simply allow ranges in switch statements, such as:

Code: [Select]
switch (x) {
  case 1 .. 10:
  case 11 .. 100:

Secondly, I think it's a more constructive way to work by actually discussing how a proposal should look if adapted to C, rather than to dismiss it early because it doesn't feel like C.

I know it is tempting to dismiss this altogether, but if we have a proposal that looks like suitable for C, it's easier to both determine whether it is an evolution or not (i.e. sufficiently small change) and if worthwhile AND to have something to refer to later on when similar proposals inevitably show up.

Finally, its a good exercise "translating" the proposal to a useful form. I would argue the same for the error proposal: first see if it can be molded into something less ugly then decide whether it should be dismissed or not.

The limited switch IS a C deficiency, just like the limited error handling, header files, preprocessor macros etc. In order to see if there's an evolutionary way out of it, an evolutionary proposal should be made and I'm submitting incomplete proposals exactly so that it's more inviting to come and offer counterproposals and changes. The incompleteness is completely deliberate.

So again, I would like to discuss how it should look, then decide if that is useful enough to be considered for inclusion. Not dismissed in its incomplete state.

General Discussion / Re: Overwriting fields in init struct.
« on: December 16, 2018, 07:28:51 PM »
Weren't there any links in that article?

Ideas / Extended switch statement
« on: December 16, 2018, 04:08:21 PM »
Something I’ve been considering is an extended switch statement that is a structured if-else. So consider something like this:

Code: [Select]
switch (x) {
  case x > 200: return 0;
  case x < 2:
  case 0:
  case x > y && a < 1:

So it’s a glorified if-else actually. It has better readability over if-else though.

I’m thinking about some other things:

  • In the code above x is explicit i conditionals, but that does not make sense for switch (foo()) {...}. However using ”broken” expressions is harder to parse and sometimes impossible. For simple cases we can do ”case >100:” but what about the case of x > 10 && x < 20? It can’t be expressed that way.
  • A solution to the above is to allow the switch to introduce switch scoped variables: switch (int i = foo()) { ... }. Multiple variables could be permitted: switch (int i = foo(); int j = bar()) {}
  • If using (2) we need to decide whether additional variables could skip initialization if they aren’t used in the case.
  • I think there is a sweet spot between the plain switch and full pattern matching, especially when dealing with ranges and matching strings.

Has anyone given this any thought?

I think C2 should certainly /evolve/ switch-case a bit. But how far and if it should be a completely new statement type or not - that’s something I’m unsure of.

The main two use-cases for me to replace (1) long if-else chains (2) switch-case where we really want ranges instead single numbers and often need to use if-else instead of switch.

Ideas / Re: Non-mixing of types bool and integer
« on: December 10, 2018, 11:51:51 AM »
This is one of the core pillars of C flexibility. How many times have you been helped / hindered by this?

Ideas / Re: Macros again
« on: December 10, 2018, 12:45:46 AM »
Another C type lang with semantic macros worth looking at:

Ideas / Re: typedef struct name_ {} name;
« on: December 07, 2018, 06:23:48 PM »
Yes, that works in C2.

On a completely different note: please change the special font you're using. It makes your texts quite unreadable.

General Discussion / Re: C2x
« on: December 07, 2018, 06:22:33 PM »
1. Follow the changes here: Volatile should apply to the access of an object through an lvalue marked volatile.

2. Issues supporting u128, i128, u256, i256 should be considered so that C2 doesn't end up with the same problems. Also, consider emulated 128, 256 integer, and a way for supporting optional integers above width of 64 bits

3. Issues with atomics that have different guarantees for ++x and x++:

4. User defined attributes: This could be useful in a macro system, where one could – for example – create a serialization system based on macros and these user defined attributes.

5. "Add a new calling conventions with error return for library functions that avoids the use of errno" relevant for updated error handling in C2.

6. "constexpr to evaluate expressions at compile time as in C++" relevant for semantic macros.

7. Introduce UTF-8 character type:

Some of these are mostly relevant when compiling to LLVM since it affects LLVM code generation.

General Discussion / Re: Overwriting fields in init struct.
« on: December 07, 2018, 11:05:24 AM »
I am concerned over the fact that we're excluding an important feature that's used in the linux kernel. I would actually claim that we're *not* adding much complexity, since we can actually prune this in the parsing step already!

Ideas / Re: Error handling [proposal]
« on: December 07, 2018, 10:49:58 AM »
I understand the scepticism. But let’s see if we can refine the proposal syntax wise first? I’ll adress the practical issues if we can create something syntactically simple.

Ideas / Re: Error handling [proposal]
« on: December 05, 2018, 05:03:22 PM »

From C, it is possible to use the generated error structs. Some additional macros can be written to ensure even smoother usage.

Calling C, it can be useful to have a seamless conversion of result-code based calls (1), errno-calls (2) and calls with error as in-parameter (3) e.g:

Code: [Select]
// 1
API_RESULT do_something(int a, Foo *foo);

// 2
bool success = do_something(int a, Foo *foo);
if (!success) { /* do something with errno */ }

// 3
Error err;
bool success = do_something(int a, Foo *foo, &err);
if (!success) { /* do something with err */ }

All of these could be automatically wrapped by C2 later on. However, it's not important for the first version.

Function signatures

Zig uses ! to denote errors or error separator, e.g:

Code: [Select]
pub fn parseU64(buf: []const u8, radix: u8) !u64 { ... } // inferred error
pub fn parseU64(buf: []const u8, radix: u8) anyerror!u64 { ... } // catchall
pub fn parseU64(buf: []const u8, radix: u8) ErrorSetX!u64 { ... } // throws errors from the ErrorSetX enum.

For Go it's just part of the signature.

For C2 there are a bunch of solutions:

Code: [Select]
func u64 parseU64(u8* buf, u8 radix) !anyerror { ... }
func u64 parseU64(u8* buf, u8 radix) anyerror!!  { ... }
func u64 ! anyerror parseU64(u8* buf, u8 radix)  { ... }
func u64 !! anyerror parseU64(u8* buf, u8 radix)  { ... }
func u64 parseU64(u8* buf, u8 radix) fails(anyerror) { ... }
func u64 parseU64(u8* buf, u8 radix) throws anyerror  { ... }
func u64, anyerror parseU64(u8* buf, u8 radix)  { ... }
func u64, anyerror!! parseU64(u8* buf, u8 radix)  { ... }
func u64 parseU64(u8* buf, u8 radix) anyerror!!  { ... }

I believe the idea of Zig to simply make the error code an enum union is good. Unlike other schemes, this means that adding additional error codes does not break the signature. The enum union should be register sized but at least 32 bit, meaning 64 or 32 bits in practice.

Encoding additional information some further discussion, there are essentially 3 approaches:

  • No additional data, just the enum
  • Error is encoded in the result
  • A thread local errno can encode information

(3) breaks purity of functions.
(2) changes size in return object if the error payload is allowed to vary. If (2) returns a pointer then cleanup of that pointer might be hard to enforce.
(1) Is limiting, and will probably mean ad-hoc solutions passing in &err-structs.

I'm slightly partial to (2) despite the drawbacks.

Ideas / Error handling [proposal]
« on: December 05, 2018, 02:34:37 PM »
The main idea is to pass results using an error channel that is separate from the underlying result. The call is compatible with C.

A return value can be described as:

Code: [Select]
struct _result {
   union {
     Result result;
     Error error;
   bool error;

Where "Result" is the result type which will vary by method.

For example, consider a method createFoo() that could return an error or a Foo*. The C signature would look like this:

Code: [Select]
struct _resultFooStar {
   union {
     Foo* result;
     Error errorResult;
   bool error;

struct _resultFooStar createFoo() {

For C it can then be used in this manner:

Code: [Select]
struct _resultFooStar res = createFoo();
if (res.error) {
  // Do something with res.errorResult
Foo *foo = res.result;

For C2 we obviously want some more syntactially pleasing. The tricky part is to assign both error and normal result, even though they are exclusive.

Go would write something like foo, errorResult := createFoo() but this still requires an extra test and is something they actively work on to improve. Not to mention that C2 doesn't have tuples.

Keyword or not?

We can decide on using keyword (catch, rethrow) or use symbols, e.g. !!, !? etc. Below I will offer both versions with keywords and without keywords.

Rough common ground

First of all we need a way to rethrow any error to the top. In Go this would be:

Code: [Select]
res, err := createFoo();
if (err) return nil, err;

Zig on the other hand uses "try" to signal that it will rethrow:
Code: [Select]
res = try createFoo();

Here I suggest we use the same method as Zig, but with postfix:

Code: [Select]
res = createFoo()!!; // !! to signal rethrow.
res = createFoo() rethrow; // using keyword instead.
res = createFoo(); // compile time error since error is not handled.

Secondly, we need a way to ignore the error and replace it with a dummy value if there was an error.

In Zig:

Code: [Select]
res = createFoo() catch defaultFoo; // Return defaultFoo in case of error.

For C2 I propose the following:

Code: [Select]
res = createFoo() !! defaultFoo; // The idea is using !! as "||"
res = createFoo() ?! defaultFoo; // Instead borrowing from ?: syntax
res = createFoo() catch defaultFoo; // Using keyword catch
res = createFoo() else defaultFoo; // Using keyword else

Different possibilities

For handling errors we have two major directions (that actually can be used together). One is using error handlers defined separately, the other is handling direct at the callpoint.

We can look at Zig's handling:

Code: [Select]
if (createFoo()) |foo| {
} else |err| switch (err) {
  error.FooCreation => {
    // handle this...
  // handle next error here ...

In the Go2 proposal we have handlers:

Code: [Select]
handle err {
  if (err == "FooCreation") {
     // handle this
  } else {
    // Handle other error

foo := check createFoo()

C2 errors with inline error handling (like Zig)

Code: [Select]
// Using !!
FILE *f = fopen("Foo") !! (err) {
  // Switch-like structure opened by default
  case error.FooCreation:
    // handle this...
  case ...
   // handle next error here ...

// Using catch keyword
FILE *f = fopen("Foo") catch (err) {
  // Switch-like structure opened by default
  case error.FooCreation:
    // handle this...
  case ...
   // handle next error here ...

// Using !=> with error variable being implicitly defined (does not allow nesting)
FILE *f = fopen("Foo") !=> {
  // Switch-like structure opened by default
  case error.FooCreation:
    // handle this...
  case ...
   // handle next error here ...

The downside of the above is when wanting to handle things in an if statement. Consider:

Code: [Select]
while (Foo* foo = createFoo()) {

Now envision error handling of the above type. Not pretty. Here error handlers shine.

C2 errors with error handlers

Code: [Select]
handler errHandler(err) {
  case error.FooCreation:
    // handle this...
  case ...
   // handle next error here ...

Foo *foo = createFoo() catch errHandler;

while (Foo* foo = createFoo() catch errHandler) {

This also allows for reuse of handlers at several points in the code.

Note that they are not mutually exclusive, they can actually coexist.


Finally I end with a few examples that can be used to imagine real life situations:

Rethrowing or using defaults:

Code: [Select]
if (a_may_throw() rethrow + 2 > 0) {
    // do something

if (a_may_throw()!! + 2 > 0) {
    // do something

if ((a_may_throw() !! 0) + 2 > 0) {
    // do something

if ((a_may_throw() ?! 0) + 2 > 0) {
    // do something

(I'll add some more later)

Proposals rules out

I considered a solution like this:

Code: [Select]
FILE *f ! err = fopen("Foo");

// read(f) - Would be compiler error
// as err is not tested in the scope

if (err) {
    printf("Found error! %s", err);
    raise err;       

// f can be used here since err is checked

But it did not seem attractive enough compared to the other methods (inline and error handlers), and it also requires more analysis and special syntax / grammar without adding clarity.

Ideas / Re: Typeinfo at runtime
« on: December 03, 2018, 02:08:56 AM »
vtype could actually be simply an enum, generated at runtime.

Pages: 1 2 [3] 4 5 ... 17