Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Welcome

Welcome to the Vega programming language! In this documentation will start from roots of the language and continue until every detail of it is explained with examples.

This documentation is written for Vega version 1.0.0

PDF version of this documentation is provided in the GitHub repository and this version can be read offline by typing orbit doc --doc

Preamble

In the software world, there are many programming languages with different paradigms and approaches for completing a given task.

Naturally, this has led to many interesting - and often fascinating - outcomes. It is undeniable that C++, Rust, Java, Erlang, Go, Haskell, and OCaml are both inspired by their priors and influenced many came after.

Therefore, it is very normal that many languages of this diverse world became torchbearers for Vega's design. To elaborate further, Vega is influenced by:

  • Lua for its simple syntax and small set of keywords
  • Golang for its single and robust way to do things, along with its profound system call library and simple FFI to cooperate with the system and 3rd party libraries
  • Scala for its dynamic typing system
  • Erlang for its lightweight and flexible concurrency
  • Rust for its balanced approach to the functional and imperative programming paradigms

Vega is licensed with BSD-3 Clause License. Hence, please feel free to tinker internals, look how they work and release your own Vega version.

- Nix-Enthusiast

Keywords

Keywords are reserved words that provide core functionality to the programming language.

Vega has 23 keywords, each linked to its relevant section.

Data Types

Data is an informative value, which is either in a structured or unstructured form and capable of both being derived and deriving others.

The primary purpose of any programming language is to store, transform, and manipulate data. Vega provides several distinct data types to accomplish this purpose.

Primitives

Primitives are the built-in essential types to build the rest of the language. Other types not mentioned here are either derived or built upon these primitives.

Integers

Integers are binary representations of whole values.

Bits/SignSignedUnsigned
\(8\)i8u8
\(16\)i16u16
\(32\)i32u32
\(64\)i64u64
\(128\)i128u128
\(pointer\)1intuint
\(2^{pointer}\)ibig

Floats

The IEEE 754-compliant binary representation of real values with limited precision.

BitsType
\(16\)f16
\(32\)f32
\(64\)f64
\(80\)f80
\(128\)f128
\(pointer\)float
\(2^{pointer}\)fbig

FFI Numbers

The aliases of integer and float primitives used to import numeric types in their appropriate C representation from external libraries.

CSignedUnsigned
charc_charc_u_char
shortc_shortc_u_short
intc_intc_u_int
longc_longc_u_long
long longc_long_longc_u_long_long
floatc_float
doublec_double
long doublec_long_double

bool

A single-bit value which can be either true or false.

Although one bit suffices for a boolean compiler has to allocate one byte for it, since the smallest memory block is 1 byte.

Main := |_, _|
good := true
bad := false
end

char

An u32 value that represents a single Unicode Scalar Value.

Main := |_, _|
a := 'a'
potato := '🥔'
end

  1. \(pointer\) is the pointer size of the system.

Composite data types

Composite data types (CDTs) are used for storing more complex data in a structured composition in the memory.

Arrays

List ([N:t]T)

Growable array which allocated from heap to store T and the optional sentinel t, where t is T, with the minimum size of N.

Main := |_, _|
x := [1, 2, 3]
x := char['h', 'e', 'l', 'l', 'o']
end

Tuples ({})

An ordered set with finite size.

Declared using {T,V,..,N}

Main := |_, _|
coordinates := {74.5, 91.4}
end

Enums

Declared using '{} notation.

Enums are both tagged numbers and sum types.

Main := |_, _|
Base := '{
  /* C++ - like tagged numbers */
  Base2 i8 := 2
  Base8 i8 := 8
  Base16 i8 := 16
  
  /* Aliasing (type of the right hand side will be used) */
  Binary := Base.Base2
  Octal := Base.Base8
  Hexadecimal := Base.Base16

  /* Sum types */
  Other u8
}

myHex := Base.Hexadecimal
end

Records

Declared using #{} notation

Records are the data types to store data in a structured composition.

Main := |_, _|
Person := #{
  name str
  age u8
  id any
  faxNumber ?str
}

Vase(T) := #{ inner: T }

/*It's not necessary to state fields unless it is necessary to write in a specific order*/
Jan := { 
  "Jan"
  20
  "8675309"
  nil
}

vaseOfi8 := Vase(i8){ 23 }
end

str

Declared using "" notation.

str is basically an immutable and internable []u8 and used for containing information in text with UTF-8 encoding.

Main := |_, _|
msg := "Hello, World!"
end

Algebraic Data Types

Rationals

BitsType
\(8\)r8
\(16\)r16
\(32\)r64
\(64\)r32
\(128\)r128
\(pointer\)rat
\(2^{pointer}\)brat

Complex

BitsType
\(8\)c8
\(16\)c16
\(32\)c64
\(64\)c32
\(128\)c128
\(pointer\)cpx
\(2^{pointer}\)bcpx

