From b8445b011d9b2a287af0e014998c42525a7fd315 Mon Sep 17 00:00:00 2001 From: JJ Date: Fri, 29 Dec 2023 16:03:06 -0800 Subject: std: restructure, refactor std.ast --- std/ast.pk | 81 ++++++++++++++++++++++++++++ std/async.pk | 19 +++++++ std/default/format.pk | 52 ------------------ std/default/iterators.pk | 38 ------------- std/default/options.pk | 97 --------------------------------- std/default/results.pk | 136 ----------------------------------------------- std/fundamental/ast.pk | 113 --------------------------------------- std/fundamental/logs.pk | 1 - std/fundamental/tests.pk | 5 -- std/logs.pk | 1 + std/prelude/format.pk | 53 ++++++++++++++++++ std/prelude/iterators.pk | 38 +++++++++++++ std/prelude/options.pk | 101 +++++++++++++++++++++++++++++++++++ std/prelude/results.pk | 136 +++++++++++++++++++++++++++++++++++++++++++++++ std/tests.pk | 5 ++ 15 files changed, 434 insertions(+), 442 deletions(-) create mode 100644 std/ast.pk create mode 100644 std/async.pk delete mode 100644 std/default/format.pk delete mode 100644 std/default/iterators.pk delete mode 100644 std/default/options.pk delete mode 100644 std/default/results.pk delete mode 100644 std/fundamental/ast.pk delete mode 100644 std/fundamental/logs.pk delete mode 100644 std/fundamental/tests.pk create mode 100644 std/logs.pk create mode 100644 std/prelude/format.pk create mode 100644 std/prelude/iterators.pk create mode 100644 std/prelude/options.pk create mode 100644 std/prelude/results.pk create mode 100644 std/tests.pk diff --git a/std/ast.pk b/std/ast.pk new file mode 100644 index 0000000..abc66ba --- /dev/null +++ b/std/ast.pk @@ -0,0 +1,81 @@ +## std.ast: Exposes the AST for building and operating on with macros. + +pub type Expr = union + Ident(string), Number(int), Float(float), Char(chr), String(str) + Struct(list[tuple[field: str, value: Expr]]) + Tuple(list[tuple[field: string?, value: Expr]]) + List(list[Expr]) + Let(id: Pattern, kind: Type?, value: ref Expr) + Var(id: Pattern, kind: Type?, value: ref Expr?) + Const(id: Pattern, kind: Type?, value: ref Expr) + FuncDecl( # effects? + id: str, + generics: list[GenericParams], + params: list[FunctionParams], + kind: Type, + body: list[Expr] + ) + TypeDecl(id: str, generics: list[str], alias: Type) + Module(id: str, body: list[Expr]) + Use(path: str) + Pub(Expr) # can easily generate incoherent statements. still desired? + Call(id: str, params: list[Expr]) + Cond(branches: list[CondBranch], else_body: list[Expr]?) + Try( + try_body: list[Expr], + catches: list[TryBranch] + finally_body: list[Expr]? + ) + Match( + item: ref Expr, + branches: list[MatchBranch] + ) + Block(id: str?, body: list[Expr]) + ConstBlock(body: list[Expr]), + For(binding: Pattern, range: ref Expr, body: list[Expr]) + While(cond: ref Expr, body: list[Expr]) + Loop(body: list[Expr]), + +pub type CondBranch = tuple + cond: Expr + body: list[Expr] + +pub type TryBranch = tuple + exceptions: list[str] + body: list[Expr] + +pub type MatchBranch = tuple + pattern: Pattern + guard: Option[Expr] + body: list[Expr] + +pub type Type = ref union + Void, Never, + Integer, Float, String, + Func(from: Type, to: Type) # todo: multiple parameters + Struct(list[tuple[id: str, kind: Type]]) + Tuple(list[tuple[id: str?, kind: Type]]) + Union(list[tuple[id: str, kind: Type]]) + Interface( # todo: generics + funcs: list[Signature] + for_type: Type? + ) + Array(size: uint, kind: Type) + List(Type) + Slice(Type) # todo: plus ownership + Alias(str) # todo: params?? huh? + Static(Type) + Mutable(Type) + Reference(Type) + Pointer(Type) +pub type Signature = struct # todo: generics + id: str + effect: Option[str] + params: list[Type] + kind: Option[Type] +pub type Pattern = union + Ident(str) + Number(int), Float(float), Char(chr), String(str) + Struct(name: str, params: list[Pattern]) + Tuple(list[Pattern]) + List(list[Pattern]) diff --git a/std/async.pk b/std/async.pk new file mode 100644 index 0000000..7c76076 --- /dev/null +++ b/std/async.pk @@ -0,0 +1,19 @@ +## std.async: Zig-style colourless async/await. +## Future types, the async macro, and the await function. + +## A standard Future type. Opaque. +pub type Future[T] = struct + ... + +## The `async` macro transforms any function call into +## an asynchronous call returning a Future of some type. +pub macro async[T](call: T): Future[T] = + ... + +## The `await` function blocks on a Future[T] call until it returns a value. +pub func await[T](self: Future[T]): T = + ... + +examples: + std.net.connect() + print 1.async.await diff --git a/std/default/format.pk b/std/default/format.pk deleted file mode 100644 index b117632..0000000 --- a/std/default/format.pk +++ /dev/null @@ -1,52 +0,0 @@ -## std/format: Niceties around printing and debugging. -## This module is imported by default. - -## The Display interface. Any type implementing `str` is printable. -## Any type that is Display must also implement Debug. -pub type Display = interface - str(Self): str - dbg(Self): str - -## The Debug interface. Broadly implemented for every type with compiler magic. -## Types can (and should) override the generic implementations. -pub type Debug = interface - 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! does not count as a side effect -pub func dbg(params: varargs[Debug]) = - stdout.write(params.map(x => x.dbg).join(" "), "\n") - -## A dummy implementation of the Display interface for strings. -pub func str(self: str): str = self -## An implementation of the Debug interface for strings. -pub func dbg(self: str): str = "\"" & self & "\"" - -## An implementation of the Debug interface for all structs. -## Uses the special `struct` typeclass. -pub func dbg(self: struct): str = - "{" & self.fields.map(field => field.id & field.value.dbg) & "}" - -## An implementation of the Debug interface for all tuples. -## Uses the special `tuple` typeclass. -pub func dbg(self: tuple): str = - result &= "(" - for i, field in self.fields.enumerate(): - result &= field.id.map(id => id & " = ").get_or("") - if i != self.fields.len: - result &= ", " - result &= ")" - -## An implementation of the Debug interface for all arrays and lists. -pub func dbg(self: Iter[Debug]): str = - "[" & self.map(x => x.str).join(", ") & "]" - -## The fmt macro. Builds a formatted string from its arguments. -pub macro fmt(formatted: static[str], args: varargs[Display]) = - # if not formatted of String: - # macro_error("fmt must take a static string parameter!") - ... diff --git a/std/default/iterators.pk b/std/default/iterators.pk deleted file mode 100644 index f7e4972..0000000 --- a/std/default/iterators.pk +++ /dev/null @@ -1,38 +0,0 @@ -## std/iterators: The Iter interface and associated functions. -## This module is imported by default. - -## The Iter interface. Any type implementing `next()` is iterable. -pub type Iter[T] = interface - next(mut Self): Option[T] - -# todo: useful functions for an iterator -# https://doc.rust-lang.org/std/iter/trait.Iterator.html#provided-methods - -pub func advance_by[T](self: Iter[T], n: uint): Result[T, ...] = - for i in 0 .. n: - if self.next().is_none(): - return Error(...) - Okay - -pub func get[T](self: Iter[T], at: uint): Option[T] - self.advance_by(at-1).ok? - self.next() - -# todo: implement iter[T](self: ...): Iter[T] funcs -# todo: efficient functional methods - -## The Peek interface. Any type implementing `Iter`, `peek`, and `peek_nth` is peekable. -pub type Peek[T] = interface - next(mut Self): Option[T] - peek(mut Self): Option[T] - peek_nth(mut Self, int): Option[T] - -# todo: implement peek[T](self: Iter[T]): Peek[T] -# todo: implement Peekable struct -# https://github.com/LukeMathWalker/multipeek/blob/main/src/lib.rs - -## We don't want a Countable. It's not terribly useful. -# pub type Countable[T] = interface -# next(mut Self): Option[T] -# len(Self): uint -# get(Self, uint): Option[T] diff --git a/std/default/options.pk b/std/default/options.pk deleted file mode 100644 index fb696ba..0000000 --- a/std/default/options.pk +++ /dev/null @@ -1,97 +0,0 @@ -## 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 - -## Checks if a type is present within an `Option` type. -pub func is_some[T](self: Option[T]): bool = - self of Some(_) -## Checks if a type is not present within an `Option` type. -pub func is_none[T](self: Option[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: Option[T], error: E): Result[T, E] = - if self of Some(x): - Okay(x) - else: - Error(error) - -## Applies a function to `T`, if it exists. -pub func map[T, U](self: Option[T], fn: T -> U): Option[U] = - if self of Some(x): - Some(fn(x)) - else: - None -## Converts `T` to a `None`, if `fn` returns false and it exists. -pub func filter[T](self: Option[T], fn: T -> bool): Option[T] = - if self of Some(x) and fn(x): - Some(x) - else: - None - -## Applies a function to T, if it exists. Equivalent to `self.map(fn).flatten`. -pub func flatmap[T, U](self: Option[T], fn: T -> Option[U]): Option[U] = - if self of Some(x): - fn(x) - else: - None -## Converts from Option[Option[T]] to Option[T]. -pub func flatten[T](self: Option[Option[T]]): Option[T] = - match self - of Some(Some(x)): - Some(x) - of _: - None - -## Returns the inner value or a default. -pub func get_or[T](self: Option[T], default: T): T = - if self of Some(x): x - else: default -## Directly accesses the inner value. Throws an exception if None. -pub func ![T](self: Option[T]): T = - if self of Some(x): x - else: raise Exception # todo: syntax?? - -## Indirect access. Propagates `None`. -## Parsed by compiler magic. -pub macro ?[T](self: Option[T]) = - quote: - match `self` - of Some(x): x - of None: return None - -## Syntactic sugar for optional type declarations. -pub macro ?(T: type) = - quote: - Option[`T`] - -## Overloads the `==` operation for use on Options. -pub func ==[T](a, b: Option[T]): bool = - match (a, b) - of (Some(x), Some(y)): - x == y - of _: - false - -## Overloads the `str()` function for use on Options. -pub func str[T](self: Option[T]): str = - if self of Some(x): - fmt("Some({})", x.str) - else: - "None" - -examples: - let x = Some(42) - if x of Some(y): - assert x! == y - -# references: -# https://nim-lang.github.io/Nim/options.html -# https://doc.rust-lang.org/std/option/enum.Option.html diff --git a/std/default/results.pk b/std/default/results.pk deleted file mode 100644 index 8e21234..0000000 --- a/std/default/results.pk +++ /dev/null @@ -1,136 +0,0 @@ -## 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 interface. Useful for dynamically dispatching errors. -pub type Err = interface - 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[Unit, 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]): Option[T] = - if self of Okay(x): - Some(x) - else: - None() -## Converts from a `Result[T, E]` to an `Option[E]`. -pub func err[T, E](self: Result[T, E]): Option[E] = - if self of Error(x): - 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): - Okay(fn(x)) - of Error(e): - 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): - Error(fn(e)) - of Okay(x): - 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): - fn(x) - of Error(e): - 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)): - Okay(x) - of Okay(Error(e)), Error(e): - Error(e) - -## Transposes a `Result[Option[T], E]` to an `Option[Result[T, E]]`. -pub func transpose[T, E](self: Result[Option[T], E]): Option[Result[T, E]] = - match self - of Okay(Some(x)): - Some(Okay(x)) - of Okay(None()), Error(_): - None() -## Transposes an `Option[Result[T, E]]` to a `Result[Option[T], E]`. Takes a default error. -pub func transpose[T, E](self: Option[Result[T, E]], error: E): Result[Option[T], E] = - match self - of Some(Okay(x)): - Okay(Some(x)) - of Some(Error(e)): - Error(e) - of None(): - 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): 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): x - of Error(e): 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): e - of Okay(x): raise x - -## Indirect access. Propagates `Error`. -pub macro ?[T, E](self: Result[T, E]) = - quote: - match `self` - of Okay(x): x - of Error(e): return Error(e) - -## Overloads the `==` operation for use on Results. -pub func ==[T, E, F](a: Result[T, E], b: Result[T, F]): bool = - match (a, b) - of (Okay(x), Okay(y)): - x == y - of _: - false - -## Overloads the `str()` function for use on Results. -pub func str[T, E](self: Result[T, E]): str = - match self - of Some(x): - fmt("Okay({})", x.str) - of Error(e): - fmt("Error({})", e.str) - -examples: - let x: Error("fuck") = Okay(42) - func idk: Result[int, string] - -# references: -# https://doc.rust-lang.org/std/result/enum.Result.html -# https://github.com/arnetheduck/nim-results -# https://github.com/codex-storage/questionable diff --git a/std/fundamental/ast.pk b/std/fundamental/ast.pk deleted file mode 100644 index 0529cbd..0000000 --- a/std/fundamental/ast.pk +++ /dev/null @@ -1,113 +0,0 @@ -## std/ast: Exposes the AST for building and operating on with macros. - -pub type Ident = string -pub type Number = int -pub type Float = float -pub type Char = char -pub type String = str -pub type Struct = list[struct[field: str, value: Expr]] -pub type Tuple = list[struct[field: Option[string], value: Expr]] -pub type List = list[Expr] - -pub type Let = struct - id: Pattern - kind: Option[Type] - value: ref Expr -pub type Var = struct - id: Pattern - kind: Option[Type] - value: Option[ref Expr] # variable bindings can be delayed -pub type Const = struct - id: Pattern - kind: Option[Type] - value = ref Expr -pub type FuncDecl = struct - public: bool - effect: Option[str] - id: str - generics: list[struct[id: str, kind: Option[Type]]] - params: list[struct[id: str, kind: Type]] - kind: Type - body: list[Expr] -pub type TypeDecl = struct - id: str - generics: list[str] - alias: Type -pub type Import = struct - mod_from: Option[string] - imports: list[str] - alias: Option[str] -pub type Module = struct - id: str - body: list[Expr] - -pub type Call = struct - id: str - params: list[Expr] -pub type Cond = struct - branches: list[struct[cond: Expr, body: list[Expr]]] - else_body: Option[list[Expr]] -pub type Try = struct - body: list[Expr] - catches: list[struct[exceptions: list[str], body: list[Expr]]] - finally_body: Option[list[Expr]] -pub type Match = struct - item: ref Expr - branches: list[struct[pattern: Pattern, guard: Option[Expr], body: list[Expr]]] -pub type Block = struct - id: Option[str] - body: list[Expr] -pub type Static = struct - body: list[Expr] -pub type For = struct - binding: Pattern - range: ref Expr - body: list[Expr] -pub type While = struct - cond: ref Expr - body: list[Expr] -pub type Loop = struct - body: list[Expr] - -# the first style of union. naming anonymous types. -pub type Type = union - Void, Never, - Integer, Float, String, - Func: struct[from, to: ref Type] # todo: multiple parameters - Struct: list[struct[id: str, kind: Type]] - Tuple: list[struct[id: Option[str], kind: Type]] - Union: list[struct[id: str, kind: Type]] - Interface: struct # todo: generics - funcs: list[Signature] - for_type: Option[ref Type] - Array: struct[size: uint, kind: ref Type] - List: ref Type - Slice: ref Type # todo: plus ownership - Alias: str # todo: params?? huh? - Static: ref Type - Mutable: ref Type - Reference: ref Type -pub type Signature = struct # todo: generics - id: str - effect: Option[str] - params: list[Type] - kind: Option[Type] -pub type Pattern = union - Ident: str - Number: int, Float: float, Char: char, String: str - Struct: struct[name: str, params: list[Pattern]] - Tuple: list[Pattern] - List: list[Pattern] - -# the second style of union. A union of literal types, no names. -pub type Expr = union - Ident, Number, Float, Char, String, Struct, Tuple, List, - Let, Var, Const, FuncDecl, TypeDecl, Import, Module, - Call, Cond, Try, Match, Block, Static, For, While, Loop, - -# todo: decide on a style of union - -# anonymous struct objects can be constructed with {} -# anonymous tuple objects can be constructed with () -# anonymous list objects can be constructed with [] -# anonymous union *types* can be constructed with | (in parameters) diff --git a/std/fundamental/logs.pk b/std/fundamental/logs.pk deleted file mode 100644 index 7a0060f..0000000 --- a/std/fundamental/logs.pk +++ /dev/null @@ -1 +0,0 @@ -## std/logs: Utility macros and functions for logging. diff --git a/std/fundamental/tests.pk b/std/fundamental/tests.pk deleted file mode 100644 index 7eb00a8..0000000 --- a/std/fundamental/tests.pk +++ /dev/null @@ -1,5 +0,0 @@ -## std/tests: Utility macros and functions for testing. - -## A macro for runnable examples. Runs them on compilation. -pub macro examples(body) = - ... diff --git a/std/logs.pk b/std/logs.pk new file mode 100644 index 0000000..98042b0 --- /dev/null +++ b/std/logs.pk @@ -0,0 +1 @@ +## std.logs: Utility macros and functions for logging. diff --git a/std/prelude/format.pk b/std/prelude/format.pk new file mode 100644 index 0000000..3adb68b --- /dev/null +++ b/std/prelude/format.pk @@ -0,0 +1,53 @@ +## std.format: Niceties around printing and debugging. +## This module is imported by default. + +## The Display interface. Any type implementing `str` is printable. +## Any type that is Display must necessarily also implement Debug. +pub type Display = interface + str(Self): str + dbg(Self): str + +## The Debug interface. Broadly implemented for every type with compiler magic. +## Types can (and should) override the generic implementations. +pub type Debug = interface + 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! does not count as a side effect +pub func dbg(params: varargs[Debug]) = + stdout.write(params.map(x => x.dbg).join(" "), "\n") + +## A dummy implementation of the Display interface for strings. +pub func str(self: str): str = self +## An implementation of the Debug interface for strings. +pub func dbg(self: str): str = "\"" & self & "\"" + +## An implementation of the Debug interface for all structs. +## Uses the special `struct` typeclass. +pub func dbg(self: struct): str = + "{{}}".fmt(self.fields.map((key, val) => key & ":" & val.dbg)) + +## An implementation of the Debug interface for all tuples. +## Uses the special `tuple` typeclass. +pub func dbg(self: tuple): str = + "({})".fmt(self.fields.map((key, val) => + key.map(x => x & ":").get_or("") & val.dbg).join(", ")) + +## An implementation of the Debug interface for all arrays and lists. +pub func dbg[T: Debug](self: Iter[T]): str = + "[{}]".fmt(self.map(x => x.str).join(", ")) + +## The fmt macro. Builds a formatted string from its arguments. +pub macro fmt(self: static[str], args: varargs[Display]): str = + let parts = self.split("{}") + if parts.len != args.len + 1: + macro_error("wrong number of arguments") + use std.ast + result = parts.get(0)! + for i, arg in args: + result &= quote(`parts` & str(`arg`) &) # fixme + result &= parts.last()! diff --git a/std/prelude/iterators.pk b/std/prelude/iterators.pk new file mode 100644 index 0000000..224460f --- /dev/null +++ b/std/prelude/iterators.pk @@ -0,0 +1,38 @@ +## std.iterators: The Iter interface and associated functions. +## This module is imported by default. + +## The Iter interface. Any type implementing `next()` is iterable. +pub type Iter[T] = interface + next(mut Self): Option[T] + +# todo: useful functions for an iterator +# https://doc.rust-lang.org/std/iter/trait.Iterator.html#provided-methods + +pub func advance_by[T](self: Iter[T], n: uint): Result[T, ...] = + for i in 0 .. n: + if self.next().is_none(): + return Error(...) + Okay + +pub func get[T](self: Iter[T], at: uint): Option[T] + self.advance_by(at-1).ok? + self.next() + +# todo: implement iter[T](self: ...): Iter[T] funcs +# todo: efficient functional methods + +## The Peek interface. Any type implementing `Iter`, `peek`, and `peek_nth` is peekable. +pub type Peek[T] = interface + next(mut Self): Option[T] + peek(mut Self): Option[T] + peek_nth(mut Self, int): Option[T] + +# todo: implement peek[T](self: Iter[T]): Peek[T] +# todo: implement Peekable struct +# https://github.com/LukeMathWalker/multipeek/blob/main/src/lib.rs + +## We don't want a Countable. It's not terribly useful. +# pub type Countable[T] = interface +# next(mut Self): Option[T] +# len(Self): uint +# get(Self, uint): Option[T] diff --git a/std/prelude/options.pk b/std/prelude/options.pk new file mode 100644 index 0000000..4f6cef2 --- /dev/null +++ b/std/prelude/options.pk @@ -0,0 +1,101 @@ +## 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 + +## Checks if a type is present within an `Option` type. +pub func is_some[T](self: Option[T]): bool = + self of Some(_) +## Checks if a type is not present within an `Option` type. +pub func is_none[T](self: Option[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: Option[T], error: E): Result[T, E] = + if self of Some(x): + Okay(x) + else: + Error(error) + +## Applies a function to `T`, if it exists. +pub func map[T, U](self: Option[T], fn: T -> U): Option[U] = + if self of Some(x): + Some(fn(x)) + else: + None +## Converts `T` to a `None`, if `fn` returns false and it exists. +pub func filter[T](self: Option[T], fn: T -> bool): Option[T] = + if self of Some(x) and fn(x): + Some(x) + else: + None + +## Applies a function to T, if it exists. Equivalent to `self.map(fn).flatten`. +pub func flatmap[T, U](self: Option[T], fn: T -> Option[U]): Option[U] = + if self of Some(x): + fn(x) + else: + None +## Converts from Option[Option[T]] to Option[T]. +pub func flatten[T](self: Option[Option[T]]): Option[T] = + match self + of Some(Some(x)): + Some(x) + of _: + None + +## Returns the inner value or a default. +pub func get_or[T](self: Option[T], default: T): T = + if self of Some(x): x + else: default + +## A dummy type signalling the empty value +type EmptyType = distinct unit + +## Directly accesses the inner value. Throws an exception if None. +pub func ![T](self: Option[T]): T = + if self of Some(x): x + else: raise EmptyType + +## Indirect access. Propagates `None`. +## Parsed by compiler magic. +pub macro ?[T](self: Option[T]) = + quote: + match `self` + of Some(x): x + of None: return None + +## Syntactic sugar for optional type declarations. +pub macro ?(T: type) = + quote: + Option[`T`] + +## Overloads the `==` operation for use on Options. +pub func ==[T](a, b: Option[T]): bool = + match (a, b) + of (Some(x), Some(y)): + x == y + of _: + false + +## Overloads the `str()` function for use on Options. +pub func str[T](self: Option[T]): str = + if self of Some(x): + fmt("Some({})", x.str) + else: + "None" + +examples: + let x = Some(42) + if x of Some(y): + assert x! == y + +# references: +# https://nim-lang.github.io/Nim/options.html +# https://doc.rust-lang.org/std/option/enum.Option.html diff --git a/std/prelude/results.pk b/std/prelude/results.pk new file mode 100644 index 0000000..1209b40 --- /dev/null +++ b/std/prelude/results.pk @@ -0,0 +1,136 @@ +## 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 interface. Useful for dynamically dispatching errors. +pub type Err = interface + 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[Unit, 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]): Option[T] = + if self of Okay(x): + Some(x) + else: + None() +## Converts from a `Result[T, E]` to an `Option[E]`. +pub func err[T, E](self: Result[T, E]): Option[E] = + if self of Error(x): + 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): + Okay(fn(x)) + of Error(e): + 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): + Error(fn(e)) + of Okay(x): + 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): + fn(x) + of Error(e): + 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)): + Okay(x) + of Okay(Error(e)), Error(e): + Error(e) + +## Transposes a `Result[Option[T], E]` to an `Option[Result[T, E]]`. +pub func transpose[T, E](self: Result[Option[T], E]): Option[Result[T, E]] = + match self + of Okay(Some(x)): + Some(Okay(x)) + of Okay(None()), Error(_): + None() +## Transposes an `Option[Result[T, E]]` to a `Result[Option[T], E]`. Takes a default error. +pub func transpose[T, E](self: Option[Result[T, E]], error: E): Result[Option[T], E] = + match self + of Some(Okay(x)): + Okay(Some(x)) + of Some(Error(e)): + Error(e) + of None(): + 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): 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): x + of Error(e): 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): e + of Okay(x): raise x + +## Indirect access. Propagates `Error`. +pub macro ?[T, E](self: Result[T, E]) = + quote: + match `self` + of Okay(x): x + of Error(e): return Error(e) + +## Overloads the `==` operation for use on Results. +pub func ==[T, E, F](a: Result[T, E], b: Result[T, F]): bool = + match (a, b) + of (Okay(x), Okay(y)): + x == y + of _: + false + +## Overloads the `str()` function for use on Results. +pub func str[T, E](self: Result[T, E]): str = + match self + of Some(x): + fmt("Okay({})", x.str) + of Error(e): + fmt("Error({})", e.str) + +examples: + let x: Error("fuck") = Okay(42) + func idk: Result[int, string] + +# references: +# https://doc.rust-lang.org/std/result/enum.Result.html +# https://github.com/arnetheduck/nim-results +# https://github.com/codex-storage/questionable diff --git a/std/tests.pk b/std/tests.pk new file mode 100644 index 0000000..8d13a4e --- /dev/null +++ b/std/tests.pk @@ -0,0 +1,5 @@ +## std.tests: Utility macros and functions for testing. + +## A macro for runnable examples. Runs them on compilation. +pub macro examples(body) = + ... -- cgit v1.2.3-70-g09d2