Example Programs

These are taken directly from the (work-in-progress) stdlib.

std.options

## std.options: Optional types.
## This module is imported by default.

use std.format

## The `Option` type.
## A type that represents either the presence or absence of a value.
pub type Option[T] = union
  Some(T)
  None

## Syntactic sugar for optional type declarations.
pub macro ?(T: type) =
  quote Option[`T`]

## Directly accesses the inner value. Throws an exception if None.
pub func ![T](self: T?): T =
  if self of Some(x) then x
  else raise "empty"

## Indirect access. Propagates `None`.
pub macro ?[T](self: Option[T]) =
  quote
    match `self`
    of Some(x) then x
    of None then return None

## Checks if a type is present within an `Option` type.
pub func is_some[T](self: T?): bool =
  self of Some(_)
## Checks if a type is not present within an `Option` type.
pub func is_none[T](self: T?): bool =
  self of None

## Converts an `Option[T]` to a `Result[T, E]` given a user-provided error.
pub func err[T, E](self: T?, error: E): Result[T, E] =
  if self of Some(x) then
    Okay(x)
  else
    Error(error)

## Applies a function to `T`, if it exists.
pub func map[T, U](self: T?, fn: T -> U): U? =
  if self of Some(x) then
    Some(fn(x))
  else
    None
## Converts `T` to a `None`, if `fn` returns false and it exists.
pub func filter[T](self: T?, fn: T -> bool): T? =
  if self of Some(x) and fn(x) then
    Some(x)
  else
    None

## Applies a function to T, if it exists. Equivalent to `self.map(fn).flatten`.
pub func flatmap[T, U](self: T?, fn: T -> U?): U? =
  if self of Some(x) then
    fn(x)
  else
    None
## Converts from Option[Option[T]] to Option[T].
pub func flatten[T](self: T??): T? =
  if self of Some(Some(x)) then
    Some(x)
  else
    None

## Returns the inner value or a default.
pub func get_or[T](self: T?, default: T): T =
  if self of Some(x) then x
  else default

## Overloads the `==` operation for use on Options.
pub func ==[T](a, b: T?): bool =
  if (a, b) of (Some(x), Some(y)) then
    x == y
  else
    false

## Overloads the `str()` function for use on Options.
pub func str[T: Display](self: T?): str =
  if self of Some(x) then
    "Some({})".fmt(x.str)
  else
    "None"

# references:
# https://nim-lang.github.io/Nim/options.html
# https://doc.rust-lang.org/std/option/enum.Option.html

std.results

## std.results: Result types.
## This module is imported by default.

use std.[options, format]

## The Result type. Represents either success or failure.
pub type Result[T, E] = union
  Okay(T)
  Error(E)

## The Err class. Useful for dynamically dispatching errors.
pub type Err = class
  str(Self): str
  dbg(Self): str

## A `Result` type that uses dynamically dispatched errors.
## The `Error` may be any type implementing `Err`.
pub type Result[T] = Result[T, ref Err]
## A `Result` type that only checks for success.
## Does not contain a value.
# pub type Success[E] = Result[void, E]
## A `Result` type that only checks for success.
## Does not contain a value. Dynamically dispatched.
# pub type Success = Result[void]

## Syntactic sugar for dynamic result type declarations.
pub macro !(T: type) =
  quote Result[`T`]

## Indirect access. Propagates `Error`.
pub macro ?[T, E](self: Result[T, E]) =
  quote
    match `self`
    of Okay(x) then x
    of Error(e) then return Error(e)

## Checks if a `Result` type was successful.
pub func is_ok[T, E](self: Result[T, E]): bool =
  self of Okay(_)
## Checks if a `Result` type was not successful.
pub func is_err[T, E](self: Result[T, E]): bool =
  self of Error(_)

## Converts from a `Result[T, E]` to an `Option[T]`.
pub func ok[T, E](self: Result[T, E]): T? =
  if self of Okay(x) then
    Some(x)
  else
    None
