C-based language

Declarations

There are 6 declaration types: namespace, struct, fn, trait, static and compile.
Top-level declarations in files are written to implicit global namespace
Declarations are independent, order does not matter

Namespace

Encapsulates other declarations inside its own namespace

namespace name { ... }

  • name has to be unique inside parent namespace

Structure

Data structure that can hold any amount of member variables

Every structure is also its own namespace and can hold other declarations

struct(gen: type, ...) annotation name {
  var alias varname: type;
  ...
}

  • annotation can be blank, compile or runtime
    • More about context in the Context section
  • name has to be unique inside parent namespace
  • varname has to be unique from other member variables
  • type is any expression returning type value
  • (gen: type, ...) are generic arguments
    • Can be ommitted if structure is not generic
    • More about generics in the Generics section
  • alias optional, members of the variable will be accessible by dot operator as if they were in the structure
    • Only the first variable with the name will be visible, including member types
Examples:

struct complex {
  var r: i32;
  var i: i32;
}

struct(A: type, B: type) pair {
  var first: A;
  var section: B;
}

Structures have no proper member functions but if a function that is in a structure have first argument reference to the structure it can be called as structure.function();

Example:

struct person {
  var name: []u8;

  fn greet: (this: &person) {
    std::cout().slice("Hello ").slice(this.name);
  }
}

fn say_hello: () {
  make me: person;
  person::greet(me); // both versions are correct
  me.greet(); // has to be always called immediately

  // only this version can be used to point to the function
  let greet_func = person::greet;
}

Implementation

Implementation is list of function required by a trait.

impl name {
  fn function: (this: &self) {
    ...
  }
}

  • name is expression returning trait type value
  • varname has to be unique from other member variables
  • type is any expression returning type value
  • fn function: (this: &self) follows function declaration rules
    • First argument has to be reference to the structure implementing the trait
    • Rest of the arguments have to match arguments from the corresponding function from the trait declaration
    • Every function from the trait has to be implemented and implementation cannot have any other functions
Example:

struct person {
  var name: []u8;

  impl HasName {
    fn greet: (this: &person) {
      std::cout().slice("Hello ").slice(this.name);
    }

    fn get_name: (this: &person) []u8 {
      return this.name;
    }
  }
}

Function

fn(gen: type, ...) annotation name: (arg: type, ...) type[return] {
  ...
}

  • annotation can be blank, compile or runtime and native or stdcall
    • More about context in the Context section
    • native function is external and has no body, arguments are not named, with architectures native calling convention
      • cdecl on any x86
      • x64 windows
    • stdcall function is external and has no body, arguments are not named, with stdcall calling convention where stdcall applies
      • stdcall on windows x86, native everywhere else
  • name has to be unique inside parent namespace
  • type is any expression returning type value
    • type[return] is return type
    • Blank if function returns no value
  • (arg: type, ...) are function arguments
  • (gen: type, ...) are generic arguments
    • Can be ommitted if function is not generic
    • More about generics in the Generics section
Examples:

fn say_hello: () {
  std::cout().slice("Hello, world!");
}

fn the_answer: (q: question) u64 {
  return 42u;
}

fn(T: type) generic_answer: (q: question) T {
  return cast(T) 42u;
}

fn native runtime external_add: (u64, u64) u64;

Trait

Trait is a interface or a class for structures. Structure can implement any number of traits

trait(gen: type, ...) annotation name {
  fn decl: (type, ...) type;
  ...
}

  • annotation can be blank, compile or runtime
    • More about context in the Context section
    • If trait is compile/runtime, its function declarations will be aslo compile/runtime
  • name has to be unique inside parent namespace
  • decl has to be unique inside trait
  • type is any expression returning type value
  • (gen: type, ...) are generic arguments
    • Can be ommitted if trait is not generic
    • More about generics in the Generics section
  • fn decl: (type, ...) type follows external function declaration rules (no body, nameless arguments) but cannot be native or stdcall
    • More about function declaration in the Function section
Examples:

trait HasName {
  fn greet: ();
  fn get_name: () []u8;
}

Static

Static variables are global storage accessible from any function

static annotation name: type;

static annotation name = value;

  • annotation can be blank, compile or runtime
    • More about context in the Context section
  • name has to be unique inside parent namespace
  • type is any expression returning type value
  • value is any expression. Type of the static will be type of the expression result
Examples:

static Year = 2020;

static Heat: f64;

Compile

Compile declaration is a code block used as a build script

Compile blocks will be executed from top to bottom inside a compile context

compile { ... }

Examples:

compile {
  compiler::require("std.crs");
}

Context

Code can be compiled in three possible contexts: normal, compile-only or runtime-only.
Code compiled in compile context can use only declarations and types marked as compile-only, the same goes for runtime-only.
Code compiled in normal context can use declarations and types with normal context, and compile-only and runtime-only can be used inside blocks with specified context (TODO).

Do not confuse compile/runtime keyword in declarations and compile/runtime keyword in functions.
More about this in Inline section.

Example: if function declaration has compile annotation, it means that it can be only called during compilation. For structure it means that it can be only used inside functions that can be used during compilation, etc.

Structures without context specifier will remain normal until specific context member is added. The same goes for function and their arguments and return types and also for traits with their function declarations

Types

Types are compiler generated structures. During compile time, type values share their type type, during runtime type type cannot be used because it is marked compile.

Every structure and Trait generates its own type. Templates generate special type marked generic. This type can be generated with request. This will create Structure/Trait instance with its own type.

Operations

  • &type will generate reference to type
  • [N] type will generate array with size N of type
  • [] type will generate slice of type
  • type(...) will create instance of type
    • Outside inline evaluation the type has to be casted to genric type(...), for example type(u32, type) which means the type is generic and has two generic arguments u32 and type. During inline evaluation this cast is done automatically.

Examples:

static Data: std::pair(&u64, []u8);
// std::pair is generic type(type,type)
// (&u64,[]u8) generates instance for generic std::pair
// &u64 is reference to u64
// []u8 is memory slice with u8 types

static Array: std::get_array_type() (type, 10);
// std::get_array_type is a function returning generic type(type, size)
// (type, 10) generates std::array instance with 10 types

Primitive types

  • void 0-bit type (function with no return type default to void)
  • bool unsigned 8bit boolean value
  • u8 unsigned 8bit integer
  • u16 unsigned 16bit integer
  • u32 unsigned 32bit integer
  • u64 unsigned 64bit integer
  • i8 signed 8bit integer
  • i16 signed 16bit integer
  • i32 signed 32bit integer
  • i64 signed 64bit integer
  • f32 32bit floating point number
  • f64 64bit floating point number
  • ptr unspecified pointer (predefined alias for &void)
  • size pointer-sized unsigned integer (32bit/64bit)
  • type pointer to type storage