Friday, July 17, 2026

Crack 1.7 Released

After a long period of release inactivity, we are pleased to report the release of Crack 1.7.  

 This release has been a long time coming, largely for personal reasons.  I've been working from home ever since the pandemic, which eliminated two hours of commuting per day during which I would do open source development.

 However, I'm happy to say that I retired from Google in June and can now work on whatever I want, so my first order of business was to get this release out the door.

 There are a number of important bug fixes in this release, mostly dealing with a few internal details that broke things under some very specific conditions.  However, there are a couple of notable features:

 Anonymous Classes

You can now create anonymous classes to handle one-off data packaging situations.  For example:

     a := (class * {
        int a;
        oper init(int a) : a = a {}
        int oper call(int b) {
            return a + b;
        }
    })(100);
 
    # "result" is set to 150.
    result := a(50);
 
As the example suggests, these are mainly useful for the implementation of closures.

Closures

The "closure" annotation uses anonymous classes to create one-off functors bound to the values of local variables:

    import crack.functor Functor1;  # Needed for a @closure macro with 1 param.
 
    @import crack.ann impl;  # Needed for the @closure macro.
    @import crack.closures closure;
 
    void enclosingFunction(int a, int b) {
        func := @closure[a, b] (int c) : int { return a + b + c };
        // func is now a Functor1 that can be called as func(value)
    }

Various Enhancements

As noted earlier, there's a regex2  module that makes use of pcre2.  crack.http.comm2srv provides HTTP serving functionality through the crack.comm2 framework.  There's now also a crack.utf8 module for dealing with UTF8 strings.

Is this the end?

 This may very well be the last version of  Crack that I release.  While I still believe in the language, and still use it regularly for lots of things, it's pretty likely that I'm the only one who does :-)  There are probably better things I can focus on than improving Crack, at this point.

I do have a lot of ideas for another language that borrows a lot of concepts from Crack -- not really a "Crack 2.0" so much, though.  It certainly won't be compatible.  I'll post here if and when there's something to show for it.  

Thursday, June 18, 2026

New Significant Fixes

The Crack main repository hasn't been very active for a while.  This has been primarily due to my involvement in other projects, a number of which make use of the language.  But the 1.7 release, specifically, has been blocked by one particular bug literally for several years -- I simply haven't had enough consecutive productive hours to focus on it.

But I'm happy to say that in the past two days I have fixed both that bug and another.  Both had extremely simple fixes, literally one or two lines of code.  Finding the problem, of course, is another matter.  The Crack executor is as magnificent a beast as has ever emerged from the twisted, steaming cauldron of my mind, and I'm sure it's been several years since I last engaged this beast beyond a superficial level.  So I feel rather proud that I'm still able to ferret out it's deepest imperfections when presented with a reproducible error case.

Both bugs were related to class instances -- constant, static, global structures that constitute Crack's RTTI mechanism.  

The older one ("older" as in "known longer") was a naming collision in Generic class instance global variables.  It results in a massive dump of the LLVM IR code for the program along with an assertion failure indicating an "uninitialized global" (without naming the global, of course).  This was caused by passing the current parser context to the function that generates the class instance for a new generic, which is meant to accept the context of the new generic class (more specific typing would have prevented this). 

I encountered the newer bug while trying to debug the older one.   It turns out that when you forward declare an appendage, the "appendage" flag of the reused TypeDef object created during processing of the forward declaration wasn't getting updated.

With these issues crushed, and a number of new features in the standard library, I now feel inspired to get to a 1.7 release.  There are still quite a few test failures that have crept in over the years to debug, but I don't expect they're anything major.  Time to move to the next milestone!

Sunday, November 2, 2025

Fresh Upgrades

The autoconf setup has been updated and a new version of crack.regex2 has been introduced which uses libpcre2 (libpcre3 is actually an older library that is EOL and being removed from distros).

These changes are as yet unreleased but are available in the latest git codebase.

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.

Thursday, April 13, 2017

Appendages

I've recently pushed the code to implement Appendages, which will likely be the primary feature of the 1.1 release.

