Tuesday, May 3, 2011

Crack 0.4 Released

We are pleased to announce the release of Crack 0.4.  The two major changes for this version are Exceptions (ala C++, Java and Python) and Ahead-of-time compiling (so you can now run "crackc" on your script and produce a nice, ready to run executable in addition to the traditional scripty mode).

There are also a few new smaller features:
  • Sequence initializers ("array[int] a = [1, 2, 3]" or "array[int]![1, 2, 3]")
  • Improved string interpolation (added calls to enter() and leave() methods at the beginning and ending of each expression, allowing us to do things like "String s = FStr() `this is $var`;")
  • Byte and integer string constants ("b'A' == 65", "i'\001\0' == 256")
  • A no-op @encoding annotation allowing scripts to identify their encoding for other tools.
This release has been a long time coming, we hope you like it. 

Friday, April 1, 2011

Exciting New Changes

During the time that we have been developing Crack, we have gotten a lot of
really good constructive criticism on the language.  This criticism has not
fallen upon deaf ears, and I am happy to announce some exciting changes coming
to the next version of Crack.

For one thing, it has been pointed out that naming the language after an
illicit drug that ruins people's lives may not have been the best decision. 
It's also hard to find information on the language by doing a search for
"crack" and the name is already overloaded in software technology to refer to
password cracking tools.

