# Example Programs These are taken directly from the (work-in-progress) stdlib. ## std.options ```puck ## 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 ```puck ## 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 ```puck ## 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 ```puck ## 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 ```puck ## std.lists: Dynamic arrays. ## This module is imported by default. ## The fundamental list type. Heap-allocated. ## Equivalent to Vec 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 ```puck ## 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 ```puck ## 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 ```puck ## 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 ```puck ## 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 ```puck ## 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 ```