A Toy with Delusions of Usefulness
use std

const main = {
    while true
        std.put("You're still reading?!?\n")
    ;;
    -> 0
} 

Myrddin is a language that I put together for fun, but which has developed delusions of usefulness. It's made by one coder with too little spare time on his hands.

It is designed to be a simple programming language that runs close to the metal, giving the programmer predictable and transparent behavior and mental model. It also attempts to strong type checking, generics, type inference, and other features not present in C.

Myrddin is not a language designed to explore the forefront of type theory or compiler technology. It is not a language that is focused on guaranteeing perfect safety. It is satisfied to be a practical, small, fairly well defined, and easy to understand language for code that needs to be close to the hardware.

In order to run, Myrddin currently requires an x86-64 computer running either Linux or OSX. Porting to other Posix-like OSes should be a matter of writing syscall wrappers.

Why Myrddin?

Are you tired of your compiler Just Working? Are you tired of having your standard library having the basic functionality you need? Do you long for the days when men were men, and debuggers operated on the assembly level? If so, Myrddin is for you.

More seriously, Myrddin has the goal of replacing C in its niche of OS and embedded development, but making it harder to shoot yourself in the foot. It's a language that matches the author's taste in design, and if other people want to use it, so much the better.

Major Features
  • Type inference. Types are inferred across the whole program.
  • Algebraic data types
  • And their friend, pattern matching
  • Generics
  • A package system
  • Low level control.
  • (Almost) no runtime library
  • Entirely self contained
Installing The Compiler

First, make sure you have the dependencies. To build you'll need Make, a C99 compiler, an AT&T syntax assembler, and a Posix YACC implementation. To clone the repository you'll need git. On debian, you can run:

sudo apt-get install bison gcc git make

Once you have the dependencies, you can clone the repo and build it. By default, it will install to '/usr/local', but it can be installed anywhere by running ./configure --prefix=/path/to/wherever. Just be sure to remember to add the prefix to $PATH.

git clone git://git.eigenstate.org/git/ori/mc.git
cd mc               # the directory you cloned into
make                # build it
sudo make install   # defaults to /usr/local 
Compiling Hello World

The next step after getting the compiler installed is using it. An example hello world program is below.

use std

const main = {args : byte[:][:] /* a slice of byte slices */
    var i                       /* the type is inferred */
    std.put("Hello-世界\n")     /* prints to stdout */
    for i = 0; i < args.len; i++
        /* our format strings are not printf compatible, beware */
        std.put("arg %i = %s\n", i, args[i])
    ;;
    -> 0
} 

To build it, put it in a file of your choice. I'll use hello.myr. Then, run

myrbuild -b hello hello.myr

This will produce a binary called hello, which will print "Hello-世界", echo all of the args given, and exit with a status of 0.

The myrbuild program wraps the entire build process for myrddin code. It determines the dependencies, the build order, and manages the set of libraries that need to be linked in to produce the final library or binary, assuming that the included libraries are installed to the conventional location ($PREFIX/lib/myr).

As a result, it should generally be sufficient to run myrbuild -b $TARG main.myr in order to build a binary. Because the sources in libraries might not have direct dependencies on each other, building a library might require you to run myrbuild -l libname full.myr sources.myr list.myr

Deconstructing Hello World
use std

The statement 'use std' tells the Myrddin compiler to load the 'std' package and make the functions in it available to our code.

const main = {args : byte[:][:];...}

This is declaration where the anonymous function {args; ...} is assigned the the constant value 'main'.. All function declarations in Myrddin are done by assigning an anonymous function to a variable, generic, or constant.

The keyword 'const' is one of the three ways of beginning a declaration. The other two are 'generic' and 'var'.

genericA generic value that the compiler will specialize
constA constant value. Should not be assignable. Mutability propagation semantics are TBD
varA mutable, assignable value.

The general form of anonymous functions is {args, go, here; body}. Semicolons and newlines are interchangable through all Myrddin code.

var i

This declares a variable named 'i', with the type to be inferred later.

std.put("Hello-世界\n")

Invokes the function 'std.put', which came from the 'std' package, printing to standard output. The string is encoded in utf8.

for i = 0; i < args.len; i++
     std.put("arg %i = %s\n", i, args[i])
;;

A bog standard for loop. The initializer, test, and increment are all single expressions terminated with a newline or semicolon, and the body is a block terminated by a ';;'.

-> 0

A return statement, returning 0 from main. Note that the return type was never specified, so it's left to be inferred from the return statement. If there was no statement at all, the return type would be 'void'

Beyond Hello World: Types
Myrddin supports a wide range of primitive types.
voidA void type
boolA boolean type
charA single unicode codepoint
int8A 8 bit integer
int16A 16 bit integer
int32A 32 bit integer
int64A 64 bit integer
intA native integer, 32 bits or greater
uint8A 8 bit unsigned integer
uint16A 16 bit unsigned integer
uint32A 32 bit unsigned integer
uint64A 64 bit unsigned integer
uintA native unsigned integer, 32 bits or greater
float32A single precision floating point
float64A double precision floating point
...A variadic argument list

In addition to primitive types, it has type paramters.

@tA type paramter that can be substituted with any other type. Only valid within generic declarations.
@t::traitA type parameter which must satisfy a trait. Only types that have the named trait can be substituted.

Composite types

@t#A pointer to type @t. No pointer arithmetic can be done without explicit casting to integers.
@t[:]A slice of type @t. The length is carried along with the slice, which can be imagined as a window into an array
@t[SIZE]An array of size SIZE. In Myrddin, the size of an array is part of it's type