Matrix (#[L:W]T)

A special list with the optional length of L and width of W, for advanced mathematical calculations.

Main := |_, _|
matrix_1 := #[1, 2, 3; 4, 5, 6; 7, 8, 9] 

matrix_2 := [3:3]int#[ 1 2 3
               4 5 6
               7 8 9 ]  
end               

Notations

Notations are short prefixes or suffixes to distinguish ambiguous literal values or prevent the compiler from using the default type for that literal.

Binary Notation (0b)

Binary notation is mostly used for bit flags.

 import std/testing/Assert
HAS_SWORD := 0b10000000
HAS_WAND := 0b01000000
HAS_ARROW := 0b00100000
HAS_ARMOR := 0b00010000
HAS_MINION := 0b00001000
MAGE := 0b00000100
WARRIOR := 0b00000010
ARCHER := 0b00000001

Main := |_, _|
/*Let's create a fellow archer with an armor, minion, and arrow*/
archer := 0b00111001

Assert archer and HAS_ARROW
Assert archer and HAS_ARMOR
Assert archer and HAS_MINION
Assert archer and ARCHER
end

Octal Notation (0o)

Octal notation is mostly used for file permissions of POSIX-compliant systems.

 import std/testing/Assert
EXEC := 0o1
WRITE := 0o2
READ := 0o4
RWX := 0o7
RE := 0o5

Main := |_, _|
perm := 0o755

user := (perm bsh 16) and RWX
group := ((perm bsh 8) and 0xFF) and RE
other := (perm and 0xFF) and RE

Assert user = RWX
Assert group = RE
Assert other = RE
end

Hexadecimal Notation (0x)

Hexadecimal notation is mostly used for reading/writing raw data as bytes.

 import std/testing/Assert
 import std/io/Read
MAGIC := [0x7f, 0x45, 0x4C, 0x46]

Main := |_, _|
read_bytes := Read("my_elf")

Assert read_bytes[..4] = MAGIC
end

Integer notation (_iN or _uN)

Integer notation is used for disambiguating a written compile-time integer to the requested type. N stands for the bit size of the integer.

 import std/testing/Assert
Main := |_, _|
number := 12.34_i16
number_big := 12.34_ibig

Assert number is i16
Assert number_big is ibig
end

Float notation (_fN)

Float notation is used for disambiguating a written compile-time float to the requested type. N stands for the bit size of the float.

 import std/testing/Assert
Main := |_, _|
number := 12.34_f16
number_big := 12.34_fbig

Assert number is f16
Assert number_big is fbig
end

Thousands Separator (_)

Thousands Separator is used to assist the readability of big integers.

 import std/testing/Assert
Main := |_, _|
Assert 100000000 = 100_000_000
end

Variables and Scopes

Variables are named pointers that point to data in the call stack or the heap.

Scopes are boundaries defined by functions (including anonymous ones), enums, unions, structs, or classes.

Vega takes a simple yet robust approach to both variables and scopes, ensuring behavioral correctness and type safety.

Variables

Vega variables are immutable and non-nullable by default, meaning their value cannot be changed or set to nil without explicit declaration.

Declaration

Declared using := (inferred form) or T := (typed form).

The type is assigned as T if it is given; otherwise, type inference will attempt to infer the type. If inference fails, any will be assigned.

integer := 4
floating := 3.14
message := "Hello from Vega!"

Nullable Variables

Declared using maybe.

If the inferred form is used and type inference fails, maybe any will be assigned.

maybe optional_int := 4
maybe optional_float float := 3.14
maybe optional_any := nil /* ?any */

Mutable Variables

Declared using mut.

Mutations are performed with <-.

If the inferred form is used and type inference fails, mut any will be assigned.

mut mutable_int := 4
mut mutable_float float := 3.14
mut maybe mutable_any := nil

mutable_int <- 5
mutable_float <- 4.14
mutable_any <- "Hi!"

The mut modifier must be written before the maybe

The reason lies on the lexical logic:

mut maybe x := 1 means

x is a mutable integer that may not have a value.

while maybe mut x := 1 means

x is maybe a mutable integer.

Scopes

Control Flow

Conditional Branching

Looping

Functions

Functions

Call Dispatching

Error Handling

Exceptions

Handling the Exceptions

Polymorphism

Any Type

Generics

Reflection

Function Overloading

Behaviors

Memory Management

Garbage Collection

Referencing

Multithreading and Concurrency

Actors

Scheduler

Mutable Variables Across Actors

FFI

External Functions

Raw Pointers

Scheduling External Functions

Standard Library

str

math

num

behaviors

collections

env

os

io

net

testing

ffi

Compilation and Execution

Compilation Pipeline

Ahead-Of-Time Compilation

Just-In-Time Compilation

Compiling As A Library

The Orbit Package Manager

Orb

Orb.vg

Orbits.io

Importing Orbs