In response, we are changing the name of the language to "MINARA`"  This is an
enormously clever abbreviation of "Minara Is Not A Recursive Acronym`" 
Unfortunately, this name is already in use by an open-source vector drawing
tool (see http://minara.sourceforge.net/) so we have added the back-tick
character to the end of our name for purposes of disambiguation.

Another long-standing criticism of Crack (excuse me, MINARA`) has been that
there are already an abundance of C-like languages.  We acknowledge this, and
as such we are changing the entire syntax of the language.  As of the next
release, the language will instead adopt a Lisp-like syntax.

Unfortunately, Lisp is impossible for humans to read and is also more targeted
towards interpretation than compilation.  So MINARA` will adopt the following
syntactic and semantic conventions:

  • The basic lisp form of a function followed by its arguments will be preserved.  A MINARA` script will be a a list of such functional forms.
  • The resulting data structure is a nested list that can be thought of as the Abstract Syntax Tree for a MINARA` module.
  • Similar to Lisp, there are macros that are forms executed at compile time.  These are free to interpret any of the special types of lists any way they want, but they are applied after the processing of the comma and semicolon syntactic sugar defined below.  All of the basic statements (if/else, while, for, class...) are implemented as macros.
  • The following special syntactic conventions apply:
    • Unlike Lisp, which has a single parenthesized list type, MINARA` has four types of lists: a-lists, b-lists, c-lists and p-lists.
    • p-lists are the normal parenthesized Lisp function calls
    • A list enclosed in curly braces is like a quoted list in Lisp: it is not evaluated, it is a value.  Example: (print {1 2 3}). A macro may choose to compile the contents of a c-list, which makes them very suitable for statement blocks.  This is a c-list (a curly bracketed list).
    • The semicolon is syntactic sugar that is equivalent to enclosing all elements to the last semicolon or the beginning of the enclosing list in parenthesis.  For example, {print "hello"; print "world";}  is equivalent to {(print "hello") (print "world")}  The terminating semicolon is optional: if at least one semicolon is in the enclosing list, the entire enclosing list is split up into p-lists.
    • The square brackets are equivalent to a call of the "expr" function in the current context.  "expr" defaults to a macro that converts its arguments from a more traditional, infix style to the canonical, prefix list style.  Example: [a = b * 2 + 3] is equivalent to (expr b * 2 + 3) which would normally be transformed at compile time to (= a ((* b 2) 3))  This is a b-list (a "bracketed list")
    • The comma is similar to the semicolon.  The comma aggregates all elements up to the previous comma with the exception of the first element in the enclosing list into a b-list.  For example, (print 'value is', 1 + 2) is equivalant to (print ['value is'] [1 + 2]) or (print (expr 'value is') (expr 1 + 2)) or (print 'value is' (+ 1 2)) 
    • an element followed by a period and an identifier is equivalent to a call of the "attrex" function with the items before and after the period passed as arguments (like "expr", "attrex" is normally implemented as a macro which expands to various kinds of low-level attribute and member function access).  So (foo.setBar 'test') would be equivalent to ((attrex foo setBar) 'test').  This is an a-list ("attribute list")

All of syntactic constructs in MINARA` will be implemented as macros.  Here's an example of a class definition with some instance variables and methods:

    # Defined using a "class" macro, note that the base classes 
    # (Object and Named) must be specified in a list
    class Person : (Object Named) {
   
        # instance variable definitions.  Since we have to start
        # these with the name "var" we adopt pascal style
        var name : String, favorite_color : Color = Color.red;
       
        # the "duh" keyword in oper init indicates that arguments 
        # map trivially to their corresponding attributes.
        oper init(name; favorite_flavor) duh;
       
        # a couple of explicit implementation of abstract 
        # functions from Named
        implement setName(name: String) { name = newName; };
        implement getName() returns String { return name; };
       
        def isCompatibleWith(other: Person) returns bool {
            if [other.favorite_color == favorite_color] {
                return true;
            } else {
                return false;
            }
        };
       
        def clone() returns Person {
            return (Person.new name favorite_color);
        };
    };

We think that everyone will appreciate these new changes to the language, and
apologize for our earlier mistakes.

Sunday, March 13, 2011

Platform Dependent Versus Universal Numeric Types

Implementing numeric types in a programming language is surprisingly difficult.  At this time, Crack supports Universal Numeric Types (UNTs - for example int32, uint64, byte) and Platform Dependant Numeric Types (PDNTs - int, uint, float).  The PDNT names are just aliases for the corresponding UNTs in the target compiler - so for example, if the C++ compiler you use to build crack has a 32 bit int, Crack's "int" will be an alias for "int32".  Additionally, the philosophy behind implicit type conversions is to allow them only if they do not result in loss of precision.

This overall approach is not without its problems:

  • It makes Crack code platform-dependent because there are expressions that will work on one platform but will result in a compile-time error on another platform.  For example, "int32 i = int(v);" works on platforms with 32 bit integers, but breaks on platforms with 64 bit integers.
  • You end up writing a lot of explicit type conversions in places where you really don't care that much (like when using a signed integer value for a function argument of type "uint").  For a scripting language valuing terse syntax, this is kind of lame.
So after some discussion on IRC, we've decided to change our approach a little bit.  The general philosophy now is that you should use a PDNT in situations where you care about performance or interoperability with C/C++ code and you should use a UNT in situations where you care about precision.  The manifestations of this decision are:

  • PDNTs will be promiscuous: any numeric type will implicitly convert to any PDNT type.  So "int i = float64(v);" will be perfectly legal on any platform.
  • There will be max-size, min-size assumptions about PDNTs.  In particular, they will all be at least 32 bits but no more than 64 bits in size.  Like everything else in the language, these assumptions are subject to change across major versions of the language.
  • UNTs will continue to apply the strict conversion rules.  However, because of the min-size/max-size assumptions, certain conversions from PDNTs will always be legal.  An example of this is "int64 i = int(v)".
There's still a platform dependency problem here because expressions like "int i = int64(v);" will vary in behavior at runtime depending on the size of an integer on the platform.  So we've essentially converted a compile-time portability issue to a runtime portability issue :-/.

To mitigate this effect, there will be a warning flag that allows you to identify the places where you could potentially lose precision with something like this.  We are also considering allowing the generation of a runtime check that would throw an exception if specific values will be truncated.

This change will probably go into the language in Crack 0.5.  For anyone interested, the formal proposal is at http://code.google.com/p/crack-language/wiki/PlatformDependentNumericTypes

Tuesday, March 8, 2011

AOT for Crack 0.4

One of the trademarks of a scripting language is of course: instant results. You edit your script and execute it immediately, skipping the traditional compile step with its associated build files, etc.

True to scripting language ideals, Crack executes scripts immediately using LLVM's excellent Just In Time (JIT) compiler to handle the heavywork of converting the compiled crack code to native instructions so that it can run both immediately and as fast as possible.


weyrick@mozek:~/crack$ cat hello.crk 
import crack.io cout;
cout `hello JIT\n`;
weyrick@mozek:~/crack$ crack hello.crk 
hello JIT


But what if you find yourself wishing for a native binary, just this once? You know, something like this instead:


weyrick@mozek:~/crack$ crackc hello.crk 
weyrick@mozek:~/crack$ ./hello 
hello JIT


What's a scripter to do? Well with Crack at least, you'll be in luck. Crack 0.4 will include an Ahead Of Time (AOT) mode which will create native binaries just like the example above. All imported crack modules will be included in the binary (something like a static link of the crack modules, although the binary itself is not statically linked). The annotation system (and macros) work for AOT binaries. We also have plans for full DWARF debugging information, which will allow source level debugging with tools like gdb.

Crack 0.4 is tentatively scheduled for release about the same time as LLVM 2.9 (beginning of April).


Friday, January 14, 2011

Crack 0.3 released

We are pleased to announce the release of crack 0.3.  The new version features the following enhancements:

  • Added support for extensions
    • Added a module to that can generate bindings for many C APIs
    • converted the runtime, GTK and PCRE modules to use the extension API
    • added (undocumented) support for SDL and OpenGL
  • Added the annotations subsystem
    • Used annotations to implement macros
    • Added the @static, @final, @FILE and @LINE built-in annotations
  • Added the math module
  • Added macro based generic containers.
  • Added the "for" statement (both C and iterator styles, so "for (x :in collection)" now works)
  • Aggregate type variables now default to null if no initializer is given.
That last item means that if you actually have written some crack code, we've probably broken it.  See the appendix of the manual for info on how to deal :-)

Dig in and send us feedback!

Sunday, December 12, 2010

Annotations

An exciting new feature for 0.3 (currently working in the latest repository code) is "annotations."  Annotations allow you to extend the compiler with the language.  For example if I have module A:

import crack.compiler CrackContext;

void myann(CrackContext ctx) {
    ctx.inject('import crack.io cout; cout `hello world`;'.buffer);
}
And module B:


@import A myann;
@myann


The code injected from myann()would be injected into the token stream of module B.


There are a number of things that you can do from an annotation, you can read tokens, put them back, set parser callbacks, and create other annotations.  You can't yet do introspection (examine classes or functions or other compile-time objects) but that feature will be coming.


We currently use annotations to implement compiler-level macros:


import crack.exp.ann define;

@define attr(type, name) {
    type name;
    type get_$$name() { return name; }
    void set_$$name(type newVal) { name = newVal; }
} 
class Foo {
@attr(String, bar)
}


To use a macro like this from another module, you need to export it:
import crack.exp.ann define, export, exporter;
@exporter  # import all of the stuff we need to define exporter functions

@define attr(type, name) {
    type name;
    type get_$$name() { return name; }
    void set_$$name(type newVal) { name = newVal; }
}

@export attr

From another module we can import the macro as an annotation:

@import attrmod attr;

class Foo {
    @attr(String, bar);
}

Tuesday, November 16, 2010

Extension API

Crack currently has a bona-fide extension API in the trunk.  See http://code.google.com/p/crack-language/wiki/ModuleExtensionAPI for details.

The short story is: you can now define functions, types and methods from a shared library, and then import that library as if it were a normal Crack module.