## Converts from a `Result[T, E]` to an `Option[E]`.
pub func err[T, E](self: Result[T, E]): E? =
  if self of Error(x) then
    Some(x)
  else
    None

## Applies a function to `T`, if self is `Okay`.
pub func map[T, E, U](self: Result[T, E], fn: T -> U): Result[U, E] =
  match self
  of Okay(x) then
    Okay(fn(x))
  of Error(e) then
    Error(e)
## Applies a function to `E`, if self is `Error`.
pub func map_err[T, E, F](self: Result[T, E], fn: E -> F): Result[T, F] =
  match self
  of Error(e) then
    Error(fn(e))
  of Okay(x) then
    Okay(x)

## Applies a function to `T`, if it exists. Equivalent to `self.map(fn).flatten`.
pub func flatmap[T, E, U](self: Result[T, E], fn: T -> Result[U, E]): Result[U, E] =
  match self
  of Okay(x) then
    fn(x)
  of Error(e) then
    Error(e)
## Converts from a `Result[Result[T, E], E]` to a `Result[T, E]`.
pub func flatten[T, E](self: Result[Result[T, E], E]): Result[T, E] =
  match self
  of Okay(Okay(x)) then
    Okay(x)
  of Okay(Error(e)), Error(e) then
    Error(e)

## Transposes a `Result[Option[T], E]` to an `Option[Result[T, E]]`.
pub func transpose[T, E](self: Result[T?, E]): Result[T, E]? =
  match self
  of Okay(Some(x)) then
    Some(Okay(x))
  of Okay(None), Error(_) then
    None
## Transposes an `Option[Result[T, E]]` to a `Result[Option[T], E]`. Takes a default error.
pub func transpose[T, E](self: Result[T, E]?, error: E): Result[T?, E] =
  match self
  of Some(Okay(x)) then Okay(Some(x))
  of Some(Error(e)) then Error(e)
  of None then Error(error)

## Returns the inner value or a default.
pub func get_or[T, E](self: Result[T, E], default: T): T =
  if self of Okay(x) then x
  else default

## Directly accesses the inner value. Throws an exception if `Error`.
pub func ![T, E](self: Result[T, E]): T =
  match self
  of Okay(x) then x
  of Error(e) then raise e
## Directly accesses the inner error. Throws an exception of type T if `Okay`.
pub func get_err[T, E](self: Result[T, E]): E =
  match self
  of Error(e) then e
  of Okay(x) then raise x

## Overloads the `==` operation for use on Results.
pub func ==[T, E, F](a: Result[T, E], b: Result[T, F]): bool =
  if (a, b) of (Okay(x), Okay(y)) then
    x == y
  else
    false

## Overloads the `str()` function for use on Results.
pub func str[T: Display, E: Display](self: Result[T, E]): str =
  match self
  of Some(x) then
    "Okay({})".fmt(x.str)
  of Error(e) then
    "Error({})".fmt(e.str)

# references:
# https://doc.rust-lang.org/std/result/enum.Result.html
# https://github.com/arnetheduck/nim-results
# https://github.com/codex-storage/questionable

std.format

## std.format: Niceties around printing and debugging.
## This module is imported by default.

## The Display class. Any type implementing `str` is printable.
## Any type that is Display must necessarily also implement Debug.
pub type Display = class
  str(Self): str
  dbg(Self): str

## The Debug class. Broadly implemented for every type with compiler magic.
## Types can (and should) override the generic implementations.
pub type Debug = class
  dbg(Self): str

## Prints all of its arguments to the command line.
pub func print(params: varargs[Display]) =
  stdout.write(params.map(x => x.str).join(" "), "\n")

## Prints all of its arguments to the command line, in Debug form.
##
## Note: this function is special! It does not count as a side effect.
## This breaks effect tracking, of course: but `dbg` is for debugging.
## It will produce a warning in code compiled for release.
@[pure]
pub func dbg(params: varargs[Debug]) =
  stdout.write(params.map(x => x.dbg).join(" "), "\n")

