Expand description

This module defines some default functions and operations available in the WLambda language.

You there are two WLambda modules provided by this module:

[]: –– REFERENCE DOC START ––

WLambda Language Reference

WLambda is a functional programming language. The main goal of this implementation is the extension of Rust applications with dynamic scripting. The syntax gravitates around the concept that everything is callable like a function. There is special syntax for composing arguments of functions, to give the programmer the ability to express his thoughts as they see fit.

You can use this document as reference or as cover to cover lecture. It starts out with functions and the base data types of WLambda, where I also explain some semantics of the language.

Please note: I expect you to know how to program and be familiar with at least one other dynamic language like JavaScript, Perl or at least Python. The syntax and semantics of WLambda are different from what you might know. Think of it more like a LISP without parenthesis. The syntax is loosely inspired from Smalltalk, LISP and Perl.


Table Of Contents:

  • 1 Variable Definition and Assignment
    • 1.1 Destructuring to Variables
    • 1.2 Global Variables
    • 1.3 Constants
  • 2 Functions (part 1/2)
    • 2.1 Closures
      • 2.1.1 Object Oriented Programming with Closures
    • 2.2 Function calling
    • 2.3 Function arity checks
      • 2.3.1 std:to_no_arity function
    • 2.4 Calling fields / Method calling
      • 2.4.1 The $self and $data special variables
      • 2.4.2 Object Oriented Programming with Prototypes
      • 2.4.3 Object Oriented with Prototypes and Inheritance
      • 2.4.4 Object Oriented with Prototypes and $self References and $data References
    • 2.5 Function call composition
      • 2.5.1 ‘|’ Tail Argument Function Chaninig
      • 2.5.2 ‘|>’ Left Hand Function Chaining
      • 2.5.3 Forward Argument Pipe arg &> fun
      • 2.5.4 Forward Argument Apply Pipe list &@> fun
      • 2.5.5 Reverse Argument Pipe fun <& arg
      • 2.5.6 Reverse Argument Apply Pipe list &@> fun
    • 2.6 Control Flow - Returning
      • 2.6.1 return [label] value
      • 2.6.2 block [label] function
      • 2.6.3 std:to_drop function (or RAII, Destructors or Drop Functions)
      • 2.6.4 std:time:now [unit]
      • 2.6.5 std:srand [seed]
      • 2.6.6 std:rand [max-or-mode]
    • 2.7 Function utilities
  • 3 Data Types
    • 3.1 None sentinel value: $n or $none
    • 3.2 Optional values $o() and $o(...)
      • 3.2.1 is_optional value
      • 3.2.2 Unwrapping optionals
    • 3.3 Error values: $e expr or $error expr
      • 3.3.1 _? [label] value
      • 3.3.2 unwrap value
      • 3.3.3 unwrap_err error-value
      • 3.3.4 on_error handler maybe-error-value
      • 3.3.5 is_err value
      • 3.3.6 std:error_to_str value
    • 3.4 Booleans
    • 3.5 64-Bit Integers
    • 3.6 64-Bit Floats
    • 3.7 Numeric Functions
      • 3.7.1 std:num:abs number
      • 3.7.2 std:num:signum number
      • 3.7.3 std:num:int_to_closed_open01 integer
      • 3.7.4 std:num:int_to_open01 integer
      • 3.7.5 std:num:int_to_open_closed01 integer
    • 3.8 Numerical Mathematical Vectors
    • 3.9 Characters and Bytes
    • 3.10 Strings
      • 3.10.1 String Literal Syntaxes
      • 3.10.2 str value
      • 3.10.3 std:write_str value
      • 3.10.4 is_str value
      • 3.10.5 std:str:cat a b
      • 3.10.6 std:str:join sep vector
      • 3.10.7 std:str:len value
      • 3.10.8 std:str:find pattern string [offset]
      • 3.10.9 std:str:replace pattern replacement string
      • 3.10.10 std:str:replace_n pattern replacement count string
      • 3.10.11 std:str:trim value
      • 3.10.12 std:str:trim_start value
      • 3.10.13 std:str:trim_end value
      • 3.10.14 std:str:pad_start len pad-str value
      • 3.10.15 std:str:pad_end len pad-str value
      • 3.10.16 std:str:to_bytes string
      • 3.10.17 std:str:to_bytes_latin1 string
      • 3.10.18 std:str:from_latin1 byte-vector
      • 3.10.19 std:str:from_utf8 byte-vector
      • 3.10.20 std:str:from_utf8_lossy byte-vector
      • 3.10.21 std:str:to_char_vec string
      • 3.10.22 std:str:from_char_vec vector
      • 3.10.23 std:str:to_lowercase string
      • 3.10.24 std:str:to_uppercase string
      • 3.10.25 std:str:edit_distance str-a _str_b
    • 3.11 Byte Vectors
      • 3.11.1 Call Properties of Bytes
      • 3.11.2 Byte Conversion Functions
      • 3.11.3 is_bytes value
      • 3.11.4 std:bytes:find pattern string [offset]
      • 3.11.5 std:bytes:replace byte-vector pattern replacement
      • 3.11.6 std:bytes:from_hex string-with-hex-chars
      • 3.11.7 std:bytes:from_vec vector-of-ints
      • 3.11.8 std:bytes:to_hex byte-vector
      • 3.11.9 std:bytes:to_base64 byte-vector [config]
      • 3.11.10 std:bytes:from_base64 byte-vector [config]
      • 3.11.11 std:bytes:to_vec byte-vector
      • 3.11.12 std:bytes:pack pack-format-string list-of-values
      • 3.11.13 std:bytes:unpack pack-format-string byte-vector
    • 3.12 Symbols
    • 3.13 Syntax $%:Block
    • 3.14 Pairs $p(a, b)
    • 3.15 Vectors (or Lists)
    • 3.16 Associative Maps (or String to Value mappings)
    • 3.17 References
    • 3.18 Iterators $iter expression
    • 3.19 Calling Semantics of Data Types
  • 4 Conditional Execution - if / then / else
    • 4.1 if/? condition then-expr [else-expr]
    • 4.2 Using Booleans for Conditional Execution
      • 4.2.1 pick bool a -b-
      • 4.2.2 Indexing by Booleans
    • 4.3 Value matching with - match value-expr
  • 5 Loops And Iteration
    • 5.1 Control Flow
      • 5.1.1 while predicate body
      • 5.1.2 iter var iterable body
      • 5.1.3 range start end step fun
      • 5.1.4 break value
      • 5.1.5 next
      • 5.1.6 jump index-val branch1last-branch
    • 5.2 Collection Iteration
      • 5.2.1 Iteration over vectors
      • 5.2.2 Iteration over maps
      • 5.2.3 for iteratable-value function
      • 5.2.4 map function iterable
      • 5.2.5 filter function iterable
    • 5.3 Accumulation and Collection
      • 5.3.1 Transforming a vector
      • 5.3.2 Example of $@@
      • 5.3.3 Transforming a vector to a map
      • 5.3.4 Iteratively concatenating strings
      • 5.3.5 Accumulating sums
    • 5.4 Utilities
      • 5.4.1 std:accum collection a b
      • 5.4.2 std:zip vector map-fn
      • 5.4.3 std:fold accumulator func iteratable
      • 5.4.4 std:enumerate map-fn
  • 6 Operators
  • 7 String and Byte Vector Formatting
    • 7.0.1 std:formatter format-string
    • 7.1 Formatting Numbers
  • 8 Data Structure Matchers, Selectors and String Patterns/Regex
    • 8.1 Data Structure Matcher
      • 8.1.1 match value-expr match-pair1 … [default-expr]
      • 8.1.2 $M expr
      • 8.1.3 Data Structure Matcher Syntax
    • 8.2 Data Structure Selectors $S(...)
      • 8.2.1 Selector and WLambda Regex Syntax:
      • 8.2.2 std:selector string
    • 8.3 String Patterns (Regex) $r/.../
      • 8.3.1 Global Patterns $rg/.../
      • 8.3.2 Pattern Substitutions $rs/.../
      • 8.3.3 Pattern Syntax Overview
      • 8.3.4 Standard Regular Expressions
      • 8.3.5 std:pattern string [mode]
  • 9 Modules
  • 10 Core Library
  • 11 Standard Library
    • 11.0.1 std:shuffle rand_func vec
    • 11.0.2 std:delete vector-or-map index-or-key
    • 11.0.3 std:ref_id value
    • 11.0.4 std:copy vec_or_map
    • 11.0.5 std:values collection-or-iter
    • 11.0.6 std:keys collection-or-iter
    • 11.0.7 std:sort [compare_fun] vec
    • 11.0.8 std:cmp:num:asc a b
    • 11.0.9 std:cmp:num:desc a b
    • 11.0.10 std:cmp:str:asc a b
    • 11.0.11 std:cmp:str:desc a b
    • 11.0.12 std:reverse value
    • 11.0.13 std:displayln arg1
    • 11.0.14 $DEBUG arg1
    • 11.0.15 std:writeln arg1
    • 11.0.16 std:eval code-string
    • 11.0.17 std:assert bool [message]
    • 11.0.18 std:assert_eq actual expected [message]
    • 11.0.19 std:assert_str_eq actual expected
    • 11.0.20 std:assert_rel_eq l r epsilon [message]
    • 11.0.21 std:measure_time unit function
    • 11.1 I/O
      • 11.1.1 std:io:line
      • 11.1.2 std:io:lines [value]
      • 11.1.3 std:io:file:read_text filename
      • 11.1.4 std:io:file:read filename
      • 11.1.5 std:io:file:write_safe filename bytes-or-string
      • 11.1.6 std:io:file:append filename bytes-or-string
      • 11.1.7 std:io:stdout:newline
      • 11.1.8 std:io:stdout:flush
      • 11.1.9 std:io:stdout:print value
      • 11.1.10 std:io:stdout:write value
      • 11.1.11 std:io:flush handle
      • 11.1.12 std:io:read_some handle
      • 11.1.13 std:io:write handle data [offs]
      • 11.1.14 std:io:write_some handle data
    • 11.2 Networking
      • 11.2.1 std:net:tcp:connect socket-addr [connect-timeout]
      • 11.2.2 std:net:tcp:listen socket-addr function
      • 11.2.3 std:net:udp:new socket-addr [connect-addr]
      • 11.2.4 std:net:udp:send socket data [socket-addr]
      • 11.2.5 std:net:udp:recv socket [byte-count]
    • 11.3 Processes
      • 11.3.1 std:process:run executable-path [arguments]
      • 11.3.2 std:process:spawn executable-path arg-vector [:inherit_out | :inherit_all]
      • 11.3.3 std:process:try_wait child-handle
      • 11.3.4 std:process:kill_wait child-handle
      • 11.3.5 std:process:wait child-handle
    • 11.4 File System
      • 11.4.1 std:fs:rename file-path new-file-name
      • 11.4.2 std:fs:copy src-file-path dst-file-path
      • 11.4.3 std:fs:read_dir path function
      • 11.4.4 std:fs:remove_file file-path
      • 11.4.5 std:fs:remove_dir dir-path
      • 11.4.6 std:fs:remove_dir_all dir-path
    • 11.5 System
    • 11.6 Threading
  • 12 Optional Standard Library
    • 12.1 serialization
      • 12.1.1 std:ser:wlambda arg
      • 12.1.2 std:ser:json data [no_pretty]
      • 12.1.3 std:deser:json string
      • 12.1.4 std:ser:csv field_delim row_separator escape_all table
      • 12.1.5 std:deser:csv field_delim row_separator data
      • 12.1.6 std:ser:msgpack data
      • 12.1.7 std:deser:msgpack bytes
    • 12.2 Regular Expressions (more classic syntax)
      • 12.2.1 std:re:match regex-string input-string function
      • 12.2.2 std:re:match_compile regex-string
      • 12.2.3 std:re:map regex-string function input-string
      • 12.2.4 std:re:replace_all regex-string replace-function input-string
    • 12.3 xml
      • 12.3.1 std:xml:read_sax xml-string event-callback-function [do-not-trim-text]
      • 12.3.2 std:xml:create_sax_writer [indent]
      • 12.3.3 std:xml:create_tree_builder
    • 12.4 chrono
      • 12.4.1 std:chrono:timestamp [format]
      • 12.4.2 std:chrono:format_utc utc-timestamp [format]
      • 12.4.3 std:chrono:format_local utc-timestamp [format]
    • 12.5 color conversion
      • 12.5.1 std:v:rgb2hsv color-vector
      • 12.5.2 std:v:hsv2rgb color-vector
      • 12.5.3 std:v:rgba2hex color-vector
      • 12.5.4 std:v:hex2rgba_f string
      • 12.5.5 std:v:hex2rgba_i string
      • 12.5.6 std:v:hex2hsva_i string
      • 12.5.7 std:v:hex2hsva_f string
    • 12.6 hash
      • 12.6.1 std:hash:fnv1a arg1
    • 12.7 rand
      • 12.7.1 std:rand:split_mix64_new
      • 12.7.2 std:rand:split_mix64_new_from seed
      • 12.7.3 std:rand:split_mix64_next sm_state [count]
      • 12.7.4 std:rand:split_mix64_next_open01 sm_state [count]
      • 12.7.5 std:rand:split_mix64_next_open_closed01 sm_state [count]
      • 12.7.6 std:rand:split_mix64_next_closed_open01 sm_state [count]
    • 12.8 Utility Functions
    • 12.9 HTTP Client
      • 12.9.1 std:http:client:new
      • 12.9.2 std:http:get http-client url-string [headers-and-options-map]
      • 12.9.3 std:http:post http-client url-string body-bytes [headers-and-options-map]
      • 12.9.4 std:http:request http-client method-string url-string [body-bytes [headers-and-options-map]]
    • 12.10 MQTT Messaging
      • 12.10.1 std:mqtt:broker:new config
        • 12.10.1.1 broker.publish topic-string payload-bytes
      • 12.10.2 std:mqtt:client:new channel client-id broker-host broker-port
        • 12.10.2.1 mqtt_client.publish topic-string payload-bytes
        • 12.10.2.2 mqtt_client.subscribe topic-string
  • 13 WLambda Lexical Syntax and Grammar
    • 13.1 Special Forms
    • 13.2 String Formatting Syntax
    • 13.3 Format String Syntax for std:bytes:pack and std:bytes:unpack

1 - Variable Definition and Assignment

As this manual assumes you have some programming knowledge, we will just take a short look at the variable definition and assignment syntax:

!a = 10;            # variable definition & initialization

.a = 20;            # assignment of a new value to a variable

WLambda also supports destructuring assignment of vectors:

!v = $[1,2,3];
!(a, b, c) = v;     # destructuring definition of variables
.(a, b, c) = v;     # destructuring assignment

std:assert_eq a 1;
std:assert_eq b 2;
std:assert_eq c 3;

This also works with maps, where the key names are matched to the variable names:

!m = ${ a = 10, b = 20, c = 30 };
!(a, b, c) = m;     # destructuring definition by map
.(a, b, c) = m;     # destructuring assignment by map

std:assert_eq a 10;
std:assert_eq b 20;
std:assert_eq c 30;

And also with pairs:

!p = $p(10, 20);
!(a, b) = p;
.(a, b) = p;

std:assert_eq a 10;
std:assert_eq b 20;

1.1 - Destructuring to Variables

Like highlighted in the previous section you can define and assign to multiple variables at once. Following data types support destructuring:

  • Vectors:
!(a, b, c) = $[1, 2, 3];

std:assert_eq a 1;
std:assert_eq b 2;
std:assert_eq c 3;
  • Maps:
!(x, foo, lol) = ${foo = 33, lol = 42, x = 2};

std:assert_eq x   2;
std:assert_eq foo 33;
std:assert_eq lol 42;
  • Pairs:
!(x, y) = $p("ex", "uepsilon");

std:assert_eq x "ex";
std:assert_eq y "uepsilon";
  • Numerical Vectors:
!(x, y, z) = $i(3, 44, 4);

std:assert_eq x 3;
std:assert_eq y 44;
std:assert_eq z 4;

!(r, g, b, a) = $f(0.3, 1.0, 0.4, 1.0);

std:assert_eq r 0.3;
std:assert_eq g 1.0;
std:assert_eq b 0.4;
std:assert_eq a 1.0;

1.2 - Global Variables

You can define global variables that are not bound to a lexical scope as follows:

{
    !:global a = 13;
}[];

std:assert_eq a 13;

Global variables however do not live beyond file or module boundaries.

1.3 - Constants

WLambda supports constant variables. These are global variables you can’t assign to. They are resolved and inserted at compile time and offer a slight performance advantage (roughly 3-4%) over (global or local) variables.

!:const X = 11;

std:assert_eq X 11;

!:const (ON, OFF) = $[$true, $false];
!:const (RED, BLUE) = ${
    BLUE = 0x0000FF,
    RED  = 0xFF0000,
};

std:assert_eq ON  $true;
std:assert_eq OFF $false;

std:assert_eq RED 0xFF0000;
std:assert_eq BLUE 0x0000FF;

However, be aware that these constants are not really constant. Due to performance reasons referential values like Lists or Maps are not copied (neither shallow, nor deep) if you access them through a constant.

!:const V = $[1,2,3];

std:assert_eq (str V) (str $[1,2,3]);

std:push V 43;  # Mutation of a 'constant'
std:assert_eq V.3 43;

Constants also work across module borders:

!:const X = 10;

!@export X = X;

2 - Functions (part 1/2)

A function can be defined using the { ... } syntax and the \ _statement_ syntax: To give functions a name, you need to assign them to a variable with the !_name_ = _expr_ syntax.

2.1 - Closures

Functions take values from the outer scope by promoting the variable at runtime to a hidden reference to their previous value:

!a = 10;
!b = 20;

!add_a_and_b = { a + b };

std:assert_eq add_a_and_b[] 30;

.a = 33;

std:assert_eq add_a_and_b[] 53;

std:assert_eq a + b         53;
2.1.1 - Object Oriented Programming with Closures

This section explains how to create objects and hide state using closures. Keep in mind, that there are also $self and $data available, which allow a different approach for referring to the object state/data than to capture the object as reference in a closure.

Keep in mind, that care must be taken (references need to be captures weakly) with the references as shown below, because otherwise you will get reference cycles and memory leaks.

!new_Cat = {!(name) = @;

    !self_ = $& ${
        name = name,
    };

    !self = $weak& $:self_;

    self.meow     = { std:displayln self.name " meows!"; };
    self.get_name = { self.name };

    $:self
};

!my_cat = new_Cat "Spot";

my_cat.meow[]; # Prints 'Spot meows!'

std:assert_eq my_cat.get_name[] "Spot";

Alternatively you can just make the cat name private:

!new_Cat = {!(name) = @;
    !cat_name = name;

    !meow     = { std:displayln cat_name " meows!"; };
    !get_name = { $*cat_name };
    !set_name = { .*cat_name = _; };

    ${
        meow     = meow,
        get_name = get_name,
        set_name = set_name,
    };
};

!my_cat = new_Cat "Spot";

my_cat.meow[]; # Prints 'Spot meows!'

std:assert_eq my_cat.get_name[] "Spot";

my_cat.set_name "Spotty";

std:assert_eq my_cat.get_name[] "Spotty";

2.2 - Function calling

To call functions, you have at least 4 alternatives. First is the bare _expr_ arg1 arg2 arg3 arg4 syntax. And the second is the fully delimited variant: _expr_[arg1, arg2, arg3, ...]. You can always delimit the first variant using the ( ... ) parenthesis around the whole call, i.e. (_expr_ arg1 arg2 arg3 arg4).

Third you can call a function with a vector as argument with _expr_[[_expr_]], where the second expression should return a vector (if it doesn’t it will use the value as first argument).

The fourth alternative is the &> and <& (and the apply variants &@> and <@&) argument pipe operators which can be conveniently used in conjunction with the first variant to prevent some parenthesis. Also belonging into the category of function calling operators there is the collection addition operators +> and <+ which are described in their own section.

Here are examples:

std:assert_eq[std:str:cat[1, 2, 3], "123"];

std:assert_eq (std:str:cat 1 2 3) "123";

!some_args = $[1, 2, 3];
std:assert_eq std:str:cat[[some_args]] "123";

std:assert_eq str <& $[1, 2, 3]     "$[1,2,3]";
std:assert_eq $[1, 2, 3] &> str     "$[1,2,3]";

The arguments passed to the function are accessible using the _, _1, _2, …, _9 variables. If you need to access more arguments the @ variable holds a vector of all arguments.

!twoify = { _ * 2 };

std:assert_eq twoify[2] 4;

!twoify2 = \_ * 2;

std:assert_eq twoify2[2] 4;

std:assert_eq ({ _ * 2 } 2) 4;

If you want to name arguments, you can use the destructuring assignment syntax:

!add = {!(a, b) = @;
    a + b
};

std:assert_eq add[1, 2] 3;

2.3 - Function arity checks

Functions check the number of arguments passed to them. The compiler tries to infer the number of arguments the function requires by looking at the parameter variables _ to _9 and @. If the compiler gets it wrong, you can:

  • Define minimum and maximum number of arguments with: {|min < max| ... }
  • Define exact number of arguments with: {|num_of_args| ... }
  • Accept any number of arguments: {|| ... }

For the shortened function syntax there is:

  • \|min < max| ...
  • \|num_of_args| ...
  • \|| ...

Here an example:

!dosomething = {|2 < 4| !(a, b, c, d) = @;

    (is_none c) { a + b } { a * b + c * d }
};

std:assert_eq dosomething[1, 2]         3;
std:assert_eq dosomething[2, 2, 3, 4]  16;
2.3.1 - std:to_no_arity function

This function disables all arity checks of a function. Use this with care and diligence.

!f = { _ }; # accepts exactly 1 param

!f2 = std:to_no_arity f;

std:assert_eq (f2 1 2 3) 1;

2.4 - Calling fields / Method calling

If you use the ‘.’ for accessing fields in a map, the object the most recent field is accessed of is passed to the called function. The object the function/method was called upon can be accessed using the special value ‘$self’.

!some_map = ${
    some_func = { $self.a_value },
    a_value = 11,
};

std:assert_eq some_map.some_func[] 11;

This in combination with the special key '_proto' can be used to implement a basic form of object orientation with prototype inheritance.

It can also be combined with the closure OOP approach or used for other purposes.

You can also use a vector/list as object, in that case the _proto field that holds the class method map is the first element of the vector. The second element of the vector can be accessed using $data.

2.4.1 - The $self and $data special variables

If you call a method using the dot ., and the value on the left side is a map or vector, you will get the map or vector by $self. However, if you define a _data key on the map, or put something in the second element of the vector, you can refer to it using $data.

You can use this to refer to the members and other functions of a structure:

!new_ab_struct = {
    ${
        _data = ${ a = 1, b = 2 },
        inc_a = { $data.a += 1; },
        inc_b = { $data.b += 1; },
        a = { $data.a },
        b = { $data.b },
        inc_both = {
            $self.inc_a[];
            $self.inc_b[];
        },
    }
};

!ab = new_ab_struct[];

ab.inc_a[];
std:assert_eq ab.a[] 2;

ab.inc_b[];
std:assert_eq ab.b[] 3;

ab.inc_both[];
std:assert_eq ab.a[] 3;
std:assert_eq ab.b[] 4;

The next seconds show how this can be used to do prototyped object oriented programming.

2.4.2 - Object Oriented Programming with Prototypes

Instead of using closures for OOP the preferred way is to use maps of functions as classes and form an inheritance hierarchy by using the '_proto' key of a map:

!class_a = ${
    new = { ${ _proto = $self } },
    generate = { "I am A" },  # A method
};

!a_instance = class_a.new[];

std:assert_eq a_instance.generate[] "I am A";

The special key '_data' can be used (and is encouraged to be used) as storage for data members of your objects. This is useful to separate method name space inside objects from the data member namespace. To quickly access the data members you can use the special value $data, which will evaluate to $self._data in case $self is a map, and to $self.1 in case $self is a vector.

Here is an example with a map and data:

!class_b = ${
    new = {
        ${
            _proto = $self, # $self is class_b
            _data = ${
                a = 10
            },
        }
    },
    gen  = { _ * $data.a },     # $data is equivalent to `$self._data` here
    gen2 = { _ * $self._data.a },
};

!inst = class_b.new[];

std:assert_eq inst.gen[2] 20;
std:assert_eq inst.gen2[2] 20;

You can also use vectors as objects, which can be beneficial as they are a bit slimmer and access to _proto and _data are reduced to a single vector index lookup instead of an array lookup.

!class_b = ${
    new = {
        $[  # return a vector
            $self, # $self is class_b
            ${ a = 10 },
        ]
    },
    gen  = { _ * $data.a },     # $data is equivalent to `$self.1` here
    gen2 = { _ * $self.1.a },
};

!inst = class_b.new[];

std:assert_eq inst.gen[3] 30;
std:assert_eq inst.gen2[4] 40;
2.4.3 - Object Oriented with Prototypes and Inheritance

You can inherit functionality from a different class by assigning it to the prototype of the class itself.

!SuperClass = ${
    init_super_class = {
        $data.inc = 0;
    },
    inc = {
        $data.inc += 1;
        $data.inc
    },
};

Please notice, that SuperClass does not have it’s own constructor, instead you should define a custom init function like init_super_class, to define the used fields. The SuperClass will refer to the $data of the object that is going to be created by MyClass in the next step.

!SuperClass = ${
    init_super_class = {
        $data.inc = 0;
    },
    inc = {
        $data.inc += 1;
        $data.inc
    },
};

!MyClass = ${
    _proto = SuperClass,
    new = {
        !self = ${
            _proto = $self,
            _data = ${ other = 10 },
        };
        self.init_super_class[];
        self
    },
    get_other = { $data.other },
    get_inc = { $data.inc },
};

!my_obj = MyClass.new[];

std:assert_eq my_obj.get_other[] 10;
std:assert_eq my_obj.inc[] 1;
std:assert_eq my_obj.inc[] 2;
std:assert_eq my_obj.inc[] 3;

std:assert_eq my_obj._data.other 10;
std:assert_eq my_obj._data.inc   3;
2.4.4 - Object Oriented with Prototypes and $self References and $data References

There might come a time, when you want to pass a reference of your object around, but you want to prevent cyclic references. For this you will need to return a strong reference $&& from your constructor as $self and if you want to refer to $data from callback functions, you are advised to also wrap it into a strong reference.

!destroyed = $false;

!MyClass = ${
    new = {
        $&& ${
            _proto = $self,
            _data  = $&& ${
                x = 1
            },
            dropper = std:to_drop { .destroyed = $t; },
        }
    },
    inc_x = { $data.x += 1 },
    install_on = {!(callchain) = @;
        !self = $w& $self;
        std:push callchain { self.inc_x[]; };
    },
    install_getter = {!(callchain) = @;
        !data = $w& $data;
        std:push callchain { data.x };
    },
};

!my_obj = MyClass.new[];

my_obj.inc_x[];

!chain = $[];
my_obj.install_on     chain;
my_obj.install_getter chain;


std:assert_eq my_obj._data.x 2;
chain.0[]; # calls my_ocj.inc_x[];
std:assert_eq my_obj._data.x 3;

std:assert_eq chain.1[] 3;

!my_obj = $n; # destroy only strong reference
std:assert destroyed;

Of course the callbacks now refer to $none to call inc_x, a more sophisticated way of cleanup is of course necessary. But this is just an example.

2.5 - Function call composition

  • chaining
  • traditional () call syntax
  • ~ syntax
  • || syntax

$[] || push 10 $[10] $[] || push 10 || push 20 $[10,20] !x = { push _1 _ }; $n $[] | x 10 | x 20 $[10,20]

  • […] syntax
2.5.1 - ‘|’ Tail Argument Function Chaninig

This syntax is useful if you have following function call composition:

(fn arg1 arg2 (fn2 arg_b1 arg_b2 (fn3 arg_c1 arg_c2 ...)))

These can be written more comfortably like this:

fn3 arg1 arg2 | fn2 arg_b1 arg_b2 | fn arg1 arg2

An example with actual values:

!x = 10 | { _ * 4 } | { _ + 2 };

std:assert_eq x 42;

Think of it as if the value 10 was piped through the functions on the right.

