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.


Topics - lerno

Pages: 1 2 [3] 4 5
31
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.

32
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

33
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?

34
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.

35
Ideas / Relax variable initialization rules
« on: November 02, 2018, 06:42:01 PM »
According to the docs, this is not allowed:

Code: [Select]
i32 foo = 1;

i32[8] array = {
    [foo] = 1,    // error: initializer element is not a compile-time constant
}

However, there is no real reason why we could not support this:

Code: [Select]
func void foo(arg i32)
{
   i32[arg + 1] array = {
     [arg] = 1;
   }
}

As this is could be syntactic sugar for the following code:

Code: [Select]
func void foo(i i32)
{
   i32[i + 1] array = {}
   array[i] = 1;
}

It makes array usage more flexible and uniform. (Even though few might use this functionality)

36
General Discussion / Other reference languages
« on: November 01, 2018, 10:42:47 PM »
Alef and Limbo (both precursors to Go) are useful to have a look at.

37
Implementation Details / ASM validation
« on: November 01, 2018, 09:12:16 PM »
It looks like ASM validation is only partial (and based on Clang) should more detailed parsing/checking be done?

38
Ideas / Anonymous structs (actually, tuples)
« on: October 30, 2018, 03:35:19 PM »
We could have tuples through anonymous structs with destructuring.

Code: [Select]
func struct { i32, f64 } foo() {
   return struct { 1, 4 };
   // also ok: return { 1, 4 };
}

i32 a, f64 f = foo(); // <- we need to consider parsing here, this collides with the C comma operator.

struct { i32, f64 } x = foo();

What's missing here is some way to only keep one part. If we had named arguments, that could work though!

Code: [Select]
func struct { i32 loop, f64 time } foo();

i32 x = foo().loop;

The rewrite to C is fairly straightforward here, since we're just using structs.

39
Implementation Details / Decoupling DiagnosticsEngine
« on: October 29, 2018, 05:34:47 PM »
It would be nice to be able to decouple the diagnostics engine. As far as I can tell it's almost always Diag.Report which is used.

Can we have some sort of convenience here?

1. A thread local global (access using macro)
2. A wrapper object (explicitly passed in like the DE object is used today)
3. Pull out the DE and push a copy into C2 that we can handle and update like we want.

40
Implementation Details / Panic type of errors.
« on: October 29, 2018, 03:58:45 PM »
I've added a pull req now to use macros FATAL_ERROR and TODO instead of assert(0) or assert(0 && "Todo") in the code. First of all it should always crash on fatal error! And secondly the TODOs will then actually break on builds of unsupported code instead of producing incorrect results...

41
Ideas / Enum improvements
« on: October 29, 2018, 01:13:13 AM »
1. Allow an automated conversion of enum value => char*

90% of my X Macros are making a mapping of enums to strings for debugging purposes. The easiest would be to make this like a struct function.

TheEnum.to_string(my_enum_value)

Also, consider making enum_max / enum_min the same:

TheEnum.max_val

TheEnum.min_val

2. Allow associated values with enums.

This is a bigger feature. An example:

Code: [Select]
type enum State [char* name, i8 bit] i32  {
   START("begin!", 0x01) = 0,
   END("end it!", 0x10)
}

State.bit(State.START) // returns 1
State.bit(State.END) // returns 16
State.name(State.START) // returns "begin!"

The associated values simply generate these new functions which are switch cases.

3. Store a list of all enum values.

This relies on fixed arrays to be useful.

Code: [Select]
// Following the example above:
State.all // returns i32[] = { 0, 1 }


42
Ideas / Dynamic arrays & fixed arrays
« on: October 28, 2018, 10:07:49 PM »
In my proposal about "real arrays" I gave the following proposal:

Code: [Select]
type struct
{
   pointer_size length;
   some_type *start;
}

It might be better putting the pointer first, since that way we can convert to the pointer with a simple cast:

Code: [Select]
type struct
{
   some_type *start;
   pointer_size length;
}

I wrote that "All arrays are always passed by reference", meaning that the struct is actually passed by value, but since it is a fat pointer, it actually becomes pass by ref. So this makes sense for fixed arrays, but for dynamic arrays that breaks down.

Our dynamic array should look like this as a struct:

Code: [Select]
type struct
{
   some_type *start;
   pointer_size length;
   pointer_size allocated;
   Allocator *allocator;
}