Formally, appendages are a way to extend the functionality of a class orthogonal to its normal inheritance hierarchy, and specifically to its instance state. In other words, they are classes consisting only of methods that can be applied to any object derived from a specified base class, called the "anchor" class.

Taking the example from the manual, let's say we have pair of classes for representing two dimensional coordinates:


    class Coord {
        int x, y;
        oper init(int x, int y) : x = x, y = y {}
    }

    class NamedCoord : Coord {
        String name;
        oper init(int x, int y, String name) :
            Coord(x, y),
            name = name {
        }
    }

Now let's say we want to add some new functionality:

  • Get the distance of the coordinate from the origin (the "magnitude" of the coordinate's vector).

  • Get the area of the rectangle defined by the coordinate and the origin.

In the absence of other considerations, we might just add two methods to Coord and be done with it. However, this doesn't always work.

If Coord and NamedCoord are in a module we don't own (an "external" module), adding methods is more complicated. Our new methods might not be appropriate for general use, and for non-final classes adding new methods breaks compatibility. So our new methods might simply not be welcome upstream, and in any case, we might not want to be blocked on waiting for our change to come back around into a released version of the external module.

We could derive a new class, "SpatialCoord", from Coord and give it the new methods. But then NamedCoord won't have them. Furthermore, if Coord comes from an external module, we might not even control the allocation of the new object: it might be produced internally by some other subsystem and merely shared with our calling code.

Prior to appendages, the only way to solve this was to define our new methods as functions accepting Coord as an argument:

    int getMagnitude(Coord c) {
        return sqrt(c.x * c.x + c.y * c.y);
    }

    int getArea(Coord c) {
        return c.x * c.y;
    }

This works, but it has a few problems as compared to having them bundled as methods in a class:

  • As separate functions, a module using them would have to import each of them separately and also retain them as separate elements in a shared namespace.

  • We lose some syntactic niceties, such as the object . method () syntax and the implicit this.

  • As standalone functions, we are unable to access protected members of the class that would be accessible to methods of a derived class. (This is not an issue in the example, however it is an issue with the approach in general).

Appendages provide a nicer solution. We can define an appendage on Coord by creating a derived class definition that uses an equal sign ("=") instead of a colon before the base class list:

    class SpatialCoord = Coord {
        int getMagnitude() {
            return sqrt(x * x + y * y);
        }

        int getArea() {
            return x * y;
        }
    }

Defining an appendage is very much like defining a class except that an appendage is limited to a set of methods. These can be applied to any instance of a class derived from the anchor class (Coord, in this case). To make this work, an appendage can have no instance data of its own. This means:

  • No instance variables.

  • No virtual methods (all methods are final or explicitly static).

  • No constructors or destructors.

To use an appendage, we must explicitly convert an instance of the anchor class using on overloaded "oper new" (which looks like ordinary instance construction):

    foo := SpatialCoord(Coord(10, 20));
    bar := SpatialCoord(NamedCoord(20, 30, 'fido'));
    cout I`bar has magnitude $(bar.getMagnitude()) \
           and area $(bar.getArea())\n`;

Note that the "foo" and "bar" assignments don't create new instances: they just convert existing instances of Coord and NamedCoord to SpatialCoord in order to allow the use of its methods. This is a zero cost abstraction.

You can also compose an appendage from several other appendages (as long as they all have the same anchor class). For example, we could have done this:


    class MagnitudeCoord = Coord {
        int getMagnitude() {
            return sqrt(x * x + y * y);
        }
    }

    class AreaCoord = Coord {
        int getArea() {
            return x * y;
        }
    }

    class SpatialCoord = MagnitudeCoord, AreaCoord {}

There are a number of places in the Crack library that will benefit from the use of appendages. Notably, appendages will make it possible to define encoding-specific String classes. The String class hierarchy (or, more properly, the Buffer class hierarchy) currently specializes around the concept of ownership (Buffer has no ownership assumptions, ManagedBuffer has an associated buffer and is growable, String owns an associated (to be treated as) immutable buffer ...). All of these are just byte buffers: there is no character concept, the user is responsible for assuming an encoding and ensuring that the string is treated correctly with respect to its encoding.

With appendages, it will be possible to define ASCIIString and UTF8String, so instaed of having to import individual methods from the crack.ascii and crack.strutil modules, we'll just be able to import (e.g.) ASCIIString and then call functions like strip() and toLower() as normal methods.

There are a few potential areas for improvement in appendages:

  • Implicit conversion (while generally an antipattern) would be useful here. If we have a function accepting an appendage as an argument, there's not a lot of value in having either the caller or the function itself do the conversion.

  • Having some way to explicitly require validation during conversion to an appendage would be nice (especially in the case of string appendages). You can currently define static members to do validation, but there's no way to exclude generation of the "oper new" methods that allow a user to more naturally bypass them.

  • It would be useful for appendages created from classes derived from the anchor class to preserve the methods of the derived class. For example, when we do

    bar := SpatialCoord(NamedCoord(20, 30, 'fido'))
    above, we're losing the ability to access the name variable from bar. It should be possible to work around this right now by defining the appendage as a generic, though at the cost of generating multiple instances of the appendage code.

Nonetheless, appendages are a feature that I have long wished for that are very much in line with Crack's original goal of expanding upon existing concepts in the Object Oriented paradigm in a very natural way.

Wednesday, February 8, 2017

Crack 1.0 Released

I am pleased to announce the release of Crack 1.0!

This milestone has been over six years in the making.  Crack is now usable for a wide variety of applications, and I've used it to build everything from quick and dirty file scanners to web applications, music software, an OpenGL FPS engine and an encrypted filesystem.

In keeping with semantic versioning, this release also marks the beginning of interface stability for the language.  Any future 1.x releases should be backwards compatible with this one, so there's no longer any concern about the ground shifting out from under you when you write Crack code.

Massive thanks to the other primary contributors: Shannon Weyrick, Conrad Steenberg and Arno Rehn.

Curiously, the biggest thing Crack is lacking right now is a user community, so feel free to dig in, play around and speak up.  More good stuff to come :-)

