DEV Community

Cover image for The Feral Programming Language
Electrux
Electrux

Posted on

The Feral Programming Language

Hey everyone! I'd like to introduce and share a programming language I have been working on - Feral.

The Boring Bookish Description

In essence, Feral is an interpreted, procedural, dynamically typed programming language which is meant primarily for scripting, and is minimalistic such that one can learn the entire language in a single day.

Yep. That was quite boring. Let's make it interesting.

Some Background

Recently, about a couple months ago, while I was writing some scripts at work in Python, I realized that I absolutely dislike its syntax (no hate to my fellow python developers ❤️). So, I started thinking about the language that I'd enjoy using myself. I started scribbling the syntax and eventually settled on something I actually liked.

I also thought of some ideas that would perhaps be fascinating to implement. And that was it! I also came up with the name Feral for 2 simple reasons:

  1. It's such a beautiful word
  2. It's wild - powerful and fast!

Armed with the ideas and motivation (and a borderline obsession) for creating the language, I started working on Feral - the minimalistic, interpreted programming language.

The Language

Hmm... Remember I said Feral is minimalistic? Well, I most certainly meant it! Feral does not contain any special syntax for structures, enumerations, maps/dictionaries, or even vectors/lists. Heck, there is no special syntax for even importing other source files.

Do note that I said it didn’t contain special syntax for those things. I did not say that it doesn’t support them. Feral does support all of those concepts. However, instead of relying on special syntax on the language side, it provides functions with which the same functionality is created while having far less core compiler codebase.

Feral contains an import() function which allows the user to import other Feral sources, instead of relying on an import keyword that most other languages have. Similarly, structures are created using a lang.struct() function and enumerations are created using lang.enum() function.

Feral also has first-class support for modules and functions! And not just that, even structures are first-class citizens in Feral (in a way, we’ll see how, below).

In Feral, the modules (cool way of saying “other feral sources”) are loaded as follows:

let io = import("std/io");
Enter fullscreen mode Exit fullscreen mode

Basically, io is now a variable which can be passed to functions, returned from functions, reassigned to another value, and so on. Basically, it has all the functionality of any other variable!

Similarly, to create a structure (not an instance/object of structure), the lang.struct() function is used as follows:

let lang = import("std/lang");
let my_struct = lang.struct(a = 5, b = "string");
Enter fullscreen mode Exit fullscreen mode

Finally, just call my_struct as a function to create an instance of that struct. Cool, right?!

Even functions themselves are Feral values which can be assigned to variables and passed around to other functions (callbacks). So, say you want to make a sort function and pass the sorting comparison mechanism separately, as a function itself? Check! (Although, for the love of everything holy, please don’t overuse callbacks... ever!)

In Feral, as with many other interpreted languages, one can write functions and types in C++ (host language) as C++ runtime loaded modules, or as Feral functions themselves. The simple thought for deciding which one to use is to only go the C++ route if the function requires high performance, otherwise Feral functions will satisfy most of the cases.

By the way, the operators are functions too! So, something like this definitely possible:

let io = import("std/io");

let "**" in str_t = fn(num) {
    let res = "";
    for i in range(0, num) {
        res += self;
    }
    return res;
};

io.println("5" ** 22);
Enter fullscreen mode Exit fullscreen mode

Here, the ** (power operator in Feral) is made to work as a repetition operator for strings! str_t is the (special) type variable for strings.

Feral's documentation is a work in progress, and can be read here: Feral-Lang/Book.

Syntax Highlighting

I don’t know about you, but I most certainly can’t live without it. I need my language syntax to be highlighted by colors after all, otherwise it would look too dull and boring!!

Keeping that in mind, I have also created simple syntax highlighting extensions for Visual Studio Code and (Neo)vim (links at the bottom).

Some Technical Details

This is a post of a programming language, on a website for developers. What fun would it be if there were no technical details!

Since Feral is an interpreted language, it required a host language which would be, ideally, natively compiled. For me, C++(11) was a no brainer here since it is natively compiled, outrageously fast, and I love to use it (I dare you to sway me to another language!).

As a matter of fact, the only issue I have with writing C++ code, is regarding the build system — I spent a lot of time trying to understand how to get my work done with CMakeLists. I don’t hate CMake, but it can get annoying to get its configuration right. Alas, it does work pretty well and is ubiquitously available, so CMake it was!

For the language implementation itself, the Lexer was nothing special, the parser on the other hand, I am quite happy to have made a Recursive-Descent Parser, and the code looks so beautiful and clean (come on, let me brag a bit! 😉). I definitely recommend it if it works for your language syntax.