But for this we need to pass the whole array by value!

Unfortunately, we can't then make slices in the same manner as with the fixed vector, a slice to a dynamic vector needs to look different from a slice to a normal vector. This is rather vexing since we would have preferred to let them convert into each other. Since the first two fields are structurally the same for the fixed array and the dynamic one, they could transform into each other.

This is safe:

Code: [Select]
func void foo(int[*] y) { ... };
func void bar(int[] x) { ... };

int[*] a = { 1, 2, 3 };
foo(a);
int[] b = cast<int[]>(a);
bar(a); // converts to foo(cast<struct FixedArray>(a))
printf("%d", b[0]);


These are unsafe:
Code: [Select]
func void foo(int[*] y); { ... }
func void bar(int[] x) { ... };

int[*] a = { 1, 2, 3 };
int[] b = cast<int[]>(a);
int[] c = a[0:1];
foo(a);
printf("%d", b[0]); // The pointer in b might have been freed.
printf("%d", c[0]); // The pointer in c might have been freed.

Either we simply accept these weaknesses... we see the errors as similar to the exceptions that occur in say, Java when getting a view of a map or a list that's later updated. OR we try to be clever. I'm somewhat for the less clever solution that might lead to errors when used incorrectly.

43
Ideas / Allocation strategies
« on: October 28, 2018, 07:01:28 PM »
One interesting thing would be to make it easy to use allocation transparently.

First of all we introduce a thread local global called "context". There are actually two ways of handling this: either context is a real thread local, or we keep an invisible context pointer locked in a register.

The context is a simple struct

Code: [Select]
type struct Context
{
   i64 thread_id;
   Context *previous;
   Allocator *allocator;
   void *data;
}

We then add func Context *push_context(), which basically creates a new Context, then pushes it on top. And the corresponding pop method that frees the data pointer (if non nil), and switches to the previous context. (Note that the lifetime of the Allocator is not tied to the Context)

The point of all this is that malloc uses the allocator in the current context and not a global allocator. By default the allocator can then be one-per-thread, which prevents need for locks in the allocator.

This functionality has a lot of "giving someone a bazooka to hunt mosquitos with", however it allows us fine grained control over growable arrays, maps and strings.

The usual problem with one of those, is that we need some kind of allocator – but we don't know exactly what is the best. If we want to play PHP we could push a bump allocator on the stack, then free everything when the page request ends. But we could still swap allocators if we needed long lived objects.

Look at Zig's problem... Zig declares "no default allocator" which is a huge problem. To see that, consider this:

Code: [Select]
// Initial design of library function:
char *getBazName(Baz *baz) {
  return baz ? baz.name : "Unknown baz";
}

// New design
char *getBazName(Baz *baz) {
  return baz ? uppercase(baz.name) : "Unknown baz";
}

Now we immediately realize that "uppercase" – by virtue of returning a char* – must allocate a new array. Consequently the real "uppercase" should be uppercase(char *, Allocator *). This also means that getBazName needs an Allocator:

Code: [Select]
char *getBazName(Baz *baz, Allocator *allocator) {
  return baz ? uppercase(baz.name, allocator) : "Unknown baz";
}

Thus the function needs to change when something internal changes. It's very bad. And if some library creator then decides to not explicitly expose the Allocator to configure? Well then you're out of luck trying to get consistent memory handling. Also, those Allocator functions pollutes the function profiles with something you're often not interested in.

The use of a thread local allocator context stack solves those problems.

44
Ideas / One more big thing: Errors
« on: October 28, 2018, 01:06:13 PM »
With Rust, Go and Zig we have alternatives to try-catch that makes sense in a low level language.

This approach is the one I think has the most things going for it:

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2289.pdf

Truly zero overhead error handling.

45
Implementation Details / Parsing numbers
« on: October 28, 2018, 11:42:56 AM »
Currently we're using C style number parsing. If we add a new lexer then possible to tweak that a little.

My idea would be:

1. Allow underscore: 0x3333_4322_ABCD, 100_000_000 etc
2. Support dec, hex, oct, bin
3. Support hex and dec floating points, e.g. 10.0e-2, 0x23A.A2p+2
4. Use o and b for bin and oct, so 0o12231, 0b102012120211



Pages: 1 2 [3] 4 5