Download 1.0
http://crack-lang.org

Friday, February 3, 2017

1.0 Release and Final Classes

I've just pushed the latest code that's been sitting on my laptop.  The exciting news is that after about six years of development, I'm finally preparing the 1.0 release!

I wanted to be announcing this about four years ago.  There have been a number of reasons that I delayed it for so long, all coming down to some level of perception that the language "wasn't ready" for a 1.0 release.

You could still argue that it's not ready.  There are a lot of things that the language lacks.  But the 1.0 designation was never about indicating completeness.  It was a promise of stability.  1.0 means that we won't break compatibility - your 1.0 code should continue to run until we release 2.0.  This doesn't matter much to the world at large, Crack doesn't have a lot of users.  But it does matter to me.  I use the language quite extensively, and I don't want to have to fix all of my projects when I make changes.

It also matters to me in a very personal way.  I've been working on this for a long time and I want to move on to other things (not that I'm abandoning Crack, quite the contrary, I plan to continue to use it and I'm already experimenting with ideas for the next major version).  I have a personal need for closure on this project, and this 1.0 label does that for me.

In the interest of ensuring compatibility, I've started work on one more feature which may or may not make it into 1.0.  The "FinalClasses" branch contains a set of changes to allow the @final annotation to be used on classes.  Final classes in Crack are similar to final classes in Java: you can't inherit from them.

The reason this change is important for 1.0 is because it lets us reserve classes in the standard library for future enhancements without breaking compatibility.  After 1.0, we can't add public or protected members to non-final public classes, because it is possible that users have derived from these classes and introduced their own members whose names would collide with those of the standard library classes.

The only problem is, at this point I'm not sure I care to delay the 1.0 release for this.  I still need to audit the entire standard library and add @final to all of the appropriate classes.  There's also the possibility that the feature may be buggy.

The alternative to introducing final classes is to simply accept that there won't be any new member additions to the standard library classes.  We can still add new functionality, but it will have to come in the form of new classes, which will likely be final themselves so they won't have this problem.

I'm going to weigh the arguments for and against final classes over the weekend.  On Monday, I'll either release 1.0 or delay it for a week or two while I make much of the library classes final.  But either way, there will be a Crack 1.0 release in February.

