- Notifications
You must be signed in to change notification settings - Fork1
The malang programming language
License
traplol/malang
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
First and foremost, this is just a place for me to try things and to learn about how one might implementa programming language.
malang is a general purpose, statically and strongly typed, structured programming language. It is earlyin development but already it features:
- a tracing garbage collector
- type inference
- primitive types such as
string
,int
,bool
,double
,buffer
s, and arrays. - first class function expressions
- block expressions
- bound/named functions that support parameter overloading
- extend any type with operators and methods
- user-defined structures
- strong type aliasing
- a file-based module system
Planned features:
- multiple return values
- string interpolation
- closures
- zero overhead generics
- traits
- exceptions
- optimized ahead of time compiliation that can be saved to disk and run at a later time.
malang is compiled into a fairly low-level stack-based bytecode which is then interpreted by the malangVM.
There are plans to support and move towards CMake, buttup is the current build tool.
- Make sure you have installed the dependencies:
g++
4.7 or later orclang++
3.x or latertup
git
- Build and run
$ git clone https://github.com/traplol/malang$cd malang$ tup init$ tup$ ./mal -q examples/hello-world.ma hello world!
Variables can be declared with their type, or the type may be entirely omitted if it can be immediately deduced.
x : intx = 42y : double = 123.45PI := 3.14159PI = 3.0 # compile error because ALL_CAPS signifies it is a constantz := "hello world"a : int = 1.5 # compile error without explicit castb := returns_int()c := if true "yep" else "nope"
An if/else block may be used as a value if the last expression for both legs is notvoid
# `and` and `&&` are the same and `or` and `||` are also the sameif one println("hello world")else if two or three and four println("wat")else if five || size && seven println("woo!")else println("hmm")x := if get_condition() "yes!" else "nope"
While loop
# poor man's for loopi := 0while i < n { do_thing(i) i += 1}
For loops expect the thing being iterated to implementfn current() -> T
andfn move_next() -> bool
wherefn move_next() -> bool
returnsfalse
when there are no more iterations left. Seerange.mafor a trivial Range implementation
for Range(0, 10) { println(it) # `it'em is implied}# You may also name the item with the `for ident in ...` syntaxfor i in Range(0, 10) { for j in Range(10, 20) { println(i * j) }}
Functions can be defined and bound to a name, assigned to a variable, or used as a value in an expression. Themain difference between binding to a name and to a variable is a function bound to a name can share that namewith other bound functions if their parameter types differ.
SeeBUILTINS for a list of builtin and always availble functions.
# Binds to 'hello' with with no parametersfn hello() -> void { println("hello world")}# Binds to 'hello' with 1 parameter (int)# notice the void return type has been ommitted/impliedfn hello(a: int) { hello() println(a)}min := fn(a: double, b: double) -> double { if a < b return a else return b}fib := fn(n: int) -> int { if n < 3 { return 1 } else { # notice the use of 'recurse' because otherwise 'fib' could be redefined to another # function with the same function signature and that one would get called instead. return recurse(n-1) + recurse(n-2) }}
Arrays consist of values/references andbuffer
s are blocks of memory with byte-index ganularity.Both are special because theirlength
,[]
and[]=
properties are optimized into theirown respective malangVM instructions. Both are bounds checked but unchecked instructions do exist forwhen the compiler can be certain an index cannot possibly be out of range.
n := 123nums := [n]int # dynamically allocate 123 uninitialized intsi := 0while i < nums.length { nums[i] = i * i i += 1}a_buf := buffer(128) # 128 byte bufferi = 0while i < a_buf.length { a_buf[i] = i i += 1}
type D = { e := 0 _private_var := "hello world" # starts with _ so this is private in this context CONST_VAR := 99 # ALL_CAPS so this is constant/readonly in this context public_var := true # not _private or CONST to this is public _ANOTHER_CONST := 111 # _private and CONST new (e: int) { self.e = e }}# A, B, and C don't have a constructor that takes parameters so the default constrcutor# is implied. In the future, types will also be order independent.type C = { d := D(42)}type B = { c := C()}type A = { b := B()}a := A()println(a.b.c.d.e)type Vec3 = { x := 0.0 y := 0.0 z := 0.0 # default, only necessary because we define another constructor new () {} new (x: double, y: double, z: double) { self.x = x self.y = y self.z = z } fn * (scalar: double) -> Vec3 { # note the implicit resolution of x,y,z return Vec3(x*scalar, y*scalar, z*scalar) } fn + (other: Vec3) -> Vec3 { # and here we can shadow fields of the same name but still access them with # the implicit "self" reference x := self.x + other.x y := self.y + other.y z := self.z + other.z return Vec3(x, y, z) } fn - (other: Vec3) -> Vec3 { x := self.x - other.x y := self.y - other.y z := self.z - other.z return Vec3(x, y, z) }}a := Vec3() # creates a Vec3 with the default constructora.x = 999.0b := Vec3(1.5, 2.3, 3.14)c := a + bprintln(c.x) # 1000.5println(c.y) # 2.3println(c.z) # 3.14
Type aliasing is a way to distinguish types which have the same internal representation from eachother without unnecessary runtime overhead. Type aliasing also allows you to write extensions foran aliased type separate from higher types and separate from any neighboring type aliases whileallowing these methods to be available to any sub-aliases.Seealias.ma for a more comprehensive example.
type alias cents = inttype alias dollars = inttype alias time_ms = intfn cents(d: dollars) -> cents { # `unalias' converts to the top type of an alias hierarchy: in this case `unalias(d)' is an `int' return unalias(d) * 100}fn dollars(c: cents) -> dollars { return unalias(c) / 100}money : dollars = 45 # ok: 45pennies := cents(money) # ok: 4500duration : time_ms = 2750 # ok: 2750#bad1 : cents = duration # compile error, no conversion exists#bad2 : dollars = duration # compile error, no conversion existsok1 : dollars = 45 # 45 is an int which is the aliased typeok2 : cents = 9999 # 9999 is an int which is the aliased type#bad3 : cents = 42.69 # compile error, 42.69 is not an int
Extensions allow you to implement new methods and operators on any existing type but they do notallow you to add more fields to them.
# string multiplication doesn't yet exist, so let us implement itextend string { fn * (n: int) -> string { # edge case if n <= 0 { return string(buffer(0)) } total_len := self.length * n tmp_buf := buffer(total_len) i := 0 while n > 0 { k := 0 while k < self.length { tmp_buf[i] = self[k] i += 1 k += 1 } n -= 1 } return string(tmp_buf, total_len) }}println("hello " * 10) # hello hello hello hello hello hello hello hello hello hello