Haste code is meant to be self-explanatory. If it looks like a function, it’s a function. If it looks like a macro call, it’s a macro call. If it looks like compile-time code, it’s compile-time code. If it looks like run-time code, it’s run-time code.
In Haste, the entry point is always the main
function, which is intended to be the first thing executed in the low-level generated code.
Any expression that starts with @
represents a macro call. For example, @println
is a macro that generates additional Haste code.
Haste uses the semicolon ;
as the statement terminator.
func main() {
@println("Hello World");
}
Haste variables must follow this syntax:
let [mut] <pattern>[: [<type>]] [= <expression>];
All variables in Haste are immutable by default. For mutability, consider using the mut
keyword after the let
keyword.
After let
and the optional mut
, Haste’s compiler expects an identifier followed by a :
and the type of the variable. Haste can infer the type of any variable if possible. Then it expects either an equal sign =
or a semicolon ;
. If it finds a semicolon, the variable is left uninitialized and can be assigned later. Otherwise, it expects an expression, followed by a semicolon.
func main() {
# Automaticaly x gets infered into int type
let x = 50;
# vvvv Some as above, This may seems useless (which it is) but auto got other uses in haste
let y: auto = 20;
# vvvvvv Explicitly tells the compiler that z is a uint64
let z: uint64 = 6541849;
# I have nothing to say here
let w := 'z';
# vvv tells the compiler that this variable may change
let mut age: uint = 18;
age++;
@prinln("{}", age);
}
There’s nothing much I can say here, those are just self-explanatory.
func main() {
if true {
@println("What's up!!");
} else if false {
@println("uhh");
} else {
@println("???");
}
# If you don't wanna write those `{ }` you could do `do`
# `do` expects only one statement and a semi colon after it
if 1 == 2
do @println("Inlined if :3");
# Infinit loop
loop {
@println("Loop!!");
stop; # Equivalent to `break` in C
}
{
let x = 0;
while x < 10 {
if x == 5 {
x++;
skip; # Equivalent to `continue` in C
}
@println("{}", x);
x++;
}
}
let xs = ["one", "two", "three", "four"];
for x in xs {
@println("{}", x);
}
}
struct Pos {
x: float,
y: float
}
# an Error is just a tagged union
error Erroryyy {
A: float,
B: string,
C: Pos
}
error FOpenError {
InvalidPath,
IsNotFile,
InvalidPermition
}
func open(path: string, mode: OpenMode): FOpenError!File;
func errored(): Erroryyy!void;
func main(): !void {
let f = try open("./testy.tast", .R) or {
let err = catch();
match err {
case .InvalidPath { @todo(); }
case .IsNotFile { @todo(); }
case .InvalidPermition { @todo(); }
}
};
let z = try errored() or {
let err = catch();
match err {
case .A |v| do @log(v);
case .B |v| do @log(v);
case .C |v| do @log(v);
}
ret (); # Local return
};
_ = f; # I did this so the compiler don't yell at you
_ = z;
}
error DivError {
ZeroDivision
}
func div(x: float, y: float): DivError!float {
if y == 0
do return DivError.ZeroDivision;
return x / y;
}
func main(): !void {
let res = try div(1, 0) or {
@println("Cannot divide by 0");
};
@println("res: {}", res);
}
import "std";
use std::io;
use std::fs;
func main(): !void {
let file = try fs::open("./names.txt") or {
let err = catch();
match err {
case .FileNotFound do @panic("The file not found");
case .AccessDenied do @panic("Access denied");
case .NotFile do @panic("Not a file");
else do @panic("Unexpected error");
}
};
}
func main() {
# Numiric types
let a: int8 = -127;
let b: uint8 = 255;
let c: int16 = -32767;
let d: uint16 = 65535;
let e: int = -2147483647: # You can do int32 too
let f: uint = 4294967295; # You can do uint32 too
let g: int64 = -9223372036854775807;
let h: uint64 = 18446744073709551615;
let i: float = 154.2548;
let j: float64 = 1588648.269899;
let k: char = 'a'; # Same as uint8
let l: rune = '🐢'; # single unicode character
let m: bool = true;
let s: isize = 0; # platform-dependent
let us: usize = 0; # platform-dependent
let mut name: string = "Hesham";
let rest: str = " Can't Fly"; # a Slice of characters
name.push(rest);
}
@derive(Debug)
error EntityError {
InvalidDirection
}
@derive(Debug)
struct Vec2 {
x: float,
y: float
}
@derive(Debug)
enum Direction {
Up,
Down,
Left,
Right,
Front,
Back
}
@derive(Debug)
enum Color: str {
Red = "red",
Blue = "blue",
Green = "green",
Purple = "purple"
}
@derive(Debug)
struct Entity {
pos: Vec2,
vel: Vec2 = { 0, 0 },
dir: Direction = .Left,
color: Color = .Red
}
impl Entity {
func __init__(pos: Vec2 = {0, 0}): Entity
= Entity { pos };
func set_direction(&mut self, dir: Direction): EntityError!void {
match dir {
case .Front, .Back do return EntityError.InvalidDirection;
else do self.dir = dir;
}
}
}
@derive(Debug)
variant EntityType { # Tagged union
Passive(Entity),
Aggressive(&EntityType, Entity),
Natural(?&EntityType, Entity)
}
func main(): !void {
let e1 = Entity {
pos = .{ 20, 20 },
vel = .{ 0, 0 },
dir = .Left,
color = .Purple
};
let e2 = Entity {
pos = .{ 0, 0 },
vel = .{ 0, 0 },
dir = Direction.Up,
color = Color.Red
};
try e2.set_direction(.Up);
let et1 = EntityType::Pasive(Entity(6.9, 80.32));
let et2 = EntityType::Aggressive(&et1, move e1);
let et3 = EntityType::Natural(&et2, move e2);
_ = et3;
}
Haste unions are just like C union except that you can’t use it except inside of a unsafe block
\let MAX_STRING_LEN = 30;
union Data {
f: float,
i: int,
string: uint8[MAX_STRING_LEN]
}
unsafe func fishy() {
@println("Data's size: {}byte", @sizeof(Data));
let flash: Data = .{ f = 10.0 };
@println("f: {}\ni: {}\nstr: {}", flash.f, flash.i, flash.string);
}
In Haste, identifiers are split into two parts: normal and special ones. At the moment, there is no difference between them except for the syntax.
- Normal identifier: A normal identifier is any identifier that is not a reserved keyword and fulfills this regular expression
(\$|_|[a-zA-Z])(\$|_|[a-zA-Z0-9])*
. - Special identifier: A special identifier is any identifier that starts with
$"
and ends with"
; anything can go in between.
In Haste, a character starts with '
and ends with '
, with one character in between.
In Haste, a string starts with "
and ends with "
, with an almost infinite amount of characters in between. Strings are simply an array of characters.
In Haste, there is a generic Number
type that is divided into two main subtypes.
- Integers: An integer is a number without a fractional or decimal component. It includes all whole numbers, both positive and negative, as well as zero. Mathematically, the set of integers is denoted as ℤ.
- Float: is a number that can represent both whole numbers and numbers with a fractional or decimal part.
In Haste their is true
and false
. yes, that’s it nothing more to say about them.
In haste, a tuple is a collection of fixed-size with different types. let x: (int, float64, string) = (6, 9, "Errmm what da sigma");
Perform mathematical calculations, and usealy returns a number.
Operator | Description | Example |
---|---|---|
+ | Addition | a + b |
- | Subtraction | a - b |
* | Division | a * b |
/ | Division | a / b |
% | Modulus | a % b |
** | Power | a ** b |
+ | Posite | +a |
- | Negation | -a |
Compare two values and returns a boolean
.
Operator | Description | Example |
---|---|---|
== | Equal to | a == b |
!= | Not Equal to | a != b |
< | Less than | a < b |
> | Greater than | a > b |
<= | Less than or equal to | a <= b |
>= | Greater than or equal to | a >= b |
is | Test the thruthness of a value | is a |
Combine or invert logical statements.
Operator | Description | Example |
---|---|---|
and | Logical AND | a and b |
or | Logical OR | a or b |
not | Logical NOT | not a |
Perform operations on binary representations of numbers.
Operator | Description | Example |
---|---|---|
& | Bitwise AND | a & b |
\vert | Bitwise OR | a \vert b |
^ | Bitwise XOR | a ^ b |
~~~ | Bitwise NOT | ~~a~ |
<< | Left shift | a << 1 |
>> | Right shift | a >> 1 |
Assign values to variables.
Operator | Description | Example |
---|---|---|
= | Assignment | a = b |
+= | Add and assign | a += b |
-= | Subtract and assign | a -= b |
*= | Multiply and assign | a *= b |
/= | Divide and assign | a /= b |
%= | Modulus and assign | a %= b |
**= | Power and assign | a **= b |
&= | Bitwise AND and assign | a &= b |
\vert= | Bitwise OR and assign | a \vert= b |
^= | Bitwise XOR and assign | a ^= b |
<<= | Left shift and assign | a <<= b |
>>= | Right shift and assign | a >>= b |
Something better and more readable that C’s ternary operator ? :
Operator | Description | Example |
---|---|---|
if then else | Conditional operator | if a > b then x else y |
Convert one data type to another (If possible)
Operator | Description | Example |
---|---|---|
as | Type casting | 1 as float |
Operator | Description | Example |
---|---|---|
& | Peek | let y = &x |
&mut | Sneak | let y = &mut x |
* | Dereferece | *y |
@derive(Debug)
variant Token {
IntLit(int),
Id(string),
StringLit(string)
}
@derive(Debug)
struct Vec2 {
x: float,
y: float
}
impl Vec2 func __init__(x: float = 0f, y: float = 0f): Vec2
= .{ 0, 0 };
@derive(Debug)
type Person = (int, string);
func main() {
{
let (age, name): Person = (17, "Hesham");
@println("Name: {}, Age: {}", name, age);
let { x, y } = Vec2(50.0, 40.0);
@println("x: {}, y: {}", x, y);
}
let tok = Token::IntLit(50);
if let Token::IntLit(v) = tok {
@println("It's an IntLit duh");
}
match tok {
case .IntLit(_) do @todo();
case .Id(_) do @todo();
case .StringLit(_) do @todo();
}
}