Beagle Update: Priorities, New Codebase, Updated Roadmap

Hey everybody! I have an update on the Beagle language’s development. I’ll go in order as appearing in the title.

I have done some careful evaluation of Beagle and what I envision it to be and I have made several significant changes to it since its theoretical conception in November 2019. As I have been keen on in the past, it was originally going to be written using Kotlin/Native. At first it was fine when all I needed to do was make kotlinx-llvm, but then some things came up while working on toylang (experimental compiler). I had realized that I needed some other frameworks to help me make my development life easier with Beagle, where I started developing a framework and Kotlin DSL for creating bytecode generators. While developing this I had begun to carefully consider what else I need to make in order to make my life easier. I had realized that perhaps something similar to kbit but for tokenizing and parsing. I had also thought about making an arena allocation compiler plugin. After I realized that, I realized that I would never be able to start work on Beagle any time soon, perhaps I’ll start more close to next year. That became rather undesirable. It was around this time I had been fed up with Gradle for the last time. It is such a clunky, bloating, slow, messy build system. I have had nothing but problems with it. Intellij also started giving me some problems and I finally got fed up with all the trouble that I should not have to deal with. I had also decided not to continue with these frameworks as of now just so I can start work on Beagle sooner than later. So I have official switched to Rust.

New Codebase
As mentioned in the previous paragraph, I have switched to Rust. Here is the new codebase. This way I can not only have a faster compiler, but I can also have a much simpler and cleaner build system. It is in my opinion (and perhaps many others’ opinions) that a good build system is necessary for a good language. Just because a language’s usage and syntax and libraries are good, doesn’t mean it’s a perfect language. Kotlin is the reason why Beagle is designed the way it is, with classes, properties, function syntax, no semicolons, interfaces, everything is final by default, infix functions, inline functions, suspend modifier, etc. Lots of things from Kotlin that will be in Beagle. However, I feel, at this point in my development and use with Rust, the production is much better. Within the last 4 days I have managed to implement a tokenizer, parser, type checker, and I’m currently designing and implementing the smart memory manager which generates the MIR (middle intermediate representation), which contains the instructions for memory management. I call it inferred resource management; not to be confused with garbage collection, as Beagle does not and will never have a garbage collector. This has led me to create a new roadmap for the language.

New Roadmap
I will soon announce v0.0.1 after I get everything I need for it to be almost turing complete.

  • 0.0.1

    • Properties

      • Properties are always heap allocated and can be referenced. They are cleaned up when their declaring scope is gone, and it no longer has living references aka, reference counting. A great point to make, as mentioned before, that this is not a runtime reference counting, this is done only at compile time.
      • Properties have built in getters and setters.
      • Properties are declared using val or var for mutable property.
    • Locals

      • Locals are always stack allocated and can only ever be passed by value by moving or copying.
      • Locals do not have built in getters and setters.
      • Locals are declared with let or let mut for mutable locals.
    • Functions

    • Lambdas (just like in Kotlin)

    • Control flow

      • If-Else
        • Else-If will be replaced with when. if-else is only for binary branching.
      • when
      • match
        • This will be used for unwrapping objects, as nullability will be expressed differently than in Kotlin. I will make another post about how type ignorance works but part of it is nullability but the way its represented in memory is an Option<T> object. If you aren’t sure what this means please refer to this Rust document on the Result and Option wrappers.
      • loop
        • Infinite loop. Sugar for the while(true) loop.
      • for
        • Ranged for or foreach using for(x in thing)
      • unless
        • Sugar for if(!x)
      • until
        • Sugar for for(i in 0..n-1). Syntax: until(10)
    • Modules

      • Modules are single files that act as a top-level domain for a set of code. The module setup will work the same way as in Rust. A root project will have a src/ where inside of that is either a or Apart from that, modules can be single files whose names have been declared by another module. When a module (mod a) is declared by another module (mod b), a then becomes a submodule of b and can be imported like this: import b::a. This syntax will import all the public code declared in that module. This will require a different post to further the information on this.
    • Expressions

      • I don’t think its necessary to explain what expressions exist in Beagle. Just know that all the control flow mentioned above can be used as expressions.
  • 0.0.2

    • Classes
      • open classes
      • abstract classes
      • member properties and methods/member functions
    • Interfaces
      • Only classes can implement interfaces.
    • Inheritance
      • Inheritance will be represented in memory as a VTable (as they usually are in languages like Rust and C++ and I think C#).
    • Extension functions
    • Encapsulation
  • 0.0.3

    • Structs
      • Structs cannot be inherited from nor do they inherit anything. Structs can be abstracted by traits.
    • Traits
      • Traits will be similar to interfaces except they are a compile time only construct that is only found in memory as a bitflag. Structs will be compiled with an extra invisible field that will act as a bitmask, which contains all the flags that represent their traits. Using bitwise AND at runtime, we can find out if an object of a struct based type has the composition of some trait. I will write more about this some other time. This allows you to focus on managing objects that are more concise in memory. It’s a complicated subject that requires an entire detailed explanation.
  • 0.0.4

    • Broad types
      • Broad types are a way of creating an abstraction that can be implemented by either classes or structs. As mentioned above classes can only implement interfaces and structs can only implement traits. A broad type allows you to create an abstraction that can span across both type systems. A great example of this is Iterable<T>. Iterable<T> is a type that provides members and composition for types to be used in a foreach loop directly. for(x in some_iterable_obj). Iterables will exist in both type systems. Class based iterables would include List, Set, Map, Deque, Tree. Struct based iterables would include Vector, Scalar, Array, Table, Dictionary
    • Enums
  • 0.0.5

    • Generics
    • Type Ignorance
      • Type ignorance allows a user to tell the compiler that they do not know everything about an object’s type, but provide the information they do know. This could be nullability, class only, struct only, struct with trait(s), broad type, or absolute ignorance (similar to an opaque type in C/C++).