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
- 2 Functions (part 1/2)
- 3 Data Types
- 3.1 None sentinel value:
$n
or$none
- 3.2 Optional values
$o()
and$o(...)
- 3.3 Error values:
$e expr
or$error expr
- 3.4 Booleans
- 3.5 64-Bit Integers
- 3.6 64-Bit Floats
- 3.6.1 float value
- 3.6.2 is_float value
- 3.6.3 std:num:acos float
- 3.6.4 std:num:acosh float
- 3.6.5 std:num:asin float
- 3.6.6 std:num:asinh float
- 3.6.7 std:num:atan float
- 3.6.8 std:num:atan2 y x
- 3.6.9 std:num:atanh float
- 3.6.10 std:num:cbrt float
- 3.6.11 std:num:ceil float
- 3.6.12 std:num:cos float
- 3.6.13 std:num:cosh float
- 3.6.14 std:num:exp float
- 3.6.15 std:num:exp2 float
- 3.6.16 std:num:exp_m1 float
- 3.6.17 std:num:floor float
- 3.6.18 std:num:hypot y x
- 3.6.19 std:num:ln float
- 3.6.20 std:num:log float
- 3.6.21 std:num:log10 float
- 3.6.22 std:num:log2 float
- 3.6.23 std:num:pow float
- 3.6.24 std:num:recip float
- 3.6.25 std:num:round float
- 3.6.26 std:num:sin float
- 3.6.27 std:num:sinh float
- 3.6.28 std:num:sqrt float
- 3.6.29 std:num:tan float
- 3.6.30 std:num:tanh float
- 3.6.31 std:num:to_degrees float
- 3.6.32 std:num:to_radians float
- 3.6.33 std:num:trunc float
- 3.6.34 std:num:lerp a b x
- 3.6.35 std:num:smoothstep a b x
- 3.6.36 std:num:fract float
- 3.7 Numeric Functions
- 3.8 Numerical Mathematical Vectors
- 3.8.1 Vector Conversions
- 3.8.2 Vector Component Access
- 3.8.3 Named Field Access and Swizzling
- 3.8.4 Euler Addition/Subtraction
- 3.8.5 Scalar Multiplication/Division
- 3.8.6 Unary Vector Operations
- 3.8.7 is_fvec value
- 3.8.8 is_ivec value
- 3.8.9 is_nvec value
- 3.8.10 nvec_len value
- 3.8.11 fvec value
- 3.8.12 fvec2 value
- 3.8.13 fvec3 value
- 3.8.14 fvec4 value
- 3.8.15 ivec value
- 3.8.16 ivec2 value
- 3.8.17 ivec3 value
- 3.8.18 ivec4 value
- 3.8.19 std:v:dims vec
- 3.8.20 std:v:mag2 vec
- 3.8.21 std:v:mag vec
- 3.8.22 std:v:norm vec
- 3.8.23 std:v:dot vec1 vec2
- 3.8.24 std:v:cross vec1 vec2
- 3.8.25 std:v:lerp vec1 vec2 t
- 3.8.26 std:v:slerp vec1 vec2 t
- 3.8.27 std:v:vec2rad vec
- 3.8.28 std:v:rad2vec radians
- 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
- 3.1 None sentinel value:
- 4 Conditional Execution - if / then / else
- 5 Loops And Iteration
- 6 Operators
- 7 String and Byte Vector Formatting
- 8 Data Structure Matchers, Selectors and String Patterns/Regex
- 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.3 Processes
- 11.4 File System
- 11.5 System
- 11.5.1 std:sys:os
- 11.6 Threading
- 12 Optional Standard Library
- 12.1 serialization
- 12.2 Regular Expressions (more classic syntax)
- 12.3 xml
- 12.4 chrono
- 12.5 color conversion
- 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.10 MQTT Messaging
- 13 WLambda Lexical Syntax and Grammar
§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;
# Destructuring works too, but only with compile time literal values
# in the vectors / maps:
!: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;
# When imported the X will remain constant:
!@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;
# function transforms a and b to hidden references
!add_a_and_b = { a + b };
std:assert_eq add_a_and_b[] 30;
# The assignment assigns to the hidden reference, so the closure add_a_and_b
# also receives the new value:
.a = 33;
std:assert_eq add_a_and_b[] 53;
# a and b are dereferenced on local variable access.
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) = @;
# Captures by closures upgrade the outer `self` variable to a _hidden_
# reference, which is then captured. As the closure is stored in
# `self`, this would create a ref cycle. This is why we needed
# to make a weak reference to self.
# Make an explicit hidden reference:
!self_ = $& ${
name = name,
};
# Create a weak reference form the hidden reference:
!self = $weak& $:self_;
self.meow = { std:displayln self.name " meows!"; };
self.get_name = { self.name };
# To keep the object alive, we retrieve a strong reference
# from the hidden reference:
$: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) = @;
# This does not make cycles, because `name` does not contain
# the closures in the end.
!cat_name = name;
!meow = { std:displayln cat_name " meows!"; };
!get_name = { $*cat_name };
!set_name = { .*cat_name = _; };
# Just holds the methods
${
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:
# All the second variant:
std:assert_eq[std:str:cat[1, 2, 3], "123"];
# Can also be written as:
std:assert_eq (std:str:cat 1 2 3) "123";
# As the third variant:
!some_args = $[1, 2, 3];
std:assert_eq std:str:cat[[some_args]] "123";
# The fourth variant:
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;
# You may also call them directly, notice the parenthesis ( ... ) syntax
# for delimiting the inner function call:
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) = @;
# Please note: We have to assign the
# parameters to named values here, because
# the arms of the conditional below have
# their own set of arguments.
(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
# f keeps its arity checks, but f2 will
# call the same function, but without arity checks.
!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 = ${
# $self is set by any key access using the '.' calling form:
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 };
},
};
# Create instance:
!my_obj = MyClass.new[];
my_obj.inc_x[];
!chain = $[];
my_obj.install_on chain;
my_obj.install_getter chain;
# There are now 3 references to 'my_obj':
# - my_obj variable
# - first callback in chain
# - second callback in chain
std:assert_eq my_obj._data.x 2;
chain.0[]; # calls my_ocj.inc_x[];
std:assert_eq my_obj._data.x 3;
# Second callback gets x:
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;
# does stuff...
(x == 10) {
return :outer 20
};
# more stuff that is not executed if x == 10.
}
§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 return
to 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 = {
# some code ...
_? func[]; # If you would not catch the error value here,
# the program would panic, as an error value
# must not be ignored!
# other code ...
panic "this will never be reached!";
# something here...
};
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!";
# The `then` branch we are currently in is a call frame.
# To jump further up the call stack, we need the label
# we defined for the function above.
!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;
# The first function of on_error will be called with the unwrapped
# error if an error occured.
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:
# Decimal:
std:assert_eq 10r99 99;
# Hexadecimal:
std:assert_eq 0xFF01 65281;
# Binary:
std:assert_eq 0b1011 11;
std:assert_eq -0b1011 -11;
# Radix 4:
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:
# Decimal:
std:assert_eq 10r9.92 9.92;
# Hexadecimal:
std:assert_eq 0xFF.1 255.0625;
# Binary:
std:assert_eq 0b1011.101 11.625;
# Radix 4:
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.
# integer vectors
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);
# float vectors
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);
# conversion
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 0
s 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 ivec
s 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;
# adding something to its inverse yields all 0s
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;
# References are not dereferenced:
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;
# References are not dereferenced:
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.
# the least number of dimensions a vector can have is 2.
std:assert_eq (std:v:dims $[]) 2;
# while the most is 4.
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 ivec
s,
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 ivec2
s that have a length of 1
.
!p1 = fvec ${ x = 20, y = 30.5 };
!p2 = fvec ${ x = -10, y = 0.5 };
# get the delta representing how far you'd have to travel to get from p1 to p2
!delta = p2 - p1;
# the normalized delta represents a single 1 sized step you could take to get to p2 from p1.
!n = std:v:norm delta;
# the length of this step is reflected in the magnitude of the vectors
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
# do you need to turn left or right to look at `goal`,
# if you're standing at `at` looking in `looking`?
# find the unit vector representing the space between where you want to look and where you're at.
!delta = std:v:norm goal - at;
# the direction you need to turn in can be found by checking the sign of
# the dot product of where you're currently looking and where you're 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};
# the cross product of these two values will represent the third axis, z, and will be
# perpendicular to both other vectors.
!z = std:v:cross x y;
std:assert_eq z (fvec ${z=1});
# because all three vectors are perpindicular, they'll all have the same dot product from each other.
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.
# compare this to the one for std:v:lerp! note that the length of this one is almost 1.
# this is definitely not the case for std:v:lerp's output with the same input.
!v = std:v:slerp $f(1,0) $f(0,1) 0.5;
# the values may not be exact because of floating point rounding errors,
# but they should be pretty close.
std:assert_rel_eq v.x 0.7071067811865476 0.000001;
std:assert_rel_eq v.y 0.7071067811865476 0.000001;
# The values are interpolated around a circle, so if you raise t high enough you'll start
# getting the same values as you get with a lower t, although not quite because of float rounding.
!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 ];
# halfway in between 0.0 and 90.0 should be 45.
# note that lerp would work here as well
!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)];
# halfway in between 0.0 and 90.0 should be 45.
# note that lerp would NOT work here as well, rad2vec returns a unit vector.
!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";
# You can use the unicode escapes up to the first 256 code points in bytes too:
std:assert_eq (char $b'\u{40}') '\u{40}';
# Beyond that, you will get the byte '?':
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";
# Unicode escapes are also working:
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
# Short form $c works too.
!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;
};
# Primary use case is `eval` and `std:thread:spawn`:
!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∑∑";
# Empty _pad-str_ is not an error but a nop:
std:assert_eq
(std:str:pad_start 8 "" "∑∑")
"∑∑";
# also works with characters
std:assert_eq
(std:str:pad_start 3 'x' "0")
"xx0";
# also works with bytes
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Ä∑";
# Empty _pad-str_ is not an error but a nop:
std:assert_eq
(std:str:pad_end 8 "" "∑∑")
"∑∑";
# also works with characters
std:assert_eq
(std:str:pad_end 3 'x' "0")
"0xx";
# also works with bytes
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:
# replacing substrings:
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";
# also works with strings and chars:
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";
# finding a character/byte:
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;
# splitting:
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 "äßÿ"]) "äßÿ"; # broken UTF8 will result in an error: 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;
# They don't match with strings:
std:assert_eq ((x == "ON") { 10 } { 20 }) 20;
# Work together nicely with `match`:
!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;
# Pairs are often used to represent map entries,
# so you can use `key` and `value`
# and the short forms `k` and `v` too:
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);
# In contrast to vectors:
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 is only explicit here for demonstration
# purposes! `iter` will make an iter from the pair
# if you don't pass one!
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];
# Index calling:
std:assert_eq (0 some_vec) 1;
std:assert_eq (1 some_vec) 20;
std:assert_eq (2 some_vec) 30;
# Field syntax:
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;
# There can be any expression after the `.` if you wrap it into `(...)`:
std:assert_eq some_vec.(1 + 1) 3;
# A more direct example:
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 };
# Symbol calling:
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;
# Field syntax:
std:assert_eq some_map.a 1;
std:assert_eq some_map.b 2;
# There can be any expression after the `.` if you wrap it into `(...)`,
# also strings:
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}";
# As a reminder, a full expression can come after the '*':
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 bystd: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;
# Set `drop_check` to $true when all (non weak) references to it are gone.
!x = $&& (std:to_drop {|| .drop_check = $true });
# Create a weakened reference to the value referred to by x:
!y = std:ref:weaken x;
# The reference to the drop function is removed and this means
# that the weak reference in y is invalidated and returns $n in future.
.x = $n;
# Deref y now gives you $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
# hide the reference for direct access via local variables
!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;
# Note that $& references in local variables are
# automatically dereferenced. Because of that we need to wrap it into
# an extra reference.
!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.
Data | Iterator return values |
---|---|
vector | Each element of the vector. |
map | Each key/value pair of the map in undefined order. |
$none | Returns nothing |
optional | Returns the optional value on first invocation. |
$o() | Returns nothing. |
int | Returns the integer value on first invocation. |
float | Returns the integer value on first invocation. |
string | Returns the individual characters as string. |
symbol | Returns the individual characters as string. |
byte vector | Returns the individual bytes as byte vector. |
$error | Returns 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:
Type | Args | Semantics |
---|---|---|
$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. |
$true | f1, f2 | Will call f1 . |
$false | f1, f2 | Will 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. |
integer | vector, string, byte_vec, iterator | Will return the element at the given integer index. |
symbol | map, userval | Will retrieve the value in the map at the key equal to the symbol. |
map | anything | Will call anything for each value and key in the map and return a list with the return values. |
string | string, byte_vec, char or byte | Append operation, works with multiple arguments. |
byte_vec | string, byte_vec, char or byte | Append 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) | string | Find operation, search from int_offs for the given string, byte_vec, char or byte. |
$p(int_offs, string/byte_vec/char/byte) | byte_vec | Find operation, search from int_offs for the given string, byte_vec, char or byte. |
$p(int_from, int_count) | vector | Vector slice operation. |
$i(int_from, int_count) | vector | Vector slice operation. |
$p(int_from, int_count) | numeric vector | Creates a vector slice from a numeric vector. |
$i(int_from, int_count) | numeric vector | Creates a vector slice from a numeric vector. |
$p(int_from, int_count) | iterator | Iterator result list slice operation. |
$i(int_from, int_count) | iterator | Iterator result list slice operation. |
$p(int_from, int_count) | string | Substring operation. (See also section about pairs) |
$i(int_from, int_count, ...) | string | Substring operation. |
$p(int_from, int_count) | byte_vec | Substring operation. (See also section about pairs) |
$i(int_from, int_count, ...) | byte_vec | Substring 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) | string | Split operation. (See also section about pairs) |
$p(byte_vec, int) | byte_vec | Split 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) | string | Replace all operation. (See also section about pairs) |
$p(byte_vec, byte_vec) | byte_vec | Replace all operation. (See also section about pairs) |
$p(pat_char, repl_char) | string or byte_vec | Replace all pat_char in string or byte_vec with repl_char. |
$p(pat_byte, repl_byte) | string or byte_vec | Replace all pat_char in string or byte_vec with repl_char. |
$p(char, char) | char or byte | Range check operation, whether char or byte is inside to/from range. |
$p(byte, byte) | char or byte | Range check operation, whether char or byte is inside to/from range. |
$p(int_a, int_b) | char or byte | Range check operation, whether char or byte is inside to/from range. |
$i(int_a, int_b) | char or byte | Range 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";
# You may also use `if` in case it suits your coding style better:
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 thewhile
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 = $[];
# Without the rebinding of the variable `i`, `i` would be captured as hidden
# reference and each iteration would update the contents of that reference.
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 * _;
};
# 40 is not in the set because the accumulation of 0.01 results
# in a value slightly above 0.4 and ends the range iteration:
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 branch1 … last-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;
# or even this:
!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> { $+ ... }
.
# Lists:
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
});
# Great for working with strings too:
std:assert_str_eq
(map std:str:to_uppercase
$["abc", "bcbc", "aaad", "afoo", "foo"])
$["ABC", "BCBC", "AAAD", "AFOO", "FOO"];
# Maps:
std:assert_str_eq
(std:sort ~ map { @ } ${a = 10, b = 20})
$[$[10, "a"], $[20, "b"]];
# Generally anything that you can pass into `$iter`:
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[_] { $+ ... } }
.
# Lists:
std:assert_str_eq
(filter { (_ 0 1) == "a" } $["abc", "bcbc", "aaad", "afoo", "foo"])
$["abc","aaad","afoo"];
# Good in combination with `map` too:
std:assert_str_eq
(map std:str:to_uppercase
~ filter { (_ 0 1) == "a" }
$["abc", "bcbc", "aaad", "afoo", "foo"])
$["ABC","AAAD","AFOO"];
# Also like `map` works fine with maps, but the function
# needs to take two arguments and returns a pair:
std:assert_str_eq
(std:sort
~ filter { @.0 % 2 == 0 }
${a = 2, b = 43, c = 16, d = 13 })
$[16 => "c", 2 => "a"];
# Generally anything that you can pass into `$iter`:
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:
Syntax | Semantics |
---|---|
$@v expr | Setup collection of values in a vector, evaluates expr and returns the vector. |
$@vec expr | Same as $@v |
$@m expr | Setup collection of key/value pairs in a map, evaluates expr and returns the vector. |
$@map expr | Same as $@m |
$@s expr | Setup appending of values to a string, evaluates expr and returns the string. |
$@string expr | Same as $@s |
$@b expr | Setup collection of values in a byte vector, evaluates expr and returns byte vector. |
$@bytes expr | Same as $@b |
$@i expr | Setup accumulation in an integer, evaluates expr and returns the integer sum. |
$@int expr | Same as $@i |
$@f expr | Setup accumulation in a float, evaluates expr and returns the float sum. |
$@flt expr | Same 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
:
# Inefficient example:
!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;
# also comparison operators work
!y = 10;
.y < = 10;
std:assert_eq y $false;
# function argument pipelining also works in this context
!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]";
# Without the `!w` the reference would just
# be auto dereferenced like `str` would do it:
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";
# Note: For floats, the "!f" is implicit if you specify a precision:
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 Value | Semantics |
---|---|
x | Matches any value and assigns it to the variable x . |
? | Matches any value, but does not assign it. |
x $M $M1 ... $Mn | Assign the value that matched $M, $M1 or $Mn to the variable x . |
? $M $M1 ... $Mn | Matches 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. |
_* $M | Placeholder for 0 or N items that match $M in the vector. |
_+ $M | Placeholder for 1 or N items that match $M in the vector. |
_? $M | Placeholder 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 $M2 | Matches if $M1 or $M2 matches. |
$M1 &and $M2 | Matches 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 $M | Matches an error value that matches $M. |
$n | Matches $none. |
literal values | Literal 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:
# Please note: Whitespace inside the pattern is allowed and will not be matched!
!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:
# Notice the usage of the `<&` function call operator:
!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 Syntax | Semantics |
---|---|
`? | $()[]*` |
whitespace | Please 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. |
$*X | Greedly matches the pattern part X 0 or N times. For grouping pattern parts use (...) like in $*(abc) . |
$<*X | Non-greedly matches the pattern part X 0 or N times. |
$+X | Greedly matches the pattern part X 1 or N times. For grouping pattern parts use (...) like in $+(abc) . |
$<+X | Non-greedly matches the pattern part X 1 or N times. |
$?X | Greedly matches 0 or 1 occurences of the pattern part X . Like usual, you can group using (...) . |
$<?X | Non-greedly matches 0 or 1 occurences of the pattern part X . |
$!X | Zero-width negative look-ahead. ($^$!a*) matches any string not starting with an a . |
$=X | Zero-width positive look-ahead. ($^$=a*) matches any string starting with an a . |
$s | Matches one (Unicode) whitespace character. |
$S | Matches one (Unicode) non-whitespace character. |
$&L | Transforms 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. |
$&U | Transforms 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.
# Always zero for scalar non sequential/collection values:
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:
Cases | Return Value |
---|---|
a > b | -1 |
a == b | 0 |
a < b | 1 |
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:
Cases | Return Value |
---|---|
a > b | 1 |
a == b | 0 |
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.
Cases | Return Value |
---|---|
a > b | -1 |
a == b | 0 |
a < b | 1 |
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.
Cases | Return Value |
---|---|
a > b | 1 |
a == b | 0 |
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.
# these two are within 1 of each other
!x = 10.5;
!y = 11.3;
std:assert_rel_eq x y 1;
# but not within 0.5 of each other, so this line is commented out.
# std:assert_eq x y 0.5;
§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
- secondsms
- millisecondsus
- microsecondsns
- 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 exitstd:process:try_wait
- to check if the process exitedstd: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"
];
# do something in your program....
!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:
# - :f for files
# - :l for symlinks
# - :d for directories
}
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 {
# Attention: Even if this example does not use any
# built in functions, it's a good practice
# to load them into the global environment
# of the thread!
!@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"]]]
]];
# Here we use the structure matchers for finding the values of the x attributes
# of the <i> elements:
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
withtoken
as value.@timeout
with a timeout duration as value. (See alsostd: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 (usually200
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:displayln ~ std:ser:json body $f;
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
# subscribed topics.
# Topics to subscribe to, if not given, '#' will be used:
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",
};
# sleep a bit until the broker is initialized:
std:thread:sleep :ms => 500;
!chan = std:sync:mpsc:new[];
!cl = std:mqtt:client:new chan "test1" "localhost" 1883;
# let it connect:
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",
};
# sleep a bit until the broker is initialized:
std:thread:sleep :ms => 500;
!chan = std:sync:mpsc:new[];
!cl = std:mqtt:client:new chan "test1" "localhost" 1883;
# let it connect:
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
.
Syntax | Semantics |
---|---|
< | Sets little endian |
> | Sets big endian |
= | Sets native endian |
x | One zero byte of padding |
y | Byte content with unspecified length. On unpack this reads to the end of the input. |
z | A zero-terminated string of bytes. |
c<n> | An n long field of bytes. |
b | A 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. |
f | A float field (32 bits). |
d | A double field (64 bits). |
<n>
can be any number.<bits>
can be 8, 16, 32, 64 or 128.
[]: –– REFERENCE DOC END ––
Functions§
- core_
symbol_ table - Returns a SymbolTable with all WLambda core language symbols.
- std_
symbol_ table - Returns a SymbolTable with all WLambda standard library language symbols.