Quick Start

All source code needed to build your first project can be found on the GitHub repository: quark-programming/quark

You can download the source code using the latest release or by cloning the repo

$ git clone https://github.com/quark-programming/quark.git

After downloading the source code, navigate to its folder and build the project using make. You can specify the output binary name using the out variable. By default, it will create a binary named qc.

$ make build
$ make build out=my_compiler

All source code is licensed under the MIT License. See the LICENSE file for more information.

When writing Quark code, source files should use the .qk file extension. You can compile your Quark source files using the compiler like so

$ ./qc path/to/source_file.qk -o output_code.c
$ cc output_code.c -o final_executable

As of 0.2.1 the compiler requires the lib folder to be present in the imports directory. If needed, you can specify a custom imports directory using the -l flag.

$ ./qc path/to/source_file.qk -o output_code.c -l path/to/imports/dir

Hello World

Let's write out first program in Quark. Start by creating a new filed with the .qk extension. Here I'll use hello.qk.

print("Hello, World!");

The Quark compiler automatically imports the standard library, including lib::io, which contains the print() function used to output text to the console.

  • Default importing lib::std 0.2.1

Primitive Types

Quark has several built-in primitive types for representing data. Here are the default types and their C equivalents as of as seen in lib::types

Quark TypeC Equivalent
u8uint8_t
i8int8_t
u16uint16_t
i16int16_t
u32uint32_t
i32int32_t
u64uint64_t
i64int64_t
f32float
f64double
isizessize_t
usizesize_t
Quark TypeC Equivalent
charchar
icharsigned char
ucharunsigned char
Shortshort
UShortunsigned short
Intint
UIntunsigned int
Longlong
ULongunsigned long
boolbool
FileFILE
voidvoid

You can reuse any other C type by using the extern keyword. For example, to use the C long long type, you can declare it like so:

type LongLong = extern "long long";

Quark also comes with helper types like auto and int. These types conform to whatever type they are matched with. For example, this is how you can use the auto type:

i32 number = 10;
auto another_number = number;

Here, the another_number variable will be of type i32 since it is being assigned the value of the number variable. int will do the same thing, but will only match number types.

  • Moved to lib::types 0.2.1

Pointer Types

In Quark, pointer types are declared using the * symbol after a type or the & before a type. For example, to declare a pointer to an integer, you would write:

i32* ptr_to_int;

or

&i32 ptr_to_int;

Pointers can be referenced and dereferenced using the & and * operators, similar to C.

i32 number = 42;
i32* ptr_to_number = &number;
i32 dereferenced_number = *ptr_to_number;

Structures

Structures in Quark are similar to structs in C, but with added support for generics. You can define a structure using the struct keyword followed by the structure name and its fields.

struct Person {
   str name;
   u8 age;
;

You can create new instances of a structure using the following syntax:

auto john = Person {
   name: "John",
   age: 30,
};

Accessing structure fields is done in the same way as in C, and structures can be dereferenced while accessing fields if you have a pointer to a structure.

str johns_name = john.name;
Person* ptr_to_john = &john;
u8 johns_age = ptr_to_john->age;

Structures can also contain functions as both static methods and instance methods. You can define both the same way as regular functions, but instance methods must include a self parameter to reference the instance.

struct Counter {
   u32 count;

   u32 get_count(self) {
       return self.count;
   }

   Counter new() {
       return Counter { count: 0 };
   }
};

With this counter struct, we can create a new instance and call its methods like so:

Counter counter = Counter::new();
u32 current_count = counter.get_count();

Instance methods can also modify the structure's fields through the &self parameter.

struct Counter {
   ...
   void increment(&self) {
       self->count++;
   }
   ...
}

You can also define methods outside of the structure definition using the :: operator in the function name. Note that the self keyword will not work here.

u32 Counter::decrement(Counter* self) {
   self->count--;
   return self->count;
}

Arrays

In Quark, you can define stack-allocated fixed-sized arrays using the [] syntax for both types and array literals.

import lib::vector;
[i32] numbers = [1, 2, 3];

Arrays are shorthand for the Slice type in the lib::vector library.

If you need to create a dynamically-sized or heap-allocated array, you can use the Vec type

import lib::vector;

Vec<i32> numbers = Vec::from([1, 2, 3]);

Generics

Quark supports generics, allowing you to create data structures and functions that can operate on different types. You can define a generic type by using angle brackets < and > to specify type parameters.

struct Array<T> {
   T* data;
   usize size;
}

T echo<T>(T value) {
   return value;
}

The Quark compiler will generate an implementation of the generic type or function for each unique type it is used with. For example, if you create an Array<int> and an Array<char>, the compiler will generate two separate structures:

struct Array__number { int* data; size_t size; };
struct Array__char { char* data; size_t size; };

Optionals

In Quark, optionals are a way to represent values that may or may not be present. An optional type is defined using the ? symbol after a type. For example, to define an optional integer, you would write:

i32? maybe_number;

Optional values are created using the Option struct from lib::containers.

maybe_number = Option::Some(42);
maybe_number = Option::None();

Optionals can also be optionally coalesced using the ? operator when using any operator with a precedence of 1.

struct MyStruct {
  i32 value;
}

MyStruct? ptr = Option::None();
i32? value = ptr?.value;
i32*? maybe_array = Option::None();
i32? maybe_first = maybe_array?[0];

Control Statements

Quark provides standard control flow statements such as if and while similar to C. Here are some examples of how to use these control statements in Quark:

if( <condition> ) {
   // code to execute if condition is true
}

while( <condition> ) {
   // code to execute while condition is true
}

Functions

Functions in Quark are defined in a similar way to C, starting with the return type, followed by the function name and parameters. Here is an example of a simple function that returns the sum of two integers:

i32 add(i32 a, i32 b) {
   return a + b;
}

Functions can also use generics to operate on different types. Here is an example of a generic function that returns the value it receives:

T echo<T>(T value) {
   return value;
}

Notice that the return type is used before the declaration of the generic type parameter(s).

Importing Other .qk Files

Quark allows you to import other .qk source files using the import keyword. This is useful for organizing your code into separate modules and reusing code across different files.

Let's say we have a print_message() function defined in a file named other/utils.qk and we want to use it in our main file main.qk. Here's how you can do that:

import other::utils;

print_message("Hello World!");

In this example, we import the utils.qk file located in the other/ directory. After importing, we can directly use the print_message() function defined in that file.

other::utils is transformed into the relative path IMPORT_PATH/other/utils.qk when searching for the file to import.

By default the IMPORT_PATH is set to ".", but it can be specified using the -l flag when running the compiler.

$ ./qc main.qk -o output.c -l path/to/imports/dir

Using External C Code

Quark allows you to interface with external C code using the extern keyword. This is useful for leveraging existing C libraries or functions within your Quark programs.

void extern puts(char* data);
T* extern malloc<T>(usize size);
File* extern stdout = extern<File*> stdout;

None of these declarations will be compiled to C, but you can use them like regular Quark functions and variables.