An Overview of qdbp

(*

  Here is a multiline comment

  (* They can nest :) *)

*)

; And here is a single line comment


(* Variables, ints, and strings *)

three := 3

hello_world := "Hello, world!"


(* We can bundle data together using prototype objects

   A prototype is a collection of labeled methods *)

prototype := {

  Method1[3] (* A method named `Method1` that returns `3` *)

  (* `Method2` takes an argument and returns it *)

  Method2[arg1 | arg1]

  (* All methods take in an implicit `this` argument that is always set to the value

     of the prototype it is in. Method3's return is equivalent to `prototype`*)

  Method3[this]

}

empty_prototype := {}


(* We can invoke methods within prototypes. Note the period at the end *)

three := prototype Method1.

(* Methods with arguments must have the argument names specified *)

four := prototype Method2 arg1: 4.

four := prototype Method3. Method2 arg1: 4.


(* qdbp has no concept of functions in the traditional sense. However,

   we can use a prototype with a single label to emulate a function. By

   convention, we use the label `!` *)

identity_fn := {![x| x]}

(* From now on, we will refer to any prototype with the label `!` as a function *)

(* We can call the function by invoking the `!` method *)

five := identity_fn! x: 5.


(* Methods within a prototype can be overloaded by parameter names *)

print_args := {

  ![arg1 | arg1 Print.]

  ![arg1 arg2 | 

    _ := arg1 Print.

    arg2 Print.

  ]

}

(* qdbp has no sequence expression. To evaluate multiple expressions, we just assign

   them successively to variables *)

_ := print_args! arg1: "Hello World".

_ := print_args! arg1: "Hello " arg2: "World".


(* qdbp has no type annotations. Instead, it infers types.

   For example, if `arg1` to `print_args` doesn't have a `Print` method, the 

   program will refuse to compile. Therefore, `print_args! arg1: {}.` will fail. *)


_ := print_args! arg1: 3. (* `print_args` is generic *)


(* To be specific, variable names must start with a lowercase letter.

   Method names within a prototype must start with an uppercase letter or a symbol.

   We can use method names with symbols to emulate operators. For example, int

   objects have a `+` method with a single argument, `that` *)

five := 3 + that: 2.


(* Here is some syntax sugar that makes common prototype patterns less verbose *)

(* To make "functions" easier to define, this: *)

my_fn := { ![x| x] }

(* is equivalent to this: *)

my_fn := {x | x} 

(* In other words, to make an object with a single method `!`, we can omit the

   `[`, `]`, and `!` *)

(* Also, if we omit the name of the first argument, it is automatically assumed

   to be `that` *)

five := 3 + that: 2. (* is equivalent to *) five := 3 + 2.

(* Periods are optional when invocation is surrounded by parentheses *)

three := identity_fn! x: 3. (* is the same as *) three := (identity_fn! x: 3)


(* All values are immutable. However, you can make a copy of a prototype with 

   changes *)

small_circle := {

  Radius[3]

  Diameter[(this Radius) * 2.]

}

(* Same as `small_circle` but `Radius` now returns 6 *)

big_circle := { small_circle

  (* `Radius` needs to have the same type as the original `Radius`*)

  Radius[6]

}

(* 

  qdbp uses a memory management strategy known as

  Perceus Reference Counting that guarantees in-place

  updates under the hood for many of these copy-and-modify

  operations.

*)

twelve := big_circle Diameter.


(* Unfortunately we don't have full support for doubles yet, but stay tuned! *)

pi := 3


(* Prototypes can also be extended with new methods *)

big_circle := { big_circle

  Area[(this Radius) * (this Radius). * pi.]

}

one_hundred_eight := big_circle Area.


(* Most of the time, the compiler can disambiguate between whether to extend

   or replace. The one exception is when the original's prototype's field list

   is dependent on a parameter. For example: *)

fn := {x | 

  { x

    Method[that | that]

  }

}

(* The compiler can't disambiguate whether or not to replace the `Method` field in

   `x` or to extend `x`. In situations like this, the compiler will always expect

   that the original prototype contains the field and so will replace it. *)


(* We also can tag an object *)

object := {}

tagged_object := #TagName object

(* In qdbp, everything is right associative *)

tagged_object := #Tag 1 + 2. (* parsed as (#Tag (1 + 2.)) *)

(* An example application of tagged objects is to represent booleans *)

true := #True{}

false := #False{}


(* We can pattern match on tagged objects. For example, this is a function that

   negates a bool. Like method invocation, notice the `.` at the end *)

negate := {that |

  that

    True? [#False{}]

    False? [#True{}].

}

(* The type system will ensure that the pattern matching is exhaustive. For example,

   if we don't handle the `#False` case for a value that could be `#False`, the 

   program won't compile *)


(* Another application of tagged objects is error types. Here is a safe

   division by 0 function *)

safe_divide := {a b|

  b = 0.

    True? [#Error{}]

    False? [#Ok a / b.].

}

two := safe_divide! a: 4 b: 2.

_ := 

  two

    Ok? [that| 

      that Print.

    ]

    Error? [

      "Error" Print.

    ].

(* Like any language, we want to be able to split our programs across multiple

   files. Each qdbp program is a single expression that returns a value. 

   `@filename`'s result is the result of evaluating `filename.qdbp`. Filenames

   can only have letters, `/`, `_`, and `.` *)


empty_list := @list

intlist := empty_list Append 3. Append 2.

(* qdbp does not support cyclic imports. 

   For example, this file cannot import itself *)


(* We can also call functions in the target language. See `samples/int.qdbp` for 

   an example *)

(* The return type of the function must be encoded in the function name.

   It can either end with `_int`, `_string`, or `_bool` *)


(* To end a program abruptly, the special keyword ABORT will kill it *)

get_val := {that |

  that

    Ok? [that| that]

    Error? [ABORT.].

}

(* If you were to import this file from another file, the result would be this 

   string *)

" This concludes the language reference and tutorial "