Friday, October 21, 2016

Crack 0.13.1 Released

We are pleased to announce the release of Crack 0.13.1.

This is a patch release that fixes a couple of test failures on 32-bit.  It also adds some nice features to "crackdoc".  Enjoy :-)

Wednesday, September 28, 2016

Crack 0.13 Released

We are pleased to announce the release of 0.13 of the Crack programming language.  Download here.

In addition to several prominent bug fixes, the primary feature of 0.13 is a complete restructuring of the HTTP server modules. As part of this, we include the #crack.http.auth# module which facilitates the implementation of complete HTTP application servers in the language.

The language environment is currently robust enough to merit a "1.0" label. However, since we aim to guarantee backwards compatibility for all 1.x versions after 1.0, the current plan is to stick with major version 0 until there is more demand for a stable interface.

Enjoy :-)

Friday, July 1, 2016

Crack 0.12 Released

I'm happy to announce the release of Crack 0.12.  Download the source from here.

Release notes:


  • Added full support for special documentation comments, created the model builder which knows how to extract them and created the "crackdoc" documentation tool.
  • Greatly improved http server support, moved it to crack.http
  • NML extensibility enhancements.
  • Convert buffer sizes from uint to uintz
  • Change alias serialization to deal with ordering issues in nested aliases.
  • Added SHA256 hashing module.
  • Added POSIX signal handling, display stack traces for fatal signals.
  • Many more small enhancements and bug fixes.
This release has been almost a year in coming, which is way too long.  I had planned to make this a 1.0 release, but since no one is clamoring for API stability I've decided to retain the 0.x versioning so I can continue to evolve the language and modules.

Tuesday, June 28, 2016

New Website!

I've finally take the time to create a real website for Crack:  http://crack-lang.org

Unsurprisingly, the site is generated by a crack script.  In the process of doing this, I've enhanced NML (the markup language that the Crack manual is written in) in order to support syntax highlighting.  At some point I'll regenerate the manual to fit in with the new theme.

Sunday, November 1, 2015

SSL Ciphers

I just checked in support for doing encryption/decryption through OpenSSL.  Crack provides a wrapper library (crack.crypt.ssl.cipher) that allows you to encrypt/decrypt using a writer, so for example to encrypt:

     import crack.crypt.ssl EVP_aes_256_cbc, EncryptWriter;

     backing := (cwd/'outfile').writer();
     out := EncryptWriter(EVP_aes_256_cbc(), key, backing);
     for (data := src.read(1024))
         out.write(data);
     out.close();   # You must explicitly close (or make them go out of scope).


There were a few ciphers (specifically the AES CCM and GCM ciphers) that didn't pass a round-trip test.  These require some special setup that I don't feel motivated to figure out.

Wednesday, July 22, 2015

Crack 0.11 Released

I am pleased to announce the release of Crack 0.11.  Enhancements include:


  • Allow importing crack.compiler outside of an annotation.
  • Fixed casting so we can now correctly cast from secondary bases and even cast across bases.
  • Added crack.protobuf.ann, which provides an annotation to allow you to expand inline protobuf messages into generated code.
  • Streamline generic serialization (only serialize file name and line number when they change).
  • Enhancements to jack and alsa modules, made alsa extension more OO.
  • Lots of smaller fixes and enhancements.

Enjoy!

Thursday, July 16, 2015

Moving to github

Sadly, googlecode is shutting down and we needed to find a new home for Crack.

I've been a long time fan of Mercurial (I think I was using it before git existed) but I seem to be in the minority in that.  Most Crack contributors appear to prefer git.  There's also a huge community that's developed around github.  So, given that our current project hosting is going away, this seemed to be a good time to convert entirely to git and relocate to github.

In truth, I've been running the project out of git for over two months now.  I migrate changes to the mercurial repo on googlecode whenever I push, and I expect I will continue to do so for the foreseeable future, but the repository in github (crack-lang/crack) should now be regarded as canonical.  Patches to the codebase should be sent as pull requests or as git patches generated by "format-patch".  We will no longer accept requests to pull from a mercurial repository.

Hopefully we'll get around to replacing our home page before googlecode disappears entirely.