Friday, January 17, 2020

Crack 1.6 Released

We are pleased to report the release of crack 1.6.  This version focuses on a few features that I have wanted to add to the language for a long time:

The "is not" operator ("!is")

This fixes a long-standing wart.  Up until 1.5, the only way to check if an object is "not identical" to another object was to negate the result of "is".  Since this is a very common thing to do when verifying that an object is not null, the codebase was scattered with code like "if (!(x is null))" which is as painful to read as it is to type.

1.6 adds the "!is" operator (which is simply syntactic sugar for "!(a is b)").

Safe Navigation 

It's not uncommon to have a sequence of field dereferences where you want to check every step along the way for null and return a null (of the type of the final dereference) if one of those dereferences fails the check.  Many modern languages support doing this in a very lightweight form using the "safe navigation" operator.  Crack is now among them.  So we can now say:

x := foo?.bar()?.baz;  # x is null if foo, foo.bar() or
                       # foo.bar().baz is null

Attribute Accessors

It is useful to be able to use the same syntax for field access whether a field is being accessed directly or via a method (i.e. a "getter" or "setter"). Crack 1.6 now provides this by allowing you to define accessor methods:

class Person {
    # We would normally just define "String name" as our field
    # unless we needed to do something special but this 
    # illustrates the usage of the feature.
    String __name;
    String oper .name() { return __name }
    String oper .name=(String val) { return __name = val }
}

cout `$(person.name)\n`;
person.name = 'Frodo';

There's also the usual round of bug fixes and smaller features.

So check it out at http://crack-lang.org

Happy hacking!

Tuesday, July 2, 2019

Crack 1.5 Released!

