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 ... 7 8 [9] 10 11 ... 17
121
Ideas / Re: Tagged unions
« on: November 07, 2018, 01:35:24 AM »
Note that tagged unions is syntax sugar for something like

Code: [Select]
type enum Foo__tag : i8 {
   i, c
};

type struct Foo {
  Foo__tag tag;
  union {
  int i;
  const char *c;
  };
};

@istag(foo.i) compiles to (foo.tag == Foo__tag.i), and @tag(foo) to foo.tag finally Foo.i is an alias for Foo__tag.i

122
Ideas / Tagged unions
« on: November 07, 2018, 01:14:15 AM »
On github Rust's enums were mentioned, and that they're essentially tagged unions.

Several languages that offer unions also have tagged unions "out of the box".

Borrowing from Cyclone a bit, consider this:

Code: [Select]
tagged union Foo {
  int i;
  const char *c;
};

Foo foo;
foo.i = 3;
@istag(foo.i) // => true
@istag(foo.c) // => false
foo.c = "hello";
@istag(foo.i) // => false
@istag(foo.c) // => true

switch(@tag(foo)) {
  case Foo.i: printf("Was %d\n", foo.i); break;
  case Foo.c: printf("Was %s\n", foo.c); break;
}

The code is a variant of what Cyclone and Rust does.

Note that I use the @-prefix for the compile-time keywords. If we decide on not having a @-prefix, then tag / istag would need to be keywords.

123
Ideas / Generics of Cyclone
« on: November 07, 2018, 12:57:48 AM »
Cyclone has generic types, called datatypes, they are used in this manner:

Code: [Select]
  datatype T {
      Integer(int);
      String(const char *@fat);
    };

    datatype T.Integer x = Integer(3);
    datatype T.String y = String("hello, world");

    void printT(datatype T@ a) {
      switch (a) {
      case &Integer(i): printf("%d",i); return;
      case &String(s): printf("%s",s); return;
      }
    }

I'm not making a proposal to add it, merely something that would be useful to discuss.

124
Ideas / Re: Macros again
« on: November 06, 2018, 08:29:14 PM »
The SMART C macro system has a bit more stuff to consider: https://repository.upenn.edu/cgi/viewcontent.cgi?referer=https://www.google.com/&httpsredir=1&article=1474&context=cis_papers

Worth a read. Note that they add macro FOR, IF-ELSE, SWITCH. I think this is a reasonable thing to do. "SWITCH" is present in the __Generic macro in C, IF-ELSE is obviously there already, then finally the FOR loop... I think that is an important additions since its lack is making a lot of macros much more complicated.