## A dummy implementation of the Display class for strings.
pub func str(self: str): str = self
## An implementation of the Debug class for strings.
pub func dbg(self: str): str = "\"" & self & "\""

## An implementation of the Debug class for all structs.
## Uses the special `struct` typeclass.
pub func dbg[T: Debug](self: struct[T]): str =
  "{{}}".fmt(self.fields.map((key, val) => key & ":" & val.dbg))

## An implementation of the Debug class for all tuples.
## Uses the special `tuple` typeclass.
pub func dbg[T: Debug](self: tuple[T]): str =
  "({})".fmt(self.fields.map((key, val) =>
    key.map(x => x & ":").get_or("") & val.dbg).join(", "))

## An implementation of the Debug class for all arrays and lists.
pub func dbg[T: Debug](self: Iter[T]): str =
  "[{}]".fmt(self.map(x => x.dbg).join(", "))

## The fmt macro. Builds a formatted string from its arguments.
pub macro fmt(self: const str, args: varargs[Display]): str =
  let parts = self.split("{}")
  if parts.len != args.len + 1 then
    macro_error("wrong number of arguments")
  use std.ast
  var res = parts.get(0)!
  for i, arg in args do
    res &= quote(`parts` & str(`arg`) &) # fixme
  res &= parts.last()!
  res

std.debug

## std.debug: Useful functions for debugging.
## This module is imported by default.

## The `assert` macro checks that a provided assertation is true,
## and panics and dumps information if it is not.
## Asserts remain in release builds. If not desired, see `dbg_assert`
pub macro assert(cond: bool) =
  quote
    if not `cond` then
      panic "assertation failed!\n {}".fmt(dbg(`cond`))

## The `dbg_assert` function provides an assert that is compiled out in release builds.
## This is useful for debugging performance-critical code.
pub macro dbg_assert(cond: bool) =
  quote
    when debug then # fixme: where is this constant coming from?
      assert `cond`

## The `discard` function consumes an object of any type.
## Useful for throwing away the result of a computation.
pub func discard[T](self: T) =
  return

## The `panic` function prints a message to `stderr` and quits.
pub func panic(message: str): never =
  stderr.write(message, "\n")
  std.os.exit(1)

## The special ... syntax is used to mark unimplemented parts of code.
## Such code will compile, but panic upon being called at runtime.
## It is usable almost anywhere, including in type declarations, thanks to compiler magic.
@[magic]
pub func ...: never =
  panic("unimplemented")

std.lists

## std.lists: Dynamic arrays.
## This module is imported by default.

## The fundamental list type. Heap-allocated.
## Equivalent to Vec<T> in other languages.
@[opaque] # opaque on a struct tells us raw field access breaks invariants.
pub type list[T] = struct
  data: ptr T
  capacity: uint
  length: uint

## A transparent, common alias for a list of bytes.
pub type bytes = list[byte]

## Initialize and return an empty list with inner type T.
pub func init[T]: list[T] =
  { data = nil, capacity = 0, length = 0 } # fixme: nil!!!!!

## Gets the length of a list.
@[inline] # idk what to do with attributes
pub func len[T](self: lent list[T]): uint =
  self.length

pub func empty[T](self: lent list[T]): bool =
  self.length == 0

## Gets the internal capacity of a list.
func cap[T](self: lent list[T]): uint =
  self.capacity

## Expands the capacity of a list.
@[safe]
func grow[T](self: mut list[T]) =
  self.capacity = max(self.length + 1, self.capacity * 2)
  self.data = self.data.realloc(self.capacity * sizeof(T))

## Pushes a new element to the end of a list.
@[safe]
pub func push[T](self: mut list[T], val: T) =
  if self.capacity == self.length then self.grow()
  self.data.set(val, offset = self.length)
  self.length += 1

