Onyx Spec Page

I figured I better at least start the Onyx Spec page, so I did. You can find int in the Menu.

Built in types are there now.

Onyx Spec

Advertisements

Static Exceptions

Dynamic Exceptions have their flaws. Herb Sutter has proposed a replacement known as Static Exceptions. Lets look at it a bit.

Before we do, we need to look at the C+11 feature std::error_code

std::error_code and Friends

Anyone who has done any coding in C knows about good old errno, the global int that many system functions will set to signal a problem.  This, of course, has many problems, not the least of which is that different platforms could and did use different integer values to represent the same error.

To bring some order to the chaos, std::error_code was added along with its friend std::error_category.

An error code is actually two numbers – an integer saying which exact error and a “category” or domain for that error. Thus the math related errors and the filesystem errors could have the same integer value, but different domains. A domain or category is nothing but a pointer to a singleton object.

For a bit more, go look at the cplusplus.com writeup as well as a tutorial on creating your own error codes from the folks behind Outcome.  And here is another writeup on a use of custom error codes.

For our purposes, std::error_code has four really nice properties:

  • It is small – the size two pointers. It could in theory be passed around in cpu registers.
  • Creating one cannot possibly throw an exception.
  • Copying and/or moving can be done with a memcpy or just two memory read/writes.
  • It does not require any RTTI – no dynamic casting is required – only (possibly) a static_cast between integer types.

Dynamic Exceptions considered harmful

Sutter does a much better job than I can of enumerating the problems with the current exception system. So go read the paper.

And error returns schemes such as Expected or Outcome aren’t much better.

Static Exceptions

Sutters proposal is to do something like the following.

Introduce a new keyword throws.

IF you define a function as :

T my_function() throws;

Then behind the scenes the compiler will act as if the function was defined.

variant<T, std::error_code> my_function();

In the body of the function anything that looks like:

throw e;

get translated to a simple

return e;

And at the call site

try {
    x = my_function();
} catch (e) {
    /* try to recover */
}

Will get translated into something like:

x = my_function();
if (compiler_magic::is_error(x)) {
     /* try to recover */
}

This eliminates the hand-rolled “if checks” that have to be written to use something like Outcome. And it propagates. If you don’t handle the call there will still be the check, but it will have a simple return to move the exception outward.

The paper is filled with more details about the interplay between the proposed mechanism and the current exception system, noexcept, and other details the language lawyers need to care about.

Onyx

I have decided to make this the standard of exception handling in Onyx. There are details to be worked out. In particular in the early stages, I will literally have to rewrite the return types in order to “reduce” Onyx to C++.

But it will be fun to try out.

The Onyx Project

I have decided to design, and build my own computer language. It will be called Onyx.

I agree, that is a pretty big task.

So we will take a bit at time.

Design Criteria

I don’t really have a whole lot yet. You might think of onyx as C++ EXCEPT – meaning, it acts like C++ EXCEPT these differences. Over time the list will grow and I’ll turn it into a full-feature language spec.

Here are the “excepts” so far.

Variable/Function declaration

Onyx has a postfix declaration for the type e.g.

var foo int;                         // int foo;
var foo * ( * int, *() ) * int;      // int* (*foo)(int*, *());
var baz * [] * int;                  // int *(*baz)[]; I think.
// functions are similar
func doit ( a * int, b MyClass *) YourClass *

Yes, there will be keywords introducing variables and function.

I haven’t decided what to do about const yet.

var foo const int = 3;
// Q: allow abbreviation as : ??
const foo int = 3;

Classes

Haven’t really given this much thought yet. Points up for thought:

  • different inheritance types (public/protected/private) or not?
  • What are the visibility levels – all three?? I’m leaning towards only two – public and protected (though I will probably relabel them).
  • virtual inheritance?

Of course, the biggest questions is – can objects live on the stack (Like C++) or only as references/pointers (like java). I want objects on the stack, but I want to see if we can do something about the awful mess of T vs T* vs T& vs T&&.

My current thinking is that there are no pointers – only T and &T, but that references would be “spelled” “*”.  So getting to members through a pointer would use dot notation.

var a * MyClass = &anObj; // fine initializing a to reference anObj
var b * MyClass = &otherObj;
a.m1 = b.m1 + 3;  // update a single member
a = b;            // copy - otherObj now has anObj's data.
a = &b;           // update a to reference otherObj (not b)
                  // a would have to have type * * MyClass for that.

Move semantics would definitely be baked in from the being.

Operator Overloading

Indeed. But I’m thinking of stealing a page from python’s playbook  and have special names rather than trying to use the actual lexical symbol. To help, however, they would be names that would otherwise not be valid function identifiers. Maybe something like this..

class MyClass {
     func op.assign() {};
     func op.plus() {};
     func op.create() {};  // constructor
     func op.destroy() {}; // destructor

The downside of this is that it would be a little weird redefining op.plus to do string concatenation.

Templates

I’d like to generalize the if constexpr paradigm to include the whole function/class, so that its entire existence can depend on an “if” of some sort.

tif isarraytype(T) {
   func foo(T) int ( /* some stuff */ };
} else {
   func foo(T) int { /* different stuff */ };
}

This may not be doable or even make sense. How does it play with normal overloading?

Where are the template parameter lists placed? I’m think of putting them at the class keyword rather than the name.

Module System

Modeled after python. But with some compiler support for “Module providers” so that the name of the file would not have to be tied to the file name (e.g. for versioning), but that would be the default.

Modules would be compiled once, of course. only things specifically marked would be exported. Would be nice to have a tool that could generate a reference guide to a module based on its exported symbol table.

Phase I implementation

The initial parser/compiler will be built in C++ use Boost.Spirit X3. This phase will be more of a translator that will spit out C++ code to implement the semantics. Module files will be some dense representation of the parse tree.

Phase II implementation

This will still utilize Spirit, but will target LLVM IR as the target output.

Conclusion

Thanks for making this far. If you have any thoughts on any of the above, I would be happy to hear them.

There is obviously a ton of work to do. And not much spare time. So, this will definitely be a slow leisurely endeavor.