wlambda/
lib.rs

1// Copyright (c) 2020-2022 Weird Constructor <weirdconstructor@gmail.com>
2// This is a part of WLambda. See README.md and COPYING for details.
3
4/*!
5WLambda - Embeddable Scripting Language for Rust
6================================================
7
8WLambda is a dynamic scripting language for Rust, where every value
9can be called and the syntax is a blend of Perl, Lua, JavaScript and LISP/Scheme/Clojure.
10It can be used as embedded scripting language or standalone with the
11provided REPL.
12
13Here are some of its properties:
14
15- Simple but unique syntax. For a reference look at the [WLambda Language Reference](https://docs.rs/wlambda/newest/wlambda/prelude/index.html#wlambda-reference).
16- An easily embeddable scripting language for Rust programs due to a simple API.
17- The language is about getting things done quickly, so performance is not a main priority.
18  Current performance is roughly in the ball park of (C)Python or Perl, which means
19  the language is quite possibly too slow where speed is the focus, but fast enough if
20  you do any heavy lifting in Rust.
21- Main data structures are Vectors and Maps.
22- Builtin data structure pattern matchers and selectors which lead to a
23very powerful `match` operation.
24- No garbage collector. Memory and resource management relies only on reference counting and RAII.
25  You can create your own drop functions.
26- Preserving Rust safety by not using `unsafe`.
27- WLambda makes no guarantees that it will not panic and crash your application
28  if bad code is executed. More hardening is required for running untrusted
29  code on the application side (resource limits (ram/cpu), catching panic
30  unwinding, limit file system access, ...).
31- No exceptions, except WLambda level panics. Error handling is accomplished by
32  a specialized data type. It can be thought of as dynamic counterpart of
33  Rust's Result type.
34- Prototyped object orientation.
35- Easy maintenance and hackability of the implementation.
36- Custom user data implementation using [VValUserData](https://docs.rs/wlambda/newest/wlambda/vval/trait.VValUserData.html).
37- Threading support with shared atoms and message queues.
38- Register based VM evaluator and code generator.
39- Builtin pattern matching and structure selector [Pattern and Selector Syntax](https://docs.rs/wlambda/newest/wlambda/selector/index.html).
40- Has a testable wasm32 version: [WASM WLambda Evaluator](http://wlambda.m8geil.de/#!/main).
41
42The embedding API and all internal operations rely on a data structure
43made of [VVal](https://docs.rs/wlambda/newest/wlambda/vval/index.html) nodes.
44
45Here you can find the [WLambda Language Reference](https://docs.rs/wlambda/newest/wlambda/prelude/index.html#wlambda-reference).
46
47# Compiling WLambda
48
49If you want to compile WLambda with all features enabled you need
50to run:
51
52```text
53    cargo build --features mqtt,http
54```
55
56or just:
57
58```text
59    cargo build --features all
60```
61
62# API Hello World
63
64```
65use wlambda::*;
66
67match wlambda::eval("40 + 2") {
68    Ok(v)  => { println!("Output: {}", v.s()); },
69    Err(e) => { eprintln!("Error: {}", e); },
70}
71```
72
73See further down below for more API usage examples!
74
75# WLambda Language Guide
76
77**Try out WLambda right away in the [WASM WLambda Evaluator](http://wlambda.m8geil.de/#!/main).**
78
79## Variables
80
81```wlambda
82!x = 10;        # Variable definition
83
84.x = 20;        # Variable assignment
85```
86
87## Operators
88
89```wlambda
90!x = (1 + 2) * (8 - 4) / 2;
91
92std:assert_eq x 6;
93```
94
95## If
96
97```wlambda
98if $true {
99    std:displayln "It's true!";
100} {
101    std:displayln "It's false!";
102};
103```
104
105```wlambda
106!x = 10 / 2;
107
108if x == 5 {
109    std:displayln "x == 5";
110};
111```
112
113## While
114
115```wlambda
116!x = 10;
117
118while x > 0 {
119    std:displayln x;
120
121    (x == 5) {
122        break[];
123    };
124    .x = x - 1;
125};
126```
127
128```wlambda
129!x = 10;
130
131while x > 0 {
132    std:displayln x;
133
134    if x == 5 {
135        # break is a function, first arg
136        # is the return value for `while`:
137        break[];
138    };
139    .x = x - 1;
140};
141
142std:assert_eq x 5;
143```
144
145## Counting Loop
146
147```wlambda
148!sum = 0;
149
150iter i 0 => 10 {
151    .sum = sum + i;
152};
153
154std:assert_eq sum 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9;
155```
156
157## Endless loop
158
159```wlambda
160!x = 10;
161
162while $true {
163    std:displayln x;
164    .x = x - 1;
165    if x == 0 break[];
166};
167```
168
169## Functions
170
171```wlambda
172!add = { _ + _1 };  # argument names _, _1, _2, ...
173
174!result = add 2 3;
175
176std:assert_eq result 5;
177```
178
179### Different function call syntaxes:
180
181```wlambda
182!add = {!(x, y) = @;    # named variables, @ evals to list of all args
183    x + y
184};
185
186std:displayln[add[2, 3]];   # [] parenthesis calling syntax
187
188std:displayln add[2, 3];    # less parenthesis
189
190std:displayln (add 2 3);    # explicit expression delimiting with `( ... )`
191
192std:displayln ~ add 2 3;    # `~` means: evaluate rest as one expression
193
194!add5 = { _ + 5 };
195
196std:displayln 3 &> add5;    # '&>' is an argument pipe operator
197
198std:displayln add5 <& 3;    # '<&' is the reverse argument pipe operator
199```
200
201### Returning from nested functions:
202
203```wlambda
204
205!test = \:ret_label_a {!(x) = @;
206
207    # an `if` is actually a call to another function, so we need to
208    # dynamically jump upwards the call stack to the given label:
209    if x > 10 {
210        return :ret_label_a x * 2;
211    };
212};
213
214std:assert_eq (test 11) 22;
215```
216
217## Vectors
218
219```wlambda
220!v = $[1, 2, 3];
221v.1 = 5;
222
223std:assert_eq v.1 5;
224
225std:assert_eq (std:pop v) 3;
226std:assert_eq (std:pop v) 5;
227std:assert_eq (std:pop v) 1;
228```
229
230## Iterating over an Vector
231
232```wlambda
233!sum = 0;
234
235iter i $[1, 2, 3, 4] { .sum = sum + i; };
236
237std:assert_eq sum 10;
238```
239
240## Accumulate values in a vector
241
242```wlambda
243
244!new_vec =
245    $@vec iter i $i(0, 4) {
246        $+ i;
247    };
248
249std:assert_eq (str new_vec) (str $[0,1,2,3]);
250```
251
252## Accumulate a sum
253
254```wlambda
255!sum =
256    $@int iter i $i(0, 4) {
257        $+ i;
258    };
259
260std:assert_eq sum 1 + 2 + 3;
261```
262
263## Hash tables/maps
264
265```wlambda
266!m = ${ a = 10, c = 2 };
267
268m.b = m.a + m.c;
269
270std:assert_eq m.b 12;
271```
272
273## Strings
274
275```wlambda
276!name = "Mr. X";
277
278std:assert_eq name.4 'X';           # index a character
279std:assert_eq (name 0 3) "Mr.";     # substring
280
281!stuff = "日本人";
282std:assert_eq stuff.0 '日';         # Unicode support
283```
284
285## Unicode identifiers:
286
287```wlambda
288!人 = "jin";
289
290std:assert_eq 人 "jin";
291```
292
293## Handling Errors
294
295```wlambda
296!some_fun = {
297    if _ == :fail {
298        $error :FAIL_HAVING_FUN
299    } {
300        :ok
301    }
302};
303
304!res1 =
305    match some_fun[:ok]
306        ($error :FAIL_HAVING_FUN) => :failed
307        ?                         => :ok;
308std:assert_eq res1 :ok;
309
310!res1 =
311    match some_fun[:fail]
312        ($error :FAIL_HAVING_FUN) => :failed
313        ?                         => :ok;
314std:assert_eq res1 :failed;
315```
316
317## Builtin Structure Selectors
318
319Selectors work similar to XPath:
320`$S( *:{a=10} /b/1 )` first selects all maps from a vector,
321checks if they got a key-value pair that matches key=`a` and value=`10`.
322The selector path is walked for the matching maps and the `b` key
323is selected. Next the element at index `1` is selected and
324captured.
325
326```wlambda
327!struct = $[
328    ${ a = 10, b = $[ 1, 2, 3 ] },
329    ${ a = 10, b = $[ 4, 5, 6 ] },
330    ${ a = 20, b = $[ 8, 9,  20 ] },
331    ${ a = 20, b = $[ 8, 10, 30 ] },
332    ${ x = 99 },
333    ${ y = 99 },
334];
335
336if struct &> $S( *:{a=10} /b/1 ) {
337    std:assert_str_eq $\    $[2,5];
338} {
339    panic "Should've matched!";
340};
341```
342
343## Builtin Structure Matchers
344
345A bit different but similar to the structure selectors `$S ...` are the `$M
346...` or `match` structure matchers:
347
348```wlambda
349!struct = $[
350    ${ a = 10, b = $[ 1, 2, 3 ] },
351    ${ a = 10, b = $[ 4, 5, 6 ] },
352    ${ a = 20, b = $[ 8, 9,  20 ] },
353    ${ a = 20, b = $[ 8, 10, 30 ] },
354    ${ x = 99 },
355    ${ y = 99 },
356];
357
358!res = $@vec iter elem struct {
359    $+ ~
360        match elem
361            ${ a = 10, b = childs }     => $[:childs_10, $\.childs]
362            ${ a = 20, b = childs }     => $[:childs_20, $\.childs]
363            :other;
364};
365
366std:assert_str_eq res $[
367    $[:childs_10,$[1, 2,   3]],
368    $[:childs_10,$[4, 5,   6]],
369    $[:childs_20,$[8, 9,  20]],
370    $[:childs_20,$[8, 10, 30]],
371    :other,
372    :other,
373];
374```
375
376## Builtin (Regex) Pattern Matching
377
378```wlambda
379!some_url = "http://crates.io/crates/wlambda";
380
381!crate  = $none;
382!domain = $none;
383
384if some_url &> $r{$^ (^$+[^:]) :// (^$*[^/]) /crates/ (^$+[a-z]) } {
385    .domain = $\.2;
386    .crate = $\.3;
387};
388
389std:assert_eq domain "crates.io";
390std:assert_eq crate  "wlambda";
391```
392
393## Object Oriented Programming with prototypes
394
395```wlambda
396!MyClass = ${
397    new = {
398        ${
399            _proto = $self,
400            _data = ${ balance = 0, }
401        }
402    },
403    deposit = {
404        $data.balance = $data.balance + _;
405    },
406};
407
408!account1 = MyClass.new[];
409
410account1.deposit 100;
411account1.deposit 50;
412
413std:assert_eq account1._data.balance 150;
414```
415
416## Object Oriented Programming with closures
417
418```wlambda
419
420!MyClass = {
421    !self = ${ balance = 0, };
422
423    self.deposit = { self.balance = self.balance + _; };
424
425    $:self
426};
427
428!account1 = MyClass[];
429
430account1.deposit 100;
431account1.deposit 50;
432
433std:assert_eq account1.balance 150;
434```
435
436## WLambda Modules
437
438```txt
439# util.wl:
440!@import std std;
441!@wlambda;
442
443!@export print_ten = { std:displayln ~ str 10; };
444```
445
446For import you do:
447
448```txt
449!@import u util;
450
451u:print_ten[]
452```
453
454# Example WLambda Code
455
456That was just a quick glance at the WLambda syntax and semantics.
457
458More details for the syntax and the provided global functions
459can be found in the [WLambda Language Reference](prelude/index.html#wlambda-reference).
460
461Currently there are many more examples in the test cases in `tests/language.rs`.
462
463# API Usage Examples
464
465## Basic API Usage
466
467Here is how you can quickly evaluate a piece of WLambda code:
468
469```
470let s = "$[1,2,3]";
471let r = wlambda::eval(&s).unwrap();
472println!("Res: {}", r.s());
473```
474
475## More Advanced API Usage
476
477If you want to quickly add some of your own functions,
478you can use the GlobalEnv `add_func` method:
479
480```
481use wlambda::vval::{VVal, VValFun, Env};
482
483let global_env = wlambda::GlobalEnv::new_default();
484global_env.borrow_mut().add_func(
485    "my_crazy_add",
486    |env: &mut Env, _argc: usize| {
487        Ok(VVal::Int(
488              env.arg(0).i() * 11
489            + env.arg(1).i() * 13
490        ))
491    }, Some(2), Some(2));
492
493let mut ctx = wlambda::compiler::EvalContext::new(global_env);
494
495// Please note, you can also add functions later on,
496// but this time directly to the EvalContext:
497
498ctx.set_global_var(
499    "my_crazy_mul",
500    &VValFun::new_fun(|env: &mut Env, _argc: usize| {
501       Ok(VVal::Int(
502          (env.arg(0).i() + 11)
503        * (env.arg(1).i() + 13)))
504    }, Some(2), Some(2), false));
505
506
507let res_add : VVal = ctx.eval("my_crazy_add 2 4").unwrap();
508assert_eq!(res_add.i(), 74);
509
510let res_mul : VVal = ctx.eval("my_crazy_mul 2 4").unwrap();
511assert_eq!(res_mul.i(), 221);
512```
513
514## Maintaining state
515
516```
517use wlambda::*;
518
519let mut ctx = EvalContext::new_default();
520
521ctx.eval("!x = 10").unwrap();
522
523ctx.set_global_var("y", &VVal::Int(32));
524
525let r = ctx.eval("x + y").unwrap();
526
527assert_eq!(r.s(), "42");
528```
529
530# Possible Roadmap
531
532Current remaining goals for WLambda are:
533
534- Fix remaining bugs.
535- DONE: Add missing standard library functions without dragging in more
536dependencies.
537- Improve and further document the VVal API for interacting with WLambda.
538- DONE: Improve [WLambda Language Reference](https://docs.rs/wlambda/newest/wlambda/prelude/index.html#wlambda-reference) documentation.
539- DONE: Complete function reference documentation in [WLambda Language Reference](https://docs.rs/wlambda/newest/wlambda/prelude/index.html#wlambda-reference).
540- DONE: Add proper module support (via `!@import` and `!@export`).
541- DONE: Add prototyped inheritance for OOP paradigm.
542- DONE: Add data structure matching/destructuring/selection primitives
543to the language.
544- DONE: Replace compiler and closure based evaluator with a VM
545and more or less clever code generator.
546
547# License
548
549This project is licensed under the GNU General Public License Version 3 or
550later.
551
552## Why GPL?
553
554Picking a license for my code bothered me for a long time. I read many
555discussions about this topic. Read the license explanations. And discussed
556this matter with other developers.
557
558First about _why I write code for free_ at all, the reasons are:
559
560- It's my passion to write computer programs. In my free time I can
561write the code I want, when I want and the way I want. I can freely
562allocate my time and freely choose the projects I want to work on.
563- To help a friend or member of my family.
564- To solve a problem I have.
565
566Those are the reasons why I write code for free. Now the reasons
567_why I publish the code_, when I could as well keep it to myself:
568
569- So that it may bring value to users and the free software community.
570- Show my work as an artist.
571- To get into contact with other developers.
572- And it's a nice change to put some more polish on my private projects.
573
574Most of those reasons don't yet justify GPL. The main point of the GPL, as far
575as I understand: The GPL makes sure the software stays free software until
576eternity. That the _end user_ of the software always stays in control. That the users
577have the means to adapt the software to new platforms or use cases.
578Even if the original authors don't maintain the software anymore.
579It ultimately prevents _"vendor lock in"_. I really dislike vendor lock in,
580especially as developer. Especially as developer I want and need to stay
581in control of the computers and software I use.
582
583Another point is, that my work (and the work of any other developer) has a
584value. If I give away my work without _any_ strings attached, I effectively
585work for free. This compromises the price I (and potentially other developers)
586can demand for the skill, workforce and time.
587
588This makes two reasons for me to choose the GPL:
589
5901. I do not want to support vendor lock in scenarios for free.
591   I want to prevent those when I have a choice, when I invest my private
592   time to bring value to the end users.
5932. I don't want to low ball my own wage and prices by giving away the work
594   I spent my scarce private time on with no strings attached. So that companies
595   are able to use it in closed source projects.
596
597## Conversion to MIT / Apache-2.0
598
599I (WeirdConstructor) herby promise to release WLambda under MIT / Apache-2.0
600license if you use it in an open source / free software game (licensed under
601MIT and/or Apache-2.0) written in Rust (and WLambda) with a playable beta
602release, non trivial amount of content and enough gameplay to keep me occupied
603for at least 2 hours. You may use WLambda for your release as if it was
604released under MIT and/or Apache-2.0. Proper attribution as required by MIT
605and/or Apache-2.0.
606
607## If you need a permissive or private license (MIT) right now
608
609Please contact me if you need a different license and want to use my code. As
610long as I am the only author, I can change the license the for code that was
611written by me. We might find an agreement that involves money or something
612else.  For your price estimations: At this point in time (May 2020) I invested
613about 6 months of my private time into this project.
614
615# Contribution
616
617Unless you explicitly state otherwise, any contribution intentionally submitted
618for inclusion in WLambda by you, shall be licensed as GPLv3 or later,
619without any additional terms or conditions.
620
621# Author
622
623* Weird Constructor <weirdconstructor@gmail.com> (WeirdConstructor on GitHub)
624  (You may find me as `WeirdConstructor` on the Rust Discord.)
625
626# Contributors
627
628* Cedric Hutchings <cedhut02@gmail.com> (cedric-h on GitHub)
629
630*/
631
632pub mod vval;
633pub mod parser;
634pub mod compiler;
635pub mod ops;
636pub mod vm;
637pub mod prelude;
638pub mod threads;
639pub mod rpc_helper;
640pub mod util;
641pub mod nvec;
642pub mod vval_user_obj;
643pub mod selector;
644pub mod struct_pattern;
645pub mod formatter;
646mod packer;
647mod prog_writer;
648mod io;
649mod str_int;
650mod stdlib;
651
652pub use stdlib::csv;
653pub use vval::VVal;
654pub use vval::Env;
655pub use vval::StackAction;
656pub use vval::VValUserData;
657pub use threads::AVal;
658pub use compiler::GlobalEnv;
659pub use compiler::GlobalEnvRef;
660pub use compiler::EvalContext;
661pub use compiler::SymbolTable;
662
663/// Evaluates a piece of WLambda code in a default global environment.
664///
665/// ```
666/// println!("> {}", wlambda::eval("${a = 10, b = 20}").unwrap().s());
667/// ```
668#[allow(dead_code)]
669pub fn eval(s: &str) -> Result<VVal, crate::compiler::EvalError>  {
670    let mut ctx = EvalContext::new_default();
671    ctx.eval(s)
672}