## Takes ownership of and pushes all the values of a list into another list.
pub func push[T](self: mut list[T], values: list[T]) =
  for val in values do
    self.push(val)

## Removes & returns an element from the end of a list, if it exists.
@[safe]
pub func pop[T](self: mut list[T]): T? =
  if self.length == 0 then
    None
  else
    self.length -= 1
    Some(self.data.get(offset = self.length))

## Returns a reference to an element of a list, if in range.
@[safe]
pub func get[T](self: lent list[T], i: uint): lent T? =
  if i > self.length then
    None
  else # fixme: interior mutability
    Some(lent self.data.get(offset = i))
## Returns a mutable reference to an element of a list, if in range.
@[safe]
pub func get[T](self: mut list[T], i: uint): mut T? =
  if i > self.length then
    None
  else # fixme: interior mutability
    Some(mut self.data.get(offset = i))

## Sets the element of a list to a value.
@[safe]
pub func set[T](self: mut list[T], i: uint, val: T) =
  assert i <= self.length, "index out of bounds"
  Okay(self.data.set(offset = i, val))

## Inserts a value at a location and shifts elements of the list accordingly.
@[safe]
pub func insert[T](self: mut list[T], i: uint, val: T) =
  assert i <= self.length, "index out of bounds"
  if self.capacity == self.length then self.grow()
  self.data.offset(i).copy(self.data.offset(i + 1), self.length - i)
  self.data.set(i, val)
  self.length += 1
## Inserts a list of values at a location and shifts elements of the list accordingly.
pub func insert[T](self: mut list[T], i: uint, vals: list[T]) =
  for val in vals.rev: # inserting backwards avoids counting
    self.insert(val, i)

## Removes a value at a location and shifts elements of the list accordingly.
@[safe]
pub func remove[T](self: mut list[T], i: uint): T? =
  if index < self.length then None
  else
    self.length -= 1
    let res = self.data.get(i)
    self.data.offset(i + 1).copy(self.data.offset(i), self.length - i)
    res

## Gets the last element of a list, if it exists.
pub func last[T](self: lent list[T]): lent T? =
  self.get(self.len - 1)
## Gets the last element of a list mutably, if it exists.
pub func last[T](self: mut list[T]): mut T? =
  self.get(self.len - 1)

# reference: https://doc.rust-lang.org/nomicon/vec/vec.html

std.strings

## std.strings: The standard implementation of strings.
## This module is imported by default.

## A primitive string type.
##
## We do not want methods defined on `list[byte]` to carry over,
## so we define `str` as a newtype.
@[opaque]
pub type str = struct
  data: list[byte]

## Initialize and return an empty string.
pub func init: str = { data = [] }

## Gets the length of a string.
## This is an O(n) operation, due to UTF-8 encoding.
pub func len(self: lent str): uint =
  var res: uint
  for _ in self do
    res += 1
  res

## Pushes a character to the end of a mutable string.
pub func push(self: mut str, val: char) =
  self.data.push(val.byte) # todo: obsolete by from/to conversion??

## Pushes an owned string to the end of a mutable string.
pub func push(self: mut str, val: str) =
  self.data.push(val.bytes) # todo: obsolete by from/to conversion??

## Removes and returns the last character of a string, if it exists.
##
## SAFETY: We return early upon an empty string.
## And decrement by one char for a non-empty string.
@[safe]
pub func pop(self: mut str): char? =
  let char = self.chars.rev.next?
  self.data.set_len(self.len - char.len) # this is normally unsafe.
  Some(char)

## Returns the character at the provided index, if it exists.
pub func get(self: str, i: uint): char? =
  ...

## Sets the character at the provided index, if it exists.
## As strings are packed, this may call str.grow and reallocate.
## oh fuck we have to insert + remove anyway
pub func set(self: mut str, i: uint, val: char) =
  ...