Functions

(arg:@type, list:@type2 -> @returntype)Functions, with arguments typed and specified

And aggregate types

[@a, @b, @c]Tuples.
struct
    member : @t
;;
Structs.
union
    `Key @t
    `Otherkey
;;
Unions (in the sense of an algebraic data type).
New types can be defined with a type statement:
type t = struct
    val : int
;;

type u = union
    `Some int
    `None
;;

var x : t
var y : u
x.val = 42
u = `Some 234
Beyond Hello World: Packages

Myrddin has a simple package system, doing away with headers. Myrddin source files can define the functions they export by including a package section, which lists the exported symbols.

To use a package installed into the compiler search path, add use package_name statement to your code. This will allow you to access 'package_name.symbol'.

To import symbols from a file within the same directory as your source code, add use "filename.use". The package name that is brought in to your namespace is determined by the 'pkg' spec within the source file being imported.

If two use statements load the same package (eg, two local files export symbols for the 'foo' package), then the packages will be merged.

To create your own package, add a pkg block to it. While it's not necessarily, stylistically, the exported symbols should have their types fully specified.

An example of a 3 source file program that uses a system import is given below.

/* This is in main.myr */
use std
use "say-hello.use"
use "say-goodbye.use"

const main = {
    demo.sayhello()
    std.put("yo!")
    demo.saygoodbye()
    -> 0
}
 /* This code is in say-hello.myr */
use std

pkg demo  =
    const sayhello : (-> void)
;;

const sayhello = {
    std.put("Hello\n")
}
 /* This code is in say-goodbye.myr */
use std

pkg demo  =
    const saygoodbye : (-> void)
;;

const saygoodbye = {
    std.put("Goodbye\n")
}

The use files loaded by the use statement are generated by the 'muse' tool. This tool will be invoked automatically as needed by 'myrbuild'.

Beyond Hello World: Pattern Matching

I personally love the way that algebraic data types and pattern matching in functional languages allows you to express only valid combinations of values in your data structures. So, of course, Myrddin has that too.

At the moment the number of pattern types that can be matched over is somewhat limited, but I hope to grow it to nearly anything that can be expressed as a literal as soon as possible.

Simple primitive values are matchable:

match v
| 123:    std.put("First branch\n")
| 234:    std.put("Second branch\n")
| x:      std.put("What? Got %i\n", x)
;;

Unions

type u = union
    `Some int
    `None
;;

match v
| `Some 123:    std.put("First branch\n")
| `Some x:      std.put("Got some %i\n, x")
| `None:        std.put("Got nothing\n")
;;
Beyond Hello World: Generics

We often want algorithms to apply over many types, not just the ones that we happened to code with. The Myrddin language provides facilities for this. Unfortunately, parameterized types are not yet fully implemented, which severely limits the usefulness of generics, but they are still useful.

A generic function is written exactly as a normal function would be, but the keyword 'generic' is used in its declaration, instead of 'const'. A type parameter is then given in the place of a concrete type, and you're off.

For the trivial example, here's a generic sizeof function.

generic gsizeof = {val : @a;
    -> sizeof(@a)
}

But that's not very useful, so here's a generic max function. Because we need to be able to use the > operator on the arguments of the function, we will need to use a trait on the generic parameter. The trait that provides the comparison operators is tcnum

generic max = {x : @a::tcnum, y : @a::tcnum
    if x > y
        -> x
    else
        -> y
    ;;
}
Beyond...

Yeah... Just read the spec, ya lazy scum. It's there for a reason. If someone wants to write a more comprehensive tutorial, I would love you forever. Or until the next distraction comes along. Whichever comes fir... Ooh! kitten!

Style

Code should be short, simple, and direct, with little gratuitous layering of abstraction. Algorithms should be described in a linear, straightforward fashion, as a set of steps to perform.

Variables, types, and functions should be named lowercase. Constants should begin with an Uppercase. Names should be short, evocative, and mnemonic, but above all, consistent. A single verb is an ideal function name.

spawn(func)                            /* good */
create_new_thread_with_function(func) /* bad */
Standard Library

Myrddin has a very small standard library. Partly because parsimony in the standard library suits a language like Myrddin, but mostly because I am only one person and only have so much time on my hands.

Contributions are gladly accepted

Partial reference for the standard library is available here, but some of the most useful functions in it are listed below

generic alloc   : (-> @a#)
generic free    : (@a# -> void)
These are some of the most common memory allocation functions. They respectively allocate and free single items of type '@a'. They are similar to 'malloc()' and 'free()' in C.
generic slalloc : (len -> @a[:])
generic slfree  : (@a[:] -> void)
These allocate and free slices of length, similar to 'alloc' and 'free' above.
const put       : (fmt : byte[:], args:... -> size)
Formats according to a format string and prints to standard output. The format specifiers used are NOT the same as the ones used in C.
%s A string, ie, a utf8 encoded byte slice.
%t A boolean.
%b A byte.
%w A 16 bit integer
%i A 32 bit integer
%l A 64 bit integer
%p A pointer
%c A char
Further Reading
Broken Bits
  • Variadic functions are still unsafe garbage
  • Bounds checks aren't yet generated by the compiler
  • Environment capture isn't yet implemented for nested functions
  • The generated code is inefficient and hard to follow
  • The standard library isn't merely small; it's microscopic
  • User defined traits aren't implemented
  • And so much more!
Contact Me
t