Myrddin

use std const main = { for x : [(1, "let's"), (2, "get"), (3, "started")] std.put("{}

", x) ;; }

Run!

Press 'Run' to see output!

Myrddin is a programming language. It aims for control and simplicity. It features strong type checking, generics, type inference, closures, and traits. It aims to fit into a similar niche as C, but with fewer bullets in your feet.

Myrddin does not aim to explore the forefront of type theory or compiler technology. It does not focus on guaranteeing perfect safety. It is satisfied to be a practical, small language.

Code

The code is available on both GitHub, and on Eigenstate

Status

Supported Platforms.

Myrddin currently compiles for x86-64. It runs on five OSes at the moment:

Linux

OSX

Plan 9 (9front)

FreeBSD

OpenBSD

NetBSD (preliminary)

It has proved relatively easy to port between these systems, needing only a few system calls, and a couple of hundred lines of code to get the bootstrap working.

NetBSD currently does not support libthread.

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.

Simple and easy to understand implementation.

Try It Online

If you want to try before you buy, there is an online playground that you can use to experiment with code. It's fairly restrictive, and prevents you from using the vast majority of system calls, but it's enough to get a feel for things.

So, give it a shot.

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 before trying to run any commands, or your shell will not be able to find them.

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

When running from git, you may need to first run make bootstrap on the first installation, or upon significant changes to mbld.

make bootstrap # generate the bootstrap tools

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 */ std.put("Hello-世界

") /* prints to stdout */ for a : args std.put("arg = {}

", a) ;; }

Run!

Press 'Run' to see output!

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

mbld -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 mbld 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 mbld -b bin input.myr in order to build a binary.

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 a declaration where the anonymous function {args; ...} is assigned 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 .

Keyword Meaning generic A generic value that the compiler will specialize const A constant value. Should not be assignable. Mutability propagation semantics are TBD var A mutable, assignable value.

The general form of anonymous functions is {arg, list; body_statements()} . Semicolons and newlines are interchangeable through all Myrddin code.

std.put("Hello-世界

")

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

for a : args std.put("arg {} = {}

", i, args[i]) ;;

A bog standard for loop. This instance uses the for pattern in sequence syntax, which will iterate over type that is iterable, matching the values with the pattern and executing the loop body if there is a valid pattern match.

The alternative syntax, for init; bound; incr syntax is also accepted. For this syntax, The initializer, test, and increment are all single expressions terminated with a newline or semicolon. The init clause may include a declaration.

In both cases, the body is a block terminated by a ';;'.

Beyond Hello World: Types

Myrddin supports a wide range of primitive types. Types are all strong, with no implicit conversions between them.

Type Description void A void type bool A boolean type char A single Unicode codepoint int8 A 8 bit integer int16 A 16 bit integer int32 A 32 bit integer int64 A 64 bit integer int A native integer, 32 bits or greater uint8 A 8 bit unsigned integer uint16 A 16 bit unsigned integer uint32 A 32 bit unsigned integer uint64 A 64 bit unsigned integer uint A native unsigned integer, 32 bits or greater flt32 A single precision floating point flt64 A double precision floating point ... A variadic argument list

In addition to primitive types, it has type parameters. Type parameters act as variables which may be substituted with any other type on invocation or use of a type.

When traits are specified, all traits must be present on the substituted type in order for the substitution to proceed.

Type Description @t A type parameter @t::trait A type parameter with a trait @t::(trait,list) A type parameter with a list of traits.

Composite types are composed of primitive types, or other composite types. There are three of these, which can be applied to any type. In Myrddin, slices carry both the array data and the length, and may be taken off of pointers, arrays, and other slices. A pointer only points to one element, and does not support pointer arithmetic. And an array is of a fixed size, with the size considered as part of its type.

Type> Description type# A pointer to type type[:] A slice of type type[SIZE] A type array of size SIZE.

Function types are specified with parentheses. The argument names are mandatory in all type specifications, although the return type is optional. Tuples are specified with parentheses. A better, but still conflict-free syntax would be welcome.

Type Description (arg : t1, list : t2 -> ret) Function with 2 args and a return type (-> ret) Function with 0 args and a return type (t1, t2, t3) Three element tuple

Structs in Myrddin behave much like they do in C, and are actually binary compatible in layout with the SysV struct ABI. Members are accessed with dot notation, as strct.memb , and can be initialized as literals with the struct literal notation [.memb=val, .other=val]

struct member : @t ;;

Unions in Myrddin are closer to algebraic data types in ML than they are to C unions. The union knows the value that is currently being held in it, and is constructed and matched with a tag. To construct a union, the `Tag val syntax is used

union `Tag1 t1 `Tag2 t2 ;;

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 a use statement, like use package_name , 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" . 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 (e.g., 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 here. main.myr will be importing all of the source files, and consuming the exported symbols. It uses the system include std , as well as the two local usefiles that provide the demo package, say-hello.use and say-goodbye.use

Unfortunately, since the runner that I have does not support multiple files, this code will not build or run online. Sorry.

/* 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 }

say-hello.myr defines a package block that exports the function sayhello , into the package demo , as well as providing the definition

/* This code is in say-hello.myr */ use std pkg demo = const sayhello : (-> void) ;; const sayhello = { std.put("Hello

") }

say-goodbye.myr defines a package block that exports the function saygoodbye , into the package demo , as well as providing the definition

/* This code is in say-goodbye.myr */ use std pkg demo = const saygoodbye : (-> void) ;; const saygoodbye = { std.put("Goodbye

") }

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

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

use std const main = { var v v = 234 match v | 123: std.put("First branch

") | 234: std.put("Second branch

") | x: std.put("What? Got {}

", x) ;; }

Run!

Press 'Run' to see output!

As are unions, structs, and any nesting of similar complex types:

use std type u = union `Some int `None ;; type s = struct a : int b : u c : (int, int) ;; const main = { var u var v : s /* initialize u to some value */ u = `Some 234 match u | `Some 123: std.put("First branch

") | `Some x: std.put("Got some {}

", x) | `None: std.put("Got nothing

") ;; v = [.a = 123, .b=`Some 456, .c=(5, 10)] match v | [.a = 234]: std.put("Should not be here

") | [.a = 123, .c=(x, y)]: std.put("extracted tuple is ({}, {})

", x, y) | [.a = 123, .c=(x, y)]: std.put("extracted tuple is ({}, {})

", x, y) | [.a = other]: std.put("got other value: {}

", other) ;; }

Run!

Press 'Run' to see output!

Beyond Hello World: Closures

Myrddin also has support for closures, including environment capture. Closures capture all variables in their scope by value at creation time. The environment is stack allocated by default, but can be heapified on demand.

use std const main = { var a, fn a = 123 fn = {x std.put("inside closure: {}

", x + a) a++ /* modify the captured copy of a */ } fn(123) /* just call the function a few times */ fn(0) /* we increment a */ fn(0) /* for each call */ std.put("a = {}

", a) /* but it doesn't change in the env */ }

Run!

Press 'Run' to see output!

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 numeric

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

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 */

Libraries

Myrddin ships with a number of useful libraries. The most common ones, and the ones that ship with the compiler, are libsys, libstd, libbio, libregex, libcrypto, and libdate. Other libraries are in the works.

Libstd handles functionality that most programs written would find useful. It's a bit of a grab bag, but it's useful.

Libbio handles buffered IO, allowing you to do fancy things with input and output, line reading linewise, or reading up to delimiters.

Libregex handles regexes. It implements a simple but powerful regex syntax, and has working Unicode support.

Libcrypto provides cryptographic functions. It implements many of the most common ones.

Libdate provides a fairly complete interface for manipulating dates, times, and timezones.

Libhtml allows for HTTP serving and handling.

Libescfmt escapes and unescapes various input and output formats.

Libfileutil handles a variety of less common but still useful file manipulation functions: directory tree walks, recursive deletion, temporary file support, and other similar things are included.

Libiter provides a number of iteration utilities in a generic form, allowing for reverse iteration, zipping, cartesian products, and a number of other things.

Libinitile handles parsing ini files.

Libtestr facilitates writing unit tests.

C Binding

It's possible on boxes that use the SysV ABI, but still has rough edges. The ABI is only identical for C and Myrddin when looking at atomic types such as pointers, integers, or floats. There's some minor support in the build system, but automated binding generation has only been started on.

Further Reading

Mailing List

Subscribe to the Mailing List

IRC: irc.eigenstate.org, in #myrddin. You can get there using your favorite client, or with kiwi IRC

Or contact me directly, at 'ori@eigenstate.org'