./smol docs/std.s --task docYou may notice that lot of dependencies are marked as unsafe; this is not worrying but only indicates that extra trust should be placed on both the person writing the implementations because some of the language's safety features are turned off to bring to you low-level functionality.
std/ and files under its subfolder. Do not import the latter by themselves to safe development speed. As a final remark, overloaded implementations are split per the file declaring them; foreign imports are not shown. @mut indicates mutability. If this is absent, the language promises to not modify values - the standard library promises to respect that too when considering whether it is allowed to modify pointer contents.@access indicates that arguments can view mutable fields, though not necessarily edit them. This marking also imports the function together with those that could result to arguments from the same import file. For example, the following snippet imports all numeric but not string operations from the standard library's core: @import std.core -> Number
This file is marked with the unsafe keyword. This means that its internal implementation (only) could be subject to bugs that the language's design otherwise eliminates. By using this file as a direct or indirect dependency you are trusting its implementation. Given this trust, consider other non-unsafe files using it as safe.
Reads several string and primitive types from the console. The user needs to press enter after entering the input. Invalid inputs create service failures, including in the case where the number is too large to properly process. Example:
printin("Give a number.")
x = f64.read
printin("Rounded.")
print(i64(x+0.5))read(@access i64) → i64Converts a String representation to various numeric formats. This is lightweight and does not consume additional memory. The current service fails if the conversion is not possible. Example:
x = f64.convert("1.2")
print(x)convert(@access i64,cstr _s) → i64This file is marked with the unsafe keyword. This means that its internal implementation (only) could be subject to bugs that the language's design otherwise eliminates. By using this file as a direct or indirect dependency you are trusting its implementation. Given this trust, consider other non-unsafe files using it as safe.
Checks if an error code corresponds to no error. If it does not, the service fails while printing the type of error. Error codes can be user errors, buffer errors, unknown errors, or no errors.
assert_ok(errcode error) → ()Prints a string interpretation of an error code.
print(errcode error) → ()Causes the current service to fail given a String message. This creates a user error code. Example:
printin("Give a number.")
x = i64.read()
if x==0.0
return fail("Cannot compute the inverse of zero")
printin("Its inverse is.")
print(1.0/x)fail(cstr error) → ()This file is marked with the unsafe keyword. This means that its internal implementation (only) could be subject to bugs that the language's design otherwise eliminates. By using this file as a direct or indirect dependency you are trusting its implementation. Given this trust, consider other non-unsafe files using it as safe.
Negation of a boolean. There is no equivalent operator, and currying-based notation should be used for manipulating single values. Here is a simple example, though this is often used in conditional statements.
print(true.not)not(@access bool x) → boolChecks if a pointer is non-null. Pointer access and manipulation is inherently unsafe and you should not encounter that in normal code writing. For those aiming to extend the standard library or who dabble with inherently unsafe use cases, this can be used to check for memory allocation failures and trigger a service failure to safely collapse the current execution state.
exists(@access ptr x) → boolPrints a primitive number, character, or boolean to the console.
print(@access f64 message) → ()Prints a primitive number, character, or boolean to the console without changing line.
printin(@access f64 message) → ()Checks if the first number is less than or equal to the second and overloads the corresponding operator. Only the same number types can be compared, and explicit casts are required otherwise. Example demonstrating the need to use an f64 zero (0.0 and not 0) to compare with another read value of the same type:
x = f64.read
if x<0.0
return print("negative")
else
return print("non-negative")le(@access u64 x,u64 y) → boolChecks if the first number is greater than or equal to the second and overloads the corresponding operator. Only the same number types can be compared, and explicit casts are required otherwise. Example demonstrating the need to use an f64 zero (0.0 and not 0) to compare with another read value of the same type:
x = f64.read
if x>0.0
return print("positive")
else
return print("non-positive")ge(@access u64 x,u64 y) → boolChecks if the first number is less than the second and overloads the corresponding operator. Example:
print(1>=2)lt(@access u64 x,u64 y) → boolChecks if the first number is greater than the second and overloads the corresponding operator. Example:
print(1>=2)gt(@access u64 x,u64 y) → boolChecks if the first number is less than or equal to the second.
leq(@access u64 x,u64 y) → boolChecks if the first number is greater than or equal to the second.
geq(@access u64 x,u64 y) → boolChecks if two primitives are equal and overloads the corresponding operator. Equality can be checked for all primitives, and is also defined for other types in the standard library, such as strings. Example:
print(1==2)eq(@access u64 x,u64 y) → boolChecks if two primitives are not equal and overloads the corresponding operator. Non-equality can be checked for all primitives, and is also defined for other types in the standard library, such as strings. Example:
print(1!=2)neq(@access u64 x,u64 y) → boolAddition of two numbers of the same type and overloads the corresponding operator. Example:
print(1+2)add(@access u64 x,u64 y) → u64Modulo operation for signed or unsigned integers. For i64, only positive divisors are allowed. Fails on zero divisor. Overloads the corresponding operator. Here is an example:
print(1%2)mod(@access u64 x,u64 y) → u64Subtraction of two numbers of the same type: Doing so for u64 will create a service failure if the result would be negative. Overloads the corresponding operator for numbers. Example that FAILS because the default integer type is u64.
print(1-2)sub(@access u64 x,u64 y) → u64Multiplication of two numbers of the same type and overloads the corresponding operator. Example:
print(3*2)mul(@access u64 x,u64 y) → u64Division of two numbers of the same type: Division by zero for i64 or u64 creates a service failure, but for f64 it yields NaN. Overloads the corresponding operator. Here is an example that yields zero by performing integer division.
print(1/2)div(@access u64 x,u64 y) → u64Returns the additive inverse (negation) of an i64 or f64. Does NOT overload any operation. Having u64 as the default type helps avoid many spurious negation errors, especially when memory handling is concerned.
Both examples below print -1.
print(0.i64-1.i64)
print(1.i64.negative)negative(@access i64 x) → i64Obtains the next element in the range. Using a combination of a range and next element traversal is safer than manually checking bounds.
Below is the main usage pattern. Notice that next's argument is an in-place constructed u64 number that is mutable to obtain the next value. The function sets the next value, progresses the range's state, and returns whether the iteration ended. The first retrieved value is the starting element of the range. Example:
range(10)
.while next(@mut u64 i)
then print(i)next(@mut u64 self.start,@mut u64 self.sup,@mut u64 self.step,@mut u64 self.pos,@mut u64 value) → boolDefines a u64 range as a structural type (instead of nominal type). When directly using variables as ranges, the position should be mutable. A couple of calling conventions are provided for default values of 0 for start and 1 for step.
range(u64 start,u64 sup,u64 step) → (u64 start,u64 sup,u64 step,@mut u64 pos)This file is marked with the unsafe keyword. This means that its internal implementation (only) could be subject to bugs that the language's design otherwise eliminates. By using this file as a direct or indirect dependency you are trusting its implementation. Given this trust, consider other non-unsafe files using it as safe.
Retrieves the next element over a Split string iteration. Example:
Split("I like bananas", " ")
.while next(@mut str word)
then print(word)next(@mut Split self,@mut str value) → boolA union between nstr and constant strings cstr.
Main usage is to abstract an argument's type by converting it to nstr. The conversion is a zero cost abstraction in that needless operations will be removed. But it still augments constant strings with length and first element inform if these are needed. Here is an example:
def foo(CString _s)
s = _s.nstr()
...
endnstr(nominal,ptr contents,u64 length,char first,ptr memory) → (nominal,ptr contents,u64 length,char first,ptr memory)A union between str, nstr, and constant strings cstr. Constant strings are those that generated by default when enclosing some text in quotients and are stored in the program memory.
Main usage is to abstract an argument's type by converting it to str. The conversion is a zero cost abstraction in that needless operations will be removed. But it still augments constant strings with length and first element inform if these are needed. Here is an example:
def foo(String _s)
s = _s.str()
...
endnstr(nominal,ptr contents,u64 length,char first,ptr memory) → (nominal,ptr contents,u64 length,char first,ptr memory)A copy of the String union that can be used when a second argument is needed for a string of a potentially different variation.
nstr(nominal,ptr contents,u64 length,char first,ptr memory) → (nominal,ptr contents,u64 length,char first,ptr memory)Compile-time check of a String exact type matching compared to an arbitrary type.
Example usage.
def foo(String s)
with s.is(str)
...
end else
...
end endis(@access cstr self,cstr) → cstrPrints strings or bools to the console.
print(@access cstr message) → ()A memory allocated string and converters from constant strings and booleans to the type. Other standard library implementations provide more converters.
str(nominal,ptr contents,u64 length,char first,ptr memory) → (nominal,ptr contents,u64 length,char first,ptr memory)A null-terminated variation of str. Many operation produce this string version, as it can be readily converted to str at no cost (for the inverse, you need to copy the string)
nstr(nominal,ptr contents,u64 length,char first,ptr memory) → (nominal,ptr contents,u64 length,char first,ptr memory)Prints strings or bools to the console without evoking a new line at the end.
printin(@access cstr message) → ()Checks for equality between String types when considering their contents. Implementation of this operation varies, ensuring that the cached first element of strings (not available for cstr) is compared first and then the lengths are taken into account to compare memory bytes. Example:
if "me"=="me" return print("me!")eq(@access char x,char y) → boolEquivalent to logical inversion of String eq. It is faster to write and run.
neq(@access char x,char y) → boolObtains a substring slice out of a String. This always produces a str results, because null termination cannot be guaranteed for most results - and is dropped even if it could be guaranteed to save computations.
Splits a String given a query String. Optionally, you may also provide a starting position, where the default is 0. The result of the split can be iterated through with next. This does not allocate memory in that a substring is retrieved, so you might consider copying the splits - or store them on data structures like maps that automatically copy data if needed.
This file is marked with the unsafe keyword. This means that its internal implementation (only) could be subject to bugs that the language's design otherwise eliminates. By using this file as a direct or indirect dependency you are trusting its implementation. Given this trust, consider other non-unsafe files using it as safe.
Go to the end of a WriteFile. This is not implemented for ReadFile, as it makes more sense to just close the latter. Returns a boolean indicating a successful operation.
to_end(@mut WriteFile f) → boolAn opened file that is meant to be read only.
ReadFile(nominal,ptr contents) → (nominal,ptr contents)An opened file that is meant to be read or written.
WriteFile(nominal,ptr contents) → (nominal,ptr contents)Reads the next chunk of a file while using it as an iterator. It accomodates Arena and Volatile memories. Here is an example where volatile memory is used to avoid repeated or large allocations:
on Heap:volatile(1024)
ReadFile
.open("README.md")
.while next_chunk(@mut str chunk)
then print(chunk)next_chunk(@mut Circular reader,@mut ReadFile f,@mut nstr value) → boolOpens a File given a String path. There might be service failure due to external factors. Opening a WriteFile, may also cause failure if it already exists - in that case remove it first and it will be created. On the other hand, a ReadFile must already exist to be opened. Files must be set as mutable variables to allow reads and writes. Otherwise, only a few operations become available. Example for overwriting a file:
if is_file("hi.txt")
then remove_file("hi.txt")
@mut file = WriteFile.open("tmp.txt")
file:print("Hello world!")
@release file // early release closes the file
open(@mut ReadFile,cstr _path) → ReadFileReads the next line of a file while using it as an iterator. It accomodates Arena and Volatile memories. Here is an example where volatile memory is used to avoid repeated or large allocations:
endl="n".str().first // optimized to just setting the new line character
on Heap:volatile(1024)
ReadFile("README.md")
.open("README.md")
.while next_line(@mut str line)
if line[line:len-1]==endl
then line = line[0 to line:len-1]
then print(line)next_line(@mut Circular reader,@mut ReadFile f,@mut nstr value) → boolA union between file types that allows common reading and positioning operations.
ReadFile(nominal,ptr contents) → (nominal,ptr contents)Writes a string on a WriteFile.
print(@mut WriteFile f,cstr _s) → ()Go to the beginning of a File. You can continue reading or writing from there. This may cause service failure due to external factors.
to_start(@mut ReadFile f) → ()Creates a WriteFile of a given size that is constrained to fixed memory provided by a Memory allocator. Due to safety mechanisms provided by operating systems, operations on this file may be slower than simple memory read and writes. If the operating system does not properly support memory mapped files, this may even end up consuming disk storage space of up to the given size by being stored as a temporary file. In general, reads and writes (with print) will be at most as slow as a normal file, with the contract that data cannot be recovered after termination. Some operating systems require manual deletion of temporary file folders if systems are abruptly powered off. This type of file should be mostly used to store temporary data or for testing purposes. Example usage:
on Heap.dynamic() // allocator for the file
@mut f = WrteFile.temp(1024)
f.print("hello from withing a temp file!")
f.to_start()
f.next_line(@mut u64 line)
print(line)temp(@mut Stack memory,@mut WriteFile,u64 size) → WriteFileComputes the size of a File in bytes. This tries to leverage operating system metadata first, but if it fails it explicitly reads through the file once.
len(@mut ReadFile f) → u64Checks if the ending of the file has been reached. This is normal to be true for WriteFile.
ended(@mut ReadFile f) → boolChecks if a String path is a file system file.
is_file(cstr _path) → existsChecks if a String path is a file system directory.
is_dir(cstr _path) → boolDeletes a file from the system. May cause service failure due to external factors, or if the file is already open.
remove_file(cstr _path) → ()Creates a directory given a String path. May cause service failure due to external factors, or if the directory already exists.
create_dir(cstr _path) → ()This file is marked with the unsafe keyword. This means that its internal implementation (only) could be subject to bugs that the language's design otherwise eliminates. By using this file as a direct or indirect dependency you are trusting its implementation. Given this trust, consider other non-unsafe files using it as safe.
This file is marked with the unsafe keyword. This means that its internal implementation (only) could be subject to bugs that the language's design otherwise eliminates. By using this file as a direct or indirect dependency you are trusting its implementation. Given this trust, consider other non-unsafe files using it as safe.
This file is marked with the unsafe keyword. This means that its internal implementation (only) could be subject to bugs that the language's design otherwise eliminates. By using this file as a direct or indirect dependency you are trusting its implementation. Given this trust, consider other non-unsafe files using it as safe.
Represents call stack memory. Allocating on this is near-zero cost by being just an arithmetic addition. But its total size is limited - typically up to a few megabytes. Prefer this for small localized data that need to be processed exceedingly fast.
Stack(nominal) → nominalRandom access memory (RAM) that can be allocated with __runtime_alloc. Writing to it and reading from it can be slow for programs that keep. Modern processors optimize heap usage by prefetching and caching nearby areas as the ones you access. For this reason, prefer creating Arena regions when you have a sense of the exact amount of data you will need. Allocating on the heap can leak memory under certain conditions, but the language's safety mechanism prevents this. Use other allocators in those cases. The standard library provides a Dynamic type that also accesses several heap allocations, though with an additional level of indirection.
Heap(nominal) → nominalRepresents allocated memory management. It keeps track of both currently used pointer addresses, for example if these are offsets of allocated base pointers with finally segments calling __runtime_free on those, and the underlying pointer addresses. Importantly, not all this information is retained after compilation, as most of it -perhaps all- is optimized away. But this structure still helps the compiler organize where to place memory releases, if needed. Users of the standard library will not generally work with this type, as it is highly unsafe to get its pointer fields and requires annotation for the language to allow that.
ContiguousMemory(nominal,u64 size,ptr mem,ptr underlying) → (nominal,u64 size,ptr mem,ptr underlying)Refers to either stack or heap memory.
Stack(nominal) → nominalAllocates memory on a predetermined device given a number of entries. Other standard library overloads implement allocation for more memory types, derived from the devices. Allocations throughout the standard library track the raw allocated memory so that usage is finally released only when the last dependent variable (e.g., the last string allocated on a heap arena) is no longer used. See ContiguousMemory.
allocate(Heap,u64 size) → ContiguousMemoryThis file is marked with the unsafe keyword. This means that its internal implementation (only) could be subject to bugs that the language's design otherwise eliminates. By using this file as a direct or indirect dependency you are trusting its implementation. Given this trust, consider other non-unsafe files using it as safe.
Copies a string on a given Memory allocator. The result is nstr, that is, a string variation is null-terminated. This operation may fail if the allocation fails.
copy(@mut Stack allocator,cstr _s) → nstrProvides methods for converting numbers to strings that are stored on provided Memory allocators. The result is a null-terminated nstr.
str(ContiguousMemory region) → str(nominal,ptr contents,u64 length,char first,ptr memory)Overloads the + operator to concatenate two strings. The result is nstr, that is, a string variation that is null-terminated. Example:
name = "Mario"
on Heap.arena(1024)
message = "It's a me, "+name+"!"
end
print(message)add(@mut Stack allocator,cstr _x,cstr _y) → nstrThis file is marked with the unsafe keyword. This means that its internal implementation (only) could be subject to bugs that the language's design otherwise eliminates. By using this file as a direct or indirect dependency you are trusting its implementation. Given this trust, consider other non-unsafe files using it as safe.
A running process whose stdout can be read as a file-like object.
Process(nominal,ptr contents) → (nominal,ptr contents)Reads all remaining output from the process without storing it.
to_end(@mut Process p) → ()Opens a Process given a command string. This starts the process and lets you read its output.When the process is eventually released, services fail if there is pending output or if the exit code is non-zero. Here is an example:
service run(String command)
@mut process = Process.open(command)
process.to_end()
@release process // explicitly release here
service main()
run("invalid command").err.assert_ok() // synchronizeopen(@mut Process,cstr _command) → ProcessReads the next chunk of process output into a provided buffer.
next_chunk(@mut Region reader,@mut Process p,@mut nstr value) → boolReads the next line of process output into a provided buffer.
next_line(@mut Region reader,@mut Process p,@mut nstr value) → boolThis file is marked with the unsafe keyword. This means that its internal implementation (only) could be subject to bugs that the language's design otherwise eliminates. By using this file as a direct or indirect dependency you are trusting its implementation. Given this trust, consider other non-unsafe files using it as safe.
Computes the next random number of a Rand sequence.Example:
@mut rnd = Rand()
range(10)
:while next(@mut u64 i)
print(rnd:next)
endnext(@mut u64 self.s0,@mut u64 self.s1,@mut u64 self.s2,@mut u64 self.s3) → f64Computes the next random number of a splitmix64 sequence using the mutable unsigned int argument as state to be updated. This is NOT cryptographically secure and also has small period of 2^64 so usage is not recommended for long-running sequences.It is, however, faster than computing a next Rand state with next. If you do not provide a seed, a number obtained from the current time is provided. That can only be the start of a sequence, and marked as a leaking resource to prevent time-based randomization (which is not random).Example:
@mut rnd = splitmix64()
range(10)
:while next(@mut u64 i)
print(rnd:splitmix64) // rnd is the state, the result is f64
endsplitmix64(@mut u64 x) → u64This a structural type for storing the progress of random number generators on four u64 state fields. It can be initialized with an optional seed, which defaults to a time-based initialization if not provided. Its period is 2^256-1.
For safety against sharing random implementations between services or repeatedly initializing them, state variables are marked as a leaking resource. The whole data type is marked as @noborrow too, to prevent sharing mutable random states across different services. These safety mechanisms help safeguard speed and prevent common mistakes, for example by making impossible to directly re-initialize Rand in each loop to get a next number.
This file is marked with the unsafe keyword. This means that its internal implementation (only) could be subject to bugs that the language's design otherwise eliminates. By using this file as a direct or indirect dependency you are trusting its implementation. Given this trust, consider other non-unsafe files using it as safe.
Make the current service wait for AT LEAST a number of f64 seconds. While yielding, other services may be called asynchronously to fill in the missing time. There is no guarantee for this, though. Sleeping for 0.0 duration does not incur delays, but may still run other services. Negative durations skip over this. Use exact_slepp to sleep without yielding and thus get a guarantee on the sleep duration. This method's exact implementation is ported from the runtime. Example:
sleep(1.0) // yields for at least 1 sec sleep(f64 duration) → ()Make the current service wait for exactly a specified number of f64 seconds. Control flow is not transferred to other services, so use sparingly (e.g., in main game loops). Example:
sleep(1.0) // waits for 1 sec of inactivity exact_sleep(f64 duration) → okRetrieve time elapsed from the start of the program in f64 seconds.
time() → f64This file is marked with the unsafe keyword. This means that its internal implementation (only) could be subject to bugs that the language's design otherwise eliminates. By using this file as a direct or indirect dependency you are trusting its implementation. Given this trust, consider other non-unsafe files using it as safe.
Represents a vector stored on contiguous memory. Prefer using the vector initializer, which can also generate vectors from random number generators. Vectors always hold f64 to ensure that invalid computations are stored as NaNs. They are also tailored for scientific use, so implementations aim to cut even the smallest corners without compromising safety. Use buffers to work with collections of u64 data instead.
Initializes a vector by using a provided memory allocator. The generated vector is zero-initialized. You can also provide a Rand random state imported from std.rand to initialize with uniformly random values in [0,1]. Example of generating a vector of 10 zero elements:
vec = Heap:dynamic:vector(10)vector(@mut Stack memory,u64 size) → VecPrints a vector to the console. To avoid large prints, at most the first 10 elements are printed.
print(Vec v) → ()Slices a vector from a given to an ending position. This is a transparent view of vector data.
slice(Vec v,u64 from,u64 to) → VecAdds two vectors element-by-element and stores the result on either a third mutable vector also of the same size, or on a newly allocated one in the provided memory. This fails if vector sizes are incompatible, or if the provided Memory cannot allocate the required space. Example where an on context is used to allow operator overloading:
@mut rnd = Rand()
on Heap:dynamic
v1 = rnd:vector(10)
v2 = rnd:vector(10)
v3 = v1+v2
endadd(@mut Stack memory,Vec x1,Vec x2) → VecSubtracts two vectors element-by-element and stores the result on either a third mutable vector also of the same size, or on a newly allocated one in the provided memory. This fails if vector sizes are incompatible, or if the provided Memory cannot allocate the required space. Example where an on context is used to allow operator overloading:
@mut rnd = Rand()
on Heap:dynamic
v1 = rnd:vector(10)
v2 = rnd:vector(10)
v3 = v1-v2
endsub(@mut Stack memory,Vec x1,Vec x2) → VecMultiplies two vectors element-by-element and stores the result on either a third mutable vector also of the same size, or on a newly allocated one in the provided memory. This fails if vector sizes are incompatible, or if the provided Memory cannot allocate the required space. Example where an on context is used to allow operator overloading:
@mut rnd = Rand()
on Heap:dynamic
v1 = rnd:vector(10)
v2 = rnd:vector(10)
v3 = v1*v2
endmul(@mut Stack memory,Vec x1,Vec x2) → VecRetrieves the length of a vector.
len(Vec v) → u64Divides two vectors element-by-element and stores the result on either a third mutable vector also of the same size, or on a newly allocated one in the provided memory. This fails if vector sizes are incompatible, or if the provided Memory cannot allocate the required space. Division may create NaN values. Example where an on context is used to allow operator overloading:
@mut rnd = Rand()
on Heap.dynamic()
v1 = rnd.vector(10)
v2 = rnd.vector(10)
v3 = v1/v2
enddiv(@mut Stack memory,Vec x1,Vec x2) → VecRetrieves a specific f64 element from a vector. This overloads the element access operation like this:
vec = Rand():vector(Heap.dynamic(), 10)
print(vev[0])at(Vec v,u64 pos) → f64