## Inserts a character at an arbitrary position within a string.
## Panics on failure. (todo: can we do better?)
pub func insert(self: mut str, i: uint, val: char) =
  ...

## Removes and returns a character at an arbitrary position within a string.
## Panics on failure. (todo: can we do better?)
pub func remove(self: mut str, i: uint): char? =
  ...

## Syntactic sugar for string appending.
pub func &=(a: mut str, b: str) =
  a.push(b)

## The concatenation operator. Consumes two strings.
pub func &(a: str, b: str): str =
  a.push(b)
  a

## Conversion from a string to a list of bytes. Zero-cost.
pub func to(self: str): list[byte] = self.data
## Conversion from a str to a list[char]. Reallocates.
pub func to(self: str): list[char] =
  var res: list[char]
  for char in self do res.push(char)
  res
## Conversion from a char to an array of bytes. Zero-cost.
@[safe] # possibly unsafe?? depends on repr of arrays
pub func to(self: char): array[byte, 4] =
  self.cast[array[byte, 4]]

# reference: https://doc.rust-lang.org/std/string/struct.String.html

std.compare

## std.compare: Classes for comparable types.

## The Eq class. For types with some notion of equivalence.
pub type Eq = class
  ==(Self, Self): bool

## A blanket implementation of a corresponding not-equal function.
pub !=[T: Eq](a: T, b: T): bool =
  not(a == b)

## The Compare class. For a type comparable with itself.
pub type Compare = class
  <(a: Self, b: Self): bool

## A blanket implementation of a corresponding greater-than function.
## Note to self: do NOT inline!
pub func >[T: Compare](a: T, b: T): bool =
  b < a

## The Ord class. For types with some notion of equivalence and comparision.
##
## Note: This is *not* a mathematical notion of an order!
## No invariants on `<` nor `==` are guaranteed to hold, as classes
## are implicitly implementable.
pub type Ord = class
  <(a: Self, b: Self): bool
  ==(a: Self, b: Self): bool

## A blanket implementation of a corresponding less-than-or-equal function.
pub func <=[T: Ord](a: T, b: T): bool =
  a < b or a == b

## A blanket implementation of a corresponding greater-than-or-equal function.
pub func >=[T: Ord](a: T, b: T): bool =
  a > b or a == b

# reference: https://doc.rust-lang.org/std/cmp

std.convert

## std.convert: Classes for type coersion and conversion.
## This module is imported by default.

## The Coerce class is used for type conversion that will not fail.
## Its associated methods, `from` and `into`, are used internally
## by the compiler for implicit type conversion (coersion).
pub type Coerce[T] = class
  to(Self): T
  # from(T): Self

## The `from` function is automatically implemented for all types that
## implement `to`: that is, all types T that are convertable to U.
pub func from[T: Coerce[U], U](self: U): T =
  to(self)

## The Convert class is used for type conversion that may fail.
# We'll see what this breaks.
pub type Convert[T, E] = class
  to(Self): Result[T, E]

std.ranges

## std.ranges: Ranges of integers and other things. For iteration.
## This module is imported by default.

type Range[T] = struct
  start: T
  end: T

type RangeIncl[T] = struct
  start: T
  end: T
  done: bool

## Exclusive ranges. Useful for iteration.
## Includes `from`, does not include `to`.
pub func ..(from: int, to: int): Range[int] = { from, to }

## Inclusive ranges. Useful for ranges.
## Includes `from` and `to`.
pub func ..=(from: int, to: int): RangeIncl[int] = { from, to, done = false }

# todo: implement for all types that can increment or smth idk
pub func next[T: int](self: mut Range[T]): T? =
  if self.start < self.end then
    self.start += 1
    Some(self.start - 1)
  else
    None

# todo: We don't need a mutable Range here to peek.
# How does this interact with classes?
pub func peek[T: int](self: mut Range[T]): T? =
  self.peek_nth(0)

