slashbinbash.de / Muckefuk

Muckefuk (Extension: .mkfk) is a scripting language with a focus on object-based and prototype-based programming.

Download Muckefuk Interpreter (muckefuk-20240630.zip)

To run the interpreter, you will need a Java 17 Runtime Environment.

Motivation

When I started looking into programming language development, I found the idea of creating a stack-oriented scripting language based on Assembly syntax interesting. This resulted in the development of SASM.

I was also interested in writing an implementation of my own Lisp dialect, which became Sikkel. This gave me the chance to dip my toes into some functional programming.

For my third language, I wanted to do something object-based, preferably with C syntax.

The development of the language was mainly driven by two ideas:

  1. Namespaces are the central concept of the language, and how objects are defined.
  2. Name-first syntax, where assignment to variables is the only means of naming objects and definitions.

The result is a language that is conceptually similar to JavaScript, which is why I named it Muckefuk (German word for coffee substitute).

In this article, I want to cover some of the key features of the language. For a more detailed language reference, check the README.md that is included in the download archive.

Name-First Syntax

A type-first syntax means that the type of the object comes before the name. Here are a few examples from different programming languages:

// C
bool someFunction(int a, int b, int c)

// C++
class MyClass { };

// JavaScript
function myFunction(a, b, c)

// Rust
struct MyStruct { }

// Zig
fn myFunc(a: i8, b: i8, c: i8) i8

A name-first syntax means that the name comes before the type. Here are a few different examples:

// Haskell
myFunction :: Integer -> Integer -> Integer -> Integer

// Odin
myFunction :: proc(a: int, b: int, c: int) -> int

You might have noticed that there is a difference between creating and naming a definition, and assigning an object to a variable. The struct definition in Zig might be one of the few cases that I know of, where the definition matches the object assignment:

// Zig
const MyStruct = struct { };   // definition

const myStruct = MyStruct { }; // instantiation and assignment

For Muckefuk, I wanted to take a name-first syntax approach and try to unify the syntax for definition and assignment.

someNum = 6;

someObj = {};

someFn = func() { };

someEnum = enum { };

someObj = {
    someNum = 6;
    someFn = func() { };
};

Note how the name is followed by an assignment. Right after the assignment comes the type of the object, i.e. func or enum. If no type is specified, it defaults to a namespace object.

This works because definitions are unnamed objects. The variables keep references to these definition objects.

Namespaces

A namespace is basically an object. It serves as a scope for names, which are symbolic references to other objects.

To create a namespace, you can write:

test = {};

After which you can assign objects to the namespace:

test.a = 10;
test.b = 20;
test.c = 30;

You can also assign these objects directly in the namespace definition:

test = {
    a = 10;
    b = 20;
    c = 30;
};

This is different from how objects are defined in JavaScript, since you can use any kind of statements inside the namespace definition:

test = {
    if (debug)
        a = 42;
    else
        a = 10;

    b = 20;
    c = 30;
    x = a + b + c;
};

The namespace definition is essentially a function body that is executed before the namespace object is assigned to the variable. The difference is that the namespace definition has no parameters and no explicit return value.

You can however use it to create a temporary scope by accessing one of the object members directly after the namespace definition:

result = {
    a = 10;
    b = 20;
    c = 30;
    x = a + b + c;
}.x;

Note the .x in the last line. The namespace object is automatically deleted after the value in x is assigned to result.

The language derives its object-based and prototype-based properties from the definition of namespace objects.

Object-Based

The namespace provides all the necessary tools for creating objects. Take for instance the following namespace definition:

Person = {
    new = func(age, name) {
        person = copy(Person);
        person.age = age;
        person.name = name;
        return person;
    };

    setAge = func(self, n) {
        self.age = n * 2;
    };
};

To create a new person, you can call new.

person = Person.new(42, "Joe");

Now you can use the setAge function on person:

person.setAge(22);

One quirk of the language is that you can also use Person to set the age of any person, without changing the definition of setAge:

Person.setAge(person, 33);

Which one you use is mostly a stylistic choice.

Once a Person object is created, you can extend by assigning other objects and functions to it. You could also take Person as a blue-print for other types. This is where the prototype-based programming paradigm comes into play.

Conclusion

There are other features that I implemented in this language, like range-based for loops, tuples, multiple return values, destructuring, and match-statements. But there is nothing in particular that sets these features apart from other languages that support them.

The most painful part about the implementation was and still is the expression parsing, which is held together by hopes and dreams, and tons of unit tests. There are a few nasty edge cases around the member access operator, or calling a function pointer that is returned by another function call, i.e. fn()(). I am glad that I do not have a subscript operator.

Otherwise, the flexibility of the language makes it fun to play around with. But being able to modify any object and its members at any point, makes it completely unpredictable at runtime. This is one of the properties that it shares with JavaScript, which is why I still can't understand why someone would use JavaScript for any serious application.


Created: 2019-04-18 Modified: 2025-11-01