Skip to content

Heather syntax

Info

Work in progress.

How a "hello world" looks like in Heather (main.hat):

1
main { print("hello world") }

Heather has no need for ; to separate between statements or , to separate between items. , \t, \n, ; and , are all treated as whitespaces and are ignored. You may use them if you prefer though. Because of Heather's regular syntax, statements and expressions can be organized in what fits better the programmer's or a particular project style. We expect that it can provide some freedom for people to experiment and define what works better for a particular situation, rather than enforcing that without a real reason. Comments are: // comment goes here for line comments, and /* comment body goes inside here */ for multiline comments.

1. Constants

Constants are defined inside consts.hat files. They have the following syntax:

1
const <name>:<type> = <value>

Examples

math/consts.hat:

1
2
const pi32:f32 = 3.14159265
const pi64:f64 = 3.141592653589793

io/consts.hat:

1
const localhost:str = "localhost"

Importing

You can import constants in functions kinds and other constants files.

For single imports:

1
use(const:math.pi)

For multiple imports:

1
2
3
4
use(
    const:math.e
    const:math.phi
)

For multiple imports inside a single const:

1
2
3
4
5
6
use(
    const:[
      math.{pi e phi}
      io.localhost
    ]
)

2. Types

Types are defined inside custom files located on src/hat_types/ path. There are basically two types of user types: structs and enums. A quantum type can contain classical types as its members/named values, but the opposite is not valid.

Structs

Definition: a composite data structure, holding a collection of members (or fields) that may have different data types.

Syntax

1
type <name> { <member>:<type> ... }

Examples

Defining struct type:

1
type point { x:i32 y:i32 }

Using struct type:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// assigning to variable p while declaring it
p:point =.{x=34 y=43}

// assigning to variable already declared
p2:point
p2.{x=15 y=51}

// (re)assigning to individual members
p3<mut>:point
p3.{x=143 y=331}
p3.y=341

// calling it
print(p.x)

Importing:

1
2
// single import
use(type:math.geometry.point)
1
2
3
4
5
// multiple imports:
use(
    type:math.geometry.point3d
    type:math.trigonometry.rad
)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// multiple imports, single call:
use(
    type:[
      math.{
        geometry.{point point3d} 
        trigonometry.rad
      }
      status.status_t
    ]
)

Enums

Definition: a tagged union (or enumerated) data structure that can contain a collection of named values, those being simple identifiers or structs, but only one can be in use at any one time. By convention, identifiers are all caps.

Syntax

1
type <name> { <enumerator?> <struct?> ... }

Examples:

Defining enum type:

1
2
3
4
5
6
7
8
type status_t { ON OFF }

type result_t {
    data{
        value:sample_t
    }
    NONE
}

Using enum type:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// declaring and assigning on a variable
status:status_t = status_t.ON
res:result_t = result_t.data.value=...
res2:result_t = result_t.NONE

// assigning on a declared variable
status2:status_t
status2 = status_t.ON

// calling it
print(status_t.OFF)
print(status)

3. Functions

Syntax:

1
fn <name> (<args?>) <type?> { <body?> }

Return has a special syntax sugar: :: prefixed on an expression.

Examples

Defining a type:

1
fn sum(a:i64 b:i64) i64 { ::add(a b) }
1
2
3
4
5
6
fn print-gt(a:u64 b:u64) {
    if(
      gt(a b): print(a)
      true: print(b)
    )
}

4. Meta-funcions

Definition: a function that defines the code behavior assigned to it. There are three kinds of meta-functions: option (or cases), body (or blocks), and option-body (or case-blocks). They have the types associated with them as optn_t, bdn_t and optbdn_t respectively.

Syntax:

1
metafn <name> (args) <type?> { <body> }

Examples

if meta-function (option type):

Defining:

1
metafn if(options:[opt-body_t]) ir_t { ... }

Calling:

1
if(gt(a b):a true:b)

pipe meta-function (body type):

Defining:

1
metafn pipe(args:[expr_t] body:ir_t) ir_t { ... }

Calling:

1
pipe(var) { double print }  // applies double on var and then print on double's result

match meta-function (option-body type):

Defining:

1
metafn match(arg:[expr_t] options:[opt-body_t]) ir_t { ... }

Calling:

1
2
3
4
match(status) {
  status_t.ON:print("on!")
  status_t.OFF:print("off!")
}

5. Modifiers

Definition: a function that can change the semantics and properties of its holder.

Syntax

1
modifier <name> (self <arg?>) <type> { <body> }

the <type> must be the same of self.

Examples

& (reference) modifier

1
2
3
modifier &(self) u32 { ... }  // for u32 type
modifier &(self) [u32] { ... }  // for array of u32 type
modifier &(self) status_t { ... }  // for status_t type
1
2
// setting a variable `var` of type "reference of status_t"
var:status_t<&>

6. Cast

Definition: a type of reflective function and semantics that convert one data into another type. If data is eagerly evaluated (strict), it should convert to target type calling a given function with given cast name and the appropriate argument types signature. If data is lazily evaluated (lazy), it will evoke its evaluation and then the result value to be cast into the target type. It has a syntax sugar * when called, as in: data * type, meaning "cast data into type".

Syntax

1
fn cast (data:<type> to:<type>) <to type> { <body> }

Examples

Casting strict data:

1
2
3
v1:u32 = 42
// now v1 data is cast into u64 and stored in v2
v2:u64 = v1 * u64

Casting lazy data:

1
2
3
4
5
6
@q:@bell_t =.{@s=@false @t=@false}
@sync(@q)
// evaluate lazy data from @q and convert
//   its result as hashmap with key u64 
//   and value bool, storing at res
res:hashmap<%u64 %bool> = @q * hashmap<%u64 %bool>