pub func peek_nth[T: int](self: mut Range[T], i: uint): T? =
  let res = self.start + i
  if res < self.end then
    Some(res)
  else
    None

pub func next[T: int](self: mut RangeIncl[T]): T? =
  if self.done then
    None
  elif self.start < self.end then
    let res = self.start
    self.start += 1
    Some(res)
  elif self.start == self.end then
    self.done = true
    Some(self.start)
  else
    self.done = true
    None

pub func peek[T: int](self: mut RangeIncl[T]): T? =
  self.peek_nth(0)

pub func peek_nth[T: int](self: mut RangeIncl[T], i: uint): T? =
  let res = self.start + i
  if res <= self.end
  then Some(res)
  else None

std.ast

## std.ast: Exposes the AST for building and operating on with macros.

## The `Expr` type represents the abstract syntax tree of Puck itself.
## It notably lacks type information. It is also not necessarily syntactically
## correct-by-construction: Cond, Try, and Match expressions must have at least
## one branch in their branches (yet this is not expressible here).
pub type Expr = union
  # Terms
  Ident(str)
  Number(int)
  Float(float)
  Char(char)
  String(str)
  Struct(list[(field: str, value: Expr)]) # {...}
  Tuple(list[(field: str?, value: Expr)]) # (...)
  List(list[Expr]) # [...]
  # Bindings
  Let(id: Pattern, kind: Type?, value: ref Expr)
  Var(id: Pattern, kind: Type?, value: ref[Expr]?)
  Constant(public: bool, id: Pattern, kind: Type?, value: ref Expr)
  FuncDecl(
    public: bool,
    id: str,
    generics: list[(id: str, kind: Type?)],
    params: list[(id: str, kind: Type)],
    kind: Type?,
    body: list[Expr])
  MacroDecl(
    public: bool,
    id: str,
    generics: list[(id: str, kind: Type?)],
    params: list[(id: str, kind: Type?)],
    kind: Type?,
    body: list[Expr])
  TypeDecl(
    public: bool,
    id: str,
    generics: list[str],
    body: Type)
  Module(
    public: bool,
    id: str,
    generics: list[str], # always empty for now
    body: list[Expr])
  Use(modules: list[(path: str, alias: str?)])
  # Control Flow
  Call(id: str, params: list[Expr])
  Cond(
    branches: list[(cond: Expr, body: list[Expr])],
    else_body: list[Expr])
  Try(
    try_body: list[Expr],
    catches: list[(exceptions: list[str], body: list[Expr])],
    finally_body: list[Expr]) # todo: throw this out
  Match(
    item: ref Expr,
    branches: list[(pattern: Pattern, guard: Expr?, body: list[Expr])])
  Block(id: str?, body: list[Expr])
  Static(body: list[Expr])
  For(binding: Pattern, range: ref Expr, body: list[Expr])
  While(cond: ref Expr, body: list[Expr])
  Loop(body: list[Expr])
  Attribute(on: ref Expr)
  Quote(body: ref Expr)
  Unquote(body: ref Expr)

pub type Type = ref union
  Never
  Int(size: uint)
  Dec(size: uint)
  Float(size: uint)
  Func(from: list[Type], to: Type)
  Struct(list[(id: str, kind: Type)])
  Tuple(list[(id: str?, kind: Type)])
  Union(list[(id: str, kind: Type)])
  Class(list[(id: str, from: list[Type], to: Type?)])
  Array(size: uint, kind: Type)
  List(Type)
  Slice(Type) # todo: plus ownership
  Alias(str) # todo: params?? huh?
  Const(Type)
  Lent(Type)
  Mut(Type)
  Ref(Type)
  Refc(Type)
  Ptr(Type)

pub type Pattern = union
  Ident(str)
  Number(int), Float(float), Char(char), String(str)
  Struct(name: str, params: list[Pattern])
  Tuple(list[Pattern])
  List(list[Pattern])

@[magic]
pub func quote(body): Expr