After a very long time (during which I've mostly been working on MAWFS) I'm finally getting around to releasing a new version of Crack.  This is mostly just bug fixes on 1.4, but there are a few new big features:

  • Lambdas.  You can now create a function as an expression, for example: lambda int(int a, int b) { return a + b }
  • Auto imports.  You can now put all of your commonly used imports in an auto-import file and have them be imported on demand by any modules that need them.
  • Experimental new command line processing.
Look for a new docker image in another day or two.

Enjoy!

Friday, July 27, 2018

Crack 1.4 Released!

I am pleased to announce the release of Crack 1.4.  The latest release includes:

  • The RawPtr class (for breaking reference cycles)
  • Function elision (let's you remove a function from a derived context at compile-time)
  • Fixed event handling in termui.
  • Fixed memory management in EventManager
  • Minor enhancements to NML HTML generation.
  • Fixed assertion failure when dereferencing an undefined forward class.

Download from here.
Happy cracking!

Thursday, March 22, 2018

Crack 1.3 Released!

I'm happy to announce the release of Crack 1.3.

1.3 mainly includes lots of fixes to keep things working on the latest versions of Debian Linux.  However, it also includes a few enhancements to crack.io and crack.net.comm2.

Enjoy!

Friday, December 15, 2017

Crack 1.2 Released!

I am pleased to announce the release of Crack 1.2. In addition to a number of bug fixes, 1.2 adds a number of new features that have been accumulating for the past few months, including:


  • The @cvars annotation (which autogenerates constructors to initialize a subset of instance variables)
  • The Functor.Wrap classes, which simplify the import requirements for wrapping functions as functors.
  • Support for relative imports.
  • Enhancements for protobufs, crypto (AES-SIV and CMAC support), the comm2 communications module and annotations.
Enjoy!

Thursday, May 4, 2017

Crack 1.1 Released

I am pleased to announce the release of Crack 1.1.

This release includes:

  • Token literals and @xmacros.
  • Appendages
  • The crack.net.comm module, which provides some higher level functionality for communication systems.
  • The crack.eventmgr module, which allows scheduling events in a poller loop.
  • Various minor enhancements and fixes.

Tuesday, April 25, 2017

@tokens and XMacros

Crack annotations are a way to extend the compiler at the parser level. A lot of them do code generation, for example the @struct annotation generates a class with a constructor:

@import crack.ann struct;

@struct Foo {
    String name;
    int val;
}

# Equivalent to:

class Foo {
    String name;
    int val;

    oper init(String name, int val) : name = name, val = val {}
}

Annotations are just crack functions that are executed at compile time. The only restriction is that they must reside in a different module from the code that uses them. An annotation is just a public function that accepts a CrackContext object:

import crack.compiler CrackContext;

void struct(CrackContext ctx) {
  ...
}

The CrackContext object is an interface to the compiler and tokenizer which references the context in which the annotation was invoked. For example, we can consume tokens from the point where the annotation is specified and generate errors at that point:

    tok := ctx.getToken();
    if (!tok.isIdent())
        ctx.error(tok, 'Identifier expected!'.buffer);

Code generation in annotations has always been done by injecting tokens and strings into the tokenizer. We make use of the fact that the tokenizer has an unlimited putback queue and just "put back" the tokens that we want the parser to get next in reverse order. There is also an "inject()" method on the crack context that lets you inject a string to be tokenized.

Neither approach has been entirely satisfactory. Obviously, generating code by injecting one token at a time is far too verbose and tedious to use for anything of any size. And while inject() fixes that part of the problem, it relies on writing code in a string, so:

  • The line numbers of the code have to be provided to the inject() function, a technique which doesn't compose well.

  • Editors don't recognize it as crack code, breaking syntax higlighting and auto-indent.

A better solution relies on the recently introduced @tokens and @xmac annotations. @tokens is effectively a "token sequence literal." It consumes the delimited tokens following it and produces an expression that evaluates to a NodeList object containing those tokens.

This lets us generate crack code defined in crack code. For example, the following ennoation emits code to print "hello world":

import crack.ann deserializeNodeList;
@import crack.ann tokens;

void hello(CrackContext ctx) {
    @tokens { cout `hello world!\n`; }.expand(ctx);
}

In the example above, we use @tokens with curly braces as delimiters. We could have also used square brackets or parenthesis. Delimiters may be nested, but the symbols that are not being used need not be paired. So we can also use @tokens for asymetric constructs:

void begin_block(CrackContext ctx) {
    # The unbalanced '{' is allowed here.
    @tokens [ if (true) { ].expand(ctx);
}

While useful, @token still doesn't let us do the kind of composition we need in order to be able to generate code. There's nothing like macro parameters for @tokens, they are essentially constants. For interpolation, we have @xmac.

@xmac is like @tokens only with parameters allowing you to expand other NodeLists. For example, here's an annotation to emit exception classes:

import crack.ann deserializeXMac;
@import crack.ann xmac;

void exception(CrackContext ctx) {
    tok := ctx.getToken();
    if (!tok.isIdent()) ctx.error(tok, 'Identifier expected!');
    @xmac {
        class $className : Exception {
            oper init() {}
            oper init(String message) : Exception(message) {}
        }
    }.set('className', tok).expand(ctx);

We have to explicitly set each of the parameters with the set() method. We'll get an error if any of them are undefined when we expand. Alternately, we can use @xmac* to do this automatically with variables of the same name:

void exception(CrackContext ctx) {
    className := ctx.getToken();
    if (!className.isIdent())
        ctx.error(tok, 'Identifier expected!');
    @xmac* {
        class $className : Exception {
            oper init() {}
            oper init(String message) : Exception(message) {}
        }
    }.expand(ctx);
}

Since it just generates a NodeList, we can use @tokens to directly generate values to interpolate into an @xmac:

    method := @tokens {
        void foo() { }
    };

    @xmac* (
        class A {
            $method
        }
    }.expand(ctx);

We can also expand an @xmac into a NodeList using the expand() method with no arguments:

    accessors := @xmac* {
        void $name() {
            return __state.$name;
        }

        void $name(int val) {
            __state.$name = val;
        }
    }.expand();

    @xmac* {
        class A {
            $accessors
        }
    }.expand(ctx);

@tokens and @xmac are both useful tools for doing code generation in Crack annotations. They will be released in Crack 1.1.