// Left Associative
// / * % **
Errors parse_expr_04( phelper_t & ph, stmt_base_t * & loc )
{
    stmt_base_t * lhs = nullptr, * rhs = nullptr;
    const lex::tok_t * oper = nullptr;

    size_t idx = ph.peak()->pos;

    if( parse_expr_03( ph, lhs ) != E_OK ) {
        goto fail;
    }

    while( ph.accept( TOK_DIV, TOK_MUL, TOK_MOD ) ||
           ph.accept( TOK_POW ) ) {
        idx = ph.peak()->pos;
        oper = ph.peak();
        ph.next();
        if( parse_expr_03( ph, rhs ) != E_OK ) {
            goto fail;
        }
        lhs = new stmt_expr_t( lhs, oper, rhs, idx );
        rhs = nullptr;
    }
    loc = lhs;
    return E_OK;
fail:
    if( lhs ) delete lhs;
    if( rhs ) delete rhs;
    return E_PARSE_FAIL;
}
Enter fullscreen mode Exit fullscreen mode

That's a small part of Feral's Recursive-Descent expression parsing.

No, I don’t use tools like yacc, or bison — I like being in total control of the codebase, plus for me, yacc/bison would have taken a lot more time to understand and then put into use.

But my oh my, the real fun was in writing the Virtual Machine. Since there was always the goal in mind of the language being minimalistic, the number of VM instructions would have to be low too. As of now, Feral contains a total of 22 VM instructions through which any Feral program is executed. I think that’s sufficiently low.

The VM itself is stack-based, therefore each instruction is quite simple — an operation, and an operand (which is optional). And it uses libgmp and libmpfr libraries for working with arbitrary sized arithmetic. This totally eliminates the need to package a separate library for working with big numbers. Aside from system libraries, these are the only external libraries required for installing and using the Feral programming language.

Currently, VM consists of the following instructions.

OP_CREATE,  // create a new variable - bool operand - if true, it contains 'in' part (x in y = z)
OP_STORE,   // store in a name: value from stack
OP_LOAD,    // load from operand, onto stack
OP_ULOAD,   // unload (pop) from stack

OP_JMP,     // unconditional jump to index
OP_JMPT,    // jump to index if top element on stack is true - will not unload if true
OP_JMPF,    // jump to index if top element on stack is false - will not unload if false
OP_JMPTPOP, // jump to index if top element on stack is true - will pop unconditionally
OP_JMPFPOP, // jump to index if top element on stack is false - will pop unconditionally
OP_JMPN,    // jump to index if top element on stack is nil (won't pop otherwise)

OP_BODY_TILL,   // jump to index which is where the body (of a function) ends + 1
OP_MKFN,    // create a function object

OP_BLKA,    // add count scopes
OP_BLKR,    // rem count scopes

OP_FNCL,    // call a function (string arg - argument format)
OP_MEM_FNCL,    // call a member function (string arg - argument format)
OP_ATTR,    // get attribute from an object (operand is attribute name)

OP_RET,     // return - bool - false pushes nil on top of stack
OP_CONTINUE,    // size_t operand - jump to
OP_BREAK,   // size_t operand - jump to

// for loops
OP_PUSH_LOOP,   // marks a loop's beginning for variable stack
OP_POP_LOOP,    // marks a loop's ending for variable stack
Enter fullscreen mode Exit fullscreen mode

As of yet, to be honest, the VM is most certainly not quite optimized. I am working on that, but it’s definitely not agonizingly slow — by a long shot!

Conclusion

Well, this is a very short introduction, as well as the main things I feel to be interesting about Feral. As the development continues, the language is becoming more and more stable, and frankly, I am incredibly excited about working with it, using it and sharing about it!

Hope you all find the post and language interesting/exciting! Comment your thoughts on them and do check out Feral. Feedback’s always welcome and most appreciated! But that’s it for today.

Until next time everyone! ❤️ ❤️

Links:

Feral Language Organization: Feral-Lang
Feral Compiler/VM: Feral-Lang/Feral
Feral Book/Guide: Feral-Lang/Book

... Just One More Example, Pretty Please!

let io = import("std/io");
let fs = import("std/fs");
let sys = import("std/sys");
let curl = import("curl");

let url = "http://212.183.159.230/10MB.zip";
let out = "10MB.zip";
let out_file = fs.open("10MB.zip", "w+");

let c = curl.new_easy();
c.set_opt(curl.OPT_URL, url);
c.set_opt(curl.OPT_FOLLOWLOCATION, 1);
c.set_opt(curl.OPT_NOPROGRESS, 0);
c.set_opt(curl.OPT_WRITEDATA, out_file);
let result = c.perform();

io.println();
if result != 0 {
    io.println("failed to download file '", url, "' to '", out, "'");
    sys.exit(result);
}
io.println("Downloaded file: ", out);
Enter fullscreen mode Exit fullscreen mode

This code uses the (Work in Progress) Curl C library and its Feral API to download a file.
It's so cute isn't it!! 🥺❤️

Discussion (1)

Collapse
bosley profile image
Bosley

This is really great! Unfortunately, I've found that the users of this site tend to be more in the category of web developers and things like this dont get much attention. (No hate, just an observation) I'm glad you posted this. I've been playing around with vm creation recently in C/C++ and I'm stoked to see someone else is too! Good job!