125
Implementation Details / Code formatting of C2's c++
« on: November 05, 2018, 07:59:56 PM »
This might seem like a minor issue, but wading through thousands of lines of Clang (I've cut away in excess of 30,000 lines of code), there are two coding habits that makes the code IMMENSELY hard to read.

One is single line if's:

Code: [Select]
if (foo)
  do_something();

This obviously is even worse when adding else as well. The problem here is when you try get an overview of  a function. Here K&R + single statement on two lines is making something really horrid:

Code: [Select]
namspace foo {

/* many lines of code */

  void a_method() {
  /* MANY lines of code */
    do_struff();
    if (foo)
      do_something();
  } // <- What ends here??

/* more lines of code */

} // <- What ends here???

Basically it's hard what block ends where.

I prefer (1) single statements ifs ALWAYS on a single line, Allman style. Any of those would solve the problem, BOTH is best:

ALLMAN
Code: [Select]
namspace foo
{

/* many lines of code */

  void a_method()
  {
  /* MANY lines of code */
    do_struff();
    if (foo)
      do_something();
  }

/* more lines of code */

}


K&R Single line:
Code: [Select]
namspace foo {

/* many lines of code */

  void a_method() {
  /* MANY lines of code */
    do_struff();
    if (foo) do_something();
  }

/* more lines of code */

}

ALLMAN single line:
Code: [Select]
namspace foo
{

/* many lines of code */

  void a_method()
  {
  /* MANY lines of code */
    do_struff();
    if (foo) do_something();
  }

/* more lines of code */

}

I understand most have a preference for K&R, so I'm not going to fight for Allman. But that two line single statement is a killer.

Switch-case where "case" does not have indention is difficult for similar reasons, although the two line if is by far the worst.

126
Ideas / Re: Add defer
« on: November 05, 2018, 06:40:08 PM »
Here is an extended example of defer and @autofree:

Code: [Select]
{
   int x = z();
   defer{ A(); }
   Foo *foo = malloc(sizeof(Foo *)) @autofree;
   if (foo == NULL) return -1;
   int x += w();
   if (x > 10) return 10;
   if (x == y()) defer { B(); }
   if (x < 0) {
     defer { C(); }
     if (D() > 10) return -2;
     x = 0;
   }
   return d() + x;
}
// Compiles to:
{
   int __res;
   int __defer_cond_1;
   void *__autofree_1;

   x = z();
   __autofree_1 = malloc(sizeof(Foo *));
   foo = __autofree_1;
   if (foo == NULL) {
     __res = -1;
     goto DEFER_2;
   }
   x += w();
   if (x > 10) {
     __res = 10;
     goto DEFER_2;
   }
   __defer_cond_1 = x == y();
   if (x < 0) {
     int __early_exit = 0;     
     if (D() > 10) {
        __early_exit = 1;
        __res = -2;
        goto DEFER_4;
     }
     x = 0;
     DEFER_4:
     C();
     if (__early_exit) goto DEFER_3;
   }
   __res = d() + x;
   DEFER_3:
   if (defer_cond_1) B();   
   DEFER_2:
   free(__autofree_1); // Assume free(NULL) is no-op
   DEFER_1:
   A();
   return temp;
}

127
Ideas / Does C2 support this? Otherwise it should. "Labels as values"
« on: November 05, 2018, 06:35:38 PM »
For some problem domains, this can be extremely effective:

https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html

128
Ideas / Add sigil to all compile-time functions.
« on: November 05, 2018, 12:15:11 AM »
Compile time resolution is important for a lot of functionality. Unfortunately, adding keywords for them pollutes the keyword space AND makes it harder at a glance to tell compile-time directives from runtime ones.

C2 already adds attributes using the @(...) syntax. There are roughly the following symbols available:

@ $ # ´ ~ ' `

My proposal is that we use the @ prefix, but I don't have a really strong opinion. Here are a few code samples:

Code: [Select]
@foo();       // expand macro foo

@sizeof(i32); // currently "sizeof"

@typeof(a);   // return the type of a

@sizeof(@typeof(a)); // the size of a

func i32 do_something() @(inline)
 

Code: [Select]
$foo();       // expand macro foo

$sizeof(i32); // currently "sizeof"

$typeof(a);   // return the type of a

$sizeof($typeof(a)); // the size of a

func i32 do_something() @(inline)
 

Code: [Select]
#foo();       // expand macro foo

#sizeof(i32); // currently "sizeof"

#typeof(a);   // return the type of a

#sizeof(#typeof(a)); // the size of a

func i32 do_something() @(inline)
 

Code: [Select]
`foo();       // expand macro foo

`sizeof(i32); // currently "sizeof"

`typeof(a);   // return the type of a

`sizeof(`typeof(a)); // the size of a

func i32 do_something() @(inline)
 


Code: [Select]
~foo();       // expand macro foo

~sizeof(i32); // currently "sizeof"

~typeof(a);   // return the type of a

~sizeof(~typeof(a)); // the size of a

func i32 do_something() @(inline)
 

I think that nesting @ is a bit ugly looking though. Opinions?

129
Ideas / Re: Add defer
« on: November 04, 2018, 07:06:19 PM »
A further refinement / extension of the idea above, suggested is to have a defer attribute which refers to a function:

Code: [Select]
void foo1() {
  Foo *f @defer(free) = malloc(sizeof(Foo));
  FILE *file @defer(fclose) = fopen("file.txt","r");
}


130
Ideas / Re: Add defer
« on: November 04, 2018, 02:14:41 PM »
I also propose that we add an attribute called @autofree that does an implicit defer:

This is ok:
Code: [Select]
func void something() {
  Foo *f = malloc(f);
  defer { free(f); }
  ... code ...
}

But this might be better:
Code: [Select]
func void something() {
  Foo *f @autofree = malloc(sizeof(Foo)) ;
  // Foo *f = nil; defer { if (f) free(f); } f = malloc(sizeof(Foo));
}

func void something() {
  Foo *f @autofree = malloc(sizeof(Foo)) @autofree;
  // Temp *t = malloc(sizeof(Foo)); if (t) defer free(t); f = t; 
}

Note that we achieve some subtle differences depending on where we put the attribute.

131
Ideas / Re: Dynamic arrays & fixed arrays
« on: November 04, 2018, 02:07:20 PM »
I've been banging my head against this problem now for quite a while.

Basically, if we have a dynamic array, resize it, then all the pointers to it will break. (Obviously). However, this might not be immediately obvious and lead to problems.

Two main possibilities:

  • A dynamic array is a pointer to a struct with a value and a pointer, e.g. i32[...] is struct { i32 size; i32 allocated, i32* arr; }*. It's unimportant that the underlying arr changes, since it always accessed using indirection. This adds a load, but the result can be cached. Copies of the array are aliases of the underlying data.
  • A dynamic array is a struct (same as above but without the pointer). It is mutable, but any copy becomes a shallow copy of the original.

(1) here our struct works as a pointer all of the time. Nothing weird. We do have the double indirection to get to a value in our array, but since dynamic arrays are likely to have mandatory bounds checking, it's not like anyone will use it for heavily optimized situations anyway.
(2) here someone using the array needs to be careful not to incur accidental copies. A way to avoid that would be to say that copies need to be explicitly done, so int[...] y = x is not allowed, only int[...] y = copy(x) is ok.

Furthermore we need to consider lifetimes. It would be nice to allocate the array on the stack, but this causes some subtle issues with setjmp, as we cannot reclaim the underlying pointer when the stack is dropped.

We're clearly in need of some way to safely dealloc the array, but using the stack probably isn't it.


132
Joining / Quick intro
« on: November 03, 2018, 10:56:56 PM »
I'm 45 years old, co-founder of a small programming company (AEGIK) that mostly does consultant work but we've also published some games.

I have been wanting to work on a language since Swift came out and we were plenty of devs that just said "why didn't we get something ObjC-3 instead?" and a lot of people started working on their own languages. That sort of triggered my interest in programming languages.

I've been wanting to contribute to some upcoming language, but have had some problems finding something I like. Either they're too OO, too high level, too crazy with making the language fool-proof in some aspect, too happy to add everything and the kitchen sink for good measure, too opinionated etc.

For myself I've looked at more advanced language ideas, scrapped those, thought of something else, scrapped that etc. I'm finding that the pursuit of language refinement and depth ends up quickly being a goal in itself.

Look at Rust, where the language is so opaque that you sit for hours just trying to get something trivial to compile.

Look at Zig, which has the big feature of compile time execution – which ends up making the code extremely hard to visually parse (there is some meta reading going on: you read the compile time code and execute it mentally to figure out what's actually compiled, then read what that compiled code would do), but that's the big idea of that language, so you're stuck with it.

These are ambitious, "exciting" languages with new ideas. But I'm not sure that the world needs another one of those.

I want a language with very little ideas and just good ergonomics. I'm also going back to the basics, realizing that the OO everyone spent about 20 years with is mostly a huge waste of time.

The fact that C2 tries to be very close to C is a very good constraint to work with for language design, which makes this project interesting.

133
Ideas / Re: Relax variable initialization rules
« on: November 03, 2018, 03:05:59 PM »
Also for consideration: VLAs are not really safe unless there is bounds checking, which is why they are removed from the linux kernel. I see three options:

  • VLAs are always heap allocated.
  • Heap allocate VLAs above a certain (configurable) size.
  • VLAs do bounds checking against available stack memory to prevent overwrites.
  • VLAs have a max limit that may be set as a compiler option. Again, this would mean bounds checking
  • Skip VLAs entirely

(2) Might be the most flexible solution I think, but obviously less predictable than the others.

134
Ideas / Re: size_t - [Ice Tea] ? :)
« on: November 03, 2018, 01:37:47 PM »
Umm.. what has this to do with C2? C2 will note use the name size_t, nor is that much of a design issue. If you want contribute, maybe put some time into critique of my suggested improvements for C2.

135
Ideas / Re: One more big thing: Errors
« on: November 03, 2018, 01:34:42 PM »
Since we want seamless interop with C, I'm thinking of two possible ways to do this.

First to recap the solution proposed:

1. For most architectures, we return a struct { union { T value; E error; } } with the carry flag set to discriminate the result on AArch64, ARM, x86 and x64. For other architectures, such as RISC-V, it is put in a dedicated register.

2. To the developer, this actually looks like returning struct { union { T value; E error;  }; bool failed; } where "failed" is set using this register.

3. We introduce some syntactic options that allows branching directly on the flag or on the register for rethrows.

4. For C compatibility we create a version with explicity boolean by funneling it through another function that has the union with the boolean explicit.

Syntax is another matter.

The paper suggests:
Code: [Select]
int some_function(int x) fails(float) {
  // Return failure if x is zero
  if (x == 0) return failure(2.0f);
  return 5;
}

A try macro:
Code: [Select]
fails(float) const char *some_other_function(int x) {
  // If calling some_function() fails, return its failure immediately
  // as if by return failure(some_function(x).error)
  int v = try(some_function(x));
  return (v == 5) ? "Yes" : "No";
}

And a caught macro
Code: [Select]
#define caught(T, E) struct caught_ ## T ## _ ## E { union { T value; E error; }; _Bool failed; }
int main(int argc, char *argv[])
{
  if (argc < 2) abort();
  caught(const char *, float) v = catch(some_other_function(atoi(argv[1])));
  if (!v.failed) {
    printf("v is a successful %s\n", v.value);
  } else {
    printf("v is failure %f\n", v.error);
  }
  return 0;
}

My initial proposal:
Code: [Select]
func i32 !! f32 some_function(i32 x) {
  // Return failure if x is zero
  if (x == 0) fail 2.0;
  return 5;
}

func const char * !! f32 some_other_function(i32 x) {
  int v = some_function(x) !!;
  return v == 5 ? "Yes" : "No";
}

func i32 main(i32 argc, char*argv[])
{
  if (argc < 2) abort();
  char * v = some_other_function(atoi(argv[1])) !! {
    printf("v is failure %f", err);
  } else {
    printf("v is a successful %s\n", v);
  }
  return 0;
}

Note that this syntax is a bit less flexible than that of Zig or the proposal, since "err" here is considered a special variable that will has the type of the last error. Also, we do an escape analysis to see that v is actually not used in case of the error.

As a comparison, consider the last part with a Zig syntax:

Code: [Select]
func i32 main(i32 argc, char*argv[])
{
  if (argc < 2) abort();
  if (some_other_function(atoi(argv[1]))) |char * v|
    printf("v is a successful %s\n", v);
  } else |f32 err| {
    printf("v is failure %f", err);
  }
  return 0;
}

Here escape is prevented by the syntax.

However, Swift has two lessons here, as they started out looking like:

Code: [Select]
if (let x = do_something()) {
  // main code with x here
} else {
  // error handling here
}

This code quickly leads to spaghetti:

Code: [Select]
if (let x = do_something()) {
  if (let y = foo()) {
    if (let z = foo()) {
      // main code with x, y, z here
    } else {
      // error handling (cleanup x, y?)
    }
  } else {
     // error handling (cleanup x?)
  }
} else {
  // error handling
}

In order to avoid this type of code, they introduced a "guard let". I actually made that proposal as feedback to Swift 1 beta, but it didn't arrive until I think Swift 2. There are some other improvements in Swift as well, but it shows the problem.

Anyway, the lesson here is that anything causing nesting is bad. A similar problem occurs with exceptions, but it's more manageable there, since you then can catch each error in a separate clause.

Pages: 1 ... 7 8 [9] 10 11 ... 17