diff options
Diffstat (limited to 'std/prelude')
-rw-r--r-- | std/prelude/arrays.pk | 24 | ||||
-rw-r--r-- | std/prelude/booleans.pk | 39 | ||||
-rw-r--r-- | std/prelude/clone.pk | 30 | ||||
-rw-r--r-- | std/prelude/compare.pk | 36 | ||||
-rw-r--r-- | std/prelude/convert.pk | 28 | ||||
-rw-r--r-- | std/prelude/debug.pk | 28 | ||||
-rw-r--r-- | std/prelude/format.pk | 32 | ||||
-rw-r--r-- | std/prelude/io.pk | 2 | ||||
-rw-r--r-- | std/prelude/iterators.pk | 91 | ||||
-rw-r--r-- | std/prelude/lists.pk | 128 | ||||
-rw-r--r-- | std/prelude/mem.pk | 39 | ||||
-rw-r--r-- | std/prelude/numbers.pk | 53 | ||||
-rw-r--r-- | std/prelude/options.pk | 97 | ||||
-rw-r--r-- | std/prelude/ranges.pk | 47 | ||||
-rw-r--r-- | std/prelude/results.pk | 112 | ||||
-rw-r--r-- | std/prelude/strings.pk | 104 |
16 files changed, 466 insertions, 424 deletions
diff --git a/std/prelude/arrays.pk b/std/prelude/arrays.pk index 2e4a4d4..6f57865 100644 --- a/std/prelude/arrays.pk +++ b/std/prelude/arrays.pk @@ -1,20 +1,30 @@ -## std.arrays: The array[T, S] primitive and associated functions. +## std.arrays: The array[T, size] primitive and associated functions. ## A stub module for documentation. Mostly compiler magic. ## Primitive fixed-size arrays. Their size is statically known at compile-time. -pub type array[T, S: static[uint]] +@[magic] +pub type array[T, size: static uint] ## Array access. Returns None if i is out of range. -pub func get[T, S: static[uint]](self: array[T, S], i: uint): T? +@[magic] +pub func get[T, size: static uint](self: lent array[T, size], i: uint): lent T? +## Array access. Returns None if i is out of range. +@[magic] +pub func get[T, size: static uint](self: mut array[T, size], i: uint): mut T? + ## Array mutation. # todo: how do we detect range errors? -pub func set[T, S: static[uint]](self: mut array[T, S], i: uint, val: T): T? +@[magic] +pub func set[T, size: static uint](self: mut array[T, size], i: uint, val: T): Success[IndexOutOfBounds] + ## A helper function to get the length of an array. ## Known to the compiler, and computed at compile-time. -pub func len[T, S: static[uint]](self: array[T, S], i: uint): T? +@[inline] +pub func len[T, size: static uint](self: lent array[T, size]): uint = size -type ArrayIter[T, S: static[uint]] = struct +type ArrayIter[T, size: static uint] = struct + ... +pub func iter[T, size: static uint](self: array[T, size]): ArrayIter[T, S] = ... -pub func iter[T, S: static[uint]](self: array[T, S]): ArrayIter[T, S] # todo: Eq, PartialEq, Ord, PartialOrd diff --git a/std/prelude/booleans.pk b/std/prelude/booleans.pk index bd81925..e92eac8 100644 --- a/std/prelude/booleans.pk +++ b/std/prelude/booleans.pk @@ -4,39 +4,26 @@ pub type unit = union[sole] pub type bool = union[false, true] -# note: puck could resolve kleene.false vs bool.false... -# this is probably not worth it compared to improved type inference -# pub type Kleene = union[False, Maybe, True] - ## Boolean equality -pub func ==(a: bool, b: bool): bool = - match a - of (true, true), (false, false): true - of (false, true), (true, false): false +@[magic] +pub func ==(a: bool, b: bool): bool -pub func !=(a: bool, b: bool): bool = - not a == b +## Boolean inequality +@[magic] +pub func !=(a: bool, b: bool): bool ## Boolean negation -pub func not(a: bool): bool = - match a - of true: false - of false: true +@[magic] +pub func not(a: bool): bool ## Boolean conjunction -pub func and(a: bool, b: bool): bool = - match (a, b) - of (true, true): true - of _: false +@[magic] +pub func and(a: bool, b: bool): bool ## Boolean disjunction -pub func or(a: bool, b: bool): bool = - match (a, b) - of (false, false): false - of _: true +@[magic] +pub func or(a: bool, b: bool): bool ## Boolean exclusive disjunction -pub func xor(a: bool, b: bool): bool = - match (a, b) - of (true, true), (false, false): false - of (true, false), (false, true): true +@[magic] +pub func xor(a: bool, b: bool): bool diff --git a/std/prelude/clone.pk b/std/prelude/clone.pk index 5b16b8b..5d11cdb 100644 --- a/std/prelude/clone.pk +++ b/std/prelude/clone.pk @@ -1,28 +1,36 @@ -## std.clone: Interfaces for explicitly Clonable types. +## std.clone: Classes for explicitly Clonable types. ## This module is imported by default. -## The Clone interface. Any type implementing Clone is cloneable. +## The Clone class. Any type implementing Clone is cloneable. ## Data structures built with pointers and references are pretty much ## the only things that are *not* implicitly clonable. -pub type Clone = interface - clone(lent Self): Self # todo +pub type Clone = class + clone(lent Self): Self ## Generic implementation of `clone` for structs. -pub func clone[T: struct](self: T): T = +## fixme: this relies on no component of a struct being a ref or ptr +## cloning an rc is fine?? +pub func clone[T: Clone](self: struct[T]): struct[T] = ... ## Generic implementation of `clone` for tuples. -pub func clone[T: tuple](self: T): T = +pub func clone[T: Clone](self: tuple[T]): tuple[T] = ... ## Implementation of `clone` for arrays of any size. -pub func clone[T, S: static[uint]](self: array[T, S]): array[T, S] = - ... +pub func clone[T: Clone, size: static[uint]](self: array[T, size]): array[T, size] = + var res: array[T, size] + for i in 0 .. size do + res.push(self.i.clone) ## Implementation of `clone` for lists. -pub func clone[T](self: list[T]): list[T] = - ... +pub func clone[T: Clone](self: list[T]): list[T] = + var res: list[T] + for elem in self do + res.push(elem.clone) # the explicit dependency on Clone gives us this ## Implementation of `clone` for strings. pub func clone(self: str): str = - ... + var res: str + for char in self do + res.push(char) diff --git a/std/prelude/compare.pk b/std/prelude/compare.pk index b506761..5f3ae74 100644 --- a/std/prelude/compare.pk +++ b/std/prelude/compare.pk @@ -1,30 +1,36 @@ -## std.compare: Interfaces for comparable types. +## std.compare: Classes for comparable types. ## reference: https://doc.rust-lang.org/std/cmp -## The Eq interface -pub type Eq = interface +## The Eq class. For types with some notion of equivalence. +pub type Eq = class ==(Self, Self): bool -## A possible Equivalence interface. -# equality, equivalence, isomorphism, homomorphism, congruence -# pub type Equiv = interface -# ... +## A blanket implementation of a corresponding not-equal function. +pub !=[T: Eq](a: T, b: T): bool = + not(a == b) -pub type PartialOrd = interface +## The Compare class. For a type comparable with itself. +pub type Compare = class <(a: Self, b: Self): bool -pub type Ord = interface +## 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 -pub !=[T: Eq](a: T, b: T): bool = - not a == b - -pub func >[T: PartialOrd](a: T, b: T): bool = - b < a - +## 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 diff --git a/std/prelude/convert.pk b/std/prelude/convert.pk index 6abf543..ec1a4a7 100644 --- a/std/prelude/convert.pk +++ b/std/prelude/convert.pk @@ -1,23 +1,19 @@ -## std.convert: Interfaces for type coersion and conversion. +## std.convert: Classes for type coersion and conversion. ## This module is imported by default. -## The Coerce interface is used for type conversion that will not fail. +## 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] = interface - from(T): Self - # into(Self): T +pub type Coerce[T] = class + to(Self): T + # from(T): Self -## The `into` function is automatically implemented for all types that -## implement `from`: that is, all types U that are convertable to T. -pub func into[T, U: Coerce[T]](self: T): U = - from(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 interface is used for type conversion that may fail. +## The Convert class is used for type conversion that may fail. # We'll see what this breaks. -pub type Convert[T] = interface - from(T): Self?! - -## A blanket implementation of a corresponding `into` function. -pub func into[T, U: Convert[T]](self: T): U = - from(self) +pub type Convert[T, E] = class + to(Self): Result[T, E] diff --git a/std/prelude/debug.pk b/std/prelude/debug.pk index 3af1def..92d31dd 100644 --- a/std/prelude/debug.pk +++ b/std/prelude/debug.pk @@ -1,27 +1,22 @@ ## std.debug: Useful functions for debugging. ## This module is imported by default. -## 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. -pub func ...: never - ## 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 `debug_assert` +## Asserts remain in release builds. If not desired, see `dbg_assert` pub macro assert(cond: bool) = - quote: - if not `cond`: + quote + if not `cond` then panic "assertation failed!\n {}".fmt(dbg(`cond`)) -## The `debug_assert` function provides an assert that is compiled out in release builds. +## The `dbg_assert` function provides an assert that is compiled out in release builds. ## This is useful for debugging performance-critical code. -pub macro debug_assert(cond: bool) - quote: - when debug: # fixme: where is this coming from? +pub macro dbg_assert(cond: bool) = + quote + when debug then # fixme: where is this constant coming from? assert `cond` -## The `discard` function consumes any type. +## 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 @@ -30,3 +25,10 @@ pub func discard[T](self: T) = 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") diff --git a/std/prelude/format.pk b/std/prelude/format.pk index 7cc1d81..6fc7c99 100644 --- a/std/prelude/format.pk +++ b/std/prelude/format.pk @@ -1,15 +1,15 @@ ## std.format: Niceties around printing and debugging. ## This module is imported by default. -## The Display interface. Any type implementing `str` is printable. +## The Display class. Any type implementing `str` is printable. ## Any type that is Display must necessarily also implement Debug. -pub type Display = interface +pub type Display = class str(Self): str dbg(Self): str -## The Debug interface. Broadly implemented for every type with compiler magic. +## The Debug class. Broadly implemented for every type with compiler magic. ## Types can (and should) override the generic implementations. -pub type Debug = interface +pub type Debug = class dbg(Self): str ## Prints all of its arguments to the command line. @@ -17,38 +17,42 @@ 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 +## +## 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. +@[no_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. +## A dummy implementation of the Display class for strings. pub func str(self: str): str = self -## An implementation of the Debug interface for strings. +## An implementation of the Debug class for strings. pub func dbg(self: str): str = "\"" & self & "\"" -## An implementation of the Debug interface for all structs. +## An implementation of the Debug class for all structs. ## Uses the special `struct` typeclass. -pub func dbg(self: struct): str = +pub func dbg[T: Debug](self: struct[T]): str = "{{}}".fmt(self.fields.map((key, val) => key & ":" & val.dbg)) -## An implementation of the Debug interface for all tuples. +## An implementation of the Debug class for all tuples. ## Uses the special `tuple` typeclass. -pub func dbg(self: tuple): str = +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 interface for all arrays and lists. +## 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: static[str], args: varargs[Display]): str = let parts = self.split("{}") - if parts.len != args.len + 1: + 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: + for i, arg in args do res &= quote(`parts` & str(`arg`) &) # fixme res &= parts.last()! res diff --git a/std/prelude/io.pk b/std/prelude/io.pk index 002cc31..026d12a 100644 --- a/std/prelude/io.pk +++ b/std/prelude/io.pk @@ -4,7 +4,7 @@ # reference: https://doc.rust-lang.org/stable/std/io/ pub type File = ... -pub type FileMode = union[Read, Write, ReadWrite, Append] +pub type FileMode = enum[Read, Write, ReadWrite, Append] # todo: file destructors call `close` diff --git a/std/prelude/iterators.pk b/std/prelude/iterators.pk index 82137ee..1403b55 100644 --- a/std/prelude/iterators.pk +++ b/std/prelude/iterators.pk @@ -1,28 +1,28 @@ -## std.iterators: The Iter interface and associated functions. +## std.iterators: The Iter class and associated functions. ## This module is imported by default. # reference: https://doc.rust-lang.org/std/iter/ # reference: https://docs.rs/itertools/latest/itertools/ use std.queue.ring -## The Iter interface. Any type implementing `next()` is iterable. -pub type Iter[T] = interface +## The Iter class. Any type implementing `next()` is iterable. +pub type Iter[T] = class next(mut Self): T? # should T be lent? -## The IntoIter interface. +## The IntoIter class. ## Any type implementing `iter()` may be converted to an iterable type. -pub type IntoIter[T] = interface +pub type IntoIter[T] = class iter(Self): Iter[T] -## The Peek interface. +## The Peek class. ## Any type implementing `Iter`, `peek`, and `peek_nth` is peekable. -pub type Peek[T] = interface +pub type Peek[T] = class next(mut Self): T? peek(mut Self): T? peek_nth(mut Self, int): T? ## A concrete Peekable structure formed from a generic Iter type. -## Implements the Peek interface. Can be formed with `.peekable()` +## Implements the Peek class. Can be formed with `.peekable()` type PeekImpl = struct iter: ref Iter[T] # this is probably bad... heap allocated... buf: Queue[T] @@ -34,26 +34,26 @@ pub func peekable[T](iter: owned Iter[T]): PeekImpl[T] = pub func next[T](self: mut PeekImpl[T]): T? = match self.buf.pop() - of None: + of None then self.iter.next() - of Some(val): + of Some(val) then val pub func peek[T](self: mut PeekImpl[T]): T? = match self.buf.peek() - of None: + of None then self.buf.push(self.iter.next()) self.buf.peek() - of Some(val): + of Some(val) then val pub func peek_nth[T](self: mut PeekImpl[T], i: uint): T? = - if self.buf.len > i: + if self.buf.len > i then self.buf.get(i)! - else: - for _ in self.buf.len .. i: # fixme + else + for _ in self.buf.len .. i do # fixme match self.buf.next() - of None: + of None then return None - of Some(val): + of Some(val) then self.buf.push(val) self.buf.peek() @@ -61,26 +61,23 @@ pub func peek_nth[T](self: mut PeekImpl[T], i: uint): T? = # https://doc.rust-lang.org/std/iter/trait.Iterator.html#provided-methods func advance_by[T](self: Iter[T], n: uint) = - for i in 0 .. n: - if self.next().is_none(): - return + for i in 0 .. n do + if self.next().is_none() then return -pub func get[T](self: Iter[T], at: uint): Option[T] +pub func get[T](self: Iter[T], at: uint): T? self.advance_by(at-1).ok? # fixme self.next() ## Returns true if every element in an Iter fulfills the conditional. pub func all[T](self: Iter[T], cond: T -> bool): bool = - for elem in self: - if not cond(elem): - return false + for elem in self do + if not cond(elem) then return false true ## Returns true if any element in an Iter fulfills the conditional. pub func any[T](self: Iter[T], cond: T -> bool): bool = - for elem in self: - if cond(elem): - return true + for elem in self do + if cond(elem) then return true false ## Returns true if an Iter contains the given element. @@ -89,52 +86,48 @@ pub func contains[T](self: Iter[T], val: T): bool = ## Returns the first element matching a condition, if it exists. pub func find[T](self: Iter[T], cond: T -> bool): T? = - for elem in self: - if cond(elem): - return Some(elem) + for elem in self do + if cond(elem) then return Some(elem) None ## Returns the position of the first element matching a condition, if it exists. pub func position[T](self: Iter[T], cond: T -> bool): T? = var i = 0 - for elem in self: - if cond(elem): - return Some(i) + for elem in self do + if cond(elem) then return Some(i) i += 1 None +@[inline] pub func fold[T, U](self: Iter[T], init: U, op: (U, T) -> U) = var res = init - for elem in self: + for elem in self do res = res.op(elem) res pub func reduce[T, U](self: Iter[T], op: (U, T) -> U): U? = match self.next() - of None: - None - of Some(val): + of None then None + of Some(val) then var res = val - for elem in self: - res = op(res, elem) + for elem in self do res = op(res, elem) Some(res) pub func count[T](self: Iter[T], element: T): uint = - self.fold(0, (elem, acc) => if elem == element: acc + 1 else: acc) + self.fold(0, (elem, acc) => if elem == element then acc + 1 else acc) pub func count[T](self: Iter[T]): uint = self.fold(0, (elem, acc) => acc + 1) pub func last[T](self: Iter[T]): T = var res = None - for elem in self: - res = elem + for elem in self do res = elem res pub func ==[T](first: Iter[T], second: Iter[T]): bool = - for (a, b) in : - if a != b: - return false + if first.len != second.len then return false + for (a, b) in zip(first, second) do + if a != b then return false true type LazyIter = struct @@ -165,16 +158,16 @@ pub func zip[T, U](first: Iter[T], second: Iter[U]): Iter[(T, U)] pub func unzip[T, U](self: Iter[(T, U)]): (Iter[T], Iter[U]) # equivalent of collect. useful for type conversion -pub func from[T](self: LazyIter[T]): list[T] -pub func from(self: LazyIter[chr]): str +pub func to[T](self: LazyIter[T]): list[T] +pub func to(self: LazyIter[chr]): str -pub type BoundIter[T] = interface +pub type BoundIter[T] = class next(mut Self): T? pop(mut Self): T? pub func rev[T](self: BoundIter[T]): BoundIter[T] -pub type SizedIter[T] = interface +pub type SizedIter[T] = class next(mut Self): T? pop(mut Self): T? len(Self): uint diff --git a/std/prelude/lists.pk b/std/prelude/lists.pk index 1e8dee5..57de91b 100644 --- a/std/prelude/lists.pk +++ b/std/prelude/lists.pk @@ -2,105 +2,113 @@ ## This module is imported by default. # reference: https://doc.rust-lang.org/nomicon/vec/vec.html -use std.pointers - -## The list type. -type list[T] = struct - data: unique[T] # hrm +## The fundamental list type. Heap-allocated. +## Equivalent to Vec<T> in other languages. +@[unsafe] # unsafe on a struct tells us raw field access breaks invariants. +pub type list[T] = struct + data: ptr T capacity: int length: int +## 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] = - ... # malloc? idk - let (data, ) - { data: , capacity: 0, length: 0 } +pub func init[T]: list[T] = + { data = nil, capacity = 0, length = 0 } # fixme: nil!!!!! ## Gets the length of a list. -pub func len[T](self: list[T]): uint = +@[inline] # idk what to do with attributes +pub func len[T](self: lent list[T]): uint = self.length -pub func empty[T](self: list[T]): bool = +pub func empty[T](self: lent list[T]): bool = self.length == 0 ## Gets the internal capacity of a list. -func cap[T](self: list[T]): uint = +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. -pub func push[T](self: mut list[T], val: owned T) = - if self.length == self.capacity: - self.grow() - unsafe: # todo: do we want unsafe blocks, as they are in rust? - self.data.raw_set(self.length, val) # fixme - # unsafe { ptr::write(self.ptr().add(self.length), val); } - self.length += 1 +@[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], val: owned list[T]) = - ... +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: + if self.length == 0 then None - unsafe: + else self.length -= 1 - Some(self.data.raw_get[T](self.length)) # fixme - # unsafe { Some(ptr::read(self.ptr().add(self.length))) } + Some(self.data.get(offset = self.length)) ## Returns a reference to an element of a list, if in range. -pub func get[T](self: list[T], i: uint): lent T? = - ... +@[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. todo: when is `val` owned? -## todo: how do we deal with having the wrong offset? -pub func set[T](self: mut list[T], i: uint, val: owned T) = - ... +## 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.cap == self.len: - self.grow() - ... - # unsafe { - # ptr::copy( - # self.ptr().add(index), - # self.ptr().add(index + 1), - # self.len - index, - # ); - # ptr::write(self.ptr().add(index), elem); - # self.len += 1; - # } + 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. -pub func remove[T](self: mut list[T], i: uint): T = - assert index < self.length, "index out of bounds" - unsafe: +@[safe] +pub func remove[T](self: mut list[T], i: uint): T? = + if index < self.length then None + else self.length -= 1 - result = ... - # unsafe { - # self.len -= 1; - # let result = ptr::read(self.ptr().add(index)); - # ptr::copy( - # self.ptr().add(index + 1), - # self.ptr().add(index), - # self.len - index, - # ); - # result - # } + 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: list[T]): lent T? = +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) # todo: many questions. owned? how does it not free? -pub func iter[T](self: owned list[T]): ListIter[T] = +pub func iter[T](self: list[T]): ListIter[T] = ... # todo: iteration... diff --git a/std/prelude/mem.pk b/std/prelude/mem.pk index 79c524b..5ad3c7a 100644 --- a/std/prelude/mem.pk +++ b/std/prelude/mem.pk @@ -1,35 +1,58 @@ ## std.mem: Unsafe functions for working with raw memory. +## This module is imported by default. ## Raw pointers. Extremely unsafe. ## While they are removed from the memory model by default (and hence can -## cause undefined behavior), pointers must know what *type* they point at. +## cause memory errors), pointers must know what *type* they point at. ## This cuts down on errors somewhat - not much, but somewhat. +@[magic] pub type ptr[T] = addr: uint ## Heap-allocated, memory-safe references. +@[magic] pub type ref[T] -pub type unique[T] +## Heap-allocated, reference-counted references. +## These are a special type - rather than anything handled by the type system +## alone - because there are significant optimizations the compiler can make. +@[magic] +pub type refc[T] + +## Tells the compiler to interpret data of one type directly as data of another. +## Extremely unsafe. Take caution to memory representations when using. +## Compiler magic. The type signature is left here for documentation purposes. +@[unsafe, magic] +pub func cast[T, U](self: T): U ## Sets the sizeof(T) bytes following data + offset * sizeof(T) to T. ## Extremely unsafe. -pub func raw_set[T](data: ptr[T], val: T, offset: uint = 0) = +@[unsafe] +pub func set[T](data: ptr T, val: T, offset: uint = 0) = ... ## Gets the value at data + offset * sizeof(T), and interprets it as type T. ## Extremely unsafe. -pub func raw_get[T](data: ptr[T], offset: uint = 0) = +@[unsafe] +pub func get[T](data: ptr T, offset: uint = 0) = ... ## Returns a pointer offset by offset * sizeof(T). ## Extremely unsafe. -pub func raw_offset[T](data: ptr[T], offset: uint): ptr[T] = +@[unsafe] +pub func offset[T](data: ptr T, offset: uint): ptr T = + ... + +@[unsafe] +pub func malloc[T](size: uint = sizeof(T)): ptr T = ... -pub func malloc[T](size: uint = sizeof(T)): ptr[T] = +@[unsafe] +pub func calloc[T](size: uint = sizeof(T)): ptr T = ... -pub func calloc[T](size: uint = sizeof(T)): ptr[T] = +@[unsafe] +pub func realloc[T](data: ptr T): ptr T = ... -pub func realloc[T](data: ptr[T]): ptr[T] = +@[unsafe] +pub func copy[T](from: ptr T, to: ptr T, length: uint) = ... diff --git a/std/prelude/numbers.pk b/std/prelude/numbers.pk index 7857d34..801591a 100644 --- a/std/prelude/numbers.pk +++ b/std/prelude/numbers.pk @@ -3,53 +3,73 @@ # reference: https://en.wikipedia.org/wiki/IEEE_754 ## The default integer type. Size depends on architecture. +@[copy, magic] pub type int ## A signed 8-bit integer. +@[copy, magic] pub type i8 ## A signed 16-bit integer. +@[copy, magic] pub type i16 ## A signed 32-bit integer. +@[copy, magic] pub type i32 ## A signed 64-bit integer. +@[copy, magic] pub type i64 ## A signed 128-bit integer. +@[copy, magic] pub type i128 ## The default unsigned integer type. Size depends on architecture. +@[copy, magic] pub type uint ## An unsigned 8-bit integer. +@[copy, magic] pub type u8 ## An unsigned 16-bit integer. +@[copy, magic] pub type u16 ## An unsigned 32-bit integer. +@[copy, magic] pub type u32 ## An unsigned 64-bit integer. +@[copy, magic] pub type u64 ## An unsigned 128-bit integer. +@[copy, magic] pub type u128 ## A floating-point type. Takes up 64 bits by default. pub type float = f64 ## A 32-bit floating point type. Single precision. +@[copy, magic] pub type f32 ## A 64-bit floating point type. Double precision. +@[copy, magic] pub type f64 ## A 128-bit floating point type. Quadruple precision. +@[copy, magic] pub type f128 ## A decimal type, according to IEEE 754. Takes up 64 bits by default. pub type decimal = dec64 ## A 64-bit decimal type. +@[copy, magic] pub type dec64 ## A 128-bit decimal type. +@[copy, magic] pub type dec128 -## 8-bit bytes. +## A primitive byte type. 8 bits wide. pub type byte = u8 -## 4-byte chars. These represent distinct Unicode characters. -pub type char = distinct u32 -## An alias to `char`, to match with `str`. -pub type chr = char +## A primitive char type. 4 bytes wide. +## These represent distinct Unicode characters, and are useful with `str`. +pub type chr = u32 + +## Get the underlying length of a char, in bytes. 1-4. +pub func len(self: chr): uint = + ... ## The IEEE floating point value of *Not a Number*. pub const NaN: f64 = 0x7FF7FFFFFFFFFFFF @@ -60,18 +80,31 @@ pub const NegInf: f64 = 0xFFF0000000000000 # todo: type conversions +# fixme: types are all wrong +@[magic] pub func +[T](a: T, b: T): T +@[magic] pub func -[T](a: T, b: T): T +@[magic] pub func *[T](a: T, b: T): T +@[magic] pub func /[T](a: T, b: T): float -pub func ^[T](a: T, b: T): T # todo: should be exp? +@[magic] +pub func exp[T](a: T, b: T = eulers_number): T +@[magic] +pub func log[T](a: T, b: T = eulers_number): T +@[magic] +pub func abs[T](a: T): T + +@[magic] pub func div[T](a: T, b: T): T -pub func mod[T](a: T, b: T): T # fixme +@[magic] +pub func mod[T](a: T, b: T): T +@[magic] pub func rem[T](a: T, b: T): T +@[magic] pub func shl[T](a: T): T +@[magic] pub func shr[T](a: T): T - -pub func abs[T](a: T): T -pub func neg[T](a: T): bool diff --git a/std/prelude/options.pk b/std/prelude/options.pk index 4f6cef2..d5cfb9d 100644 --- a/std/prelude/options.pk +++ b/std/prelude/options.pk @@ -9,91 +9,84 @@ 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: 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: Option[T]): bool = +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: Option[T]): bool = +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: Option[T], error: E): Result[T, E] = - if self of Some(x): +pub func err[T, E](self: T?, error: E): Result[T, E] = + if self of Some(x) then Okay(x) - else: + 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): +pub func map[T, U](self: T?, fn: T -> U): U? = + if self of Some(x) then Some(fn(x)) - else: + 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): +pub func filter[T](self: T?, fn: T -> bool): T? = + if self of Some(x) and fn(x) then Some(x) - else: + 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): +pub func flatmap[T, U](self: T?, fn: T -> U?): U? = + if self of Some(x) then fn(x) - else: + 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)): +pub func flatten[T](self: T??): T? = + if self of Some(Some(x)) then Some(x) - of _: + else 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`] +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: Option[T]): bool = - match (a, b) - of (Some(x), Some(y)): +pub func ==[T](a, b: T?): bool = + if (a, b) of (Some(x), Some(y)) then x == y - of _: + else 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: +pub func str[T: Display](self: T?): str = + if self of Some(x) then + "Some({})".fmt(x.str) + else "None" -examples: +examples let x = Some(42) - if x of Some(y): + if x of Some(y) then assert x! == y # references: diff --git a/std/prelude/ranges.pk b/std/prelude/ranges.pk index e3af8a4..8410c5e 100644 --- a/std/prelude/ranges.pk +++ b/std/prelude/ranges.pk @@ -5,7 +5,7 @@ type Range[T] = struct start: T end: T -type RangeInclusive[T] = struct +type RangeIncl[T] = struct start: T end: T done: bool @@ -17,49 +17,48 @@ pub func ..(from: int, to: int): Range[int] = ## Inclusive ranges. Useful for ranges. ## Includes `from` and `to`. -pub func ..=(from: int, to: int): RangeInclusive[int] = +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: - result = Some(self.start) + if self.start < self.end then self.start += 1 - else: - result = None + Some(self.start - 1) + else + None # todo: We don't need a mutable Range here to peek. -# How does this interact with interfaces? +# 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: + if res < self.end then return Some(res) - else: + else return None -pub func next[T: int](self: mut RangeInclusive[T]): T? = - if self.done: - return None - elif self.start < self.end: +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 - return Some(res) - elif self.start == self.end: + Some(res) + elif self.start == self.end then self.done = true - return Some(self.start) - else: + Some(self.start) + else self.done = true - return None + None -pub func peek[T: int](self: mut RangeInclusive[T]): T? = +pub func peek[T: int](self: mut RangeIncl[T]): T? = self.peek_nth(0) -pub func peek_nth[T: int](self: mut RangeInclusive[T], i: uint): T? = +pub func peek_nth[T: int](self: mut RangeIncl[T], i: uint): T? = let res = self.start + i - if res <= self.end: - return Some(res) - else: - return None + if res <= self.end + then Some(res) + else None diff --git a/std/prelude/results.pk b/std/prelude/results.pk index 81bccf0..61b0c8a 100644 --- a/std/prelude/results.pk +++ b/std/prelude/results.pk @@ -8,8 +8,8 @@ pub type Result[T, E] = union Okay(T) Error(E) -## The Err interface. Useful for dynamically dispatching errors. -pub type Err = interface +## The Err class. Useful for dynamically dispatching errors. +pub type Err = class str(Self): str dbg(Self): str @@ -18,7 +18,21 @@ pub type Err = interface 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] +# 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 = @@ -28,110 +42,94 @@ 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): +pub func ok[T, E](self: Result[T, E]): T? = + if self of Okay(x) then Some(x) - else: - None() + 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): +pub func err[T, E](self: Result[T, E]): E? = + if self of Error(x) then Some(x) - else: - None() + 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): + of Okay(x) then Okay(fn(x)) - of Error(e): + 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): + of Error(e) then Error(fn(e)) - of Okay(x): + 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): + of Okay(x) then fn(x) - of Error(e): + 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)): + of Okay(Okay(x)) then Okay(x) - of Okay(Error(e)), Error(e): + 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[Option[T], E]): Option[Result[T, E]] = +pub func transpose[T, E](self: Result[T?, E]): Result[T, E]? = match self - of Okay(Some(x)): + of Okay(Some(x)) then Some(Okay(x)) - of Okay(None()), Error(_): - None() + 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: Option[Result[T, E]], error: E): Result[Option[T], E] = +pub func transpose[T, E](self: Result[T, E]?, error: E): Result[T?, E] = match self - of Some(Okay(x)): - Okay(Some(x)) - of Some(Error(e)): - Error(e) - of None(): - Error(error) + 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): x - else: default + 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): x - of Error(e): raise e + 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): 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) - -## Syntactic sugar for dynamic `Result` type declarations. -pub macro ?!(T: type) = - quote: - Result[`T`] + 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 = - match (a, b) - of (Okay(x), Okay(y)): + if (a, b) of (Okay(x), Okay(y)) then x == y - of _: + else false ## Overloads the `str()` function for use on Results. -pub func str[T, E](self: Result[T, E]): str = +pub func str[T: Display, E: Display](self: Result[T, E]): str = match self - of Some(x): - fmt("Okay({})", x.str) - of Error(e): - fmt("Error({})", e.str) + of Some(x) then + "Okay({})".fmt(x.str) + of Error(e) then + "Error({})".fmt(e.str) -examples: +examples let x: Error("fuck") = Okay(42) func idk: Result[int, string] diff --git a/std/prelude/strings.pk b/std/prelude/strings.pk index 2f48b67..e86d520 100644 --- a/std/prelude/strings.pk +++ b/std/prelude/strings.pk @@ -2,100 +2,82 @@ ## This module is imported by default. # reference: https://doc.rust-lang.org/std/string/struct.String.html -use std.pointers - -# possible approaches: list[byte], distinct list[byte], this -# should *really* just rely on lists, i don't want to duplicate slicing logic, but... maybe... -type str = struct - data: ptr[byte] - length: uint - capacity: uint +## A primitive string type. +## +## We do not want methods defined on `list[byte]` to carry over, +## so we define `str` as a newtype. +pub type str = struct + data: list[byte] ## Initialize and return an empty string. -pub func init(): str = - ... +pub func init: str = { data = [] } ## Gets the length of a string. -pub func len(self: str): uint = - self.length - -pub func empty(self: str): bool = - self.length == 0 - -## Gets the internal capacity of a string. -func cap(self: str): uint = - self.capacity - -## Expands the capacity of a string. -func grow(self: mut str) = - ... +## 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: chr) = # todo: `add` alias? - ... +pub func push(self: mut str, val: chr) = + 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): chr? = - ... + 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): chr? = ... ## Sets the character at the provided index, if it exists. -## todo: what if it does not exist? +## 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: chr) = ... ## Inserts a character at an arbitrary position within a string. -## Error handling: todo +## Panics on failure. (todo: can we do better?) pub func insert(self: mut str, i: uint, val: chr) = ... ## Removes and returns a character at an arbitrary position within a string. -## Panics on failure. +## Panics on failure. (todo: can we do better?) pub func remove(self: mut str, i: uint): chr = ... -## Returns the *byte* at an arbitrary position within a string. -## Explicitly distinct from `get` as this breaks safety conventions idk -pub func get_byte(self: str): byte = - ... - -## Sets a byte at the provided index, if it exists. Do we want this? -pub func set_byte(self: mut str, i: uint, val: byte) = - ... - -## Inserts a byte into a string. Breaks string convention. Do we want this? -pub func insert_byte(self: mut str, i: uint, val: byte) = - ... - -## Removes a byte at a given offset from a string. Do we want this? -pub func remove_byte(self: mut str, i: uint): byte? = - ... - -## Converts a given string to a list of bytes for iteration purposes. -pub func bytes(self: str): list[byte] = - ... # many performance considerations - -## Converts a given string to a list of chars for iteration purposes. -pub func chars(self: str): list[chr] # todo: useful? - -## The concatenation operator. -pub func &(a: str, b: str): str = - ... - ## Syntactic sugar for string appending. -pub func &=(a: mut str, b: owned str) = +pub func &=(a: mut str, b: str) = a.push(b) -# todo: many things +## 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[chr]. Reallocates. +pub func to(self: str): list[chr] = + var res: list[chr] + 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: chr): array[byte, 4] = self.cast[array[byte, 4]] -# pub func iter(self: str): StrIter # pub func next(self: mut StrIter) # pub func peek(self: mut StrIter) # pub func peek_nth(self: mut StrIter, i: uint) |