The call reordering of the | operator looks like this:

    fn1 a1 a2 | fn2 b1 b2 (   )   =>   fn2 b1 b2 (fn1 a1 a2)
    """""""""               ^
        v                   |
        --------------------|
2.5.2 - ‘|>’ Left Hand Function Chaining

This syntax is useful if you want to make deep call chains like these:

(((fn arg1 arg2 ...) arg_b1 arg_b2 ...) arg_c1 arg_c2 ...)

These can be written more comfortably like this:

fn arg1 arg2 |> arg_b1 arg_b2 |> arg_c1 arg_c2

or nicer formatted:

fn arg1 arg2
    |> arg_b1 arg_b2
    |> arg_c1 arg_c2

Here an actual example:

!res = $@v
    1 + 1
    |> $["abc", "def", "ceg"]
    |> { $+ ~ std:str:cat "|" _ "|" };

std:assert_eq res.0 "|c|";
std:assert_eq res.1 "|e|";
std:assert_eq res.2 "|g|";

The call reordering of the |> operator looks like this:

    fn1 a1 a2 |> b1 b2    =>   ((   )   )
    """""""""    """""            ^   ^
        v          v              |   |
        -----------|--------------|   |
                   -------------------|
2.5.3 - Forward Argument Pipe arg &> fun

This operator has the highest precedence over all other operators and is used to be able to write this:

if "foob" &> $r/f(^*)b/ {
    std:assert_eq $\.1 "oo";
} {
    std:assert $false;
}

That means f a &> b is equivalent to writing f[b[a]] or (f (b a)). Chaining multiple is also possible and left associative: a &> b &> c is (c (b a)). You can see it as piping operation:

!r = "ABC" &> std:str:to_lowercase &> \std:str:pad_start 10 "0" _;

std:assert_eq r "0000000abc";
2.5.4 - Forward Argument Apply Pipe list &@> fun

This operator is like &>. But it will call the function with the elements in the given list as arguments.

That means: list &@> fun is equivalent to fun[[list]].

std:assert_eq $[2, 5] &@> `+`   7;
2.5.5 - Reverse Argument Pipe fun <& arg

Like the &> operator this operator, but it has a lower precedence (does not bind as strongly as &>) and is right associative. That means you can write this:

!r = (\std:str:pad_start 10 "0" _) <& std:str:to_lowercase <& "ABC";

std:assert_eq r "0000000abc";

That means, writing f <& a <& x becomes f[a[x]] or (f (a x)).

2.5.6 - Reverse Argument Apply Pipe list &@> fun

This operator is like <&. But it will call the function with the elements in the given list as arguments.

That means: fun <@& list is equivalent to fun[[list]].

std:assert_eq `+` <@& $[2, 5]   7;

2.6 - Control Flow - Returning

WLambda uses labelled blocks for control flow, as returning from the current function would not be very helpful for the control flow in wlambda in case of conditional execution using the boolean calling semantics.

!some_func = \:outer {
    !x = 10;


    (x == 10) {
        return :outer 20
    };

}
2.6.1 - return [label] value

Returns value from the current function if no label is given. If label is given, the call stack will unwind until either a block or a function with the given label is encountered.

!f = {
    10;
    return 20;
    30
};

std:assert_eq f[] 20;

Here an example for unwinding two call frames:

!f = \:x {
    10;
    { return :x 20 }[];
    30;
};

std:assert_eq f[] 20;

The labels do not adhere to lexical scoping and are dynamically scoped:

!g = { return :x 30 };

!f = \:x { 20; g[]; 40 };

std:assert_eq f[] 30;
2.6.2 - block [label] function

Calls the function with the given label for returnto jump to.

If you just want to setup a point inside a function to jump to with return the block function is more convenient to use:

!y = 1;

!res = block :x {
    .y = y + 1;
    (y >= 2) \return :x 20;
    .y = y + 1;
    .y = y + 1;
};

std:assert_eq res 20;

The alternative is the less clear syntax would be in this case:

!y = 1;

!res = \:x {
    .y = y + 1;
    (y >= 2) \return :x 20;
    .y = y + 1;
    .y = y + 1;
}[];

std:assert_eq res 20;
2.6.3 - std:to_drop function (or RAII, Destructors or Drop Functions)

You can create a function that is called when it is dropped/its reference count goes to 0.

!dropped = $false;

!x = std:to_drop { .dropped = $true; };

std:assert not[dropped];

.x = $none;

std:assert dropped;

Please note, that the drop function will be executed in a newly constructed default EvalContext, this means there is some overhead and that the EvalContext dependent results of std:eval might be different.

2.6.4 - std:time:now [unit]

Returns the current system time since the UNIX epoch (1970-01-01 00:00:00 UTC). If no unit is specified, the default is :ms. Following units are available:

  • seconds: "s"
  • milliseconds: "ms"
  • microseconds: "us"
  • nanoseconds: "ns"
std:assert (std:time:now :s)  > 1000;
std:assert (std:time:now :ms) > 1000;
std:assert len[str[std:time:now :ns]] > 18;
2.6.5 - std:srand [seed]

With this function you can seed the internal pseudo random number generator based on an unspecified PRNG algorithm, that might or might not change in the next WLambda version. If no seed is provided, the current system time (in ns resolution) is used. If seed is provided, it is set to the integer value of that.

std:srand[];

std:srand 1000;
2.6.6 - std:rand [max-or-mode]

Returns a random number between 0 and max. The interval 0 to max-or-mode is closed/open, that means 0 is included but max-or-mode is not included.

If max-or-mode is a string "i64" or symbol :i64, then std:rand will return a random signed 64 bit integer.

If max-or-mode is not provided, a float number between 0.0 and 1.0 (including 0.0 but not including 1.0) is returned.

std:srand 1234567890;

!zeros = $@i iter i 0 => 1000 {
    if std:rand[100] == 0 \$+ 1;
};

!count_100 = $@i iter i 0 => 1000 {
    if std:rand[100] == 100 \$+ 1;
};

std:assert zeros     >  0;
std:assert count_100 == 0;

std:assert std:rand[] < 1.0;
std:assert std:rand[] >= 0.0;

Please note: The PRNG algorithm used for std:rand may change without further notice. If you require your project to have consistent PRNG results across all WLambda versions use std:rand:split_mix64_*.

2.7 - Function utilities

2.7.1 - is_fun value

Returns $true if value is a function.

std:assert ~ is_fun {};
std:assert ~ is_fun is_fun;
std:assert ~ not ~ is_fun ${a=10};

3 - Data Types

3.1 - None sentinel value: $n or $none

This is a special sentinel value that is returned by functions and when a non existing field of a datastructure is accessed. It’s semantic meaning is that there is no value.

Most functions that expect a string value will turn a $none into an empty string. If you need an unambigous representation use std:ser:wlambda for dumping WLambda data structures.

Please note for API design: In case of errornous states you should not return a $none but an $error value.

std:assert ~ $n                  == $none;
std:assert ~ int[$n]             == 0;
std:assert ~ float[$n]           == 0.0;
std:assert ~ str[$n]             == "";
std:assert ~ std:ser:wlambda[$n] == "$n";
std:assert ~ is_none[$n];
3.1.1 - is_none value

Returns $true if value is $none or $o().

std:assert ~ is_none $none;
std:assert ~ is_none $o();
std:assert ~ not ~ is_none $false;
std:assert ~ not ~ is_none $o(10);
3.1.2 - is_some value

Returns $true if value is anything except $none or $o().

std:assert ~ not ~ is_some $none;
std:assert ~ not ~ is_some $o();
std:assert ~ is_some $false;
std:assert ~ is_some 30;
std:assert ~ is_some $o(30);

3.2 - Optional values $o() and $o(...)

An optional value can either contain another value, or contain no value at all. An empty optional value is not much different from $none, but it is sometimes desirabel to make a difference between an optional value and a $none value if the $none value is used as sentinel value.

Optional values were introduced for functions that lookup stuff and either return something that might be $none (eg. if some element in a vector is searched for), or return that nothing was found.

The functions is_none and is_some like stated above work for these optional values too:

std:assert ~ is_none $o();
std:assert ~ is_some $o(10);
std:assert ~ is_some $o($none);
std:assert ~ is_some $o($o());

Calling an optional value will return it’s contents or $none:

std:assert_eq $o()[]     $none;
std:assert_eq $o(10)[]   10;

!do_something = {
    if _ == 0 {
        $o()
    } {
        $o(_ + 10)
    }
};

!result = do_something 11;
std:assert_eq result[] 21;

!result = do_something 0;
std:assert_eq result[] $none;

In a boolean context an optional becomes $true if it contains something and $false if it has nothing.

std:assert ~ not ~ bool $o();
std:assert ~ bool $o(10);

!x = $o();
!res1 = if x "something" "nothing";
std:assert_eq res1 "nothing";

.x = $o(30);
!res2 = if x "something" "nothing";
std:assert_eq res2 "something";

Many other operations are just forwarded to the contents of the optional value:

std:assert_eq $o(33) + 44    77;

!x = $o($[1,2,3]);
std:push x 4;
std:assert_eq (str x) (str $[1,2,3,4]);

std:assert_eq (float $o(4.4))   4.4;
std:assert_eq (int $o(4.4))     4;

An optional value can also be dereferenced:

std:assert_eq $*$o()    $none;
std:assert_eq $*$o(10)  10;

Calls with more than zero arguments are forwarded to the contents:

std:assert_eq ($o("xx") "yy")  "xxyy";

!x = { _ * 20 };
std:assert_eq ($o(x) 30)    600;
3.2.1 - is_optional value

Returns $true if value is an optional value. That means either $o() or $o(...).

std:assert ~ is_optional $o();
std:assert ~ is_optional $o($none);
std:assert ~ is_optional $o(10);

std:assert ~ not ~ is_optional $true;
std:assert ~ not ~ is_optional $none;
std:assert ~ not ~ is_optional $false;
std:assert ~ not ~ is_optional 303;
3.2.2 - Unwrapping optionals

You can unwrap an optional with unwrap. It will panic if there is no value provided. Otherwise it will return the contents.

std:assert_eq unwrap[$o(10)] 10;

3.3 - Error values: $e expr or $error expr

There are no exceptions in WLambda, except the panic, that halts all execution of the currently running WLambda program. To signal errors, you return an $error value.

These error values, if not handled, will cause a panic of your program. This means, you need to handle returned error values one way or the other.

The error value wraps any value you pass to the $error or $e constructor syntax.

std:assert ~ is_err ~ $e "something went wrong!"

There are more routines except is_err to handle an error. _? will return from the currently executed function up until some given label. on_error executes a function if the second argument was an error value. Otherwise it just passes through the value. unwrap will explicitly cause an panic if an error value was passed to it. All other values will be passed through. And unwrap_err unwraps an error value, it’s the opposite of unwrap because it will cause a panic if you don’t pass an error value.

Most functions don’t accept errors in their arguments. If an error is encountered, a panic will occur. There are only a few functions that accept error values in their arguments:

  • panic
  • _?
  • unwrap_err
  • std:error_to_str
  • unwrap
  • on_error
  • return
  • break
  • bool
  • type
  • match
  • assert
  • assert_eq
  • is_some
  • is_none
  • is_err
  • is_map
  • is_vec
  • is_fun
  • is_str
  • is_wref
  • is_ref
  • is_bool
  • is_bytes
  • is_sym
  • is_float
  • is_optional
  • is_int
  • ==
  • !=
  • std:to_ref
  • std:ref_id
  • std:ser:wlambda

All other functions don’t accept errors as their argument.

3.3.1 - _? [label] value

Unwind the call stack from the current function to a given label if value is an error value. If no label is given only the current function is returned from with the error value. If there is no error, the given value is returned.

The best usecase is, if you just want to hand any errors that might be returned further upwards the call stack for the parent functions to handle.

!func = { $e "this failed!" };

!other = {

    _? func[]; # If you would not catch the error value here,


    panic "this will never be reached!";

};

std:assert ~ (unwrap_err other[]) == "this failed!";

_? can take up to 2 arguments. If so, the first argument is interpreted as jump label. That is handy if you want to jump up multiple call frames:

!failing_func = { $e :FAIL };

!func = \:some_unique_label {
    ( _ == 42 ) {
        std:displayln "We got 42!";

        !val = _? :some_unique_label failing_func[];

        std:displayln "Returned:" val;
    }
};

std:assert_eq (unwrap_err ~ func 42) :FAIL;

A more elaborate example:

!do_fail = $false;

!maybe_fails1 = { 10 };
!maybe_fails2 = {
    do_fail { $error "something is wrong" }
            { .do_fail = $true; 2 };
};

!a = {
    !x = _? maybe_fails1[];
    .x = x + (_? maybe_fails2[]);
    x
};

!first  = a[];
!second = a[];

std:assert_eq first 12;
std:assert (is_err second);
3.3.2 - unwrap value

Unwraps the given value. If the value is an error object it will panic. Otherwise it will just return the given value. If the value is an optional value, it will return the value that is wrapped in the optional value. If it is an empty optional, it will also panic.

Here an demonstration of the unwrap panic:

match (std:eval $code { unwrap $e XXX })
    ($e err) => {
        std:assert ~ std:str:find "Variable 'XXX' undefined" $\.err;
    }
    { std:assert $false };

And here how to unwrap optionals:

std:assert_eq (unwrap $o(123))  123;

match (std:eval $code { unwrap $o() })
    ($e err) => {
        std:assert ~ std:str:find "unwrap empty option" $\.err;
    }
    { std:assert $false };
3.3.3 - unwrap_err error-value

Unwraps an error value. Does panic if error-value is not an error value. If it is an error value, the inner wrapped value is returned.

!v = unwrap_err $e "Some Error";

std:assert_eq v "Some Error";
3.3.4 - on_error handler maybe-error-value

The first parameter to on_error should be a handler function, which will be called with four parameters. The first of these parameters is the error text, followed by the line number, column number and file name from which the error originates.

The given handler is called when an error value is encountered as second argument, the maybe-error-value.

An example to demonstrate the handler arguments:

on_error {!(func, line, col, filename) = @;
} ($e "test");

A usage example:

!func = {
    (_ == 13) {
        $e "this failed!"
    } {
        "all ok!"
    }
};

!x = $n;

on_error {|4| .x = _; } ~ func 13;
std:assert_eq x "this failed!";

!ret = on_error {|4| .x = _; } ~ func 1;
std:assert_eq ret "all ok!";
3.3.5 - is_err value

Returns $true if value is an error value.

std:assert ~ is_err $e "foo";
std:assert ~ not ~ is_err $none;
std:assert ~ not ~ is_err 10;
3.3.6 - std:error_to_str value

This function accepts an error value in contrast to str, but does not panic but transform the error value into its string representation.

!r = std:error_to_str $e "TEST";

std:assert_eq r "$e \"TEST\" [@ <wlambda::eval>:1:26 Err]";

WARNING: The string representation might change between wlambda versions. Please use on_error to access the individual parts (line, column, filename, error value) of the error.

3.4 - Booleans

True and false are represented by $t and $f or $true and $false, whatever suits your coding style better.

You can either use a boolean value with one or two arguments, where $true will call the first argument, and $false the second argument. If a second argument isn’t provided and the value is $false, $none is returned. So to check for truthness you can just do:

!x = 10;
!some_num =
    (x == 10) { "it is ten" } { "it is not ten" };

std:assert_eq some_num "it is ten";

.x = 20;
.some_num =
    (x == 10) { "it is ten" } { "it is not ten" };
std:assert_eq some_num "it is not ten";
3.4.1 - is_bool any-value

You can check if something is a boolean with is_bool:

std:assert ~ is_bool $true;
std:assert ~ is_bool $false;
std:assert ~ not[is_bool $n];
std:assert ~ not[is_bool ""];
std:assert ~ not[is_bool 0];
3.4.2 - bool any-value

You can cast any-value into a boolean with the bool function:

std:assert_eq (bool 1)          $true;
std:assert_eq (bool 0)          $false;
std:assert_eq (bool $e :x)      $false;
std:assert_eq (bool $n)         $false;
std:assert_eq (bool "")         $false;
std:assert_eq (bool "0")        $false;
std:assert_eq (bool "1")        $true;
std:assert_eq (bool :0)         $false;
std:assert_eq (bool :1)         $true;
std:assert_eq (bool 0.0)        $false;
std:assert_eq (bool 0.1)        $false;
std:assert_eq (bool 1.0)        $true;
std:assert_eq (bool {})         $true;
std:assert_eq (bool $b"")       $false;
std:assert_eq (bool $b"\x00")   $false;
std:assert_eq (bool $b"\x01")   $true;
3.4.3 - not value

This function negates the boolean value. If it is not a boolean, it will be casted into one before negating.

std:assert ~ not $false;
std:assert ~ not 0;
std:assert ~ not $none;
3.4.4 - Boolean List Indexing

Booleans can also be used to pick a value from a list by calling the boolean with a list as first argument:

std:assert_eq ($true  $[:a, :b]) :b;
std:assert_eq ($false $[:a, :b]) :a;

3.5 - 64-Bit Integers

WLambda’s most basic numeric data type is the 64-Bit integer, aka i64 in Rust. Like with other numbers multiple radix literal forms are supported:

std:assert_eq 10r99         99;

std:assert_eq 0xFF01        65281;

std:assert_eq  0b1011       11;
std:assert_eq -0b1011      -11;

std:assert_eq 4r31          13;
3.5.1 - int value

Returns the integer casted version of value. Mostly interesting for converting a string to an integer (in radix 10) or for getting the truncated value of a float.

std:assert_eq (int 4.2)         4;
std:assert_eq (int "402")       402;
std:assert_eq (int "a3")        0;

std:assert_eq (int $b"@")       0x40;   # Returns the byte value of the first char

std:assert_eq (int $[4,4,4])    3; # Same as `len`
std:assert_eq (int ${a=4,b=4})  2; # Same as `len`
3.5.2 - is_int value

Returns $true if value is of data type integer. Otherwise it returns $false.

3.5.3 - std:neg_i64 integer

Negates the integer, which makes a negative from a positive and positive from a negative number.

std:assert_eq (std:neg_i64 -1)      1;
std:assert_eq (std:neg_i64 1)      -1;

std:assert_eq (std:neg_i64 0xFF)  -255;
3.5.4 - std:not_i64 integer

Flips the bits of the signed 64-Bit integer.

std:assert_eq (std:not_i64 -1)      0;
std:assert_eq (std:not_i64 1)      -2;

std:assert_eq (std:not_i64 0xFF)  -256;
3.5.5 - std:neg_u32 integer

Negates the integer as if it was an unsigned 32-Bit integer.

std:assert_eq (std:neg_u32 0xFF)   4294967041;
std:assert_eq (std:neg_u32 0x1)    4294967295;
std:assert_eq (std:neg_u32 0x0)    0;
3.5.6 - std:not_u32 integer

Flips the bits of the integer as if it was an unsigned 32-Bit integer.

std:assert_eq (std:not_u32 0xFF)   4294967040;
std:assert_eq (std:not_u32 0x1)    4294967294;
std:assert_eq (std:not_u32 0x0)    4294967295;

3.6 - 64-Bit Floats

WLambda supports 64-Bit floating point numbers, aka f64 in Rust. Like with other numbers multiple radix literal forms are supported:

std:assert_eq 10r9.92       9.92;

std:assert_eq 0xFF.1        255.0625;

std:assert_eq 0b1011.101    11.625;

std:assert_eq 4r3.3         3.75;
3.6.1 - float value

This function casts value into a float:


std:assert_eq (float 10)       10.0;
std:assert_eq (float $t)        1.0;
std:assert_eq (float $f)        0.0;
std:assert_eq (float :"32.2")  32.2;
std:assert_eq (float "5.42")   5.42;
std:assert_eq (float "5.42")   5.42;
std:assert_eq (float $b"\xFF") 255.0;
3.6.2 - is_float value

Returns $true if value is a float, otherwise $false is returned.

std:assert ~ is_float 4.4;
std:assert ~ is_float 1.0 + 1;
std:assert ~ not ~ is_float 1 + 1.0;
std:assert ~ not ~ is_float 4;
std:assert ~ not ~ is_float $true;
3.6.3 - std:num:acos float

Computes the arccosine of a number. Return value is in radians in the range [0, pi] or NaN if the number is outside the range [-1, 1].

3.6.4 - std:num:acosh float

Inverse hyperbolic cosine function.

3.6.5 - std:num:asin float

Computes the arcsine of a number. Return value is in radians in the range [-pi/2, pi/2] or NaN if the number is outside the range [-1, 1].

3.6.6 - std:num:asinh float

Inverse hyperbolic sine function.

3.6.7 - std:num:atan float

Computes the arctangent of a number. Return value is in radians in the range [-pi/2, pi/2].

3.6.8 - std:num:atan2 y x

Computes the four quadrant arctangent of y and other x in radians.

  • x = 0, y = 0: 0
  • x >= 0: arctan(y/x) -> [-pi/2, pi/2]
  • y >= 0: arctan(y/x) + pi -> (pi/2, pi]
  • y < 0: arctan(y/x) - pi -> (-pi, -pi/2)
3.6.9 - std:num:atanh float

Inverse hyperbolic tangent function.

3.6.10 - std:num:cbrt float

Takes the cubic root of a number.

3.6.11 - std:num:ceil float

Returns the smallest integer (still a float) greater than or equal to a number.

3.6.12 - std:num:cos float

Computes the cosine of a number (in radians).

3.6.13 - std:num:cosh float

Hyperbolic cosine function.

3.6.14 - std:num:exp float

Returns e ^ float, (the exponential function).

3.6.15 - std:num:exp2 float

Returns 2 ^ float.

3.6.16 - std:num:exp_m1 float

Returns (e ^ float - 1) in a way that is accurate even if the number is close to zero.

3.6.17 - std:num:floor float

Returns the largest integer (still as float) less than or equal to a number.

3.6.18 - std:num:hypot y x

Calculates the length of the hypotenuse of a right-angle triangle given legs of length x and y.

3.6.19 - std:num:ln float

Returns the natural logarithm of the number.

3.6.20 - std:num:log float

Returns the logarithm of the number with respect to an arbitrary base.

The result may not be correctly rounded owing to implementation details; std:log2 can produce more accurate results for base 2, and std:log10 can produce more accurate results for base 10.

3.6.21 - std:num:log10 float

Returns the base 10 logarithm of the number.

3.6.22 - std:num:log2 float

Returns the base 2 logarithm of the number.

3.6.23 - std:num:pow float

Raises a number to a floating point power. You may also use the ^ operator, which also works for integers.

3.6.24 - std:num:recip float

Takes the reciprocal (inverse) of a number, 1/x.

3.6.25 - std:num:round float

Returns the nearest integer (still a float) to a number. Round half-way cases away from 0.0.

3.6.26 - std:num:sin float

Computes the sine of a number (in radians).

3.6.27 - std:num:sinh float

Hyperbolic sine function.

3.6.28 - std:num:sqrt float

Takes the square root of a number.

3.6.29 - std:num:tan float

Computes the tangent of a number (in radians).

3.6.30 - std:num:tanh float

Hyperbolic tangent function.

3.6.31 - std:num:to_degrees float

Converts radians to degrees.

3.6.32 - std:num:to_radians float

Converts degrees to radians.

3.6.33 - std:num:trunc float

Returns the integer part of a number.

3.6.34 - std:num:lerp a b x

Linear interpolation between a and b by x. Where x is in the range of [0.0, 1.0].

!res = int ~ std:num:lerp 0.0 100.0 0.5;

std:assert_eq res 50;
3.6.35 - std:num:smoothstep a b x

Interpolates smoothly from 0.0 to 1.0 where x is in the range of [a, b].

!res = int ~ 1000.0 * (std:num:smoothstep 0.0 100.0 10.0);

std:assert_eq res 28;
3.6.36 - std:num:fract float

Returns the fractional part of the floating point number float.

std:assert ((std:num:fract 4.25) - 0.25) < 0.00001

3.7 - Numeric Functions

These functions work for all types of numbers.

3.7.1 - std:num:abs number

Takes the absolute value of number. If number is not a number it will be converted into an integer.

std:assert_eq (std:num:abs -10)     10;
std:assert_eq (std:num:abs -13.3)   13.3;
3.7.2 - std:num:signum number

Returns either 1 or -1, depending on the sign of the given number.

std:assert_eq (std:num:signum -4)  -1;
std:assert_eq (std:num:signum  4)   1;

std:assert_eq (std:num:signum -4.0)  -1.0;
std:assert_eq (std:num:signum  4.0)   1.0;
3.7.3 - std:num:int_to_closed_open01 integer

Transforms the given 64-Bit integer into a number in the range 0.0 to 1.0. Inclusive 0.0, exclusive 1.0. This function is mainly useful if you generated the integer from a random number generator.

std:assert_rel_eq (std:num:int_to_closed_open01 0)  0.0         0.00000001;
std:assert_rel_eq (std:num:int_to_closed_open01 -1) 0.999999999 0.00000001;
3.7.4 - std:num:int_to_open01 integer

Transforms the given 64-Bit integer into a number in the range 0.0 to 1.0. Exclusive 0.0, exclusive 1.0. This function is mainly useful if you generated the integer from a random number generator.

std:assert (std:num:int_to_open01 0)  > 0.0;
std:assert (std:num:int_to_open01 -1) < 1.0;
3.7.5 - std:num:int_to_open_closed01 integer

Transforms the given 64-Bit integer into a number in the range 0.0 to 1.0. Inclusive 0.0, inclusive 1.0. This function is mainly useful if you generated the integer from a random number generator.

std:assert (std:num:int_to_open_closed01 0)  > 0.0;
std:assert (std:num:int_to_open_closed01 -1) == 1.0;

3.8 - Numerical Mathematical Vectors

In order to aid in the development of GUIs, games, and other physics/geometry adjacent software, WLambda comes with a built in datatype for mathematical vectors, which can contain floats and integers and have between two and four dimensions.

std:assert ~ $i(-1, 2).y                == 2;
std:assert ~ (ivec ${z=3})              == $i(0,0,3);
std:assert ~ (ivec4 $[])                == $i(0,0,0,0);
std:assert ~ $i(1.49, -2.72)            == $i(1,-2);
std:assert ~ $f(1.00, -33).x            == $f(1, 200).first;
std:assert ~ $f(-0, 2.4).y              == $f(1.6, 2.4).second;
std:assert ~ (fvec3 ${w=0.1})           == $f(0,0,0);
std:assert ~ (fvec3 $i(1, 2))/10        == $f(0.1, 0.2, 0);
std:assert ~ (ivec2 $f(1.3, 2.7, -5.8)) == $i(1, 2);
std:assert ~ (ivec $f(1.3, 2.7, -5.8))  == $i(1, 2, -5);
3.8.1 - Vector Conversions

There are eight functions for converting other values into vectors and vectors of integers into vectors of floats:

  • ivec
  • ivec2
  • ivec3
  • ivec4
  • fvec
  • fvec2
  • fvec3
  • fvec4

The functions without a dimension suffix fill in as many dimensions as are present in the object being converted. The functions with dimension suffixes fill in any missing dimensions with 0s and ignore dimensions as necessary.

NOTE: ivec will always truncate (i.e. round down) floats into integers when converting, just like when converting floats into integers implicitly elsewhere in WLambda.

3.8.2 - Vector Component Access

There are 12 functions for accessing the components of vectors, but only four have unique behavior (the rest are aliases).

  • x/r/h/0/first,
  • y/g/s/1/second,
  • z/b/v/2/third,
  • w/3/fourth
!my_vec = $f(39.3, 404.504, 333.8);
std:assert_eq my_vec.x my_vec.0;
std:assert_eq my_vec.x my_vec.first;

std:assert_eq my_vec.y my_vec.1;
std:assert_eq my_vec.y my_vec.second;

std:assert_eq my_vec.z my_vec.2;
std:assert_eq my_vec.z my_vec.third;

std:assert_eq my_vec.w my_vec.3;
std:assert_eq my_vec.w my_vec.fourth;
3.8.3 - Named Field Access and Swizzling

You can access the fields of numeric vectors with different keys:

std:assert_eq $i(2,3,4,5).x     2;
std:assert_eq $i(2,3,4,5).y     3;
std:assert_eq $i(2,3,4,5).z     4;
std:assert_eq $i(2,3,4,5).w     5;

std:assert_eq $i(5,6,7,8).r     5;
std:assert_eq $i(5,6,7,8).g     6;
std:assert_eq $i(5,6,7,8).b     7;
std:assert_eq $i(5,6,7,8).a     8;

std:assert_eq $i(5,6,7,8).h     5;
std:assert_eq $i(5,6,7,8).s     6;
std:assert_eq $i(5,6,7,8).v     7;
std:assert_eq $i(5,6,7,8).a     8;

std:assert_eq $i(5,6,7,8).0     5;
std:assert_eq $i(5,6,7,8).1     6;
std:assert_eq $i(5,6,7,8).2     7;
std:assert_eq $i(5,6,7,8).3     8;

You can also use swizzling to quickly make a new vector:

std:assert_eq $i(2,3,4).xx      $i(2,2);
std:assert_eq $i(2,3,4).xyxz    $i(2,3,2,4);
std:assert_eq $i(2,3,4).bgr     $i(4,3,2);
std:assert_eq $i(2,3).xyrg      $i(2,3,2,3);
std:assert_eq $i(2,3,4,5).zw    $i(4,5);
3.8.4 - Euler Addition/Subtraction

You can add vectors to each other and subtract them from each other.

The type of the resulting vector will be the same as the vector on the left.

The number of dimensions in the resulting vector will be the same as the vector with the highest number of dimensions that was involved in the operation.

If the value on the right isn’t a vector, it will be converted into one, just as if it were passed through ivec or fvec, meaning that as many dimensions are kept as are present.

std:assert_eq[ $i(0.1, 0.9) + $i(1, 0) , $i(1, 0) ];
std:assert_eq[ $f(0.1, 0.9) + $i(1, 0) , $f(1.1, 0.9) ];
std:assert_eq[ $f(0.1, 0.9) + ${ w=7 } , $f(0.1, 0.9, 0, 7) ];
std:assert_eq[ std:v:mag2 $i(-1, 5) + $i(1, -5) , 0.0 ];
3.8.5 - Scalar Multiplication/Division

You can multiply and divide integer and float vectors by single numbers. This copies the vector, multiplies or divides each component of the vector by the single number, and returns the result.

NOTE: Dividing ivecs will always truncate (i.e. round down) floats into integers.

std:assert ~ $i(3, 6)/2       == $i(1, 3);
std:assert ~ $f(3, 6)/2       == $f(1.5, 3);
std:assert ~ $f(0.5, 0) * 1.3 == $f(0.65,0);
std:assert ~ (std:v:mag (std:v:norm $[40.19, 0.399]) * 10) == 10.0;
3.8.6 - Unary Vector Operations

Calling - on a vector returns a new vector with all of its fields negated. This is equivalent to multiplying the vector by -1.

Calling + on a vector returns a copy of the exact same vector. This is equivalent to multiplying the vector by 1.

!my_vec = $f(1.2, 2.3, 3.4);
std:assert_eq (ivec (-my_vec)) $i(-1, -2, -3);
std:assert_eq (+my_vec) my_vec;
std:assert_eq[ my_vec + (-my_vec), my_vec * 0 ];
3.8.7 - is_fvec value

Returns $true if value is a float vector.

std:assert_eq   (is_fvec $f(1,2))       $true;
std:assert_eq   (is_fvec $f(1,2,3))     $true;
std:assert_eq   (is_fvec $f(1,2,3,4))   $true;
std:assert_eq   (is_fvec $none)         $false;
std:assert_eq   (is_fvec $i(1,2))       $false;
std:assert_eq   (is_fvec $[3.4, 4.5])   $false;

std:assert_eq   (is_fvec fvec <& $[3.4, 4.5])   $true;

std:assert_eq   (is_fvec $&&$f(3,4))    $false;
std:assert_eq   (is_fvec $*$&&$f(3,4))  $true;
3.8.8 - is_ivec value

Returns $true if value is an integer vector.

std:assert_eq   (is_ivec $i(1,2))       $true;
std:assert_eq   (is_ivec $i(1,2,3))     $true;
std:assert_eq   (is_ivec $i(1,2,3,4))   $true;
std:assert_eq   (is_ivec $none)         $false;
std:assert_eq   (is_ivec $[3, 4])       $false;

std:assert_eq   (is_ivec ivec <& $[3.4, 4.5])   $true;

std:assert_eq   (is_ivec    $&& $i(3,4))  $false;
std:assert_eq   (is_ivec $* $&& $i(3,4))  $true;
3.8.9 - is_nvec value

Returns $true if value is either a numerical float or integer vector.

std:assert_eq   (is_nvec $i(1,2))       $true;
std:assert_eq   (is_nvec $i(1,2,3))     $true;
std:assert_eq   (is_nvec $i(1,2,3,4))   $true;
std:assert_eq   (is_nvec $f(1,2))       $true;
std:assert_eq   (is_nvec $f(1,2,3))     $true;
std:assert_eq   (is_nvec $f(1,2,3,4))   $true;
std:assert_eq   (is_nvec $none)         $false;
std:assert_eq   (is_nvec $[3, 4])       $false;

std:assert_eq   (is_nvec fvec <& $[3.4, 4.5])   $true;
std:assert_eq   (is_nvec ivec <& $[3.4, 4.5])   $true;
3.8.10 - nvec_len value

Returns the length of a numerical vector, commonly known as the dimension. Either 2, 3 or 4.

std:assert_eq (nvec_len $i(1, 2))       2;
std:assert_eq (nvec_len $i(1, 2, 3))    3;
std:assert_eq (nvec_len $i(1, 2, 3, 4)) 4;

std:assert_eq (nvec_len $f(1.1, 2.2))           2;
std:assert_eq (nvec_len $f(1.1, 2.2, 3.3))      3;
std:assert_eq (nvec_len $f(1.1, 2.2, 3.3, 4.4)) 4;
3.8.11 - fvec value

Will cast value into a float vector. You can cast a multitude of data types into a float vector:

std:assert_eq   (fvec  $[1,2,3,4])      $f(1,2,3,4);
std:assert_eq   (fvec  $[1,2,3])        $f(1,2,3);
std:assert_eq   (fvec  $[1,2])          $f(1,2);

std:assert_eq   (fvec $i(1,2))          $f(1,2);
std:assert_eq   (fvec $i(1,2,3))        $f(1,2,3);
std:assert_eq   (fvec $i(1,2,3,4))      $f(1,2,3,4);

std:assert_eq   (fvec $p("2", "3.4"))   $f(2,3.4);

!i = $iter $[] +> $p(3,4) +> $[5,6];
std:assert_eq   (fvec i)    $f(3,4);
std:assert_eq   (fvec i)    $f(5,6);

std:assert_eq   (fvec ${x = 1, y = 2})                 $f(1,2);
std:assert_eq   (fvec ${x = 1, y = 2, z = 3})          $f(1,2,3);
std:assert_eq   (fvec ${x = 1, y = 2, z = 3, w = 4})   $f(1,2,3,4);
3.8.12 - fvec2 value

Like fvec but always returns a 2 dimensional vector.

std:assert_eq  (fvec2 $i(3,4,5))    $f(3,4);
std:assert_eq  (fvec2 $[4,5,6,7,8]) $f(4,5);

std:assert_eq  (fvec2 ${x = 1, y = 2, z = 3, w = 4})   $f(1,2);
3.8.13 - fvec3 value

Like fvec but always returns a 3 dimensional vector.

std:assert_eq  (fvec3 $i(3,4,5))    $f(3,4,5);
std:assert_eq  (fvec3 $[4,5,6,7,8]) $f(4,5,6);

std:assert_eq  (fvec3 ${x = 1, y = 2, z = 3, w = 4})   $f(1,2,3);
3.8.14 - fvec4 value

Like fvec but always returns a 4 dimensional vector.

std:assert_eq  (fvec4 $i(3,4,5))    $f(3,4,5,0);
std:assert_eq  (fvec4 $[4,5,6,7,8]) $f(4,5,6,7);

std:assert_eq  (fvec4 ${x = 1, y = 2, z = 3, w = 4})   $f(1,2,3,4);
3.8.15 - ivec value

Will cast value into a float vector. You can cast a multitude of data types into a float vector:

std:assert_eq   (ivec  $[1,2,3,4])      $i(1,2,3,4);
std:assert_eq   (ivec  $[1,2,3])        $i(1,2,3);
std:assert_eq   (ivec  $[1,2])          $i(1,2);

std:assert_eq   (ivec $f(1.1,2.1))          $i(1,2);
std:assert_eq   (ivec $f(1.1,2.1,3.2))      $i(1,2,3);
std:assert_eq   (ivec $f(1.1,2.1,3.2,4))    $i(1,2,3,4);

std:assert_eq   (ivec $p("2", "3"))   $i(2,3);

!i = $iter $[] +> $p(3,4) +> $[5,6];
std:assert_eq   (ivec i)    $i(3,4);
std:assert_eq   (ivec i)    $i(5,6);

std:assert_eq   (ivec ${x = 1, y = 2})                 $i(1,2);
std:assert_eq   (ivec ${x = 1, y = 2, z = 3})          $i(1,2,3);
std:assert_eq   (ivec ${x = 1, y = 2, z = 3, w = 4})   $i(1,2,3,4);
3.8.16 - ivec2 value

Like ivec but always returns a 2 dimensional vector.

std:assert_eq  (ivec2 $f(3,4,5))    $i(3,4);
std:assert_eq  (ivec2 $[4,5,6,7,8]) $i(4,5);

std:assert_eq  (ivec2 ${x = 1, y = 2, z = 3, w = 4})   $i(1,2);
3.8.17 - ivec3 value

Like ivec but always returns a 3 dimensional vector.

std:assert_eq  (ivec3 $f(3,4,5))    $i(3,4,5);
std:assert_eq  (ivec3 $[4,5,6,7,8]) $i(4,5,6);

std:assert_eq  (ivec3 ${x = 1, y = 2, z = 3, w = 4})   $i(1,2,3);
3.8.18 - ivec4 value

Like ivec but always returns a 4 dimensional vector.

std:assert_eq  (ivec4 $f(3,4,5))    $i(3,4,5,0);
std:assert_eq  (ivec4 $[4,5,6,7,8]) $i(4,5,6,7);

std:assert_eq  (ivec4 ${x = 1, y = 2, z = 3, w = 4})   $i(1,2,3,4);
3.8.19 - std:v:dims vec

You can use this function to retrieve the number of dimensions in vec.

Like most other std:v functions, it will coerce whatever value is passed into it into a ivec, if that value is not a fvec.

This function always returns an integer, regardless of whether an ivec or fvec is passed in.

std:assert_eq (std:v:dims $[]) 2;
std:assert_eq (std:v:dims ${w=0}) 4;
std:assert_eq (std:v:dims $f(1,2)) (std:v:dims $i(1,2));
3.8.20 - std:v:mag2 vec

Returns the magnitude of vec, squared.

Calculating the squared magnitude is a little bit faster, so you should prefer this method where performance is paramount.

The magnitude is always a float, regardless of whether the parameter is an ivec or fvec.

std:assert_eq (std:v:mag2 ${w=4}) 16.0;
3.8.21 - std:v:mag vec

Returns the magnitude (also known as the length) of vec.

The magnitude is always a float, regardless of whether the parameter is an ivec or fvec.

std:assert_eq (std:v:mag ${w=4}) 4.0;
3.8.22 - std:v:norm vec

Returns a new vector which has a magnitude of 1, but points in the same direction as vec. Vectors with a length of one are also known as unit vectors.

Note that this still returns an ivec when used on ivecs, meaning that when used on an ivec2 only four values are possible:

  • $i(1, 0)
  • $i(-1, 0)
  • $i(0, 1)
  • $i(0, -1)

These are the only ivec2s that have a length of 1.

!p1 = fvec ${ x = 20, y = 30.5 };
!p2 = fvec ${ x = -10, y = 0.5 };

!delta = p2 - p1;
!n = std:v:norm delta;

std:assert_eq[ (std:v:mag delta) - 1, std:v:mag (p1 + n) - p2 ];
3.8.23 - std:v:dot vec1 vec2

Returns the sum of all components after multiplying each component in vec1 with the corresponding component of vec2.

This can be used to represent the “sameness” of two vectors (especially unit vectors): the degree to which they are pointing in the same direction.

Returns an integer when used on an ivec, and a float when used on an fvec.

If vec1 is an fvec, then vec2 will also be coerced into one. If vec1 isn’t an fvec, then it’s coerced into an ivec, just like the other std:v functions.

!at      = fvec ${ x = 20 , y = 30.5 };           # where you're at
!goal    = fvec ${ x = -10, y = 0.5  };           # where you want to look
!looking = std:v:rad2vec (std:num:to_radians 90); # direction you're looking in


!delta = std:v:norm goal - at;

!dir = std:v:dot delta looking;

std:assert_eq[ (dir < 0) "left" "right", "left" ];
3.8.24 - std:v:cross vec1 vec2

Returns a vector perpendicular to vec1 and vec2.

Similar to the dot product, but instead of returning a single value it returns another vector, and is only useful in three (and seven, but WLambda’s vectors don’t support so many) dimensions.

Regardless of the number of dimensions in the input vectors, this function will return a 3d vector.

!x = fvec ${x=1};
!y = fvec ${y=1};


!z = std:v:cross x y;

std:assert_eq z (fvec ${z=1});

std:assert_eq[(std:v:dot x y), (std:v:dot y z)];
std:assert_eq[(std:v:dot y z), (std:v:dot z x)];
3.8.25 - std:v:lerp vec1 vec2 t

lerp stands for linear interpolation. This function is useful when animating positions, whereas slerp is useful for animating rotations.

Creates a new vector in a new position relative to vec1 and vec2. Aside from the two reference vectors, this function also takes a variable, t, which represents how far relative to the first and second vector the new vector should be.

If t is 0, vec1 is returned. If t is 1, then vec2 is returned. If t is 0.5, the resulting vector will be halfway in between the first and second vector.

std:assert_eq[ std:v:lerp $f(1,0) $f(0,1) 0.5 , $f(0.5, 0.5) ];
std:assert_eq[ std:v:lerp $f(5,10) ${y=10} 0.75 , $f(1.25, 10) ];
std:assert_eq[ std:v:lerp $[-2,5] $[2,-5] 0.5 , $i(0, 0) ];
!a = $f(83, -49.5);
std:assert_eq[ (std:v:mag a) / 2 , std:v:mag (std:v:lerp a $[] 0.5) ];
std:assert_eq[ (std:v:mag a) * 2 , std:v:mag (std:v:lerp $f(0,0) a 2.0) ];
!b = $f(-484.58, -19);
std:assert_eq[ std:v:lerp b a 1.5 , std:v:lerp a b -0.5 ];
3.8.26 - std:v:slerp vec1 vec2 t

slerp stands for spherical linear interpolation. This function is useful when animating rotations, whereas lerp is useful for animating positions.

In most cases, you’ll want to pass in unit vectors representing rotations to slerp. You should get back unit vectors in the vast majority of cases, but if perfect accuracy is required normalizing the output of this function is suggested.

Creates a new vector in a new position relative to vec1 and vec2. Aside from the two reference vectors, this function also takes a variable, t, which represents how far relative to the first and second vector the new vector should be.

If t is 0, vec1 is returned. If t is 1, then vec2 is returned. If t is 0.5, the resulting vector will be halfway in between vec1 and vec2.

!v = std:v:slerp $f(1,0) $f(0,1) 0.5;
std:assert_rel_eq v.x 0.7071067811865476 0.000001;
std:assert_rel_eq v.y 0.7071067811865476 0.000001;

!half = (std:v:slerp $f(1,0) $f(0,1) 0.5);
!four = (std:v:slerp $f(1,0) $f(0,1) 4.5);
std:assert_rel_eq half.x four.x 0.000001;
std:assert_rel_eq half.y four.y 0.000001;
3.8.27 - std:v:vec2rad vec

Creates a rotation in radians from the x and y components of vec.

Always returns a float.

Coerces the argument into an ivec unless it’s a fvec.

std:assert_eq[ std:num:to_degrees (std:v:vec2rad ${x=1}) , 0.0 ];
std:assert_eq[ std:num:to_degrees (std:v:vec2rad ${y=1}) , 90.0 ];

!h = std:v:slerp $f(1, 0) $f(0, 1) 0.5;
std:assert_eq[ std:num:to_degrees (std:v:vec2rad h) , 45.0 ];
3.8.28 - std:v:rad2vec radians

Creates a unit vector from radians.

Always returns an fvec.

std:assert_eq[ std:v:rad2vec (std:num:to_radians 0.0) , $f(1, 0)];
std:assert_eq[ ivec (std:v:rad2vec (std:num:to_radians 90.0)), $i(0, 1)];

!h = std:v:slerp $f(1, 0) $f(0, 1) 0.5; # slerp because rotations
!r = std:v:rad2vec (std:num:to_radians 45.0);
std:assert_rel_eq r.x h.x 0.0001;
std:assert_rel_eq r.y h.y 0.0001;

3.9 - Characters and Bytes

WLambda has a data type for single characters and bytes. The lexical syntax is a character or escape sequence delimited by ':

std:assert_eq (type 'a')   "char";
std:assert_eq (type $b'a') "byte";

std:assert_eq (type '\u{40}') "char";
std:assert_eq (type $b'\x40') "byte";

std:assert_eq (char $b'\u{40}') '\u{40}';

std:assert_eq (char $b'\u{3131}') '?';

The can be used interchangeably almost everywhere. They can often also be used instead of a string, because they are handled like a single character long string.

They are useful because they do not require an extra allocation in the background. They are not boxed like strings:

std:assert_eq ("foo" $p(0, 1))  "f"; # requires allocation
std:assert_eq ("foo".0)         'f'; # requires NO allocation
3.9.1 - byte value

Converts the value to a byte. If value is a number, it must be below or equal to 255, otherwise it will result in the byte '?'.

std:assert_eq (byte 64)           $b'@';
std:assert_eq (byte 300)          $b'?';
std:assert_eq (byte "ABC")        $b'A';
std:assert_eq (byte $b"\xFF\xF0") $b'\xFF';
std:assert_eq (byte "\xFF\xF0")   $b'\xC3'; # first byte of an utf-8 sequence!
3.9.2 - char value

Converts the value to a Unicode character.

std:assert_eq (char $b'\xFF') 'ÿ';
std:assert_eq (char 0x262F)   '☯';
std:assert_eq (char "☯xyz")   '☯';
3.9.3 - is_byte value

Checks if value is of the byte data type.

std:assert (is_byte $b'X');
std:assert not[is_byte 'X'];
std:assert not[is_byte 123];
std:assert (is_byte $b"abc".0);
std:assert not[is_byte "abc".0];
std:assert not[is_byte $b"abc"];
3.9.4 - is_char value

Check if value is of the Unicode character data type.

std:assert (is_char 'X');
std:assert not[is_char $b'X'];
std:assert not[is_char 123];
std:assert (is_char "abc".0);
std:assert not[is_char $b"abc".0];
std:assert not[is_char $b"abc"];
std:assert not[is_char "abc"];
3.9.5 - std:char:to_lowercase value

Turns the value into a lower case Unicode character.

std:assert_eq (std:char:to_lowercase 'A') 'a';
std:assert_eq (std:char:to_lowercase 65)  'a';
3.9.6 - std:char:to_uppercase value

Turns the value into an upper case Unicode character.

std:assert_eq (std:char:to_uppercase 'a') 'A';
std:assert_eq (std:char:to_uppercase 97)  'A';

3.10 - Strings

Strings in WLambda are like Rust UTF-8 encoded immutable Unicode strings. There are two types of literal forms for strings:

"abc def \"foo\"";
std:assert_eq $q/any delimiter may be used instead of/
    "any delimiter may be used instead of";
std:assert_eq "\u{2211}" "∑";
3.10.1 - String Literal Syntaxes

There are multiple kinds of syntax constructs you can use to notate string (and byte vector) literals:

  • Regular strings
!s = "a b c";

std:assert_eq s "a b c";
  • Byte vectors $b"\x02FOO\x03"
  • Quoted strings $q(123433)
  • Quoted byte vectors $Q(XZY)
  • WLambda code strings
!code = $code {
    !this = is a block;
    It just needs to be in valid WLambda[:Syntax];
    .x = But it does not need to pass the compiler
        phase.x;
};

!v = (std:thread:spawn $code {
    !@import std std;
    !res = "x" "y" "z";
    std:str:cat res 33;
}).join[];

std:assert_eq v "xyz33";
3.10.2 - str value

Casts value to a string and returns it. Also dereferences a value.

std:assert_eq (str "\xFF")     "ÿ";
std:assert_eq (str "\x0A")     "\n";
std:assert_eq (str 1)          "1";
std:assert_eq (str $n)         "";
std:assert_eq (str $t)         "$true";
std:assert_eq (str $f)         "$false";
std:assert_eq (str $&10)       "10";
std:assert_eq (str $&&10)      "10";
std:assert_eq (str ${a=10})    "${a=10}";
std:assert_eq (str $[1,2,3])   "$[1,2,3]";
std:assert_eq (str $o(42))     "42";
std:assert_eq (str $o())       "";

!x = $&&10;
std:assert_eq (str ~ std:ref:weaken x)   "10";
3.10.3 - std:write_str value

Writes a WLambda syntax representation of the given value to a string. This is useful for debugging purposes or in combination with std:eval.

std:assert_eq (std:write_str "foo")         "\"foo\"";
std:assert_eq (std:write_str $&&10)         "$&&10";
std:assert_eq (std:write_str $[1, 2, 3])    "$[1,2,3]"

Here an example in combination with std:eval:

!code =
    std:str:cat
        "$@i iter i "
        (std:write_str $[1, 2, 3, 4])
        " { $+ i }";

std:assert_eq (std:eval code) 10;
3.10.4 - is_str value

Returns $true if value is a string.

std:assert ~ is_str "foo";

std:assert ~ not ~ is_str $b"foo";
std:assert ~ not ~ is_str :foo;
std:assert ~ not ~ is_str 324;

std:assert ~ not ~ is_str $&&"foo";
std:assert ~ is_str $*$&&"foo";
3.10.5 - std:str:cat a b

Stringifies (like with str) and concatenates all its arguments. If an argument is a vector, it’s elements will be stringified and concatenated.

std:assert_eq
    (std:str:cat :a 10 23.2 "ab" "cd" $[1, 2, 3])
    "a1023.2abcd123";

If a vector argument is given, it’s elements are stringified, thats useful if you prepare substrings to be concatenated in one single action:

!out = $[];
std:push out "abc";
std:push out "123";
std:push out "XXX";

!s = std:str:cat out;
std:assert_eq s "abc123XXX";
3.10.6 - std:str:join sep vector

Join’s the stringified elements of vector with the sep string. Will return an error if vector is not a vector.

std:assert_eq
    (std:str:join "::" $[1,2,3])
    "1::2::3";
3.10.7 - std:str:len value

Returns the length of the stringified value in unicode characters. The core function len does return the number of bytes in the string instead.

std:assert_eq (len         "∑") 3;
std:assert_eq (std:str:len "∑") 1;
std:assert_eq (len         "∑ÄÄ") 7;
std:assert_eq (std:str:len "∑ÄÄ") 3;
std:assert_eq (len         "abcd") 4;
std:assert_eq (std:str:len "abcd") 4;
3.10.8 - std:str:find pattern string [offset]

Searches for the string pattern in the string and returns the 0 based position in the string the given pattern starts. If no pattern was found $none is returned.

std:assert_eq (std:str:find "xyz" "abcxyz")         3;
std:assert_eq (std:str:find "xyz" "abcxyzxyz" 6)    6;
std:assert_eq (std:str:find "xyz" "abcxyzfooxyz" 6) 9;
3.10.9 - std:str:replace pattern replacement string

Replaces every occurence of pattern in string with replacement and returns a new string. All values will be casted to a string if they aren’t.

!s = std:str:replace "dog" "cat"
    "I really like my dog, because when you dog, you can put dog in the dog!";
std:assert_eq s
    "I really like my cat, because when you cat, you can put cat in the cat!";

!s = std:str:replace "9" "1" "9999";
std:assert_eq s "1111";
3.10.10 - std:str:replace_n pattern replacement count string

Replaces count occurences of pattern in string with replacement and returns a new string. All values will be casted to a string if they aren’t.

!s = std:str:replace_n "dog" "cat" 2
    "I really like my dog, because when you dog, you can put dog in the dog!";
std:assert_eq s
    "I really like my cat, because when you cat, you can put dog in the dog!";

!s = std:str:replace_n "9" "1" 3 "9999";
std:assert_eq s "1119";
3.10.11 - std:str:trim value

Trims off any (unicode) white space from the start and end of the stringified value.

std:assert_eq
    (std:str:trim "\nfooo bar ")
    "fooo bar";
3.10.12 - std:str:trim_start value

Trims off any (unicode) white space from the start of the stringified value.

std:assert_eq
    (std:str:trim_start "  \nfooo bar \n")
    "fooo bar \n";
3.10.13 - std:str:trim_end value

Trims off any (unicode) white space from the end of the stringified value.

std:assert_eq
    (std:str:trim_end "  \nfooo bar \n")
    "  \nfooo bar";
3.10.14 - std:str:pad_start len pad-str value

Pads the stringified value by pad-str up to len characters, inserting at the start of the string. The output string is guaranteed to be exactly len unicode characters long and not longer. If pad-str is empty, nothing is done.

std:assert_eq
    (std:str:pad_start 2 "Ä" "0")
    "Ä0";
std:assert_eq
    (std:str:pad_start 5 "∑∑∑" "∑∑")
    "∑∑∑∑∑";
std:assert_eq
    (std:str:pad_start 8 "Ä∑ßs" "∑∑")
    "ßsÄ∑ßs∑∑";

std:assert_eq
    (std:str:pad_start 8 "" "∑∑")
    "∑∑";

std:assert_eq
    (std:str:pad_start 3 'x' "0")
    "xx0";
std:assert_eq
    (std:str:pad_start 3 $b'x' "0")
    "xx0";
3.10.15 - std:str:pad_end len pad-str value

Pads the stringified value by pad-str up to len characters, appending at the end. The output string is guaranteed to be exactly len unicode characters long and not longer. If pad-str is empty, nothing is done.

std:assert_eq
    (std:str:pad_end 2 "Ä" "0")
    "0Ä";
std:assert_eq
    (std:str:pad_end 5 "∑∑∑" "∑∑")
    "∑∑∑∑∑";
std:assert_eq
    (std:str:pad_end 8 "Ä∑ßs" "∑∑")
    "∑∑Ä∑ßsÄ∑";

std:assert_eq
    (std:str:pad_end 8 "" "∑∑")
    "∑∑";

std:assert_eq
    (std:str:pad_end 3 'x' "0")
    "0xx";
std:assert_eq
    (std:str:pad_end 3 $b'x' "0")
    "0xx";
3.10.16 - std:str:to_bytes string

Encodes string in UTF-8 and returns a byte vector containing all it’s bytes.

!b = std:str:to_bytes "1234";
std:assert_eq b $b"1234";

!b = std:str:to_bytes "Äß日本人";
std:assert_eq b $b"\xC3\x84\xC3\x9F\xE6\x97\xA5\xE6\x9C\xAC\xE4\xBA\xBA";
3.10.17 - std:str:to_bytes_latin1 string

Encodes string as bytes in Latin1 (ISO-8859-1) encoding and returns a byte vector containing all it’s bytes. If a character is outside the Latin1 Unicode range, it will be replaced by a “?”.

!b = std:str:to_bytes_latin1 "\u{FF}\u{F0}";
std:assert_eq b $b"\xFF\xF0";
!b = std:str:to_bytes_latin1 "\u{FE00}\u{FF}\u{3232}";
std:assert_eq b $b"?\xFF?";
3.10.18 - std:str:from_latin1 byte-vector

Converts the byte-vector to a Unicode string, assuming Latin 1 (ISO-8859-1) encoding and returns it.

!s = std:str:from_latin1 $b"Ä";
std:assert_eq s "\u{C3}\u{84}";

!s = std:str:from_latin1 $b"\xFF\xF0";
std:assert_eq s "\u{FF}\u{F0}";
3.10.19 - std:str:from_utf8 byte-vector

Converts the byte-vector to a Unicode string and returns it. If the byte-vector contains invalid UTF-8 sequences an error value is returned.

!s = _? ~ std:str:from_utf8 $b"\xC3\x84\xC3\x9F\xE6\x97\xA5\xE6\x9C\xAC\xE4\xBA\xBA";
std:assert_eq s "Äß日本人";

!r = on_error {|| _ } ~ std:str:from_utf8 $b"\xFF\xFF";
std:assert_eq r "str:from_utf8 decoding error: invalid utf-8 sequence of 1 bytes from index 0";
3.10.20 - std:str:from_utf8_lossy byte-vector

Converts the byte-vector to a Unicode string and returns it. If the byte-vector contains invalid UTF-8 sequences a "�" will be inserted.

!s = _? ~ std:str:from_utf8_lossy
    $b"\xC3\x84\xFF\xC3\x9F\xE6\x97\xA5\xE6\x9C\xAC\xE4\xBA\xBA\xFF\xFF\x00";
std:assert_eq s "Ä�ß日本人��\0";
3.10.21 - std:str:to_char_vec string

Converts the string into a vector of integers which represent the Unicode character number.

!v = std:str:to_char_vec "1234";
std:assert_eq (str v) ~ str $[49,50,51,52];

!v = std:str:to_char_vec "Äß日本人";
std:assert_eq (str v) ~ str $[196,223,0x65E5,0x672C,0x4EBA];
3.10.22 - std:str:from_char_vec vector

The reverse operation of std:str:to_char_vec. It converts a vector of integers to a unicode string. Any integer that has no associated Unicode character will be converted to "?".

std:assert_eq (std:str:from_char_vec $[9999999999]) "?";
std:assert_eq
    (std:str:from_char_vec
        $[49,50,196,223,0x65E5,0x672C,0x4EBA])
    "12Äß日本人";
3.10.23 - std:str:to_lowercase string

Swaps all (Unicode) characters in string to their lowercase version.

std:assert_eq (std:str:to_lowercase "ZABzabÄßÜÖ") "zabzabäßüö";
3.10.24 - std:str:to_uppercase string

Swaps all (Unicode) characters in string to their lowercase version.

std:assert_eq (std:str:to_uppercase "ZABzabäßüö") "ZABZABÄSSÜÖ";
3.10.25 - std:str:edit_distance str-a _str_b

Calculates the Levenshtein distance between two (Unicode) strings.

std:assert_eq (std:str:edit_distance "aaa" "aba") 1;

3.11 - Byte Vectors

Bytes (plural of Byte) are a vector of bytes. Unlike strings they don’t have any encoding. Literal syntax however supports inserting unicode characters:

$b"abc";
$b"\xFF\xFD\x00";
$Q/ABCDEF\xFD/;      # \xFD is not an escape sequence here!
3.11.1 - Call Properties of Bytes

You can index inside a byte array by calling it with an integer:

std:assert_eq ($b"ABC" 1) $b'B';

You can extract a whole range when calling with 2 integers:

std:assert_eq ($b"ABCDEF" 2 3) $b"CDE";

If you call a bytes value with a map as argument, the bytes value is converted to a string internally using str and the value from the map is returned:

!some_map = ${ a = 20, b = 30 };

std:assert_eq ($b"a" some_map) 20;
std:assert_eq ($b"b" some_map) 30;

std:assert_eq some_map.$b"a" 20;   # with method call syntax

If you call bytes with a pair as argument, you can do a multitude of operations, from replacement to finding a byte:

std:assert_eq ($b"a,b,c,d" $p($b',', $b';')) $b"a;b;c;d";
std:assert_eq ($b"a,b,c,d" $p($b"a,", $b"XXX")) $b"XXXb,c,d";
std:assert_eq ($b"a,b,c,d" $p("a,", "XXX")) $b"XXXb,c,d";
std:assert_eq ($b"a,b,c,d" $p("a,", 'O')) $b"Ob,c,d";

std:assert_eq ($b"a,b,c,d" $p(0, $b'c')) 4;
std:assert_eq ($b"a,b,c,d" $p(0,   'c')) 4;

std:assert_str_eq ($b"A\<SOH>B\<SOH>C" $p($b'\<SOH>', 0)) $[$b"A", $b"B", $b"C"];

See also the section Calling Semantics of Data Types.

3.11.2 - Byte Conversion Functions

You can convert bytes to strings in a multitude of ways:

  • str bytes
    std:assert_eq (str $b"abc")        "abc";
    std:assert_eq (str $b"abc\xFF")    "abcÿ";
    std:assert_eq (str $Q/ABCDEF\xFD/) "ABCDEF\\xFD";
  • std:bytes:to_hex bytes [group-len [group-sep]]
    std:assert_eq (std:bytes:to_hex $b"\xFF\x0A\xBE\xEF")
                  "FF0ABEEF";
    std:assert_eq (std:bytes:to_hex $b"\xFF\x0A\xBE\xEF" 2)
                  "FF 0A BE EF";
    std:assert_eq (std:bytes:to_hex $b"\xFF\x0A\xBE\xEF" 2 ":")
                  "FF:0A:BE:EF";
  • std:str:from_latin1 bytes
    std:assert_eq (std:str:from_latin1 $b"\xFF\xF0") "\u{FF}\u{F0}";
  • std:str:from_utf8 bytes
    std:assert_eq (std:str:from_utf8 $b"\xC3\xA4\xC3\x9F\xC3\xBF") "äßÿ";
    std:assert_eq (std:str:from_utf8 [std:str:to_bytes "äßÿ"])         "äßÿ";
    std:assert ~ is_err (std:str:from_utf8 $b"\xC3\xC3\xA4\xC3\x9F\xC3\xBF");
  • std:str:from_utf8_lossy bytes
    std:assert_eq (std:str:from_utf8_lossy $b"\xC3\xC3\xA4\xC3\x9F\xC3\xBF") "�äßÿ";

You can even convert bytes to vectors of integers back and forth:

!v = std:bytes:to_vec $b"ABC";
std:assert_eq (str v) (str $[65, 66, 67]);

std:push v 64;
!b = std:bytes:from_vec v;
std:assert_eq b $b"ABC@";

There is also an inverse operation to bytes:to_hex:

std:assert_eq (std:bytes:from_hex ~ std:bytes:to_hex $b"ABC") $b"ABC";
3.11.3 - is_bytes value

Returns $true if value is a byte vector.

std:assert ~ is_bytes $b"ABC";
std:assert ~ not ~ is_bytes "ABC";
3.11.4 - std:bytes:find pattern string [offset]

Searches for the string pattern in the string and returns the 0 based position in the string the given pattern starts. If no pattern was found $none is returned.

std:assert_eq (std:bytes:find $b"xyz" $b"abcxyz")         3;
std:assert_eq (std:bytes:find $b"xyz" $b"abcxyzxyz" 6)    6;
std:assert_eq (std:bytes:find $b"xyz" $b"abcxyzfooxyz" 6) 9;
3.11.5 - std:bytes:replace byte-vector pattern replacement

Replaces all occurences of pattern in byte-vector with replacement.

std:assert_eq
    (std:bytes:replace $b"XXX\x01\x02\x03OOO" $b"\x01\x02\x03" $b"---")
    $b"XXX---OOO";

std:assert_eq
    (std:bytes:replace $b"XXX\x01\x02\x03OOO" $b"\x01\x02\x03" $b"")
    $b"XXXOOO";

std:assert_eq
    (std:bytes:replace $b"XXX\x01\x02\x03OOO" $b"\x01\x02\x03" $b"\xFF\xFF\xFF\xFF")
    $b"XXX\xFF\xFF\xFF\xFFOOO";
3.11.6 - std:bytes:from_hex string-with-hex-chars

This function decodes a string of hex characters into a byte vector.

!bv = std:bytes:from_hex "62797465";
std:assert_eq bv $b"byte";
3.11.7 - std:bytes:from_vec vector-of-ints

Decodes a vector of integers in the range 0-255 into a byte vector. If an integer is larger than 255 don’t expect a sensible result. But it will most likely just wrap around.

std:assert_eq
    (std:bytes:from_vec $[1,2,3,0x62,0x79,0x74,0x65])
    $b"\x01\x02\x03byte";
3.11.8 - std:bytes:to_hex byte-vector

Converts the given byte vector to a string of hex encoded bytes.

std:assert_eq
    (std:bytes:to_hex $b"byte")
    "62797465";
3.11.9 - std:bytes:to_base64 byte-vector [config]

Converts the given byte vector to a Base64 encoded string. With config you can define the encoding style:

  • :std
  • :std_no_pad
  • :url
  • :url_no_pad
std:assert_eq
    (std:bytes:to_base64 $b"\x00\xFF")
    "AP8=";
std:assert_eq
    (std:bytes:to_base64 "test")
    "dGVzdA==";
std:assert_eq
    (std:bytes:to_base64 "test" :std_no_pad)
    "dGVzdA";
std:assert_eq
    (std:bytes:from_base64 (std:bytes:to_base64 "test"))
    $b"test";
std:assert_eq
    (std:bytes:from_base64 (std:bytes:to_base64 "test" :url) :url)
    $b"test";
3.11.10 - std:bytes:from_base64 byte-vector [config]

Converts the given byte vector to a Base64 encoded string. With config you can define the encoding style, see also to_base64 for a list of possible values.

std:assert_eq
    (std:bytes:from_base64 "AP8=")
    $b"\x00\xFF";
std:assert_eq
    (std:bytes:from_base64 "dGVzdA")
    $b"test";
3.11.11 - std:bytes:to_vec byte-vector

Converts the given byte vector to a vector of integers in the range 0-255.

std:assert_str_eq
    (std:bytes:to_vec $b"byte")
    $[98, 121, 116, 101];
3.11.12 - std:bytes:pack pack-format-string list-of-values

Returns a byte vector containing the values of list-of-values serialized in binary form (packed) according to the given pack-format-string.

If the syntax of the pack-format-string has errors, an error value is returned.

See also the section Format String Syntax for a description of the syntax for pack-format-string.

This function is very useful for constructing binary data for file formats and network protocols.

std:assert_eq
    (std:bytes:pack "> i16 x s8 x f" $[1, "test", 0.5])
    $b"\0\x01\0\x04test\0?\0\0\0";
3.11.13 - std:bytes:unpack pack-format-string byte-vector

Decodes the given byte-vector according to the pack-format-string and returns it as list of values.

If the syntax of the pack-format-string has errors or the given byte-vector is too short, an error value is returned.

See also the section Format String Syntax for a description of the syntax for pack-format-string.

std:assert_str_eq
    (std:bytes:unpack
        "< i16 x c3 s16 x y"
        $b"\x10\x00\x00ABC\x02\x00XY\x00This is the rest")
    $[16, $b"ABC", $b"XY", $b"This is the rest"];

3.12 - Symbols

Symbols are a special kind of strings that are interned by the runtime. That means, comparing two symbols is an O(1) operation and not an O(n) operation on the length of the string. Symbols are also used as keys for maps. Use them however you see fit. They will do a key lookup (on maps, vectors (as indices) and user values) if they are called with an argument.

std:assert_eq (:1 $[1,2,3]) 2;
std:assert_eq (:a ${a=30}) 30;

They are basically the same as string, but strings have slightly different calling semantics and a different literal syntax. Often you can use them as shortform literal in places where a string is expected:

std:assert_eq (std:str:replace :A :a "All AbabA") "all ababa";

They can be very useful as sentinel values or custom enums:

!x = :ON;
!y = :OFF;

std:assert_eq ((x == :ON) { 10 }) 10;

std:assert_eq ((x == "ON") { 10 } { 20 }) 20;


!state = "";
match x
    :ON  => { .state = "is on" }
    :OFF => { .state = "is off" };
std:assert_eq state "is on";

match y
    :ON  => { .state = "is on" }
    :OFF => { .state = "is off" };
std:assert_eq state "is off";

Keep in mind, that all symbols are interned strings. And if you create many symbols that are not used anymore, you might need to trigger a cleanup with std:symbols::collect.

The collection of dead symbols is also run automatically for every 100th newly allocated symbol.

3.12.1 - sym value

Casts the given value into a symbol.

std:assert_eq (sym "a")     :a;
std:assert_eq (sym $b"a")   :a;
std:assert_eq (sym $[])     :"$[]";
std:assert_eq (sym 10)      :10;
3.12.2 - is_sym value

Returns $true if the value is symbol.

std:assert ~ is_sym :a;
std:assert ~ is_sym ~ sym "a";
std:assert ~ is_sym ~ sym "a";
std:assert ~ is_sym ~ sym $b"a";

std:assert ~ not ~ is_sym "a";
std:assert ~ not ~ is_sym $b"a";
std:assert ~ not ~ is_sym $&&:a;
std:assert ~ not ~ is_sym ${};
std:assert ~ not ~ is_sym $none;
std:assert ~ not ~ is_sym $true;
3.12.3 - std:symbols:collect

Collect and remove all interned symbols in the current thread that are no longer used. Returns the number of freed symbols. Please keep in mind, that the std:ref_id of any collected symbol will be different from a symbol that is created later with the same characters.

The collection of dead symbols is also run automatically for every 100th newly allocated symbol.

If you rely on the reference ID of a symbol, you should make sure to keep it around. Literal symbols are always kept around as long as the code is running or referenced somewhere (eg. by a function).

std:symbols:collect[];

!probably_unique_sym = sym "onceonly_used";

std:assert_eq
    (std:ref_id ~ sym "onceonly_used")
    (std:ref_id probably_unique_sym);

std:assert_eq std:symbols:collect[] 0;

.probably_unique_sym = $none;

std:assert_eq std:symbols:collect[] 1;

3.13 - Syntax $%:Block

A syntax element is an element of an abstract syntax tree as it is returned by std:wlambda:parse for instance. They carry the type of syntax node and debug information with them.

3.13.1 - std:syn:pos syntax

Returns the position of the syntax element in the source code.

!ast = std:wlambda:parse "1 + 2";
!add_syntax = ast.1.0;

std:assert_str_eq
    (std:syn:pos add_syntax)
    $["<wlambda:parse:input>", 1, 3];
3.13.2 - std:syn:type syntax

Converts the syntax element to a symbol, so you can determine it’s type:

!ast = std:wlambda:parse "1 + 2";
!add_syntax = ast.1.0;

std:assert_str_eq
    (std:syn:type add_syntax)
    :BinOpAdd;

3.14 - Pairs $p(a, b)

A pair is an immutable tuple of 2 values. You can use it for returning two values from a function as it is a slight bit slimmer than a vector with two values. Unlike a vector, pairs are compared by == according to their contents and not by referencial equality.

There are two ways to form a pair:

  • Pair value syntax: $p(a, b)
  • Pair right associative operator: a => b

You can access the pair values by the following keys:

!v = $p(11, 12);

std:assert_eq v.0     11;
std:assert_eq v.1     12;
std:assert_eq (0 v)   11;
std:assert_eq (1 v)   12;

std:assert_eq v.car   11;
std:assert_eq v.cdr   12;

std:assert_eq v.head  11;
std:assert_eq v.tail  12;

std:assert_eq v.first    11;
std:assert_eq v.second   12;

std:assert_eq v.value 11;
std:assert_eq v.key   12;

std:assert_eq v.v     11;
std:assert_eq v.k     12;

Comparison does happen by their contents:

std:assert $p(1, 2) == $p(1, 2);
std:assert $p(2, 2) != $p(1, 2);

std:assert not ~ $[1, 2] == $[1, 2];

The index is wrapping around, that means $p(a, b).2 is the first element again:

!v = $p(33, 44);
!l = $[];

iter i $i(0, 4)
    ~ std:push l v.(i);

std:assert_eq (str l) (str $[33, 44, 33, 44]);

A pair is a referencial data type, that means you can use std:ref_id on it:

!a = $p(1, 2);
!b = $p(2, 3);
!id_a = std:ref_id a;
!id_b = std:ref_id b;

std:assert (id_a != id_b);
std:assert std:ref_id[a] == id_a;

!v = $[a];
std:assert std:ref_id[v.0] == id_a;
3.14.1 - Pair Operator a => b

Writing a => b operator is the same as writing $p(a, b). However, the precedence of the => operator is the lowest and right associative, so writing this is possible:

!p = 1 + 2 => 3 + 4;

std:assert_eq p $p(3, 7);

The following example shows off the associativity of the operator:

!a = 1 => 2;
!b = 2 => 3 => 4;

std:assert_eq a $p(1, 2);
std:assert_eq b $p(2, $p(3, 4));
std:assert_eq b 2 => 3 => 4;
3.14.2 - cons a b

Creates a new pair from the values a and b.

!p = cons 3 4;

std:assert_eq p $p(3, 4);
3.14.3 - Pair string/byte vector operations

If you call a pair with a string or byte vector as argument, there are some operations that can be done:

3.14.3.1 - $p( from , count ) string-or-byte-vec

Returns a substring starting at from with the length count.

std:assert_eq ($p(2, 4) "abcdefgh") "cdef";

The same works for byte vectors:

std:assert_eq ($p(2, 4) $b"abcdefgh") $b"cdef";
3.14.3.2 - $p( pattern , replacement ) string-or-byte-vec

Replaces all pattern occurences in string by replacement.

std:assert_eq ($p(";", "_") "A;B;D;EFG;HI") "A_B_D_EFG_HI";

The same works for byte vectors:

std:assert_eq ($p($b";", $b"_") $b"A;B;D;EFG;HI") $b"A_B_D_EFG_HI";
3.14.3.3 - $p( split-pattern , max ) string-or-byte-vec

Splits string at split-pattern a max number of times. If max is 0, it is split completely.

std:assert_eq str[$p(";", 3) "A;B;D;EFG;HI"] ~ str $["A", "B", "D;EFG;HI"];

std:assert_eq str[$p(";", 0) "A;B;D;EFG;HI"] ~ str $["A", "B", "D", "EFG", "HI"];

The same works for byte vectors:

std:assert_eq str[$p($b";", 0) $b"A;B;D;EFG;HI"] ~ str $[$b"A", $b"B", $b"D", $b"EFG", $b"HI"];
3.14.4 - Pair to Iterator

Pairs play a special role if you make an iterator from it. It can be used to create a specialized iterator that only iterates over keys or values of a map. Or that enumerates a vector or map.

3.14.4.1 - Iter - Range

$iter $p(0, 10) is the same as $iter $i(0, 10) and will construct an iterator that iterates from 0 to 9 (inclusive). Because of the pair operator a => b we can nicely write a counting loop like this:

!sum = $@i
    iter i 0 => 10 {
        $+ i;
    };
std:assert_eq sum 45;
3.14.4.2 - Iter - Enumerate

If the first value of the pair is :enumerate it will enumerate entries in a map or values in a vector.

!v = $[];

iter i ($iter $p(:enumerate, $[:a, :b, :c]))
    ~ std:push v i;

std:assert_eq (str v) (str $[0, 1, 2]);

For maps:

!v = $[];
iter i $p(:enumerate, ${a = 10, b = 20})
    ~ std:push v i;

std:assert_eq (str v) (str $[0, 1]);
3.14.4.3 - Iter - Values

This is useful for iterating over the values in a map in an undefined order:

!m = ${ a = 10, b = 20, c = 33 };

!sum = $@i iter v $p(:values, m) ~ $+ v;

std:assert_eq sum 63;
3.14.4.4 - Iter - Keys

You can also iterate over map keys in an undefined order:

!m = ${ :10 = :c, :20 = :b, :30 = :a };

!sum = $@i iter v $p(:keys, m) ~ $+ v;

std:assert_eq sum 60;
3.14.5 - is_pair value

Checks if value is a pair.

std:assert ~ is_pair $p(1, 2);
std:assert not ~ is_pair $[1, 2];
std:assert not ~ is_pair $i(1, 2);

3.15 - Vectors (or Lists)

The literal syntax for vectors (or sometimes also called lists in WLambda) is $[...]. You may write any kind of expression in it and you will get a vector from it.

For iteration over a vector please refer to 5.2 Collection Iteration.

To access the elements of a vector you have to call a number with a vector as first argument. The field syntax is a more convenient shorthand syntax. The following example demonstrates it:

!add20 = { _ + 20 };

!some_vec = $[1, 2 * 10, add20 10]; 

std:assert_eq (0 some_vec) 1;
std:assert_eq (1 some_vec) 20;
std:assert_eq (2 some_vec) 30;

std:assert_eq some_vec.0 1;
std:assert_eq some_vec.1 20;
std:assert_eq some_vec.2 30;

To add elements to a vector, you can use the prepend and append operators +> and <+ too:

!v = $[1];

0 <+ v;
v +> 2;

std:assert_str_eq v $[0,1,2];
3.15.1 - std:push vector item

Pushes item to the end of vector. Returns item. Be aware, that there is also the +> operator, that will append elements to a vector.

!v = $[1,2];

std:push v 3;

std:assert_eq (str v) (str $[1,2,3]);
3.15.2 - std:pop vector

Pops off the last element of vector. Returns $none if the vector is empty or if vector is not a vector.

!v = $[1,2,3];

std:assert_eq (std:pop v) 3;
std:assert_eq (str v) (str $[1,2]);
3.15.3 - std:unshift vector item

Inserts item at the front of vector. Returns item and mutates vector inplace. Be aware that this operation is of O(n) complexity. Be aware, that there is also the <+ operator, that will prepend elements to a vector (with O(n) complexity however).

!v = $[1,2];

std:unshift v 3;

std:assert_eq (str v) (str $[3,1,2]);
3.15.4 - is_vec value

Returns $true if value is a vector.

std:assert ~ is_vec $[];
std:assert ~ is_vec $[1,2,3];

std:assert ~ not ~ is_vec 0;
std:assert ~ not ~ is_vec $none;
std:assert ~ not ~ is_vec $true;
std:assert ~ not ~ is_vec $p(1,2);
std:assert ~ not ~ is_vec $i(1,2);
std:assert ~ not ~ is_vec $f(1,2);
std:assert ~ not ~ is_vec ${a = 10};
3.15.5 - Vector Splicing

You can splice vectors directly into their literal form with the $[..., * vec_expr, ...] syntax. Here is an example:

!make_some = { $[_ + 1, _ + 2] };

!some_vec = $[ 0, *make_some 1 ];

std:assert_eq some_vec.1 2;
std:assert_eq some_vec.2 3;

std:assert_eq some_vec.(1 + 1) 3;

std:assert_eq (str $[1,2,*$[3,4]]) "$[1,2,3,4]";
3.15.6 - std:append vec-a value-or-vec

Appends value-or-vec and all following items to vec-a. If value-or-vec is a vector, all its items will be appended to vec-a.

!v = std:append $[1,2,3] :a :b $[:c, :d];

std:assert_eq (str v) "$[1,2,3,:a,:b,:c,:d]";

If vec-a is not a vector, a vector containing it will be created:

!v = std:append 1 :a :b $[:c, :d];

std:assert_eq (str v) "$[1,:a,:b,:c,:d]";
3.15.7 - std:prepend vec-a value-or-vec

Prepends value-or-vec and all following items to the front of vec-a. If value-or-vec is a vector, all its items will be prepended to vec-a.

!v = std:prepend $[1,2,3] :a :b $[:c, :d];

std:assert_eq (str v) (str $[:d, :c, :b, :a, 1, 2, 3]);

If vec-a is not a vector, a vector containing it will be created:

!v = std:prepend 1 :a :b $[:c, :d];

std:assert_eq (str v) (str $[:d, :c, :b, :a, 1]);
3.15.8 - std:take count vector

Takes and returns the first count elements of vector. Does not mutate vector.

!v = $[1,2,3,4,5,6];

!t = std:take 4 v;

std:assert_eq (str v) "$[1,2,3,4,5,6]";
std:assert_eq (str t) "$[1,2,3,4]";
3.15.9 - std:drop count vector

Drops count elements from vector and returns them as new vector. Does not mutate vector.

!v = $[1,2,3,4,5,6];

!t = std:drop 4 v;

std:assert_eq (str v) "$[1,2,3,4,5,6]";
std:assert_eq (str t) "$[5,6]";

3.16 - Associative Maps (or String to Value mappings)

Aside from vectors there are associative maps in WLambda. Their syntax is ${ key = expr, ... }. The keys of these maps have to be symbols (or strings), the values in the literals can be any expression. Keys for maps are interned strings, so keep that in mind if you fill a map with garbage keys.

For iteration over a map please refer to 5.2 Collection Iteration.

You can call a symbol or a string with an associative map to get the value in the map with the string value as key. There is also, like vectors, the field calling syntax. Here are some examples:

!some_map = ${ a = 1, b = 2 };

std:assert_eq (:a some_map) 1;
std:assert_eq (:b some_map) 2;
std:assert_eq ("a" some_map) 1;
std:assert_eq ("b" some_map) 2;

std:assert_eq some_map.a 1;
std:assert_eq some_map.b 2;

std:assert_eq some_map.("a") 1;
std:assert_eq some_map.("b") 2;

Keys can also be computed at runtime in the literal form:

!some_map = ${ (std:str:cat "a" "b") = 10 };

std:assert_eq (str some_map) "${ab=10}";

If you call a field that is being accessed directly using the field accessing syntax some_map.a, the function is passed the map some_map via the special value $self. There is another special variable $data that allows you to access the $self._data field.

3.16.1 - Map Splicing

Like vectors you can splice map values directly into map literals:

!map_gen = { ${ (std:str:cat "_" _) = _ } };

!some_map = ${ a = 10, *map_gen "x" };

std:assert_eq some_map.a 10;
std:assert_eq some_map._x "x";

std:assert_eq (str ${*${a=10}}) "${a=10}";


std:assert_eq (str ${*map_gen "y"}) $q/${_y="y"}/;
3.16.2 - is_map value

Returns $true if value is a map.

std:assert ~ is_map ${};
std:assert ~ is_map ${a = 10};

std:assert ~ not ~ is_map $&&${};
std:assert ~ not ~ is_map $&${};
std:assert ~ not ~ is_map $[:a, 10];
std:assert ~ not ~ is_map $p(:a, 10);
std:assert ~ not ~ is_map $none;
std:assert ~ not ~ is_map $true;

3.17 - References

TODO - 3 types: strong, hidden, weak - strong: - $&& - mention DWIM’ery - hidden: - $& - mention usage for closures - how to “Unhide” a reference using $: - weak: - std:ref:weaken and $w& - how to break reference cycles - how weak references are also caught weakly by closures and not strongly. - how to get a strong reference using $:

Some data structures already have reference characteristics, such as strings, vectors and maps. But you can also wrap other values like integers, floats, … into a reference. There are 3 types of references in WLambda that have different usecases and semantics. These referential types are neccessary to mutate lexical variables from a parent scope. To give a rather natural example:

!x = 10;
{ .x = 20; }[];
std:assert_eq x 20;

The example works rather intuitively. There is however lots of implicit referential stuff going on. Once x is captured by a closure its contents is implicitly changed in to a hidden $& reference. The closure then stores this hidden reference too. You have to be aware of this, because in some use cases this can lead to cyclic reference structures, which are not automatically freed. Please use weak references $w& for mitigating this.

Hidden references and weak references captured by a closure are dereferenced implicitly if you access the variables. Weak references in the local scope are not implicitly dereferenced. However, sometimes it’s desirable to have a more explicit reference data types. For this the strong references $&& are available. Use $* for accessing the value of a strong reference or a weak reference in the local scope.

TODO

These types of references exist:

  • $& - A hidden reference, that is captured by closures or constructed using $&.
  • $w& - A weak reference, can’t be constructed literally, only indirectly as upvalue of a closure or by std:ref:weaken.
  • $&& - A strong reference, that is captured strongly by closures. Inside closures they are also implicitly dereferenced by assignment and access by variable name.
!x = $& 10;

{ .x = 20; }[]; # Closures implicitly handle weak references

std:assert_eq x 20;

And the same with strong references:

!x = $&& 10;

.*x = 11;

{ .*x = 20; }[]; # Closures need explicit handling of strong references

std:assert_eq $*x 20;

Strong references can also be created using the std:to_ref function and the $: operation.

3.17.1 - std:to_ref value

Creates a new strong reference that refers to a cell that stores value.

!x = std:to_ref 10;

std:assert_eq (std:ser:wlambda x) "$&&10";

std:assert_eq $*x 10;
3.17.2 - std:ref:weaken ref

You can weaken any of those two types of references manually using the std:ref:weaken function.

!drop_check = $& $f;

!x = $&& (std:to_drop {|| .drop_check = $true });

!y = std:ref:weaken x;

.x = $n;

std:assert_eq $*y $n;

std:assert drop_check;

Please note that you can use $w&/$weak& as a shortcut to calling the library function:

!x      = $&& 10;
!x_weak = $w& x;

std:assert_eq x      &> type "ref_strong";
std:assert_eq x_weak &> type "ref_weak";
3.17.3 - std:ref:hide value

Creates a hidden reference from a given value or reference.

!r = $&& 10;            # strong ref to value 10

!h = std:ref:hide r;

.h += 11;

std:assert_eq $*r       21;
std:assert_eq h         21;
std:assert_eq (std:write_str $:h) "$&&21";

std:assert_eq (std:write_str $[r, $:h]) "$[$<1=>$&&21,$<1>]";
3.17.4 - is_ref value

Returns $true if value is a reference (strong, hidden or weak).

!x = $&&10;
std:assert ~ is_ref ~ std:ref:weaken x;
std:assert ~ is_ref $&10;
std:assert ~ is_ref $&&10;

std:assert ~ not ~ is_ref $[1,2,3];
std:assert ~ not ~ is_ref ${a=10};
std:assert ~ not ~ is_ref $true;
std:assert ~ not ~ is_ref $none;
3.17.5 - is_wref value

Returns $true if value is a weak reference.

!x = $&& 10;
!y = std:ref:weaken x;
std:assert ~ is_wref y;
std:assert ~ not ~ is_wref x;
3.17.6 - std:ref:strengthen ref

You can convert a weak reference (weakened by std:ref:weaken) or a captured weak reference $& to strong with `std:ref:strengthen

!x = $&&10;
!y = std:ref:weaken x;

.x = $none;
std:assert ~ is_none $*y;

.x = $&&10;
.y = std:ref:weaken x;
!y2 = std:ref:strengthen y; # Here we take a second strong reference from a weak one

.x = $none;
std:assert ~ is_some $*y;
std:assert ~ is_some $*y2;

.y2 = $none;
std:assert ~ is_none $*y;
3.17.7 - std:ref:set ref value

Sets the value of the reference ref to value. If ref is not a strong, hidden or weak reference nothing happens.

Returns value or $none.

!r1 = $&&1;
std:ref:set r1 10;
std:assert_eq $*r1 10;

!r2 = $& $& 1;
std:ref:set r2 11;
std:assert_eq $*r2 11;

!r3 = $& $& 1;
!w3 = std:ref:weaken r3;
std:ref:set w3 14;      # Set reference via the weak reference in w3 to r3.
std:assert_eq $*r3 14;

3.18 - Iterators $iter expression

As a companion to the iter operation there are the iterator values. These are a special kind of values that generate a value when they are called. It supports to make an iterator from the same values as the iter operation.

You can create an iterator from vectors and maps, but also specialized iterators that return a range of numbers or only keys of a map. About this see the section Iterator Kinds below.

The $iter syntax takes a complete expression as argument, that means you can directly write $iter function arg1 arg2 ... without delimiting the function call.

Here is an example how to make an iterator over a vector:

!it = $iter $[1,2,3,4];

!first  = it[];     # returns an optional value $o(1)
!second = it[];     # returns an optional value $o(2)

std:assert_eq first    $o(1);
std:assert_eq second   $o(2);

You can also directly cast an iterator, which will also make it return a value:

!it = $iter $[1,2,3,4];

std:assert_eq (int it)  1;
std:assert_eq (int it)  2;

You can pass an iterator also to the iter operation:

!it = $iter $[1,2,3];

!sum = 0;

iter i it {
    .sum = sum + i;
};

std:assert_eq sum 6;
3.18.1 - Iterator Kinds

Here is a table of the behaviour of iterators created from WLambda data.

DataIterator return values
vectorEach element of the vector.
mapEach key/value pair of the map in undefined order.
$noneReturns nothing
optionalReturns the optional value on first invocation.
$o()Returns nothing.
intReturns the integer value on first invocation.
floatReturns the integer value on first invocation.
stringReturns the individual characters as string.
symbolReturns the individual characters as string.
byte vectorReturns the individual bytes as byte vector.
$errorReturns the error value on first invocation.
$i(a, b)The integers in the range of a to b, not including b.
$i(a, b, step)The integers in the range of a to b advanced by step, not including b.
$f(a, b)The floats in the range of a to b advanced by 1.0, not including b.
$f(a, b, step)The floats in the range of a to b advanced by step, not including b.
$p(:enumerate, map)Returns integers in the range of 0 to len map.
$p(:enumerate, vector)Returns integers in the range of 0 to len vector.
$p(:values, map)Returns the values of the map in undefined order.
$p(:keys, map)Returns the keys of the map in undefined order.
$p(int_a, int_b)The same as $i(a, b). This makes it possible to write $iter 0 => 10.
$p(iterator_a, iterator_b)Returns a zip operation of the elements returned by the iterator_a and iterator_b until one of both returns $o().
$p(iterator, x)Returns a zip operation of the elements returned by the iterator and the newly created iterator$iter x.
3.18.2 - Iterators on mutated data

Iterators hold a reference to the collection values. That means, if you mutate a vector while you iterate over it, it will not crash but it might produce weird effects.

!v = $[1,2,3];
!it = $iter v;

iter i v {
    if i <= 3 {
        std:push v i + 10;  # This is not recommended however...
    };
};

std:assert_eq (str v) (str $[1, 2, 3, 11, 12, 13]);

This will also work for maps, but as the order of the map entries is undefined it will produce very indeterministic effects and it’s really not recommended.

3.18.3 - Splicing an Iterator

You can directly insert the values produced by an iterator into a vector or map:

!it = $iter $[1,2,3,4];

!v = $[10, 20, *it, 99];

std:assert_eq (str v) (str $[10, 20, 1, 2, 3, 4, 99]);

Same goes for maps:

!it = $iter ${a = 10, b = 20};

!m = ${ x = 99, *it };

std:assert_eq m.a 10;
std:assert_eq m.b 20;
std:assert_eq m.x 99;
3.18.4 - Calling an Iterator with a Function

When an iterator gets called with a function as first argument it will repeatedly call that function until no more values are available:

!it = $iter $[1,2,3];

!sum = 0;

it { .sum = sum + _ };

std:assert_eq sum 6;
3.18.5 - Zip Iterators

To highlight this feature from the table above: You can zip two iterators if you pass an iterator as first part of a pair $p(a, b):

!v = $["a", "b", "c"];

!elems = $@vec
    iter i $p($iter v, $iter $i(0, 10)) {
        $+ i;
    };

std:assert_eq
    (str elems)
    (str $[$p("a", 0), $p("b", 1), $p("c", 2)]);
3.18.6 - is_iter value

Returns $true if value is an iterator.

std:assert   (is_iter $iter $n);
std:assert   (is_iter $iter 0 => 30);
std:assert   not <& (is_iter $[1,2,3]);
std:assert   (is_iter $iter $[1,2,3]);

std:assert   (not <& is_iter <& $true);
std:assert   (not <& is_iter <& $false);
std:assert   (not <& is_iter <& 4);
std:assert   (not <& is_iter <& $p(1, 2));

3.19 - Calling Semantics of Data Types

You can call almost all basic data types of WLambda. Here is an overview of the data type calling semantics:

TypeArgsSemantics
$none-Any call to $none will result in a panic.
$error-Any call to $error will result in a panic.
function*Will call the function with the specified arguments.
$truef1, f2Will call f1.
$falsef1, f2Will call f2 or return $n if f2 is not provided.
$true$[1,2]Will return the second element 2 of the list.
$false$[1,2]Will return the first element 1 of the list.
integervector, string, byte_vec, iteratorWill return the element at the given integer index.
symbolmap, uservalWill retrieve the value in the map at the key equal to the symbol.
mapanythingWill call anything for each value and key in the map and return a list with the return values.
stringstring, byte_vec, char or byteAppend operation, works with multiple arguments.
byte_vecstring, byte_vec, char or byteAppend operation, works with multiple arguments.
string$p(int_offs, string/byte_vec/char/byte)Find operation, search from int_offs for the given string, byte_vec, char or byte. See also std:str:find and std:bytes:find.
byte_vec$p(int_offs, string/byte_vec/char/byte)Find operation, search from int_offs for the given string, byte_vec, char or byte. See also std:str:find and std:bytes:find.
$p(char or byte, int_count)-Create a string or byte_vec containing int_count of copies.
$p(int_offs, string/byte_vec/char/byte)stringFind operation, search from int_offs for the given string, byte_vec, char or byte.
$p(int_offs, string/byte_vec/char/byte)byte_vecFind operation, search from int_offs for the given string, byte_vec, char or byte.
$p(int_from, int_count)vectorVector slice operation.
$i(int_from, int_count)vectorVector slice operation.
$p(int_from, int_count)numeric vectorCreates a vector slice from a numeric vector.
$i(int_from, int_count)numeric vectorCreates a vector slice from a numeric vector.
$p(int_from, int_count)iteratorIterator result list slice operation.
$i(int_from, int_count)iteratorIterator result list slice operation.
$p(int_from, int_count)stringSubstring operation. (See also section about pairs)
$i(int_from, int_count, ...)stringSubstring operation.
$p(int_from, int_count)byte_vecSubstring operation. (See also section about pairs)
$i(int_from, int_count, ...)byte_vecSubstring operation on the byte vector.
string$i(int_from, int_count, ...)Substring operation.
byte_vec$i(int_from, int_count, ...)Substring operation on the byte vector.
$p(string, int)stringSplit operation. (See also section about pairs)
$p(byte_vec, int)byte_vecSplit operation. (See also section about pairs)
string$p(string, int)Split operation.
byte_vec$p(byte_vec, int)Split operation.
string$p(string, string)Replace all operation.
byte_vec$p(byte_vec, byte_vec)Replace all operation.
$p(string, string)stringReplace all operation. (See also section about pairs)
$p(byte_vec, byte_vec)byte_vecReplace all operation. (See also section about pairs)
$p(pat_char, repl_char)string or byte_vecReplace all pat_char in string or byte_vec with repl_char.
$p(pat_byte, repl_byte)string or byte_vecReplace all pat_char in string or byte_vec with repl_char.
$p(char, char)char or byteRange check operation, whether char or byte is inside to/from range.
$p(byte, byte)char or byteRange check operation, whether char or byte is inside to/from range.
$p(int_a, int_b)char or byteRange check operation, whether char or byte is inside to/from range.
$i(int_a, int_b)char or byteRange check operation, whether char or byte is inside to/from range.
char or byte$p(char, char)Range check operation, whether char or byte is inside to/from range.
char or byte$p(byte, byte)Range check operation, whether char or byte is inside to/from range.
char or byte$p(int_a, int_b)Range check operation, whether char or byte is inside to/from range.
char or byte$i(int_a, int_b)Range check operation, whether char or byte is inside to/from range.
$o()-Returns $none.
$o(x)-Returns x.
$o()*Calls $none with arguments, leading to a panic.
$o(x)*Calls x with the given arguments.

4 - Conditional Execution - if / then / else

4.1 - if/? condition then-expr [else-expr]

The keyword for conditional execution is either if or just the question mark ?. It takes 3 arguments: The first is an expression that will be evaluated and cast to a boolean. If the boolean is $true, the second argument is evaluated. If the boolean is $false the thrid argument is evaluated. The third argument is optional.

!x = 10;

!msg = "x is ";
if x > 4 {
    .msg = std:str:cat msg "bigger than 4";
} {
    .msg = std:str:cat msg "smaller than or equal to 4";
};

std:assert_eq msg "x is bigger than 4";

if x == 10 {
    std:assert $true;
} {
    std:assert $false;
};

The condition can also be a function block, which will be evaluated:

!res =
    if { !x = 2; x > 1 } "x > 1";

std:assert_eq res "x > 1";

4.2 - Using Booleans for Conditional Execution

Conditional execution is also provided by the bool data type. As in WLambda everything can be called like a function, you can just pass other functions as arguments to $true and $false. If you pass a function as first argument to $true, it will be executed. If you pass a function as second argument to $false then that will be executed.

(10 == 10) { std:displayln "10 is 10" };         #=> prints "10 is 10"
(10 != 10) { std:displayln "10 is not 10" };     #=> doesn't print anything

!x = 20;

(x == 20) {
    std:displayln "x is 20";
} {
    std:displayln "x is 20";
}; # Do not forget the ";"!

Actually, as the values $true and $false can be called like any other function you may write it also like this, which is not the recommended syntax, but still works:

(10 == 10)[{ std:displayln "10 is 10" }];

!x = 21;
(x == 20)[{ std:displayln "x is 20" }, { std:displayln "x isn't 20" }]; #=> print "x isn't 20"
4.2.1 - pick bool a -b-

Often, you may want to choose one variable (a) or another (b) based on some predicate (bool). For these situations, the pick function is available. For example, perhaps you want to make a function which can take any number of parameters, or a single list parameter.

!sum = \|| std:fold 0 { _ + _1 } ~ pick (is_vec _) _ @;
4.2.2 - Indexing by Booleans

Booleans can also be used to index into lists. When this is done, $t represents 1 and $f represents 0. This means that we can also express our sum function as:

!sum = \|| std:fold 0 { _ + _1 } $[@, _].(is_vec _);

Furthermore, as a.b is equivalent to b[a], one can also write this sum function by simply invoking (is_vec _) and passing in the list of options as a parameter.

!sum = \|| std:fold 0 { _ + _1 } ~ (is_vec _) $[@, _];

When comparing the pick and indexing approaches it is important to note that the two possible return values are inverted:

!x = 20;
!res = pick (x == 20) "x is 20" "x isn't 20";
std:assert_eq res "x is 20";

.res = $["x isn't 20", "x is 20"].(x == 20);
std:assert_eq res "x is 20";

With pick, the value to return in the $t case comes first, followed by the $f case’s value, whereas with indexing approach, the opposite is true.

4.3 - Value matching with - match value-expr

See also 8.1.1 for a more comprehensive discussion of match and structure matchers.

match allows for easily select from a set of values:

!check_fun = {
    match _
        20 =>   "It's 20"
        30 =>   "It's 20"
        "No idea?"
};

std:assert_eq check_fun[20] "It's 20";
std:assert_eq check_fun[34] "No idea?";

Also works for deeper data structures:

!val = $[1, ${a = 10}, ${a = 10}, ${a = 10}, ${a = 10}, 2, 2, 2, 10];

!res =
    match val
        $[1, _*, 3, 10] =>  :a
        $[1, a ~ _* ${ a = y }, b ~ _+ 2, 10] => {
            ${
                a_elems = $\.a,
                a_value = $\.y,
                b_vals  = $\.b,
            }
        };

std:assert_str_eq res.a_elems $[${ a = 10 }, ${ a = 10 }, ${ a = 10 }, ${ a = 10 }];
std:assert_str_eq res.a_value 10;
std:assert_str_eq res.b_vals  $[2,2,2];

5 - Loops And Iteration

WLambda has many ways to loop and iterate:

  • Counting loop with range
  • While some condition is $true with the while special form.
  • Over the items in a vector or map with the iter special form.
  • Calling an $iter iterator value with a function as first argument.
  • Over the items in a vector with either for or by calling the vector with a function as first argument.
  • Over the items in a map with either for or by calling the map with a function as first argument.
  • Over the characters in a string with either for or by calling it with a function.
  • Over the bytes in a byte vector with either for or by calling it with a function.

for just iterates through the value and provides the individual items as first argument to the iteration function. But if you call the value with a function as first argument a mapping iteration is done. That means, the return value of the operation is a list with the return values of the iteration function. If you don’t need that list you should use for.

5.1 - Control Flow

5.1.1 - while predicate body

while will evaluate body until the evaluation of predicate function returns $false. Or break is used to end the loop. The loop can be restarted using next. This is the most basic loop for iteration:

!i   = 0;
!out = $[];

while i < 10 {
    std:push out i;
    .i = i + 1;
};

std:assert_eq (str out) "$[0,1,2,3,4,5,6,7,8,9]";

If you need an endless loop you can pass $true as predicate:

!i = 0;

while $true {
    (i >= 4) break;
    .i = i + 1;
};

std:assert_eq i 4;

The first

5.1.2 - iter var iterable body

This is the primary syntax of WLambda to iterate over collections, numeric ranges and generally everything you can create an iterator from using the $iter syntax.

The var will be defined inside the body and be filled with the value that was generated for the current iteration. And iterable is everything that $iter can make an iterator from. Please refer to the section Iterator Kinds for a listing of this.

Like usual, the control flow manipulators next and break also work for this kind of loop.

5.1.2.1 - Counting loop with iter

Here is an example how to iterate over a range from 1 to 9 and collect the sum of those integers using an accumulator:

!sum = $@int iter i $i(1,10) ~ $+ i;

std:assert_eq sum 45;

Because $iter $p(1, 10) is the same as $iter $i(1, 10) and because there is the pair constructor operator a => b, the above can also be written as:

!sum = $@int iter i 1 => 10 ~ $+ i;

std:assert_eq sum 45;
5.1.2.2 - Vector iteration with iter

Here is a simple example of how to iterate over all items of a vector in order:

!sum = 0;

iter i $[1,2,3,4,5,6] {
    .sum = sum + i;
};

std:assert_eq sum 21;

Even if you pass the syntax for constructing a function to iter it will create a block of statements from it. So this will work too (also for while above):

!sum = 0;

iter i $[1,2,3,4,5,6] \.sum = sum + i;

std:assert_eq sum 21;

However body does not have to be a function definition or block, it can also be just a regular call argument:

!sum = 0;

!inc = { .sum = sum + _; };

iter i $[1,2,3,4] inc[i];

std:assert_eq sum 10;

To iterate over a vector by index you can use this:

!v = $[1,2,3,4,5];
!sum = 0;

iter i $i(0, len v) {
    .sum = sum + v.(i);
};

std:assert_eq sum 15;
5.1.2.3 - Map iteration with iter

Iteration over a map is also easy and concise. The map entry will be represented using a pair value $p(value, key). You can access the first and second element of a pair using the v/value and k/key keys of a pair (but also all other pair accessors defined in the section for pairs):

!sum = 0;

iter i ${ a = 10, b = 20 } {
    .sum = sum + i.v;
};

std:assert_eq sum 30;

Very useful for iterating just over the keys or values of a map can also be the special iterator values you get from the pair constructors:

!m = ${ a = 10, b = 20 };
!sum = 0;

iter v $p(:values, m) {
    .sum = sum + v;
};

std:assert_eq sum 30;

Or if you need the keys:

!m = ${ a = 10, b = 20 };
!sum = 0;

iter k $p(:keys, m) {
    std:displayln "FOO" k;
    .sum = sum + m.(k);
};

std:assert_eq sum 30;
5.1.2.4 - Closures and iter iter i ...

If you need a new variable for capturing it in a closure on each iteration you need to make a new variable binding for each iteration:

!closures = $[];

iter i $i(0, 10) {
    !i = i;
    std:push closures { i * 10 };
};

std:assert_eq ($@i closures \$+ _[]) 450;
5.1.3 - range start end step fun

range counts from start to end by increments of step and calls fun with the counter. The iteration is inclusive, this means if start == end the function fun will be called once.

In contrast to iter this is not a special syntax, but just a regular function that calls another function repeatedly. You can control it using the break and next functions however.

!out = $[];
range 0 9 1 {!(i) = @;
    std:push out i;
};

std:assert_eq (str out) "$[0,1,2,3,4,5,6,7,8,9]";

The construct also works for floating point numbers, but be aware of the inherent floating point errors:

!out = $[];
range 0.3 0.4 0.01 {
    std:push out ~ std:num:round 100.0 * _;
};

std:assert_eq (str out) "$[30,31,32,33,34,35,36,37,38,39]";
5.1.4 - break value

break stops the inner most iterative construct, which then will return value. This should work for all repeatedly calling operations, such as for, while, iter and when calling lists directly. Also most library functions that iteratively call you react to it, like std:re:map and std:re:replace_all. Be aware, that returning a value might not be supported by all iterative constructs.

!ret = range 0 9 1 {!(i) = @;
    (i > 4) { break :DONE };
};

std:assert_eq ret :DONE;

An example where the list iteration is stopped:

!val = $[1,2,3,4] { (_ > 3) { break :XX }; _ };

std:assert_eq val :XX;
5.1.5 - next

next stops execution of the current function or statement block and continues with the next iteration of the inner most iteration.

!sum = $@i $[1,2,3,4] {
    (_ == 3) next;
    $+ _;
};
std:assert_eq sum 7;
!sum = $@i range 1 10 1 {
    (_ % 2 == 0) next;
    $+ _;
};
std:assert_eq sum 25;
5.1.6 - jump index-val branch1last-branch

This is a jump table operation, it’s a building block for the more sophisticated match operation. The first argument is an index into the table. If the index is outside the table the last-branch is jumped to. The branches are compiled like the bodies of while, iter, match and if into a runtime evaluated block.

!x   = 10;
!idx = 2;

!res =
    jump idx
        { x + 3 }
        { x + 4 }
        { x + 5 };

std:assert_eq res 15;

The arms don’t have to be in { ... } because they are blocks and the above could be written like this:

!x   = 10;
!idx = 2;

!res =
    jump idx
        x + 3
        x + 4
        x + 5;
std:assert_eq res 15;

!res = x + (jump idx 3 4 5);
std:assert_eq res 15;

5.2 - Collection Iteration

5.2.1 - Iteration over vectors

Iterating over a vector is the most basic iteration supported by WLambda. You just call the vector with a function as first argument:

!sum = 0;
$[1, 2, 3] {
    .sum = sum + _;
};

std:assert_eq sum 6;

You can also use for if you like.

5.2.2 - Iteration over maps

Iterating over a map is as simple as iterating over a vector. The map can be called with a function as first argument and it starts iterating over its key/value pairs. The first argument of the function is the value, the second argument is the key.

!sum  = 0;
!keys = $[];

${a = 10, b = 20, c = 30} {
    !(v, k) = @;
    .sum = sum + v;
    std:push keys k;
};

std:assert_eq sum 60;
std:assert_eq (std:str:join "," ~ std:sort keys) "a,b,c";

You can also use for if you like.

5.2.3 - for iteratable-value function

Calls function for every element of iteratable-value. Iteratable values are:

  • Vectors
!product = 1;

for $[3,4,5] {
    .product = product * _;
};

std:assert_eq product 60;
  • Maps
!product = 1;
!keys    = $[];

for ${a = 10, b = 20, c = 30} {
    !(v, k) = @;
    .product = product * v;
    std:push keys k;
};

std:assert_eq (std:str:join "," ~ std:sort keys) "a,b,c";

std:assert_eq product 6000;
  • Byte Vectors
!byte_sum = 0;

for $b"abc" {
    .byte_sum = byte_sum + (int _);
};

std:assert_eq byte_sum 294;
  • Strings
!str_chars = $[];

for "abc" {
    std:push str_chars _;
};

std:assert_eq (str str_chars) (str $['a', 'b', 'c']);
  • Symbols
!str_chars = $[];

for :abc {
    std:push str_chars _;
};

std:assert_eq (str str_chars) (str $['a', 'b', 'c']);
5.2.4 - map function iterable

Maps anything that is iterable by calling function with each item as first argument and collecting the return values in a vector.

If a map is passed as iterable then function is called with two arguments, the first being the map entry value and the second the key. Note: When iterating over maps, don’t assume any order.

It is very similar to $@vec iter i <iterable> { $+ ... }.


std:assert_str_eq
    (map { float[_] / 2.0 } $[1,2,3,4,5])
    $[0.5, 1, 1.5, 2, 2.5];

std:assert_str_eq
    (map { _ * 10 } $[$b'a', $b'b', $b'c', 10, 20])
    ($@vec
        iter i $[$b'a', $b'b', $b'c', 10, 20] {
            $+ i * 10
        });


std:assert_str_eq
    (map std:str:to_uppercase
        $["abc", "bcbc", "aaad", "afoo", "foo"])
    $["ABC", "BCBC", "AAAD", "AFOO", "FOO"];


std:assert_str_eq
    (std:sort ~ map { @ } ${a = 10, b = 20})
    $[$[10, "a"], $[20, "b"]];


std:assert_str_eq
    (map { _ * 2 } 0 => 10)
    $[0,2,4,6,8,10,12,14,16,18];
5.2.5 - filter function iterable

Filters anything that is iterable by the given function. The function is called with each item and if it returns a $true value, the item will be collected into a vector that is returned later.

If a map is passed as iterable then function is called with two arguments, the first being the map entry value and the second the key.

It is very similar to $@vec iter i <iterable> { if some_function[_] { $+ ... } }.


std:assert_str_eq
    (filter { (_ 0 1) == "a" } $["abc", "bcbc", "aaad", "afoo", "foo"])
    $["abc","aaad","afoo"];


std:assert_str_eq
    (map std:str:to_uppercase
        ~ filter { (_ 0 1) == "a" }
            $["abc", "bcbc", "aaad", "afoo", "foo"])
    $["ABC","AAAD","AFOO"];


std:assert_str_eq
    (std:sort
        ~ filter { @.0 % 2 == 0 }
            ${a = 2, b = 43, c = 16, d = 13 })
    $[16 => "c", 2 => "a"];


std:assert_str_eq
    (filter { _ % 2 == 0 } 0 => 10)
    $[0,2,4,6,8];

5.3 - Accumulation and Collection

WLambda provides special syntax and semantics for accumulating or collecting values while iterating through lists. There are following special syntax constructs:

SyntaxSemantics
$@v exprSetup collection of values in a vector, evaluates expr and returns the vector.
$@vec exprSame as $@v
$@m exprSetup collection of key/value pairs in a map, evaluates expr and returns the vector.
$@map exprSame as $@m
$@s exprSetup appending of values to a string, evaluates expr and returns the string.
$@string exprSame as $@s
$@b exprSetup collection of values in a byte vector, evaluates expr and returns byte vector.
$@bytes exprSame as $@b
$@i exprSetup accumulation in an integer, evaluates expr and returns the integer sum.
$@int exprSame as $@i
$@f exprSetup accumulation in a float, evaluates expr and returns the float sum.
$@flt exprSame as $@f
$+Evaluated to a function that can be called to add/append a new value to the current collection/accumulation.
$@@Access the current accumulation value.

These syntaxes are not lexically scoped. That means $+ and $@@ can be used in other functions:

!out_mul = { $+ _ * 20 };

!v = $@vec iter i $i(1,5) ~ out_mul i;

std:assert_eq (str v) (str $[20, 40, 60, 80]);

However, due to issues with coupling your functions to the usage of accumulators this style is recommended:

!mul = { _ * 20 };

!v = $@vec iter i $i(1,5) ~ $+ mul[i];

std:assert_eq (str v) (str $[20, 40, 60, 80]);
5.3.1 - Transforming a vector

If you just want to do something with items in a vector and construct a new one from the results:

!result = $@vec $[1,2,3,4] \$+ _ * 2;   # multiply each item by 2

std:assert_eq (str result)  "$[2,4,6,8]";
5.3.2 - Example of $@@

Here is an interesting example how $@@ might be used:


!list_of_lists = $[];
!result = $@vec $[1,2,3,4] {
    $+ 2 * _;               # put the value into the list
    std:push list_of_lists
        ~ std:copy $@@; # construct a list of intermediate results
};

std:assert_eq (str result) "$[2,4,6,8]";

std:assert_eq (str list_of_lists)
    "$[$[2],$[2,4],$[2,4,6],$[2,4,6,8]]";
5.3.3 - Transforming a vector to a map

For constructing maps the $@map construct is available. In the following example we transform a vector of pairs into a map:


!result = $@map $[ $[:a, 10], $[:b, 33], $[:c, 99] ] {
    !(key, value) = _;
    $+ key value;
};

std:assert_eq result.a 10;
std:assert_eq result.b 33;
std:assert_eq result.c 99;
5.3.4 - Iteratively concatenating strings

In case you need to construct a longer text the $@string construct allows you to efficiently create a long string. For demonstration purposes we compare the following inefficient code with the usage of $@string:


!accum = "";
$["abc", "def", "ghi", "XXX"] {
    .accum = accum _;   # allocates a new string each iteration
};

std:assert_eq accum "abcdefghiXXX";

In theory for this constructed example the quickest way would be to use std:str:join:

!accum = std:str:join "" $["abc", "def", "ghi", "XXX"];

std:assert_eq accum "abcdefghiXXX";

But maybe you need to transform or construct the strings before joining:

!transform = { ">" _ };

!accum = $@string $["abc", "def", "ghi", "XXX"] {
    $+[transform _] # appends the string to the accumulation string
};

std:assert_eq accum ">abc>def>ghi>XXX";
5.3.5 - Accumulating sums

The following examples show how accumulation of values with $@int and $@float work.

!sum = $@int $[1,2,3,4] {
    $+ _
};

std:assert_eq sum 10;

And with floats:

!sum = $@float $[1.2,1.3,2.2,3.4] {
    $+ _
};

std:assert_eq (std:num:round 10.0 * sum) 81.0;

5.4 - Utilities

5.4.1 - std:accum collection a b

This function accumulates all its arguments in the collection. It does the same form of accumulation as $+ does.

std:assert_eq (str ~ std:accum $[] 1 2 3)   "$[1,2,3]";
std:assert_eq (std:accum "" 1 2 3)          "123";
std:assert_eq (str ~ std:accum $b"" 1 2 3)  "\x01\x02\x03";
std:assert_eq (str ~ std:accum 10 1 2 3)    "16";
5.4.2 - std:zip vector map-fn

Creates a generator that calls map_fn with the consecutive elements of vector as the last argument of map-fn. All arguments passed to std:zip are appended to the argument list.

This is useful for combining the iteration over two vectors or collections.

!l = $@v $[13, 42, 97] ~ std:zip $["Foo", "Bar", "Baz"] { $+ @ };
std:assert_eq (str l) (str $[$[13, "Foo"], $[42, "Bar"], $[97, "Baz"]]);
5.4.3 - std:fold accumulator func iteratable

This function iterates over iteratable while providing the current element from iteratable as first and the accumulator variable to func as second argument. The accumulator for the next iteration is always the return value of the previous execution of func.

This is a convenience function in cases where the accumulator syntax $@ does not fit the use-case.

Returns the most recently returned value from func.

Calculate the product of the first 5 integers.

!v = std:fold 1 {!(x, acc) = @;
    x * acc
} $[1,2,3,4,5];

std:assert_eq v 120;

Another contrived example:

!v = std:fold $[] {!(x, acc) = @;
    std:displayln @;
    ((std:cmp:str:asc "c" x) > 0) {
        std:push acc x;
    };
    acc
} "abcdef";

std:assert_eq (str v) (str $['d', 'e', 'f']);
5.4.4 - std:enumerate map-fn

Creates a generator that calls map-fn with a counter that is incremented after each call, starting with 0. The counter is appended to the argument list after the regular arguments.

!l = $@v $["lo", "mid", "hi"] ~ std:enumerate { $+ $[_1, _] };
std:assert_eq (str l) (str $[$[0, "lo"], $[1, "mid"], $[2, "hi"]]);

6 - Operators

6.1 - Operator Assignment

Please note, that you can use all these operators, as well as special operators like =>, &> and <& with assignment operations:

!x = 10;
.x += 3;
std:assert_eq x 13;

!y = 10;
.y < = 10;
std:assert_eq y $false;

!f = \_ * 10;
.f <&= 10;
std:assert_eq f 100;

!x = 10;
.x &>= \_ * 10;
std:assert_eq f 100;

6.2 - Arithmetic

The output type (float vs. integer) of the numerical arithmetic operators is defined by the first operand of the operation. Use the casting functions float or int if you are unsure.

Please note that not all operators are available as plain identifiers and need to be quoted when used in their prefix form or as functions, some of them are *, /, % and some others.

6.2.1 - + operand-1 operand-2

This function implements arithmetic addition. If the first operand is a float number, the substraction will return a float result. If it is an integer or anything else (like a string), an integer result is returned.

std:assert_eq (+ 5.5 0.5) 6.0;
std:assert_eq (5.5 + 0.5) 6.0;
std:assert_eq (+ 5 2) 7;
std:assert_eq (+ "5" 2) 7;
std:assert_eq (+ :5 2) 7;
6.2.2 - - operand-1 operand-2

This function implements arithmetic substraction. If the first operand is a float number, the substraction will return a float result. If it is an integer or anything else (like a string), an integer result is returned.

std:assert_eq (- 5.5 0.5) 5.0;
std:assert_eq (5.5 - 0.5) 5.0;
std:assert_eq (- 5 2) 3;
std:assert_eq (- "5" 2) 3;
std:assert_eq (- :5 2) 3;
6.2.3 - * op-a op-b

Returns the multiplication of the two operands.

std:assert 10   * 4 == 40;
std:assert 10.1 * 4 == 40.4;
std:assert "10" * 4 == 40;

std:assert (`*` 10 4) == 40;

std:assert (float "10.1") * 4 == 40.4;
6.2.4 - / op-a op-b

Returns the division of the two operands.

std:assert 10   / 4 == 2;
std:assert 10.0 / 4 == 2.5;
std:assert "10" / 2 == 5;

std:assert (`/` 10 4) == 2;

std:assert (float "10.1") * 4 == 40.4;
6.2.5 - % op-a op-b

Returns the remainder of the division of op-a by op-b.

std:assert     5 % 4 == 1;
std:assert (`%` 5 4) == 1;
6.2.6 - ^ op-a op-b

Returns op-a raised by the power of op-b. Supports float and integers.

std:assert_eq 2 ^ 4     16;
std:assert_eq std:num:round[(2.0 ^ 2.1) * 1000] 4287.0;
std:assert_eq 2 ^ 2.1   4; # first arg type matters!

6.3 - Comparison

6.3.1 - == op-a op-b

Checks whether the two operands are equal to each other. Data types like booleans, integers, floats, symbols and strings are compared by their contents. Other types like vectors, maps, functions, errors or references are compared by referential equality.

std:assert              $none == $none;
std:assert                  1 == 2 - 1;
std:assert               "aa" == ("a" "a");
std:assert               :xxy == :xxy;
std:assert       not ~ $[1,2] == $[1,2];
std:assert            $p(1,2) == $p(1,2);
std:assert            $i(1,2) == $i(1,2);
std:assert          $i(1,2,3) == $i(1,2,3);
std:assert    not ~ $i(1,2,3) == $f(1.0,2.0,3.0);
std:assert    $f(1.0,2.0,3.0) == $f(1.0,2.0,3.0);

std:assert ~ `==` 1 (2 - 1); # prefix form
6.3.2 - != op-a op-b

Checks whether the two operands are distinct from each other. Data types like booleans, integers, floats, symbols and strings are compared by their contents. Other types like vectors, maps, functions, errors or references are compared by referential equality.

It’s generally the opposite of ==.

std:assert         1 != 2;
std:assert     not[2 != 2];
std:assert     "foo" != "bar";
std:assert not["foo" != "foo"];

std:assert ~ `!=` 1 2;

!r1 = $[1,2];
!r2 = $[1,2];
std:assert r1 != r2;
6.3.3 - < op-a op-b

Numerical comparison operator that checks whether op-a is less than op-b

std:assert   10   < 11;
std:assert   10.1 < 10.2;
std:assert not[10 < 10.1];  # the type of the first argument decides return type!
6.3.4 - <= op-a op-b

Numerical comparison operator that checks whether op-a is less or equal to op-b

std:assert 10   <= 11;
std:assert 10.1 <= 10.2;
std:assert 10   <= 10.1;  # integer <=, the type of the first argument decides return type!
6.3.5 - > op-a op-b

Numerical comparison operator that checks whether op-a is greater than op-b

std:assert   11.1 > 11;
std:assert   11.1 > 11.0;
std:assert not[10 > 10.1];  # the type of the first argument decides return type!
6.3.6 - >= op-a op-b

Numerical comparison operator that checks whether op-a is greater or equal to op-b

std:assert 11   >= 11;
std:assert 10.2 >= 10.1;
std:assert 10 >= 10.1;  # integer >=, the type of the first argument decides return type!

6.4 - Bit Operations

6.4.1 - & op-a op-b

Binary and operation between two integers.

std:assert (0b0011 & 0b1011) == 0b011;
std:assert (3      &     11) == 3;
6.4.2 - &^ op-a op-b

Binary xor operation between two integers.

std:assert (0b0011 &^ 0b1011) == 0b1000;
std:assert (3      &^     11) == 8;
6.4.3 - &| op-a op-b

Binary or operation between two integers.

std:assert (0b0011 &| 0b1000) == 0b1011;
std:assert (3      &|      8) == 11;
6.4.4 - << op-a op-b

Binary left shift operation of op-a by op-b bits.

std:assert (0b0011 << 3)   == 0b11000;
std:assert (`<<` 0b1011 2) == 0b101100
6.4.5 - >> op-a op-b

Binary right shift operation of op-a by op-b bits.

std:assert (0b0011 >> 2)      == 0b0;
std:assert (0b1100 >> 2)      == 0b11;
std:assert (`>>` 0b1011000 3) == 0b1011

6.5 - Collection Addition Operators +> and <+

+> and <+ are special operators for convenient and quick collection creation. You can use them also to call a function with multiple arguments.

!vec = $[] +> 1 +> 2 +> 3;

std:assert_str_eq vec $[1, 2, 3];

!map = ${}
    +> (:a => 1)
    +> (:b => 2);

map +> (:c => 3);

std:assert_str_eq map ${a=1,b=2,c=3};

Usually these operators just append (+>) or prepend (<+) the right/left hand side to the collection. But there are some special values which do special things.

First and foremost the iterator data type. If you pass an iterator to this operator, the iterator will be iterated and all returned elements are added to the collection in the order of the operator:

!v = $[] +> ($iter 0 => 4) +> ($iter "abc");

std:assert_str_eq v $[0,1,2,3,'a','b','c'];

As the <+ operator prepends the individual elements, the same is happening with the iterated elements. Which means that their order is reversed:

!v = ($iter 0 => 4) <+ ($iter "abc") <+ $[];

std:assert_str_eq v $[3,2,1,0,'c','b','a'];

The following data types can be used as collection for these operators:

  • Vectors $[]
  • Maps ${}
  • Strings ""
  • Byte vectors $b""
  • Functions

The most special cases are maps and functions, which are described in more detail in the next sections.

Collection Addition with Maps

If you add to a map, there is some special behavior for some data types.

Adding a key value pair is done with pairs:

!m = ${}
    +> $p(:a, 10)
    +> :b => 20;

std:assert_str_eq m ${a=10,b=20};

If you add an iterator, the iterator is walked and the given keys are added if present:

!m = ${}
    +> ($iter $[1, 2, 3])
    +> ($iter ${ a = 10, b = 20 });

std:assert_str_eq m ${1=1,2=2,3=3,a=10,b=20};

If you add a list to a map, the first element of that list is used as key, and the list as value:

!m = ${}
    +> $[:a, 1, 2]
    +> $[:b, 3, 4];

std:assert_str_eq m ${a=$[:a,1,2],b=$[:b,3,4]};
Collection Addition with Function

If you pass a function as collection to either +> or <+ the function is called for each added element. The return value of the expression is the return value of the most recent function call.

!v = $[];

!v2 = { std:push v _; v } +> 1 +> 2 +> ${ a = 3, b = 4 };

std:assert_str_eq v  $[1,2,${a=3,b=4}];
std:assert_str_eq v2 $[1,2,${a=3,b=4}];
6.5.1 - +> collection a

Append to collection operator.

!v  = $[] +> 1 +> "x" +> $b"y" +> ($iter 0 => 3);

!v2 = `+>` $[] 1 "x" $b"y" ($iter 0 => 3);

std:assert_str_eq v v2;
6.5.2 - <+ collection a

Prepend to collection operator. Please note that the arguments are reversed to the order in an operator expression.

!v  = ($iter 0 => 3) <+ 1 <+ "x" <+ $b"y" <+ $[];

!v2 = `<+` $[] $b"y" "x" 1 ($iter 0 => 3);

std:assert_str_eq v v2;

7 - String and Byte Vector Formatting

WLambda comes with a built in functionality for string (and byte vector) formatting. It works by creating a specialized formatting function from a given string literal at compile time with the $F"..." syntax, or a string at runtime with the std:formatter _str_ function.

The formatter syntax is documented in detail at 12.2 String Formatting Syntax. It is basically the Rust std::fmt Syntax with a few extensions for WLambda data types and the dynamically typed nature of WLambda.

The WLambda syntax for $F is: $F string-literal. This means, you can use any WLambda string literal after $F:

$F"...";        # normal string
$F$b"...";      # byte vector
$F$q/.../;      # normal string, quote syntax
$F$Q"...";      # byte vector quote syntax
$F$code{ };     # code block string

(Please note, that $code{ ... } is not as useful in this context, because the formatter placeholders usually are not valid WLambda syntax.)

This is a very simple example:

!x = "abc";
!s = $F"x = {}" x;

std:assert_eq s "x = abc";

You can also use string formatting to generate byte vectors:

!bv = $F$b"x={}" $b"\xFF";

std:assert_eq bv $b"x=\xFF";

If you want to generate the WLambda written representation of a piece of data like std:write_str would return it, you have to specify the special formatting syntax {:...!w}:

std:assert_eq ($F"x={:!w}" $&&$[1, 2, 3, 4]) "x=$&&$[1,2,3,4]";

std:assert_eq ($F"x={}"   $&&$[1, 2, 3, 4]) "x=$[1,2,3,4]";
7.0.1 - std:formatter format-string

Returns a formatting function that takes exactly the arguments specified in the format-string. If the format syntax is wrong, an error is returned.

This is useful, if you need to build a format string at runtime, because $F only allows string/byte vector literals.

!fmt = ">6.2";
!fmt_fun = (std:formatter (std:str:cat "{1} [{0:" fmt "}]"));

std:assert_eq (fmt_fun 3.43554 1.2323) "1.2323 [  3.44]";

7.1 - Formatting Numbers

Number formatting, that is integers, float and numerical vectors, require an extension of the formatting syntax. You need to specify whether an integer "{:...!i}" or a float {:...!f} is formatted. Otherwise WLambda will cast everything to a string for formatting.

Here are some examples:

std:assert_eq ($F "{:8!i}"  123)   "     123";
std:assert_eq ($F "{:08!i}" 123)   "00000123";
std:assert_eq ($F "{:<8!i}" 123)   "123     ";
std:assert_eq ($F "{:^8!i}" 123)   "  123   ";
std:assert_eq ($F "{:>8!i}" 123)   "     123";

std:assert_eq ($F "{:8.2!f}"  123.567)   "  123.57";
std:assert_eq ($F "{:08.2!f}" 123.567)   "00123.57";
std:assert_eq ($F "{:<8.2!f}" 123.567)   "123.57  ";
std:assert_eq ($F "{:^8.2!f}" 123.567)   " 123.57 ";
std:assert_eq ($F "{:>8.2!f}" 123.567)   "  123.57";

std:assert_eq ($F "{:8.2}"  123.567)   "  123.57";
std:assert_eq ($F "{:08.2}" 123.567)   "00123.57";
std:assert_eq ($F "{:<8.2}" 123.567)   "123.57  ";
std:assert_eq ($F "{:^8.2}" 123.567)   " 123.57 ";
std:assert_eq ($F "{:>8.2}" 123.567)   "  123.57";

You can even format numbers in numerical vectors, data vector, pairs or maps:

std:assert_eq ($F "{:8.2}" $f(1.2, 3.456, 8.232)) "(    1.20,    3.46,    8.23)";
std:assert_eq ($F "{:8.2}" $[1.2, 3.456, 8.232])  "[    1.20,    3.46,    8.23]";
std:assert_eq ($F "{:8.2}" $p(1.2, 3.456))        "(    1.20,    3.46)";
std:assert_eq ($F "{:8.2}" ${x = 1.2, y = 3.456, z = 8.232})
              "{x:    1.20, y:    3.46, z:    8.23}";

std:assert_eq
    ($F "{:>8!i}" $i(1.2, 3.456, 8.232))
    "(       1,       3,       8)";

Also hexadecimal, octal and binary are supported for integers, they come after the !i:

std:assert_eq ($F "{:5!ix}" 321)    "  141";
std:assert_eq ($F "{:5!io}" 321)    "  501";
std:assert_eq ($F "{:<11!ib}" 321)  "101000001  ";
std:assert_eq ($F "{:011!ib}" 321)  "00101000001";

8 - Data Structure Matchers, Selectors and String Patterns/Regex

WLambda comes with a builtin DSL (domain specific language) for shallow data structure matches and deep data structure selection and regular expression (regex) pattern matching on strings. A selector (structure selection) gives you the ability to search deep into WLambda data structures like CSS Selectors into HTML DOM trees or XPath into XML. While a structure matcher, as used by the match operation, allows you to directly match a certain WLambda piece of data.

A subset of the selectors are the patterns, which are able to match strings like regular expressions. The syntax of patterns is a bit different from normal regular expressions like Perl, Python or JavaScript has. This is partly due to the fact that these patterns aim to be easily used to match parts of a specific string like filename globs photo_???_*.jpg.

For an in depth description of the selector and pattern syntax please refer to the Pattern and Selector Syntax.

8.1 - Data Structure Matcher

This is probably one of the most convenient matching features of WLambda. While selectors ($S[a / * / b]) allow searching deep into data structures, the matches allow to efficient precise shallow selection and matching. The match operation allows to match a value against multiple matchers, while the $M ... syntax allows to define a matcher function for a single match (commonly used in an if expression).

For a reference of the matcher syntax see below.

8.1.1 - match value-expr match-pair1 … [default-expr]

The match operation is a very versatile control flow operation.

8.1.2 - $M expr

This is a structure matcher expression. It will compile expr into a structure matcher function. The reslting function will match it’s first argument agianst the match and return a map containing the capture variables (or just an empty map).

It will also bind the result map to $\. This makes it possible to easily match a data structure in an if statement:

!some_struct = $[:TEST, ${ a = 10, b = 1442 }];

if some_struct &> ($M $[sym, ${ a = 10, b = x }]) {
    std:assert_eq $\.sym :TEST;
    std:assert_eq $\.x   1442;
} {
    panic "It should've matched!";
};
8.1.3 - Data Structure Matcher Syntax

This the the compiletime syntax that is understood by the structure matchers that are used by $M ... and match.

  • $M, $M1, $M2, … in the following table stands for a structure matcher expression.
  • All other tokens or values stand for themself.
WLambda ValueSemantics
xMatches any value and assigns it to the variable x.
?Matches any value, but does not assign it.
x $M $M1 ... $MnAssign the value that matched $M, $M1 or $Mn to the variable x.
? $M $M1 ... $MnMatches if $M, $M1 or $Mn matches.
_*Placeholder for 0 or N items that match any items in the vector.
_+Placeholder for 1 or N items that match any items in the vector.
_?Placeholder for 0 or 1 items that match any items in the vector.
_* $MPlaceholder for 0 or N items that match $M in the vector.
_+ $MPlaceholder for 1 or N items that match $M in the vector.
_? $MPlaceholder for 0 or 1 items that match $M in the vector.
_type? :integer ...Matches an element of one of the given types. Symbol names should have the same name as the type names returned by the type function.
$r/.../Matches any element, where it’s string contents matches the given pattern.
$rg/.../Matches any element, where it’s string contents matches the given pattern. Returns a list with all global matches.
$M1 &or $M2Matches if $M1 or $M2 matches.
$M1 &and $M2Matches if $M1 and $M2 matches.
$[$M1, $M2, ...]Matches a vector.
${ $Mkey1 = $Mval1, ...}Matches a map. $Mkey1 can also be a $M match, but keep in mind that maps can only have symbols as keys. You can however match symbols using regex patterns for instance. If you only use symbols as keys in this match, the map access is optimized a bit, because there is no need to iterate over all keys then.
$p($M1, $M2)Matches a pair.
$i($M1, ...)Matches an integer vector.
$f($M1, ...)Matches a float vector.
$o($M)Matches an optional where the value matches $M.
$e $MMatches an error value that matches $M.
$nMatches $none.
literal valuesLiteral values like booleans, strings, symbols and numbers match their value.

8.2 - Data Structure Selectors $S(...)

This section shows how data structure selectors can be used.

TODO

8.2.1 - Selector and WLambda Regex Syntax:
    (* NOTE: Whitespace is not part of a pattern in most places. This means
             if you want to match whitespace, you will have to escape
             it either with a '\', with a [ ] character class or match
             one whitespace char with $s. *)

    class_char  = { ?any character except "]"? }
                  (* special sequence: "\^" => "^" and "\\" => "\"
                     and "\]" => "]" *)
                ;

    ident_char_in_selector =
                  (* if regex is used inside a selector: *)
                  { ?any character except whitespace,
                    "!", "?", "/", "\", "|", "^", ",",
                    "'", "&", ":", ";", "$", "(", ")",
                    "{", "}", "[", "]", "*" and "="? }
                  (* allows the usual backslash escaping from strings! *)
                ;

    ident_char_in_direct_pattern =
                | (* if regex is used as pattern directly: *)
                  { ?any character except whitespace,
                    "?", "|", "$", "(", ")", "[", "]" and "*"? }
                  (* allows the usual backslash escaping from strings! *)
                ;
    ident_char  = ident_char_in_direct_pattern
                | ident_char_in_selector
                ;

    ident       = ident_char, { ident_char }
                ;

    index       = digit, { digit }
                ;

    rx_atom     = pat_glob
                | ident_char
                ;

    glob_atom   = pat_glob
                | ident
                ;

    rx_match_mod = "L"             (* transforms the input string from the match
                                      position on to lower case. *)
                 | "U"             (* transforms the input string from the match
                                      position on to upper case. *)
                 ;

    pat_regex   = "*", rx_atom     (* matches sub pattern 0 or N times *)
                | "+", rx_atom     (* matches sub pattern 1 or N times *)
                | "<", [ ("*" | "+" | "?") ], rx_atom
                                   (* non greedy version of the above *)
                | "?", rx_atom     (* matches sub pattern 0 or 1 times *)
                | "!", rx_atom     (* matches (zero width) if next pattern does not match *)
                | "=", rx_atom     (* matches (zero width) if next pattern does match *)
                | "^"              (* matches (zero width) start of string *)
                | "$"              (* matches (zero width) end of string *)
                | "s"              (* matches one whitespace character *)
                | "S"              (* matches one non-whitespace character *)
                | "&", rx_match_mod
                ;

    glob_group  = "(", "^", pattern, ")"    (* capturing sub group *)
                | "(", pattern, ")"         (* sub group *)
                ;

    class_range = class_char, "-", class_char (* contains a range of chars, eg. [a-z] *)
                ;

    glob_cclass = "[",  { class_char | class_range }, "]" (* character class match for 1 char *)
                | "[^", { class_char | class_range }, "]" (* negated character class match for 1 char *)
                ;

    pat_glob    = "*"                       (* 0 or N any characters *)
                | "?"                       (* any character *)
                | "$", pat_regex
                | glob_cclass
                | glob_group
                ;

    pat_branch  = { glob_atom }
                ;

    pattern     = pat_branch, [ "|", pattern ]
                ;

    key         = index | pattern
                ;

    kv          = key, "=", pattern
                ;

    kv_item     = "{", kv, { ",", kv }, "}"
                ;

    node_match  = ":", ["!"], "(", selector, ")"
                | ":", ["!"], kv_item
                | ":", ["!"], "type", "=", pattern
                  (* pattern is matched against
                     vval type as returned by `type` *)
                | ":", ["!"], "str",  "=", pattern
                  (* pattern is matched against
                     the string contents or stringified
                     representation of the value *)
                ;

    node_cond   = node_match
                | node_match, "&", node_cond
                | node_match, "|", node_cond
                ;

    reckey_cond = "!", "key", "=", pattern
                  (* recurse only into values if they are not referred
                     to by a key matching the given pattern. *)
                ;
    recval_cond = "=", node_cond
                  (* recurse only into values if they match the given
                     condition *)
                ;

    node        = key, { node_cond }
                  (* marks it for referencing it in the result set *)
                | "**", [ reckey_cond ], [ recval_cond ], { node_cond }
                  (* deep expensive recursion *)
                | "^", node
                ;

    selector    = node, { "/", node }
                ;
8.2.2 - std:selector string

Parses the given string as WLambda data structure selector and returns a function that takes a data structure as first argument. That function will then query the data structure according to the given selector. That function will also set the global variable $\ to the result.

The main usage of this function is, when you want to define the selector at runtime. Otherwise WLambda provides the handy $S(...) syntax for generating the structure pattern function at compile time.

!runtime_name = "foo";
!sel = std:selector (" * / " runtime_name);

if sel <& $[${foo = 1}, ${foo = 2}, ${foo = 3}] {
    std:assert_str_eq $\ $[1,2,3];
} {
    std:assert $false
};

8.3 - String Patterns (Regex) $r/.../

This section shows how to use the builtin pattern regex engine in WLambda. You can embed patterns directly in your WLambda source with $rQ...Q. Where Q stands for the usual string quoting mechanism in WLambda (like $q/foo/, $q(foo bar), …). This has the advantage that the pattern syntax is checked on compile time of your WLambda program.

The result of the expression $r/foo/ is a function, which takes as first arguments a string and returns a vector of substrings of that input string. First element of that vector is always the matched sub string of the input string. All elements after that correspond to a pattern capture (^...) like in $r/foo(^bar)/. The function returns $none if the pattern could not be found in the input string.

Lets start off with a simple example:


!res = $r/a (^*) b/ "fooaxxxxbber";

std:assert_eq res.0 "axxxxbb";
std:assert_eq res.1 "xxxxb";

To match a whole string you can anchor using $^ and $$:

!res = $r/$^ a (^*) b $$/ "axxxxbb";

std:assert_eq res.0 "axxxxbb";
std:assert_eq res.1 "xxxxb";

To match special the characters $ you can use the backslash escaping \$:

std:assert_eq ($r/$+ \$/ "FF$$$FF").0   "$$$";

To access captured groups you can either use the return value of the matcher function, or use the global variable $\ which will contain the results of the latest match that was executed:

!res =
    if "foo//\\/foo" &> $r| $<*? (^$+[\\/]) * | {
        std:assert_eq $\.0 "foo//\\/foo";

        $\.1
    };

std:assert_eq res "//\\/";
8.3.1 - Global Patterns $rg/.../

With the g modifier the regex can be modified and will match the input string with the given pattern repeatedly and call a given function for each match.

The match function will receive the input string as first argument and a function that will be called for each match as second argument.

Inside the match function, you can use the control flow functions break and next to skip ahead.

The match function receives the contents of $\ as first argument, the offset of the match in the input string as second argument and the length of the match as third argument:

!found = $@vec $rg/x(^?)y/ "aax9yaaxcy" {!(match, offs, len) = @;
    $+ $[match.1, offs, len]
};

std:assert_str_eq found $[$["9", 2, 3], $["c", 7, 3]];
8.3.2 - Pattern Substitutions $rs/.../

The s modifier creates a substitution that will substitute each match of the pattern in the given input string with the return value of the match function. The match function is called with the same values as $rg does.

!digits =
    $["zero", "one", "two", "three", "four",
      "five", "six", "seven", "eight", "nine"];

!ret = $rs/[0-9]/ "on 1 at 0 of 8" {!(match, offs, len) = @;
    digits.(int match.0)
};

std:assert_eq ret "on one at zero of eight";

Inside the match function, you can use the control flow functions break and next. You can use that to control which occurence within the string to replace:

!res = $rs/xxx/ "fxxxfxxxfxxxf" { break "O" };

std:assert_eq res "fOfxxxfxxxf";
8.3.3 - Pattern Syntax Overview

While Selector and WLambda Regex Syntax describes the pattern syntax in detail, here is the WLambda pattern regex syntax in a nutshell:

Pattern SyntaxSemantics
`?$()[]*`
whitespacePlease note, that whitespace to be matched must be escaped using ’' or inside a character calss [ ].
\.Backslash escapes work the same as in regular WLambda strings. \ escapes the following character to have no special syntactic meaning in a pattern except matching itself. While escape sequences like \x41 match the character A or \u{2211} matches . These also work inside of character classes.
*Match 0 to N occurences of any character.
?Match 1 occurences of any character.
(...)A match group (does not capture).
(^...)A capturing match group.
[abcA-Z]A character class, matching the listed characters or ranges.
[^abcA-Z]A negative character class, matching all character except the listed characters or ranges.
$^String start anchor. Matches only the start of the string. Useful for specifying patterns that match the complete string (in combination with $$).
$$String end anchor. Matches only the end of the string. Useful for specifying patterns that must match the complete string.
$*XGreedly matches the pattern part X 0 or N times. For grouping pattern parts use (...) like in $*(abc).
$<*XNon-greedly matches the pattern part X 0 or N times.
$+XGreedly matches the pattern part X 1 or N times. For grouping pattern parts use (...) like in $+(abc).
$<+XNon-greedly matches the pattern part X 1 or N times.
$?XGreedly matches 0 or 1 occurences of the pattern part X. Like usual, you can group using (...).
$<?XNon-greedly matches 0 or 1 occurences of the pattern part X.
$!XZero-width negative look-ahead. ($^$!a*) matches any string not starting with an a.
$=XZero-width positive look-ahead. ($^$=a*) matches any string starting with an a.
$sMatches one (Unicode) whitespace character.
$SMatches one (Unicode) non-whitespace character.
$&LTransforms the input string for the following pattern matching parts to lowercase (attention: O(n) operation on the complete rest of the string!). Useful for matching case-insensitively.
$&UTransforms the input string for the following pattern matching parts to uppercase (attention: O(n) operation on the complete rest of the string!). Useful for matching case-insensitively.
8.3.4 - Standard Regular Expressions

Please note that WLambda can optionally be compiled with the regex crate, which implements a more common syntax for regular expressions. Please refer to the functions std:re:match in the WLambda standard library for this.

8.3.5 - std:pattern string [mode]

Compiles the regex pattern string to a function just like $r/.../ would do. The mode can either be :g (global match like $rg...), :s (substitution like $rs...) or $none. Useful for composing WLambda patterns at runtime:

!rx = std:pattern ~ std:str:cat "(^" "$+" "[a-z]" ")";

std:assert_eq (rx "foo").1 "foo";

Returns an error if the syntax failes to parse as pattern:

!err = unwrap_err ~ std:pattern "($+[a-z]";

std:assert_eq $i(0, 11)[err] "bad pattern";

Here an example of substitution:

!subs = std:pattern "$+x" :s;
std:assert_eq subs["fooxxxoxx", \"a"] "fooaoa";

9 - Modules

9.1 - export


!expr = { _ + 30 };

!@export symbol = expr; # exports symbol with value of expr (a function)

Warning: Do not expect the declared variables in the module to exist beyond execution time. Weak caught values will vanish like usual once the module scope is exited. This means, if you declare helper functions in local variables, do this with the :global modifier:

!:global helper = { _ * 2 };

!@export doit = { helper 10 };

Alternatively make the helper a strong reference:

!helper = $&& { _ * 2 };

!@export doit = { helper 10 };

9.2 - import


!@import x = tests:test_mod; # prefixes everything from modixes with x:

std:assert ~ (x:symbol 10) == 40;

You can also skip the prefix:

!@import std;

!v = $[];

std:push v 10;
std:push v 20;

std:assert_eq (str v) "$[10,20]";

10 - Core Library

This library contains all the core functions which belong to the core of the WLambda Programming Language. These functions can be seen as keywords of WLambda. Some functions are also available as operators.

10.0.1 - type value

Returns the name of the data type of value as string.

std:assert_eq (type 10)         "integer";
std:assert_eq (type 10.0)       "float";
std:assert_eq (type {})         "function";
!y = $&&std:to_drop { };
std:assert_eq (type y)          "drop_function";
std:assert_eq (type :s)         "symbol";
std:assert_eq (type "s")        "string";
std:assert_eq (type $[])        "vector";
std:assert_eq (type ${})        "map";
std:assert_eq (type $b"")       "bytes";
std:assert_eq (type $n)         "none";
std:assert_eq (type $t)         "bool";
std:assert_eq (type $e $n)      "error";
std:assert_eq (type $&&10)      "ref_strong";
std:assert_eq (type $&10)       "ref_hidden";
!x = $&&10;
std:assert_eq (type ~ $w&x)     "ref_weak";
10.0.2 - len value

Returns the length of value. Depending on the data type you will get different semantics.

std:assert_eq (len 10)              0;
std:assert_eq (len 10.1)            0;
std:assert_eq (len $t)              0;
std:assert_eq (len $f)              0;
std:assert_eq (len $n)              0;

std:assert_eq (len "\xFF")          2; # byte length of the UTF-8 string
std:assert_eq (len $b"\xFF")        1;
std:assert_eq (len $[1,2,3,4,5])    5;
std:assert_eq (len ${a=1, b=2})     2;
std:assert_eq (len ${a=1, b=2})     2;
10.0.3 - panic message

If your program runs into something that deserves a slap on the fingers of the developer you can use panic to do that.

11 - Standard Library

11.0.1 - std:shuffle rand_func vec

Shuffles the vec in place. The function rand_func needs to return a random 64 bit integer on each call. Here is an example:

std:srand 1234;
!vec = $[1,2,3,4,5,6,7,8];
std:shuffle { std:rand :i64 } vec;

std:assert_eq (str vec) "$[2,1,7,4,8,5,3,6]";

An Example with std:rand:split_mix64_next:

!sm  = std:rand:split_mix64_new_from 1234;
!vec = $[1,2,3,4,5,6,7,8];
std:shuffle { std:rand:split_mix64_next sm } vec;

std:assert_eq (str vec) "$[2,1,7,4,8,5,3,6]";
11.0.2 - std:delete vector-or-map index-or-key

This removes the designated element from the collection (either vector or map). This works for:

  • Vectors:
!v = $[1,2,3];

std:assert_eq (std:delete v 1) 2;
std:assert_eq (str v) (str $[1,3]);
  • Maps:
!m = ${a = 10, b = 20};

std:assert_eq (std:delete m :a) 10;
std:assert_eq (str m) (str ${b = 20});

Please note that this operation is potentially O(n) on vectors.

11.0.3 - std:ref_id value

Returns an integer identifier for a given referential value. The ID will stay the same as long as the reference is allocated. This returns a value for all data types that have some form of internal reference to a value on the heap.

The main usage of this function is to get a pre process unique ID for an allocated value. But be aware, that once the value is deallocated, the reference ID does not belong to that value anymore.

!v = $[1,2,3];

!v_id1 = std:ref_id v;
std:push v 4;

!v_id2 = std:ref_id v;

std:assert_eq v_id1 v_id2;
11.0.4 - std:copy vec_or_map

Makes a shallow copy of the given vector or map.

!a = $[1,2,3];
!b = std:copy a;
b.0 = 10;

std:assert_eq a.0 1;
std:assert_eq b.0 10;
11.0.5 - std:values collection-or-iter

This function returns all values in the given collection or iterator as vector. collection-or-iter can have be one of the following data types:

  • vector
  • numerical float or integer vector
  • map
  • iterator $iter
std:assert_str_eq (std:values $iter 0 => 5)      $[0,1,2,3,4];
std:assert_str_eq (std:values ${a = 10})         $[10];
std:assert_str_eq (std:values $iter ${a = 10})   $[10];
std:assert_str_eq (std:values $[1,2,3])          $[1,2,3];
std:assert_str_eq (std:values $i(1,2,3))         $[1,2,3];
11.0.6 - std:keys collection-or-iter

This function returns all keys in the given collection or iterator. It’s most useful for the map data type, but also returns the indices in a vector or numerical vector.

std:assert_str_eq (std:keys ${a = 10})           $["a"];
std:assert_str_eq (std:keys $iter ${a = 10})     $["a"];
std:assert_str_eq (std:keys $[3,3,3])            $[0,1,2];
std:assert_str_eq (std:keys $i(4,4,4))           $[0,1,2];
std:assert_str_eq (std:keys $i(4,4))             $[0,1];
11.0.7 - std:sort [compare_fun] vec

Sorts the given vec in place. The comparison function compare_fun gets the two values a and b and needs to return -1 if a < b, 0 if a = b and 1 if a > b.

There are four functions that implement numeric and lexicographic ordering:

  • std:cmp:num:asc
  • std:cmp:num:desc
  • std:cmp:str:asc
  • std:cmp:str:desc

If no compare_fun is given, the ordering will be ascending and lexicographic vs. numeric will be chosen by the type of the a value (if it is an integer or float it will be numeric, otherwise lexicographic).

!v = $[$[1], $[-1], $[3]];
std:sort { std:cmp:num:desc _.0 _1.0 } v;

std:assert_eq v.0.0 3;
std:assert_eq v.1.0 1;
std:assert_eq v.2.0 -1;
11.0.8 - std:cmp:num:asc a b

Compares a and b numerically and returns:

CasesReturn Value
a > b-1
a == b0
a < b1
std:assert_eq (std:cmp:num:asc 20 2)        -1;
std:assert_eq (std:cmp:num:asc "20" "20")    0;
std:assert_eq (std:cmp:num:asc 20 21)        1;
11.0.9 - std:cmp:num:desc a b

Compares a and b numerically descending and returns:

CasesReturn Value
a > b1
a == b0
a < b-1
std:assert_eq (std:cmp:num:desc "20" "2")     1;
std:assert_eq (std:cmp:num:desc "20" "20")    0;
std:assert_eq (std:cmp:num:desc 20 21)       -1;
11.0.10 - std:cmp:str:asc a b

Compares a and b lexicographically by their byte values. This orders Unicode code points based on their positions in the code charts.

CasesReturn Value
a > b-1
a == b0
a < b1
std:assert_eq (std:cmp:str:asc "abc" "aba") -1;
std:assert_eq (std:cmp:str:asc "abc" "abc")  0;
std:assert_eq (std:cmp:str:asc "abc" "abd")  1;
11.0.11 - std:cmp:str:desc a b

Compares a and b lexicographically by their byte values. This orders Unicode code points based on their positions in the code charts.

CasesReturn Value
a > b1
a == b0
a < b-1
std:assert_eq (std:cmp:str:desc "abc" "aba")  1;
std:assert_eq (std:cmp:str:desc "abc" "abc")  0;
std:assert_eq (std:cmp:str:desc "abc" "abd") -1;
11.0.12 - std:reverse value

Reverses the given sequence of values. This works for following data types:

  • Strings
  • Byte vectors
  • Vectors
  • Numeric vectors
  • Iterators
std:assert_str_eq (std:reverse $[1, 2, 3, 4])       $[4,3,2,1];
std:assert_str_eq (std:reverse "ABC")               "CBA";
std:assert_str_eq (std:reverse $b"ABC")             $b"CBA";
std:assert_str_eq (std:reverse $i(1,2,3,4))         $i(4,3,2,1);
std:assert_str_eq (std:reverse $f(1.1,2.2,3.3,4.4)) $f(4.4,3.3,2.2,1.1);
11.0.13 - std:displayln arg1

This function writes a humand readable version of all the arguments (with a space inbetween) to the standard output. This means that:

std:displayln "foo"

Will just print foo and a newline.

If you need a less ambigous form, use std:writeln, which handles its argument like written via std:ser:wlambda instead of str.

11.0.14 - $DEBUG arg1

This is a special value that evaluates to a print function that supplies the current position in the source code. For example this:

!k = $[1, 2, 3];

$DEBUG "I got values:" k 99;

Will print this (assuming it’s at line 1 col 3 in file file_foo.wl):

[1,3:<file_foo.wl>] DEBUG: "I got values:"(string) $[1,2,3](vector) 99(integer)

In case you want to directly write a string or some value, you will have to prefix the argument with the symbol :\:

!k = 30;
$DEBUG :\ "k =" :\ k;

Will print like this:

[1,11:<wlambda::eval>] DEBUG: k = 30
11.0.15 - std:writeln arg1

This function writes the WLambda representation of its arguments (with a space inbetween) to standard output. This means that:

std:displayln "foo"

Will print "foo" and a newline.

See also the description of std:ser:wlambda.

If you need a more human readable form use std:displayln.

11.0.16 - std:eval code-string

Evaluates code-string in the current global environment and returns the generated value. If the code leads to any kind of evaluation error, an error object is returned.

std:assert_eq (std:eval "1 + 2") 3;
!:global X = 20;
std:assert_eq (std:eval "1 + X") 21;
11.0.17 - std:assert bool [message]

Just a simple assertion function that panics if the first argument is not true. Returns the passed value if it is a true value. You can pass an optional message as second parameter.

std:assert $false; #=> Panic
std:assert 120;    #=> 120
11.0.18 - std:assert_eq actual expected [message]

This function checks if the actual value is equal to the expected value and panics if not. The optional message is passed in the panic for reference.

!x = 30 * 2;
std:assert_eq x 60 "30 * 2 == 60";
11.0.19 - std:assert_str_eq actual expected

This function stringifies actual and expected using the str function and compares the resulting strings.

This is very useful to compare data structures, as map keys are sorted if the maps are stringified using str:

std:assert_str_eq $[1, 2, 3]        $[1, 2, 3];
11.0.20 - std:assert_rel_eq l r epsilon [message]

This function checks if l is within epsilon of r. If the absolute value of the difference between l and r is greater than epsilon, this function will panic, also displaying the optional message if present.

!x = 10.5;
!y = 11.3;
std:assert_rel_eq x y 1;
11.0.21 - std:measure_time unit function

This function measures the time the given function took to execute. The unit defines how precisely the time is measured. Following strings are supported units:

  • s - seconds
  • ms - milliseconds
  • us - microseconds
  • ns - nanoseconds

The return value is a vector where the first element is the time it took to execute the function, and the second element is the return value of that function.

!res = std:measure_time :ns { $@i iter i 0 => 100000 { $+ i } };
std:assert res.0 > 100;
std:assert_eq res.1 4999950000;

11.1 - I/O

11.1.1 - std:io:line

Reads a line from standard input and returns it. Returns an error if something went wrong.

!line = unwrap std:io:line[];
std:displayln "you entered: " std:str:trim_end[line];
11.1.2 - std:io:lines [value]

Calls the given value for each line in standard input until EOF and returns the last returned value from that call. If value is not given, all lines will be appended to a new vector and returned. Returns an error if some IO error occured.

!lines = std:io:lines[];

!lines = $@v std:io:lines $+;

std:io:lines {!(line) = @;
    std:displayln "You entered: [" std:str:trim[line] "]";
};
11.1.3 - std:io:file:read_text filename

Opens the file filename and returns its contents interpreted as UTF8 text as string.

std:io:file:write_safe "prelude_test.txt" "abcäöü";

!t = std:io:file:read_text "prelude_test.txt";
std:assert_eq t "abcäöü" "reading text from file works";
11.1.4 - std:io:file:read filename

Opens the file filename and returns its contents as byte buffer.

std:io:file:write_safe "prelude_test.txt" "abcäöü";

!t = std:io:file:read "prelude_test.txt";
.t = std:str:from_utf8 t;
std:assert_eq t "abcäöü" "reading binary from file works";
11.1.5 - std:io:file:write_safe filename bytes-or-string

Creates a new file with the given filename but with a “~” appended and writes the contents into it. After successful write, it renames the file to the given filename.

11.1.6 - std:io:file:append filename bytes-or-string

Opens the given filename in append mode and appends bytes-or-string to the end of the file.

11.1.7 - std:io:stdout:newline

Writes a newline to standard output. Returns an error if an error occured or $true otherwise.

11.1.8 - std:io:stdout:flush

Flushes the standard output buffer. Returns an error if an error occured or $true otherwise.

11.1.9 - std:io:stdout:print value

Writes the given value to standard output in a human readable form like std:displayln does. Returns an error if an error occured. $true if everything is fine.

std:io:stdout:write "xxx"; # => Writes `xxx` to standard output
11.1.10 - std:io:stdout:write value

Writes a WLambda representation of value to standard output. Returns an error if an error occured. $true if everything is fine.

std:io:stdout:write "xxx"; # => Writes `"xxx"` to standard output
11.1.11 - std:io:flush handle

Flushes the internal buffers of handle. handle can be any kind of IO handle, like a file handle or networking socket.

!socket = unwrap ~ std:net:tcp:connect "127.0.0.1:80";

std:io:write socket $b"GET / HTTP/1.0\r\n\r\n";
std:io:flush socket;
11.1.12 - std:io:read_some handle

Reads some amount of data from handle. The default maximum amount of bytes read is 4096. This function returns $o(bytes) if something was read. It returns $o() when EOF is encountered. $none is returned when the IO operation was interrupted or did timeout. An $error is returned if some kind of error happened, like loss of TCP connection.

Here is an example how to read everything from a socket until EOF is encountered:

!socket = unwrap ~ std:net:tcp:connect "127.0.0.1:80";

std:io:write socket $b"GET / HTTP/1.0\r\n\r\n";
std:io:flush socket;

!buf = $b"";
!done = $f;
while not[done] {
    match std:io:read_some[socket]
        $o(buf) => { .buf = buf +> $\.buf; }
        $o()    => { .done = $t; }
        ($e _)  => { .done = $t; };
};
11.1.13 - std:io:write handle data [offs]

Write all data as byte vector to the IO handle (socket, file handle, …), starting at offs (0 default). Returns the number of bytes written, an error or $none if interrupted. Note: This function does not respect the underlying write timeout in handle properly.

!socket = unwrap ~ std:net:tcp:connect "127.0.0.1:80";

std:io:write socket $b"GET / HTTP/1.0\r\n\r\n";
11.1.14 - std:io:write_some handle data

Try to write some data as byte vector to the IO handle (socket, file handle, …), starting at offs (0 default). Returns the number of bytes written, an error or $none if a timeout or interrupt ended the write operation.

!socket = unwrap ~ std:net:tcp:connect "127.0.0.1:80";

!bytes = $b"GET / HTTP/1.0\r\n\r\n";
!written = 0;

while len[bytes] > written {
    match (std:io:write_some socket bytes written)
        $o(n)  => { .written += n; }
        $none  => {}
        ($e _) => { break[] };
}

11.2 - Networking

11.2.1 - std:net:tcp:connect socket-addr [connect-timeout]

This tries to connect to socket-addr. If a connect-timeout is given, it will try to connect within that time. Please note that the returned sockets are thread safe and can be passed to another thread via an Atom, Atom Value Slot or Channel for instance.

socket-addr can be:

  • A pair $p(host, port)
  • A string like “host:port”.
  • A pair within a pair to specify whether to use IPv4 or IPv6 addresses only:
    • $p(:v4, "host:port")
    • $p(:v4, $p(host, port))
    • $p(:v6, "host:port")
    • $p(:v6, $p(host, port))

About connect-timeout see std:thread:sleep.

!socket =
    match (std:net:tcp:connect "127.0.0.1:8328")
        ($e err) => { panic ("Couldn't connect: " + str[$\.err]) }
        socket   => $\.socket;
11.2.2 - std:net:tcp:listen socket-addr function

Tries to bind a local port to socket-addr (see std:net:tcp:connect about socket-addr. Note: you can’t use $p(:v4 / :v6, ...)). Returns an error if something bad happened.

For every new connection function is called with the socket as first argument:

unwrap ~ std:net:tcp:listen "0.0.0.0:8292" {!(socket) = @;
    std:io:write socket "Hello!\r\n";
};

Please note that you can share the socket with other threads, see also std:net:tcp:connect.

11.2.3 - std:net:udp:new socket-addr [connect-addr]

Creates a new UDP socket and binds it to an endpoint. The arguments socket-addr and connect-addr have the same properties as the socket-addr that std:net:tcp:connect receives.

If connect-addr is given, a connected UDP port is created and std:net:udp:send does not need to pass a socket-addr.

Returns a socket or an error.

The socket can be shared between threads, so you can have a receiving thread and a sending one.

!socket = std:net:udp:new "0.0.0.0:31889";

std:net:udp:send socket $b"TEST" "127.0.0.1:31888";

Here is a more elaborate example using threads:

!hdl = std:thread:spawn $code {
    !socket = std:net:udp:new "0.0.0.0:31889";
    _READY.send :ok;

    !(data, addr) = std:net:udp:recv socket;
    std:displayln "PING" data;
    unwrap ~ std:net:udp:send socket ("Test:" data) addr;
};

hdl.recv_ready[];

!socket = std:net:udp:new "0.0.0.0:31888" "127.0.0.1:31889";
unwrap ~ std:net:udp:send socket $b"XYB123";

!(data, addr) = unwrap ~ std:net:udp:recv socket;

std:displayln "PONG" data;

std:assert_eq data $b"Test:XYB123";

hdl.join[];
11.2.4 - std:net:udp:send socket data [socket-addr]

Sends the data to the given socket-addr or to the connected address of the socket.

Returns the number of bytes sent or an error.

!socket = std:net:udp:new "0.0.0.0:31889";

std:net:udp:send socket $b"TEST" "127.0.0.1:31888";
11.2.5 - std:net:udp:recv socket [byte-count]

Receives byte-count number of bytes from the given socket. If byte-count is omitted 512 is assumed.

Returns the byte vector with the data and the endpoint address that it was received from. Or an error.

!socket = std:net:udp:new "0.0.0.0:31889";

!(buf, addr) = std:net:udp:recv socket;

11.3 - Processes

This chapter documents how to execute new processes.

11.3.1 - std:process:run executable-path [arguments]

Starts the given executable-path with the given (but optional) arguments. arguments can be a vector or an iterator. A data structure containing information about the finished child process or an error is returned if something went wrong.

This function will block the current thread while the child process is executed. It collects the output of the child process and returns it in a data structure of the following form:

{
    status  = 0,        # exit code
    success = $true,    # $true or $false
    stdout  = $b"...",  # data printed to stdout
    stderr  = $b"...",  # data printed to stderr
}

Here is an example for Linux:

!ret = unwrap ~ std:process:run "sh" $["-c", "echo \"test\""];

std:assert_eq ret.status  0;
std:assert_eq ret.success $true;
std:assert_eq ret.stdout  $b"test\n";
std:assert_eq ret.stderr  $b"";
11.3.2 - std:process:spawn executable-path arg-vector [:inherit_out | :inherit_all]

Like std:process:run starts a process from the executable-path and arg-vector. But it does not wait until the process finished running, it returns a child process handle or an error if something went wrong.

The handle can then be used by functions like:

  • std:process:kill_wait - to kill and wait for the process to exit
  • std:process:try_wait - to check if the process exited
  • std:process:wait - to wait until the process exits

The third argument specifies what happens with the standard I/O file handles. By default the child process gets null handles, so neither output is captured nor input is passed:

  • default - child process gets null handles for stdin, stdout and stderr.
  • :inherit_out - child process inherits stdout and stderr, but stdin will be null.
  • :inherit_all - child process inherits all (stdout, stderr and stdin) from the parent and uses them until it exits.

TODO: Implement pipe to/from the child process to be read/written to via std:io:read_some and std:io:write.

!hdl = unwrap ~ std:process:spawn "bash" $[
    "-c", "for i in `seq 0 10`; do echo $i; sleep 0.2; done; exit 20"
];


!result = unwrap ~ std:process:wait hdl;

std:assert ~ not result.success;
std:assert result.status == 20;
11.3.3 - std:process:try_wait child-handle

Checks if the child process behind child-handle exited. Returns $none if it did not exit yet. Returns a map with the structure ${ status = ..., success = $true / $false } if the child exited. Or an error if something failed.

!hdl = unwrap ~ std:process:spawn "bash" $[
    "-c", "for i in `seq 0 10`; do echo $i; sleep 0.2; done; exit 20"
];

!counter = 0;
!ret = $none;
while $true {
    std:thread:sleep :ms => 250;
    .counter += 1;

    .ret = unwrap ~ std:process:try_wait hdl;
    if ret {
        break ret;
    };
};

std:assert counter > 0;
std:assert ~ not ret.success;
std:assert ret.status == 20;
11.3.4 - std:process:kill_wait child-handle

Kills the child process behind child-handle and waits for it to return the exit status. Returns a map with the structure ${ status = ..., success = $true / $false } if the child exited. Or an error if something failed.

!hdl = unwrap ~ std:process:spawn "bash" $[
    "-c", "for i in `seq 0 10`; do echo $i; sleep 0.2; done; exit 20"
];

!res = std:process:kill_wait hdl;

std:assert ~ not res.success;
std:assert_eq res.status -1;
11.3.5 - std:process:wait child-handle

Waits until the child process behind child-handle exits by itself. Returns a map with the structure ${ status = ..., success = $true / $false } if the child exited. Or an error if something failed.

!hdl = unwrap ~ std:process:spawn "bash" $[
    "-c", "for i in `seq 0 10`; do echo $i; sleep 0.2; done; exit 20"
];

!res = std:process:wait hdl;

std:assert ~ not res.success;
std:assert_eq res.status 20;

11.4 - File System

11.4.1 - std:fs:rename file-path new-file-name

Renames the file at file-path to the new name new-file-name. This usually does only work on a single file system. Returns $true if renaming was successful, and an error object if it was not successful.

11.4.2 - std:fs:copy src-file-path dst-file-path

Copies the file src-file-path to the dst-file-path. Returns an error if something went wrong.

11.4.3 - std:fs:read_dir path function

Calls function with the first argument being the directory entry as map of this structure:

    ${
        atime     = 1587628635, # seconds since UNIX Epoch
        ctime     = 1557382099, # seconds since UNIX Epoch
        mtime     = 1557382099, # seconds since UNIX Epoch
        len       = 478,        # bytes
        name      = "test",     # file name
        path      = "..\\test", # path name
        read_only = $false,     # read only flag
        type      = :f          # possible values:
    }

If the function is called with a directory, you can recurse into that directory by returning a $true value.

You can format the timestamps using std:chrono:format_utc.

11.4.4 - std:fs:remove_file file-path

Removes the file at the given file-path. Returns an error if the file is missing or some other error occured.

11.4.5 - std:fs:remove_dir dir-path

Removes the dir at the given dir-path. Returns an error if the dir is missing, is not empty or some other error occured.

11.4.6 - std:fs:remove_dir_all dir-path

Removes the given dir-path recursively. Use with care! Returns an error if the directory does not exist.

11.5 - System

This chapter contains a few system as in operating system related functions.

11.5.1 - std:sys:os

Returns the name of the operating system. These are some possible values:

  • linux
  • macos
  • ios
  • freebsd
  • dragonfly
  • netbsd
  • openbsd
  • solaris
  • android
  • windows
std:assert (len std:sys:os[]) > 0;

11.6 - Threading

WLambda leverages the std::thread implementation of Rust’s standard library to provide safe threading. Threading works by spawning new threads that get sent a piece of WLambda code (as string) and some arguments.

Most WLambda data can be shared between threads. An exception are UserData values that are not thread safe. Also things like sharing cyclic data structures are not possible, as the references are currently broken up.

Sharing data is done by WLambda by transforming the VVal data structures into a thread safe shareable represenation called AVal. An AVal is a deep copy of the original VVal and can additionally contain atoms (see std:sync:atom:new), MPSC queues (see std:sync:mpsc:new) and value slots (see std:sync:slot:new).

The scope of threading in WLambda is primarily to provide a way to do asynchronous work and not so much for high performance computing. You can of course enhance performance a bit with threading, but if you want to do heavy computing I recommend implementing your algorithm in Rust instead. You can of course still manage thread orchestration in WLambda if you just provide a simple function API to your algorithms.

11.6.1 - std:thread:spawn string [globals-map]

This evaluates the given string as WLambda code in a new thread. It returns a thread handle, that can be used to join the thread or wait for it to have properly started.

The new thread starts out with a completely empty global environment. If you need any builtin functions, use !@wlambda, !@import std and other import statements to load the needed functions.

If a globals-map is given, inside the thread the given global variables will be set to the given value.

Inside the thread, a global variable called _READY is set to an atomic slot, which can be used to signal the parent thread that the new thread has successfully spawned.

This is a very basic example how to calculate something in a worker thread and wait in a blocking manner:

!handle = std:thread:spawn $code {
    !@wlambda;
    !@import std;

    !sum = $@i iter i 0 => 10 {
        $+ i;
    };
    _READY.send $[:ok, sum];

    100
};

std:assert_str_eq handle.recv_ready[] $[:ok, 45];

!res = handle.join[];

std:assert_str_eq res 100;

Here an example on how to pass values to the thread. Please be aware, that only a limited set of data types can be passed to a thread. Functions and iterators can not be passed for instance.

!globals = ${
    a = 99,
    b = $[1, 3, 4],
};

!handle = std:thread:spawn $code {
    a + b.1
} globals;

!res = handle.join[];

std:assert_str_eq res 102;
11.6.2 - std:thread:sleep duration

Lets the current thread sleep for the given duration. duration can either be an integer that will be interpreted as milliseconds to sleep. Or a pair, containing the duration unit as first element and the integer as second element. Following units are supported:

  • $p(:s, _seconds_)
  • $p(:ms, _milliseconds_)
  • $p(:us, _microseconds_)
  • $p(:ns, _nanoseconds_)
!before = std:time:now :ms;
!thrd = std:thread:spawn $code {
    !@import std;
    std:thread:sleep :ms => 150;
};

thrd.join[];

!after = std:time:now :ms;

std:assert (after - before) >= 150;
11.6.3 - Thread Handle API
11.6.3.1 - thdl.join

This method will wait for the thread to finish and return the return value of the thread.

!thdl = std:thread:spawn "4 + 3";
std:assert_eq thdl.join[] 7;

This method will return an error if the thread handle was already joined.

11.6.3.2 - thdl.recv_ready

Waits for the global _READY atomic value slot to be sent a value by the thread. This is useful for waiting until the thread has started without an error before expecting it to run properly. If an error happens, you will receive an error object as return value of recv_ready:

!thdl = std:thread:spawn $code {
    !@wlambda; # importing `panic`
    panic "SOME ERR";
    _READY.send :ok;
};

!err_msg =
    match thdl.recv_ready[]
        ($e err) => { $\.err.0 }
        v        => $\.v;

$DEBUG err_msg;
std:assert err_msg &> $r/ *SOME\ ERR* /;

thdl.join[];

This method might return an error if the thread provider made a handle without a ready slot.

11.6.4 - Atom API

For threads a VVal (WLambda data value) is transformed into a value that can be shared between threads safely. For this the data values are cloned deeply and transformed into a structure of atomic values.

These values are then stored in a so called Atom. They can be safely changed by threads.

11.6.4.1 - std:sync:atom:new value

Creates an Atom, containing the given value. The data types for value is limited to these:

  • Numbers (Integer, Float)
  • Numerical Vectors
  • Vectors
  • Maps
  • Strings
  • Symbols
  • Byte vectors
  • Pairs
  • Booleans
  • Optionals
  • Errors

And also these special types:

  • Atom
  • Atom Value Slot
  • Channel
!at = std:sync:atom:new $[1, 2, 3];

!thdl = std:thread:spawn $code {
    at.write ~ $@i iter i at.read[] {
        $+ i;
    };
    _READY.send :ok;
} ${ at = at };

thdl.recv_ready[];

std:assert_eq at.read[] 6;

thdl.join[]
11.6.4.2 - atom.read

Returns the value stored in the atom.

!at = std:sync:atom:new 99;

std:assert_eq at.read[] 99;

This method might return an error if the internal mutex was poisoned.

11.6.4.3 - atom.write value

Overwrites the contents of the atom with the given value.

!at = std:sync:atom:new 99;

at.write 100;

std:assert_eq at.read[] 100;

This method might return an error if the internal mutex was poisoned.

11.6.4.4 - atom.swap value

Returns the previous value of the atom and writes in the given value.

!at = std:sync:atom:new 99;

std:assert_eq at.swap[100] 99;

std:assert_eq at.read[] 100;

This method might return an error if the internal mutex was poisoned.

11.6.5 - Atom Value Slot API

An Atom value slot offers more synchronization than a normal Atom value. It allows you to set the value of the slot, wait for it to be collected and wait for a value becoming available. It can be thought of a single element queue, where the element will be overwritten when a new value is sent to the slot.

You can theoretically receive or wait from multiple threads and also write from multiple threads. But be aware, that the order of which threads get to read or write is determined by the operating system and might lead to reader or writer starvation.

Best recommendation here is to use a slot only from a single writer and a single reader.

11.6.5.1 - std:sync:slot:new

Constructs a new Atom slot and returns it. The slot has the initial status of being empty. If a value is sent to it, it will not be empty anymore. After a value is received from the slot, the status is empty again.

11.6.5.2 - atom_slot.send value

This method sends the value into the slot, overwriting any previous set values. The slot can also ha

!slot = std:sync:slot:new[];

std:assert slot.check_empty[];

slot.send $[:ok, $i(1,2,3)];

std:assert ~ not slot.check_empty[];

std:assert_eq slot.recv[].1 $i(1,2,3);

std:assert slot.check_empty[];

This method might return an error if there was an issue with locking the internal mutex or the mutex was poisoned.

11.6.5.3 - atom_slot.recv

If the slot is empty, it will wait for a value to become available. Once a value is available it is returned and the slot is set to empty again.

!slot = std:sync:slot:new[];

!thrd = std:thread:spawn $code {
    slot.send 99;
} ${ slot = slot };

std:assert_eq slot.recv[] 99;

thrd.join[];

This method might return an error if there was an issue with locking the internal mutex or the mutex was poisoned.

11.6.5.4 - atom_slot.try_recv

This method returns an optional value. It will provide an empty optional value if no value is stored in the slot. But if the slot contains a value, it will return the value (wrapped in an optional) and set the slot to be empty again.

!slot       = std:sync:slot:new[];
!start_flag = std:sync:slot:new[];

!thrd = std:thread:spawn $code {

    start_flag.recv[]; # sync with parent

    slot.send 99;

    _READY.send :ok;

} ${ slot = slot, start_flag = start_flag };

std:assert_eq slot.try_recv[] $o();

start_flag.send :ok;
thrd.recv_ready[];

std:assert_eq slot.try_recv[] $o(99);

thrd.join[];

This method might return an error if there was an issue with locking the internal mutex or the mutex was poisoned.

11.6.5.5 - atom_slot.recv_timeout duration

Acts like atom_slot.recv, but it will only wait for the given duration. If no value was received in the given duration (see std:thread:sleep), $o() is returned. Otherwise the optional value will contain the received value.

!slot = std:sync:slot:new[];

std:assert_eq (slot.recv_timeout :ms => 100) $o();

slot.send 4;

std:assert_eq (slot.recv_timeout :ms => 100) $o(4);

This method might return an error if there was an issue with locking the internal mutex or the mutex was poisoned.

11.6.5.6 - atom_slot.check_empty

Returns $true if the slot is empty.

This method might return an error if there was an issue with locking the internal mutex or the mutex was poisoned.

11.6.5.7 - atom_slot.wait_empty

Waits until the slot is empty and then returns $true.

This method might return an error if there was an issue with locking the internal mutex or the mutex was poisoned.

11.6.5.8 - atom_slot.wait_empty_timeout duration

Waits a predefined timeout until the slot is empty. If it did become empty within the given duration (see std:thread:sleep) it will return $true. Otherwise it will return $false.

This method might return an error if there was an issue with locking the interal mutex or the mutex was poisoned.

11.6.6 - Channel API

A channel is a multiple sender, single consumer queue. It can be used to establish a message passing based communication between threads.

It is basically a wrapper around the Rust std::sync::mpsc::channel.

!chan = std:sync:mpsc:new[];

!thdl = std:thread:spawn $code {
    _READY.send :ok;
    iter i 0 => 10 {
        chan.send $p(:val, i);
    };
    chan.send $p(:quit, $none);
} ${ chan = chan };

match thdl.recv_ready[]
    ($e ?) => { std:assert $false };

!item = $none;

!sum = $@i
    while { .item = chan.recv[]; item.0 == :val } {
        $+ item.1;
    };

std:assert_eq sum 45;

thdl.join[];
11.6.6.1 - std:sync:mpsc:new

This creates a new channel. You can safely send from multiple threads while reading from one thread at a time.

!chan = std:sync:mpsc:new[];

chan.send :a;
chan.send :b;
chan.send :c;

std:assert_eq chan.recv[] :a;
std:assert_eq chan.recv[] :b;
std:assert_eq chan.recv[] :c;
11.6.6.2 - channel.send value

Sends the given value to the channel queue.

!chan = std:sync:mpsc:new[];

chan.send :a;
std:assert_eq chan.recv[] :a;

This method might return an error if the channel failed, for instance due to a poisoned internal mutex.

11.6.6.3 - channel.recv

Receives the next element from the channel. If no element is available this method will block the thread until an element becomes available.

!chan = std:sync:mpsc:new[];

chan.send :a;
std:assert_eq chan.recv[] :a;

This method might return an error if the channel failed, for instance due to a poisoned internal mutex.

11.6.6.4 - channel.try_recv

Tries to receive the next element from the channel and return it wrapped into an optional. If no element is available an empty optional $o() is returned.

!chan = std:sync:mpsc:new[];

std:assert_eq chan.try_recv[] $o();

chan.send :a;

std:assert_eq chan.try_recv[] $o(:a);

This method might return an error if the channel failed, for instance due to a poisoned internal mutex.

11.6.6.5 - channel.recv_timeout duration

Tries to receive the next element in the given duration (see std:thread:sleep) and return it wrapped into an optional. If no element could be received within that time an empty optional is returned $o().

!chan = std:sync:mpsc:new[];

std:assert_eq (chan.recv_timeout $p(:ms, 100)) $o();

chan.send :x;

std:assert_eq (chan.recv_timeout $p(:ms, 100)) $o(:x);

This method might return an error if the channel failed, for instance due to a poisoned internal mutex.

12 - Optional Standard Library

12.1 - serialization

12.1.1 - std:ser:wlambda arg

Returns the serialized WLambda representation of the value arg as string.

Most values have the same represenation like a WLambda literal, but there are other values that don’t have a literal representation.

Warning: Consider all values that don’t have a fixed literal representation in the WLambda syntax as debug output that might change in future versions.

std:assert_eq (std:ser:wlambda "foo") $q|"foo"|;
std:assert_eq (std:ser:wlambda $none) $q|$n|;
std:assert_eq (std:ser:wlambda $[1,:a]) $q|$[1,:a]|;
12.1.2 - std:ser:json data [no_pretty]

Serializes the data and returns a JSON formatted (and pretty printed) string. Optionally not pretty printed if no_pretty is a true value.

!str = std:ser:json $[1,2.3,${a=4}] $t;
std:assert_eq str "[1,2.3,{\"a\":4}]";
12.1.3 - std:deser:json string

Deserializes the JSON formatted string into a data structure.

!data = std:deser:json ~ std:ser:json $[1,2.3,${a=4}];
std:assert_eq data.0 1;
std:assert_eq data.1 2.3;
std:assert_eq data.(2).a 4;
12.1.4 - std:ser:csv field_delim row_separator escape_all table

This serializes the table as CSV with the given field_delim and row_separator. If escape_all is $true all fields will be put into ‘“’.

!csv_str =
    std:ser:csv
        ";" "|" $f
        $[ $[1,2,3,4,$q/foo"bar/],
           $[44,55],
           $[]]
    | std:displayln;

std:assert_eq csv_str $q/1;2;3;4;"foo""bar"|44;55||/;

std:assert_eq
    (std:ser:csv ";" "|" $f $[$[:a,$q/;/, $q/|/, $q/ /]])
    "a;\";\";\"|\";\" \"|";
12.1.5 - std:deser:csv field_delim row_separator data

Parses the string data as CSV. With the field delimiter field_delim and the row_separator for the data rows.

!table = std:deser:csv ";" "\r\n" "foo;bar\r\nx;y\r\n";
std:assert_eq table.0.0 "foo";
std:assert_eq table.0.1 "bar";
std:assert_eq table.1.1 "y";
12.1.6 - std:ser:msgpack data

Serializes the data and returns a msgpack bytes value.

std:assert_eq (std:ser:msgpack $b"abc") $b"\xC4\x03abc";
12.1.7 - std:deser:msgpack bytes

Deserializes the msgpack bytes value into a data structure.

std:assert_eq (std:deser:msgpack $b"\xC4\x03abc") $b"abc";

12.2 - Regular Expressions (more classic syntax)

WLambda supports a more common form of regular expression syntax if the “regex” feature is enabled when compiling WLambda.

It uses the regex syntax as described in the Rust “regex” crate: https://docs.rs/regex/newest/regex/.

12.2.1 - std:re:match regex-string input-string function

If the given regular expression regex-string matches the given input-string the function will be called with a vector containing the matched string and the captures as first argument. The return value is the return value of the function call or $none. Returns an error if the regex-string has a syntax error.

!ret = std:re:match $q|(\d+)-(\d+)-(\d+)| "2020-05-01" {
    std:str:join "." std:reverse <& (1 => 3) <&_;
};

std:assert_str_eq ret "01.05.2020";
12.2.2 - std:re:match_compile regex-string

This function compiles the given regex-string and returns a function that will execute the regex matching. If the syntax in regex-string is wrong, an error is returned.

The returned function takes the string to match against as first parameter and the function that is called if the regex matches as second parameter.

!match_fun = std:re:match_compile $q|(\d+)-(\d+)-(\d+)|;

!ret = match_fun "2020-05-01" {
    std:str:join "." std:reverse <& (1 => 3) <&_;
};

std:assert_str_eq ret "01.05.2020";
12.2.3 - std:re:map regex-string function input-string

Executes function for each match of the regex defined by regex-string on the input-string. Returns an error if there is a syntax error in the regex. Otherwise returns the last return value of function.

!res = $@vec std:re:map $q|(\d+)-(\d+)-(\d+)| {!(str, d1, d2, d3) = _;
    $+ $[int d1, int d2, int d3]
} "foo bar 2020-05-01 fofo 2020-06-01 bar 2019-12-31";

std:assert_str_eq res $[$[2020,5,1], $[2020,6,1], $[2019,12,31]];
12.2.4 - std:re:replace_all regex-string replace-function input-string

This function replaces all matches of the regex regex-string in the input-string by the return value of the replace-function. Returns an error if there is a syntax error in the regex-string.

!res = std:re:replace_all $q"(\d+)" {
    str (int _.1) + 1
} "foo 32 fifi 99";

std:assert_eq res "foo 33 fifi 100";

12.3 - xml

12.3.1 - std:xml:read_sax xml-string event-callback-function [do-not-trim-text]
\:x {
    $@v _? :x ~ std:xml:read_sax
        "x<x a='12'>fooo fweor weio ew <i/> foefoe</i></x>y"
        $+;
}[]
12.3.2 - std:xml:create_sax_writer [indent]

Creates an XML SAX event based writer function. The function should be called with single events as received by std:xml:read_sax. To receive the final output of the writer, call the returned function without any arguments.

!writer = std:xml:create_sax_writer[];
writer $[:start, "test"];
writer $[:end, "test"];

std:assert_eq writer[] "<test></test>";
12.3.3 - std:xml:create_tree_builder

This function returns a function that acts as WLambda data structure builder that accepts SAX events as given to the callback of std:xml:read_sax.

The returned data structure is a tree build of the following elements:

  • $[:decl, ${ version=..., encoding=..., standalone=... }]
  • $[:elem, name, ${ <attributes> }, $[ <child elements> ]]
  • $[:comment, text]
  • $[:pi, text]
  • $[:text, text]
  • $[:doctype, text]
  • $[:cdata, text]

Here is a more elaborate example on how to use it:

!tb = std:xml:create_tree_builder[];

!tree = std:xml:read_sax $q$
    <foo>
        <i x="here"/>
        hello
    </foo>
    <foo>
        blop<i x="10"/>
        lol
    </foo>$
    tb;

std:assert_str_eq
    tree
    $[:root,$n,$n,$[
        $[:elem,"foo",$n,$[
            $[:elem,"i",${x="here"},$n],$[:text,"hello"]]],
        $[:elem,"foo",$n,$[
            $[:text,"blop"],$[:elem,"i",${x="10"},$n],$[:text,"lol"]]]
    ]];

std:assert_str_eq
    $S[ 3 / *:{0=elem,1=foo} /
        3 / *:{0=elem,1=i} /
        2 / x
    ] <& tree
    $["here","10"];

12.4 - chrono

12.4.1 - std:chrono:timestamp [format]

For the documentation of format please consule the chrono Rust crate documentation: chrono crate strftime format.

!year_str = std:chrono:timestamp "%Y";
std:displayln :XXXX ~ (year_str | int) == 2022;
std:assert ~ (year_str | int) == 2022;

!now_str = std:chrono:timestamp[];
12.4.2 - std:chrono:format_utc utc-timestamp [format]

Formats the given utc-timestamp in seconds according to format.

For the documentation of format please consule the chrono Rust crate documentation: chrono crate strftime format.

!year_str = std:chrono:format_utc 1603796989 "%H:%M:%S %Y";

std:assert_str_eq year_str "11:09:49 2020";
12.4.3 - std:chrono:format_local utc-timestamp [format]

Formats the given utc-timestamp in seconds according to format in the local timezone.

For the documentation of format please consule the chrono Rust crate documentation: chrono crate strftime format.

!year_str = std:chrono:format_local 1603796989 "%H:%M:%S %Y";

std:assert_str_eq year_str "12:09:49 2020";

12.5 - color conversion

This section highlights the color conversion functions available in WLambda. Numerical vectors are used in WLambda to represent colors. There are two representations of a color.

If you use a float vector $f(r, g, b, a) the values for RGB are in the range of 0.0 to 1.0. For HSV $f(h, s, v, a) h is within 0.0 to 360.0 while the others are in the range 0.0 to 1.0.

For integer vectors the values for RGB are in the range 0 to 255. And the values for HSV are in the range 0 to 360, and the others in the range 0 to 100.

You can also use 3 dimensional vectors without the alpha value: $i(r, g, b) / $i(h, s, v) and $f(r, g, b) / $f(h, s, v).

12.5.1 - std:v:rgb2hsv color-vector

Converts an RGB color into a HSV color representation.

std:assert_eq std:v:rgb2hsv <& $i(0, 255, 0, 255)   $i(120, 100, 100, 100);
std:assert_eq std:v:rgb2hsv <& $i(0, 255, 0)        $i(120, 100, 100);

std:assert_eq std:v:rgb2hsv <& $f(0, 1.0, 0, 1.0)   $f(120, 1, 1, 1);
std:assert_eq std:v:rgb2hsv <& $f(0, 1.0, 0)        $f(120, 1, 1);

std:assert_eq std:v:rgb2hsv <& $f(0, 0.5, 0, 1.0)     $f(120, 1, 0.5, 1);
std:assert_eq std:v:rgb2hsv <& $f(0.1, 0.5, 0.1, 1.0) $f(120, 0.8, 0.5, 1);
12.5.2 - std:v:hsv2rgb color-vector

Converts a color from HSV to RGB representation.

std:assert_eq std:v:hsv2rgb <& $i(120, 80, 50, 100)   $i(25,128,25,255);

!clr = std:v:hsv2rgb <& $f(120, 0.8, 0.5, 1.0);
std:assert_rel_eq clr.r 0.1 0.001;
std:assert_rel_eq clr.g 0.5 0.001;
std:assert_rel_eq clr.b 0.1 0.001;
12.5.3 - std:v:rgba2hex color-vector

This function converts a color to a string of hex digits (without the common ‘#’ prefix however).

std:assert_eq std:v:rgba2hex <& $i(255, 128, 64, 32)       "ff804020";
std:assert_eq std:v:rgba2hex <& $f(1.0, 0.5, 0.25, 0.125)  "ff804020";
12.5.4 - std:v:hex2rgba_f string

Interprets string as an hex encoded color and returns a 4 element big float vector. The color components of the float vector go from 0.0 to 1.0.

The string can be:

  • 8 characters: "RRGGBBAA"
  • 6 characters: "RRGGBB", alpha will be 1.0
  • 4 characters: "RGBA"
  • 3 characters: "RGB", alpha will be 1.0
  • 2 characters: "YY", where YY is put into R, G and B. Alpha will be 1.0.
!color = std:v:hex2rgba_f "FF00FFFF";

std:assert_rel_eq color.r 1.0 0.001;
std:assert_rel_eq color.g 0.0 0.001;
std:assert_rel_eq color.b 1.0 0.001;
std:assert_rel_eq color.a 1.0 0.001;

!color2 = std:v:hex2rgba_f "C83F";
std:assert_rel_eq color2.r 0.8   0.001;
std:assert_rel_eq color2.g 0.533 0.001;
std:assert_rel_eq color2.b 0.2   0.001;
std:assert_rel_eq color2.a 1.0   0.001;
12.5.5 - std:v:hex2rgba_i string

Like std:v:hex2rgba_f this function converts a hex encoded color from string but returns an integer vector with 4 elements. The integers are in the range of 0 to 255.

About the format of string please refer to std:v:hex2rgba_f.

!color = std:v:hex2rgba_i "FF00FFFF";

std:assert_eq color.r 255;
std:assert_eq color.g 0;
std:assert_eq color.b 255;
std:assert_eq color.a 255;

!color2 = std:v:hex2rgba_i "C83F";
std:assert_eq color2.r 204;
std:assert_eq color2.g 136;
std:assert_eq color2.b 51;
std:assert_eq color2.a 255;
12.5.6 - std:v:hex2hsva_i string

Converts the hex represenation of a HSVA color to an integer vector $i(h, s, v, a). This function is probably not that useful, as the bit distribution along the 3 bytes is not ideal. If you want to store colors properly, don’t use this. It’s mostly useful for testing and quick experiments.

!color = std:v:hex2hsva_i "FF8040FF";
std:assert_eq color $i(360,50,25,100);
12.5.7 - std:v:hex2hsva_f string

Converts the hex represenation of a HSVA color to a float vector $i(h, s, v, a). This function is probably not that useful, as the bit distribution along the 3 bytes is not ideal. If you want to store colors properly, don’t use this. It’s mostly useful for testing and quick experiments.

!color = std:v:hex2hsva_f "FF8040FF";

std:assert_rel_eq color.0 360.0 1.0;
std:assert_rel_eq color.1  50.0 1.0;
std:assert_rel_eq color.2  25.0 1.0;
std:assert_rel_eq color.3 100.0 1.0;

12.6 - hash

12.6.1 - std:hash:fnv1a arg1

Hashes all the arguments as FNV1a and returns an integer.

12.7 - rand

12.7.1 - std:rand:split_mix64_new

Initializes the sm_state from the current time (seconds) and returns it. The time is retrieved in seconds, so don’t expect different seed states if you call this multiple times in the same wall clock second. The returned value is supposed to be passed to rand:split_mix64_next or rand:split_mix64_next_open01.

12.7.2 - std:rand:split_mix64_new_from seed

Initializes the sm_state from the given seed and returns it. The returned value is supposed to be passed to rand:split_mix64_next or rand:split_mix64_next_open01.

12.7.3 - std:rand:split_mix64_next sm_state [count]

Returns the count next integer values generated from the given sm_state.

12.7.4 - std:rand:split_mix64_next_open01 sm_state [count]

Returns the count next float values (in an open [0, 1) interval) generated from the given sm_state.

12.7.5 - std:rand:split_mix64_next_open_closed01 sm_state [count]

Returns the count next float values (in an open (0, 1] interval) generated from the given sm_state.

12.7.6 - std:rand:split_mix64_next_closed_open01 sm_state [count]

Returns the count next float values (in an open [0, 1) interval) generated from the given sm_state.

12.8 - Utility Functions

12.8.1 - std:dump_upvals function

Returns a vector of all the upvalues of the function. Please use this function for debugging purposes only, as the order of the variables, while consistent for a specific WLambda version, is not defined at this point.

!x = 1;
!y = 2;
!fun = { _ + x + y };

std:assert_eq fun[3]   6;
.x = 3;
std:assert_eq fun[3]   8;

!upvs = std:dump_upvals fun;
std:assert_eq (str upvs) "$[$&3,$&2]";
.y = 4;
std:assert_eq (str upvs) "$[$&3,$&4]";

std:assert_eq $*(upvs.0) 3;
std:assert_eq $*(upvs.1) 4;
12.8.2 - std:wlambda:version

Returns the version number of the WLambda crate when called.

12.8.3 - std:wlambda:sizes

Writes a table of internal data structure sizes to stdout. Just for development purposes.

12.8.4 - std:wlambda:parse string

Parses string as if it was a piece of WLambda code and returns an abstract syntax tree.

std:assert_str_eq
    (std:wlambda:parse "1 + 2")
    $[$%:Block,$[$%:BinOpAdd,1,2]]

12.9 - HTTP Client

WLambda offers an optional integrated HTTP client by enabling the reqwest feature at compile time. With this you can create a new client using std:http:client:new and make HTTP requests using std:http:get, std:http:post and std:http:request. Also support for basic authentication and token based bearer authentication is there.

12.9.1 - std:http:client:new

Creates a new HTTP client instance and returns it. You can use it to make HTTP requests afterwards.

!client = std:http:client:new[];
!response = std:http:get client "https://duckduckgo.com/";

!body = std:str:from_utf8_lossy response.body;
std:assert ($p(0, "</html>") body) > 0;
std:assert_eq response.status 200;
std:assert_eq response.headers.content-type "text/html; charset=UTF-8";

See also std:http:get for a more elaborate example with providing headers.

12.9.2 - std:http:get http-client url-string [headers-and-options-map]

Performs a HTTP GET request to the given url-string using the http-client. The headers-and-options-map can contain following special keys apart from your custom HTTP headers themself:

  • @basic_auth with $[user, $none] or $[user, password] as value.
  • @bearer_auth with token as value.
  • @timeout with a timeout duration as value. (See also std:thread:sleep).
  • @query with a map of query parameters and their values to modify the query string of the url-string. This properly encodes the strings.

The client will either return an error or a response map with following keys:

  • status contains the HTTP response code (usually 200 if everything went fine).
  • reason contains a human readable canonical reason string for the status.
  • body contains the byte vector with the body data.
  • headers contains a map of all headers, where the keys are the lower case header names.

Here is an example on how to use this while providing HTTP Headers:

!client = std:http:client:new[];
!response =
    std:http:get client "https://crates.io/api/v1/crates?page=1&per_page=10&q=wlambda" ${
        Accept        = "application/json",
        Cache-Control = "no-cache",
        DNT           = 1,
        User-Agent    = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:97.0) Gecko/20100101 Firefox/97.0",
    };

!body = std:deser:json ~ std:str:from_utf8_lossy response.body;
std:assert_eq body.crates.0.name            "wlambda";
std:assert_eq response.headers.content-type "application/json; charset=utf-8";
std:assert_eq response.status               200;
12.9.3 - std:http:post http-client url-string body-bytes [headers-and-options-map]

This call is like std:http:get but makes a HTTP POST request with the given payload body. For HTTP requests with other methods please look at std:http:request. The rest of the options are the same as std:http:get. But here is an example how to transmit a piece of JSON easily:

!client = std:http:client:new[];
!response =
    std:http:post client "http://httpbin.org/post"
        (std:str:to_bytes ~ std:ser:json $[
            :x => 10,
            ${ y = 20 },
        ]);

!body = std:deser:json ~ std:str:from_utf8_lossy response.body;

std:assert_eq body.url "http://httpbin.org/post";
std:assert_str_eq body.json  $[ $["x", 10], ${ y = 20 } ];
std:assert_eq response.status 200;
12.9.4 - std:http:request http-client method-string url-string [body-bytes [headers-and-options-map]]

This call is like std:http:post but makes HTTP requests with an almost arbitrary method with the optional given payload body. The rest of the options are the same as std:http:get.

!client = std:http:client:new[];
!response =
    std:http:request client :GET "http://httpbin.org/get";

!body = std:deser:json ~ std:str:from_utf8_lossy response.body;
std:assert_eq body.url "http://httpbin.org/get";

Or a POST request:

!client = std:http:client:new[];
!response =
    std:http:request client :POST "http://httpbin.org/post"
        (std:str:to_bytes ~ std:ser:json $[
            :x => 10,
            ${ y = 20 },
        ]);

!body = std:deser:json ~ std:str:from_utf8_lossy response.body;

std:assert_eq body.url "http://httpbin.org/post";
std:assert_str_eq body.json  $[ $["x", 10], ${ y = 20 } ];
std:assert_eq response.status 200;

12.10 - MQTT Messaging

WLambda offers an optional support for the MQTT protocol. You can setup a MQTT client as well as an embedded MQTT broker. The very simple integration offers you a very easy way to setup inter process communication between WLambda applications.

Support for MQTT has to be explicitly compiled into WLambda by selecting the mqtt feature.

12.10.1 - std:mqtt:broker:new config

This function sets up an embedded MQTT broker. A handle is returned that you can use to publish messages using the locally connected client link. You can configure it’s endpoints via the config. The config offers following keys:

${
    id             = 0,                 # Broker ID
    listen         = "0.0.0.0:1883",    # Broker server endpoint
    console_listen = "0.0.0.0:18088",   # An extra HTTP console endpoint
    link = ${                           # Configure the local link
        client_id = "some_id",          # Link client ID, default is 'wl_local'
        recv = <std:sync:mpsc channel>, # Channel to receive messages for the
        topics = $["test/me", "another/topic", ...]
    },
}

The default maximum MQTT payload the broker is setup to support is 10240 bytes (10 kb).

Here is an example:

!broker = std:mqtt:broker:new ${
    listen         = "0.0.0.0:1883",
    console_listen = "0.0.0.0:18080",
};

std:thread:sleep :ms => 500;

!chan = std:sync:mpsc:new[];
!cl = std:mqtt:client:new chan "test1" "localhost" 1883;

std:thread:sleep :ms => 200;

!_ = cl.subscribe "test/me";
!_ = cl.publish "test/me" $b"test123\xFF";

std:assert_str_eq chan.recv[] $p(:"$WL/connected", $n);
std:assert_str_eq chan.recv[] $p("test/me", $b"test123\xFF");
12.10.1.1 - broker.publish topic-string payload-bytes

Publishes the payload-bytes under the topic-string. Returns an error if something went wrong (client not connected, or some other error). It might block.

12.10.2 - std:mqtt:client:new channel client-id broker-host broker-port

This sets up a MQTT client that connects to the given broker-host and broker-port. It will connect and reconnect upon connection failure in the background automatically for you. So you don’t have to manage the connection yourself.

This function returns a client handle that is describe a little bit further below.

The client-id should be a unique ID to identify your MQTT client.

The channel must be a std:sync:mpsc channel that you can create using std:sync:mpsc:new. It’s the source of incoming messages and connection control information. It will send you following possible message data:

  • $p(:"$WL/connected", $n) - When the client connection was setup
  • $p(:"$WL/error", "some message here...") - When an error occurred in the connection handling.
  • $p(topic, payload_bytes) - An incoming MQTT message that belongs to the topic.

Here is an example of a common client setup:

!broker = std:mqtt:broker:new ${
    listen         = "0.0.0.0:1883",
    console_listen = "0.0.0.0:18080",
};

std:thread:sleep :ms => 500;

!chan = std:sync:mpsc:new[];
!cl = std:mqtt:client:new chan "test1" "localhost" 1883;

std:thread:sleep :ms => 200;

!_ = cl.subscribe "test/me";
!_ = cl.publish "test/me" $b"test";
!_ = cl.publish "test/me" $b"quit";

!got_some_stuff = $n;

while $t {
    !msg = chan.recv[];
    match msg
        $p(topic, $b"quit") => { break[]; }
        $p(topic, data)     => { .got_some_stuff = std:copy $\; }; # std:copy because $\ is changing!
};

std:assert_eq got_some_stuff.topic "test/me";
std:assert_eq got_some_stuff.data  $b"test";

The returned client handle understands the following methods:

12.10.2.1 - mqtt_client.publish topic-string payload-bytes

Publishes the payload-bytes under the topic-string. Returns an error if something went wrong (client not connected, or some other error). It might block.

12.10.2.2 - mqtt_client.subscribe topic-string

Subscribes to the topic-string. Returns an error if something went wrong. It might block.

13 - WLambda Lexical Syntax and Grammar

White space is everything that satisfies std::char::is_whitespace, so unicode white space is respected. Comments have the following syntax:

    comment = "#" ?anything except "\n"? "\n"

In the following grammar, white space and comments are omitted:


    ident_start   = ( ?alphabetic? | "_" | "@" | "?" )
    ident_end     = { ?any character?
                     - ( ?white space?
                         | "." | "," | ";"
                         | "{" | "}" | "[" | "]" | "(" | ")"
                         | "~" | "|" | "=" ) }
                  ;
    qident        = ident_end
                  (* a quoted identifier can not appear anywhere,
                     it's usually delimited or follows something that
                     makes sure we are now expecting an identifier *)
                  | "`", { ?any character except '`'? }, "`" (* quoted identifier *)
                  ;
    ident         = ident_start, [ ident_end ]
                  | "`", { ?any character except '`'? }, "`" (* quoted identifier *)
                  ;
    ref_specifier = ":", qident
                  ;

    digit         = "0" | "1" | "2" | "3" | "4" | "5"
                  | "6" | "7" | "8" | "9"
                  ;
    integer       = digit, { digit }
                  ;
    radix         = integer
                  ;
    radix_digits  = (* digits in the radix specified
                       earlier in the number.
                       Default radix is of course 10. *)
    number        = [ "-" | "+" ],
                    [ ( radix, "r"
                      | "0", "x"
                      | "0", "b"
                      | "0", "o"
                      ) ],
                    radix_digits,
                    [ ".", radix_digits ]
                  ;
    hexdigit      = ?hexdigit, upper or lower case?
                  ;
    ascii_c_name  = (* note: upper and lower case versions are possible *)
                    "NULL" | "SOH" | "STX" | "ETX" | "EOT" | "ENQ" | "ACK"
                  | "BEL" | "BS" | "HT" | "LF" | "VT" | "FF" | "CR" | "SO"
                  | "SI" | "DLE" | "DC1" | "DC2" | "DC3" | "DC4" | "NAK"
                  | "SYN" | "ETB" | "CAN" | "EM" | "SUB" | "ESC" | "FS"
                  | "GS" | "RS" | "US" | "DEL" | "SPACE" | "NBSP"
                  ;
    string_escape = "x", hexdigit, hexdigit  (* byte/ascii escape *)
                  | "n"                      (* newline *)
                  | "r"                      (* carriage return *)
                  | "t"                      (* horizontal tab *)
                  | "0"                      (* nul byte/char *)
                  | "u", hexdigit, { hexdigit }
                                             (* unicode char, or in byte strings
                                                their utf-8 encoded form *)
                  | "\""
                  | "\'"
                  | "\\"
                  | "\\<", ascii_c_name, ">" (* the corresponding ascii value *)
                  ;
    string_lit    = string
                  | character
                  | "$", quote_string
                  | "$", byte_string
                  | "$", code_string
                  ;
    character     = "'", ( "\\", string_escape | ?any character? - "\\" and "'" ), "'"
                  ;
    string        = "\"", { "\\", string_escape | ?any character? - "\\" and "\"" },"\""
                  ;
    byte_char     = "b", character
                  ;
    byte_string   = "b", string
                  ;
    quote_string  = "q", ?any character as quote?,
                         { ?any character? },
                         ?any character as quote?
                  | "Q", ?any character as quote?,
                         { ?any character? },
                         ?any character as quote?
                    (* but Q generates a byte string instead! *)
    selector      = "S", ?any character as quote?,
                         selector_rs_syntax,
                         ?any character as quote?
                    (* parses substring like 'q', but constructs a
                       selector_rs_syntax matcher at compile time *)
                  ;
    pattern       = "r", [ "g" ], (* "g" for find all *)
                         ?any character as quote?,
                         selector_rs_pattern_syntax,
                         ?any character as quote?
                    (* parses substring like 'q', but constructs a
                       pattern matcher at compile time *)
                  ;
    struct_match  = "M", expr   (* compiles expr as structure matcher function.
                                   If called, it matches the first argument against
                                   the literal structure and returns a map of
                                   matched variables. If nothing matches $none
                                   is returned. *)
    formatter     = "F", string_lit (* compiles the string into a formatter, that
                                       takes as many arguments as there are format
                                       specifiers in the string. See also: String
                                       formatting syntax in the next section. *)
    list_expr     = "*", expr   (* splices the vector result of 'expr'
                                   into the currently parsed list *)
                  | expr
                  ;
    list          = "[", [ list_expr, { ",", list_expr }, [ "," ] ],"]"
                  ;
    map_expr      = (ident | expr), "=", expr
                  | "*", expr   (* splices the map result of 'expr'
                                   into the currently parsed map *)
                  ;
    map           = "{", [ map_expr, { ",", map_expr }, [ "," ] ], "}"
                  ;
    self          = "s" | "self"
                  ;
    true          = "t" | "true"
                  ;
    false         = "f" | "false"
                  ;
    none          = "n" | "none"
                  ;
    code_string   = ("c" | "code" ), block
                  | ("c" | "code" ), expr
                  ;
    pair          = "p", "(", expr, "," expr, ")"
                  ;
    err           = ("e" | "error"), expr
                  ;
    nvec          = ("i" | "f"), "(", expr, { ",", expr }, ")"
                  ;
    ref           = "&&", value
                  ;
    ref_hidden    = "&", value
                  ;
    ref_weak      = ("w&" | "weak&"), value
                  ;
    syntax        = "%:", {? any possible Syntax Type Identifier
                             eg. "Block", "Call", ... ?}
                    (* A syntax VVal, the possible identifiers are one of the
                       possible result symbols of std:syn:type. This also
                       saves the current parser position. *)
                  ;
    accumulator   = "@", ("i" | "int"
                         |"s" | "string"
                         |"f" | "float"
                         |"b" | "bytes"
                         |"v" | "vec"
                         |"m" | "map" ), expr
                    (* defines a new accumulator context *)
                  | "@@" (* returns the current accumulator value *)
                  | "+"  (* resolves to the current accumulator function *)
                  ;
    debug_print   = "DEBUG" (* evaluates to a debug print function that
                               also prints source position and dynamic type.
                               very useful for debugging. *)
                  ;
    import        = "@import", ident, [ ident ]
                  ;
    export        = "@export", ident, [ "=" ], expr
                  ;
    capture_ref   = ":", var
                  ;
    deref         = "*", value
                  ;
    special_value = list
                  | map
                  | none
                  | true
                  | false
                  | self
                  | err
                  | nvec
                  | pair
                  | ref
                  | ref_hidden
                  | ref_weak
                  | deref
                  | capture_ref
                  | accumulator
                  | selector
                  | pattern
                  | struct_match
                  | debug_print
                  | "\"             (* The global variable with the name "\" *)
                  ;
    arity_def     = "|", number, "<", number, "|" (* set min/max *)
                  | "|", number, "|"              (* set min and max *)
                  | "|", "|"                      (* no enforcement *)
                  ;
    function      = [ "\:", ident ], "{", [ arity_def ], block, "}"
                  | "\", [ arity_def ], statement
                  ;
    var           = ident
                  ;
    symbol        = ":", qident
                  | ":", "\"", (? any char, quoted \\ and \" ?), "\""
                  (*
                     symbols are usually used to specify
                     fields in literal map definitions
                     and lots of other places as stringy sentinel values
                  *)
                  ;
    value         = number
                  | string_lit
                  | "$", special_value
                  | "(", expr, ")"
                  | function
                  | symbol
                  | var
                  ;
    op            = (* here all operators are listed line by line regarding
                       their precedence, top to bottom *)
                    "&>" | "&@>"      (* call rhs with lhs operator *)
                  | "<&" | "<@&"      (* call lhs with rhs operator *)
                  | "^"
                  | "*" | "/" | "%"
                  | "-" | "+"
                  | "<<" | ">>"       (* binary shift *)
                  | "<" | ">" | "<=" | ">="
                  | "==" | "!="
                  | "&"               (* binary and *)
                  | "&^"              (* binary xor *)
                  | "&|"              (* binary or *)
                  | "&and"            (* logical and, short circuit *)
                  | "&or"             (* logical or, short circuit *)
                  | "=>"              (* pair constructor *)
                  | "+>"              (* take lhs, wrap it into list if not already
                                         and append the right side.
                                         if lhs is an iterator, append all elements. *)
                  | "<+"              (* take rhs, wrap it into list if not already
                                         and prepend the left side.
                                         if rhs is an iterator, prepend all elements. *)
                  ;
    bin_op        = call_no_ops, { op, bin_op } (* precedence parsing is done
                                                   in a Pratt parser style *)
                  ;
    arg_list      = "[", [ expr, { ",", expr }, [ "," ] ], "]"
                  | "[[", expr, "]]"  (* apply result vector of expr as argument list *)
                  ;
    field         = ".", ( integer | ident | value ), [ field ]
                  ;
    field_access  = field, [ op ], "=", expr
                  | field, arg_list
                  | field
                  (* please note, that a field access like:
                     `obj.field` is equivalent to the call:
                     `field[obj]`. That also means that
                     `obj.field[...]` is transformed into
                     `field[obj][...]`.
                     The exception is "=" which assigns
                     the field as specified.
                     BUT: There is a special case, when you specify
                          an identifier as field, it is quoted and
                          interpreted as symbol. *)
                  ;
    call_no_ops   = value, { arg_list | field_access }
                  ;
    call          = value,
                    { arg_list | field_access | bin_op | value },
                    [ "~", expr ] (* this is a tail argument, if present the
                                     expr is appended to the argument list *)
                  ;
    expr          = call, { "|", call }
                  | call, { "|>", call }
                  | call, { "||", call }
                  ;
    simple_assign = qident, [ op ], "=", expr
                  ;
    destr_assign  = "(", [ qident, { ",", qident } ], ")", "=" expr
                  ;
    definition    = [ ref_specifier ], ( simple_assign | destr_assign )
                  ;
    import        = "!", "@import", symbol, [ [ "=" ], symbol ]
                  | "!", "@wlambda"
                  ;
    export        = "!", "@export", symbol, [ "=" ], expr
                  ;
    statement     = "!" definition
                  | "." simple_assign
                  | "." destr_assign
                  | import
                  | export
                  | expr
                  ;
    block         = "{", { statement, ";", {";"}}, [ statement, {";"} ], "}"
                  | { statement, ";", {";"} }, [ statement, {";"} ]
                  ;
    code          = block
                  ;

13.1 - Special Forms

There are certain calls that are handled by the compiler differently.

  • if _condition_ _then-block-or-expr_ [_else-block-or-expr_]
  • while _condition_ _block-or-expr_
  • iter _var_ _value-expr_ _block-or-expr_
  • next _x_
  • break
  • `match value-expr $p(structure_pattern, branch_block) … [ branch_block ]
  • jump _idx-expr_ _block1_ ...

13.2 - String Formatting Syntax

The $F special value takes a string and creates a formatting function. The syntax for formatting is very similar to Rust’s string formatting:

    format_string = <text>, { maybe-format, <text> }
                  ;
    maybe-format  = '{', '{'
                  | '}', '}'
                  | <format>
                  ;
    format        = '{' [ argument ], [ ':' format_spec ] '}'
                  ;
    argument      = integer    (* index into argument vector *)
                  | identifier (* requires a map as parameter *)
                  ;

    format_spec   = [[fill]align][sign]['#']['0'][width]['.' precision]['!' cast_type][type]
                  ;
    fill          = character
                  ;
    align         = '<' | '^' | '>'
                  ;
    sign          = '+' | '-'
                  ;
    width         = count
                  ;
    precision     = count | '*'
                  ;
    cast_type     = 'i'             (* interpret arg as integer *)
                  | 'f'             (* interpret arg as float *)
                  | 's'             (* (default) interpret arg as string *)
                  ;
    type          = 'x'             (* hex lower case *)
                  | 'X'             (* hex upper case *)
                  | 'b'             (* binary *)
                  | 'o'             (* octal *)
                  | 'j', character  (* a string join of a vector,
                                       separated by 'character' *)
                  | 'C', character  (* A CSV row, separated by 'character'.
                                       This also supports proper
                                       escaping using '"' *)
                  | 'J', ['p']      (* JSON, optional pretty printed *)
                  | 'w', ['p']      (* print written WLambda representation,
                                       optional pretty printed *)
                  | ''
                  ;
    count         = parameter | integer
                  ;
    parameter     = argument, '$'
                  ;

13.3 - Format String Syntax for std:bytes:pack and std:bytes:unpack

This syntax describes the accepted format strigns for the std:bytes:pack and std:bytes:unpack. The format is loosely adapted from the Lua syntax for string.pack and string.unpack.

SyntaxSemantics
<Sets little endian
>Sets big endian
=Sets native endian
xOne zero byte of padding
yByte content with unspecified length. On unpack this reads to the end of the input.
zA zero-terminated string of bytes.
c<n>An n long field of bytes.
bA single byte.
s<bits>A string of bytes that is prefixed with a <bits> wide binary length field.
u<bits>A <bits> wide unsigned integer field.
i<bits>A <bits> wide signed integer field.
fA float field (32 bits).
dA double field (64 bits).
  • <n> can be any number.
  • <bits> can be 8, 16, 32, 64 or 128.

[]: –– REFERENCE DOC END ––

Functions

Returns a SymbolTable with all WLambda core language symbols.

Returns a SymbolTable with all WLambda standard library language symbols.