- Clean a list of tags with atom functions
- Catalogue of math and physics constants with eval
- Add a language and translate the messages of an application
- Request context in session and environment variables
- Run user formulas safely: eval, try and error codes
- User rights as a bitmask
- Breadcrumb links from a page path
- Sort CSV columns: text sort versus numeric sorts
- Stable A/B test buckets from a hash
- Cache a slow result in a node with a TTL
Clean a list of tags with atom functions
Users typed tags with spaces, duplicates and padding. The script trims every atom, removes the duplicates, counts and finds a tag with and without trimming, and gives the first and last clean tags: the differences between the plain and the _lrtrim variants show at once.
#1. Tags typed by users; -> "[tags]" " java, mql ,sql,mql, java ,json,mql"; #2. Clean and deduplicate; -> "[trimmed]" (atom lrtrim [tags] ","); -> "[unique]" (atom distinct_lrtrim [tags] ","); #3. Count and find, raw atoms versus trimmed atoms; -> "[cnt]" (concat (atom count [tags] "mql" ",") " raw, " (atom count_lrtrim [tags] "mql" ",") " trimmed"); -> "[dist]" (concat (atom count_distinct [tags] "mql" ",") " raw, " (atom count_lrtrim_distinct [tags] "mql" ",")); -> "[pos]" (concat (atom find [tags] "mql" ",") " raw, " (atom find_lrtrim [tags] "mql" ",") " trimmed, position " (atom position [tags] "mql" ",") "/" (atom position_lrtrim [tags] "mql" ",")); #4. Ends of the list and display; concat "trimmed: " [trimmed] [_n_] "unique: " [unique] " = " (atom size [unique] ",") " tags" [_n_] "display: " (atom distinct_lrtrim_1sbefore [tags] ",") [_n_] "distinct raw: " (atom distinct [tags] ",") [_n_] "count mql: " [cnt] ", distinct count: " [dist] [_n_] "find mql: " [pos] [_n_] "first: " (atom get_first_lrtrim [tags] ",") ", last: " (atom get_last_lrtrim [tags] ",") ", second: " (atom get_lrtrim [tags] 2 ",");
trimmed: java,mql,sql,mql,java,json,mql unique: java,mql,sql,json = 4 tags display: java, mql, sql, json distinct raw: java, mql ,sql,mql, java ,json count mql: 2 raw, 3 trimmed, distinct count: 1 raw, 1 find mql: 4 raw, 2 trimmed, position 4/2 first: java, last: mql, second: mql
- Lowercase first with
string lowerso that MQL and mql are the same tag - Loop over the unique tags with
forandatom get
Catalogue of math and physics constants with eval
A library needs a table of the 37 built-in constants with their value, uncertainty and unit. The script builds the command name of each constant, runs it with eval, splits the answer with atom functions and computes the relative uncertainty.
#1. Constants to document; -> "[names]" "math_pi,math_two_pi,math_half_pi,math_four_pi,math_pi_square,physics_c,physics_c_square,physics_planck,physics_planck_over_two_pi,physics_e,physics_k,physics_n,physics_r,physics_f,physics_g,physics_g0,physics_newtonian_g,physics_m_electron,physics_m_proton,physics_m_neutron,physics_m_muon,physics_m_deuteron,physics_amu,physics_mp,physics_lp,physics_tp,physics_a0,physics_rinf,physics_eh,physics_fine_structure,physics_bohr_magneton,physics_nuclear_magneton,physics_magn_flux_quantum,physics_perm_vac_electric,physics_perm_vac_magnetic,physics_z0,physics_stefan_boltzmann"; -> "[out]" ""; -> "[n]" (atom size [names] ","); for (-> "[i]" 1) (<= [i] [n]) (++ "[i]") { -> "[nm]" (atom get [names] [i] ","); #2. eval runs a command built as a string; -> "[raw]" (string trim (eval (concat "constant " [nm] ";"))); #3. Exact constants have no parenthesis; if (string starts_with [raw] "(") { -> "[inner]" (string regexp_extract [raw] "^\(([^)]*)\)" 1); -> "[val]" (atom get [inner] 1 " "); -> "[rel]" (concat "rel. " (/ (atom get [inner] 3 " ") [val])); -> "[unit]" (string trim (string regexp_extract [raw] "^\([^)]*\)(.*)$" 1)); } { -> "[val]" (atom get_first [raw] " "); -> "[rel]" "exact"; -> "[unit]" (atom get_last [raw] " "); }; #4. Two constants per line; -> "[sep]" (case (== [i] [n]) {"";} (== (math mod [i] 2) 0) {[_n_];} {" | ";}); -> "[out]" (concat [out] [nm] " = " [val] (if (is empty [unit]) {"";} {concat " " [unit];}) " (" [rel] ")" [sep]); }; [out];
math_pi = 3.1415926535897931 (rel. 7.002817496043395E-17) | math_two_pi = 6.2831853071795862 (rel. 7.002817496043395E-17) math_half_pi = 1.5707963267948966 (rel. 7.002817496043395E-17) | math_four_pi = 12.5663706143591725 (rel. 7.082394967589343E-17) math_pi_square = 9.869604401089358 (rel. 9.017585344168063E-17) | physics_c = 299792458 m/s (exact) physics_c_square = 89875517873681764 m²/s² (exact) | physics_planck = 6.626069E-34 J·s (rel. 1.6601094857297744E-7) physics_planck_over_two_pi = 1.0545717E-34 J·s (rel. 1.7068540716577167E-7) | physics_e = 1.6021765E-19 C (rel. 8.73811343506786E-8) physics_k = 1.380651E-23 J/K (rel. 1.7383104057433776E-6) | physics_n = 6.022142E23 1/mol (rel. 1.6605387252575578E-7) physics_r = 8.31447 J/(mol·K) (rel. 1.9243559721786233E-6) | physics_f = 96485.34 C/mol (rel. 2.4874245144391884E-7) physics_g = 9.8066499999999976 m/s² (rel. 9.075474295503565E-17) | physics_g0 = 0.00007748092 S (rel. 3.3556648527147077E-7) physics_newtonian_g = 6.674E-11 m³/(kg·s²) (rel. 1.4983518130056938E-4) | physics_m_electron = 9.109383E-31 kg (rel. 1.7564307044725203E-7) physics_m_proton = 1.6726217E-27 kg (rel. 1.7338050797738665E-7) | physics_m_neutron = 1.6749273E-27 kg (rel. 1.731418432310465E-7) physics_m_muon = 1.8835314E-28 kg (rel. 1.752028131837887E-7) | physics_m_deuteron = 3.3435833E-27 kg (rel. 1.7047578865464484E-7) physics_amu = 1.6605389E-27 kg (rel. 1.6861995825572048E-7) | physics_mp = 2.1765E-8 kg (rel. 7.351252010107971E-5) physics_lp = 1.6162E-35 m (rel. 7.424823660438064E-5) | physics_tp = 5.3912E-44 s (rel. 7.60498590295296E-5) physics_a0 = 5.291772E-11 m (rel. 3.4015070944099634E-7) | physics_rinf = 1.097373156852E7 1/m (rel. 6.652249469033196E-12) physics_eh = 4.3597442E-18 J (rel. 1.6514730382576115E-7) | physics_fine_structure = 0.007297353 (rel. 3.4258997748909776E-7) physics_bohr_magneton = 9.274009E-24 C·J·s/kg (rel. 4.313129305783508E-7) | physics_nuclear_magneton = 5.050783E-27 C·J·s/kg (rel. 4.355760285088471E-7) physics_magn_flux_quantum = 2.0678337E-15 Wb (rel. 2.5147089923140336E-7) | physics_perm_vac_electric = 8.854187817620389E-12 A²·s²/(N·m²) (rel. 2.7105817602195534E-16) physics_perm_vac_magnetic = 0.0000012566370614359171 N/A² (rel. 8.753521870054245E-17) | physics_z0 = 376.73031346177061 Ω (rel. 7.432372442426604E-17) physics_stefan_boltzmann = 5.6704E-8 J/(K^4·s·m²) (rel. 7.406884875846501E-6)
- Store the table in a JSON array with
json iarrayto serve it from a web service - Use the constants directly in
math calcwith_c,_h,_k...
Add a language and translate the messages of an application
An application goes German. The script creates and activates the language if needed, stores the same message in three languages, refreshes the cache, reads the German text, and shows how to remove the key and the language later. It writes to the server, so it is not run here.
#1. Create the language once; if (not (translate exist_language de)) { translate add_language de "Deutsch"; }; translate active_language de true; #2. The same key in three languages; translate set "cart.empty" en "Your cart is empty."; translate set "cart.empty" fr "Votre panier est vide."; translate set "cart.empty" de "Ihr Warenkorb ist leer."; #3. Refresh the cache and read the German text; translate reset_cache; -> "[msg]" (translate get "cart.empty" de); log trace (concat "cache: " (translate stat_cache)); #4. Later, to remove the key then the language; translate delete "cart.empty"; translate delete_language de; [msg];
- Pick the language of the user from a session variable, see the next recipe
- Deactivate a language without deleting it:
translate active_language de false
Request context in session and environment variables
A script needs to know who runs it and to carry a small context (language, counters, a greeting) between steps. It reads the user and the session, sets, increments, decrements, concatenates, tests and deletes environment variables, and translates the greeting with a fallback to English.
#1. Who runs the code, in which session; -> "[user]" (who); -> "[session]" (sid); -> "[open]" (count sessions); #2. A context kept in the environment; env set var "[lang]" "fr"; env set var "[calls]" 10; env incr var "[calls]" 5; env decr var "[calls]" 2; #3. Translation with a fallback to English; if (not (translate exist_language (env get var "[lang]"))) {env set var "[lang]" "en";}; concat_var "[greeting]" (translate get "hello" (env get var "[lang]")) " " [user] ", " (env get var "[calls]") " calls"; #4. Delete a variable and show what is left; -> "[tmp]" "scratch"; env del var "[tmp]"; concat [greeting] [_n_] "session " [session] " among " [open] " open, temp deleted: " (not (env exist var "[tmp]")) [_n_] "commands run by this user: " (cmdid) [_n_] (env show);
hello admin, 13 calls
session 265 among 3 open, temp deleted: 1
commands run by this user: 2
{
"[CLIENT_HOST_NAME]": "localhost",
"[session]": "265",
"[open]": "3",
"[calls]": "13",
"[greeting]": "hello admin, 13 calls",
"[CLIENT_HOST_ADDRESS]": "127.0.0.1",
"[user]": "admin",
"[CLIENT_PORT]": "59851",
"[lang]": "fr"
}Run user formulas safely: eval, try and error codes
A tool lets users type small MQL formulas. The script runs each one with eval inside try, raises a coded exception for a forbidden word, routes the error with switch, counts the errors in a synchronized block and builds a JSON report of the results and errors.
#1. Formulas typed by users, separated by |; -> "[formulas]" "+ 2 3;|* 4 (+ 1 1);|unknown_fx 3;|script show ghost;|bd/ 10 4;"; json load "rx_eval" "[]"; -> "[errors]" 0; for (-> "[i]" 1) (<= [i] (atom size [formulas] "|")) (++ "[i]") { -> "[f]" (atom get [formulas] [i] "|"); try { #2. Refuse some words with a coded exception; if (string contains [f] "script ") {exception (403) ("script functions are not allowed");}; -> "[r]" (eval [f]); json iarray "rx_eval" "/" (concat "{\"mql\": \"" (string json_escape [f]) "\", \"result\": \"" [r] "\"}") OBJ; } { #3. Route the error by its code; -> "[code]" (string regexp_extract [err] "([0-9]+): [^\n]*$" 1); -> "[why]" (switch ([code]) ("403") {"forbidden"} {"invalid MQL"}); synchronized ("rx_eval_errors") {++ "[errors]";}; json iarray "rx_eval" "/" (concat "{\"mql\": \"" (string json_escape [f]) "\", \"error\": \"" [why] "\"}") OBJ; } "[err]"; }; #4. Keep the report, free the memory; -> "[report]" (json doc "rx_eval"); json unload "rx_eval"; concat [errors] " errors" [_n_] [report];
2 errors
[
{
"result": "5",
"mql": "+ 2 3;"
},
{
"result": "8",
"mql": "* 4 (+ 1 1);"
},
{
"mql": "unknown_fx 3;",
"error": "invalid MQL"
},
{
"mql": "script show ghost;",
"error": "forbidden"
},
{
"result": "2.5",
"mql": "bd/ 10 4;"
}
]synchronizedlets only one session at a time run the block of a key: use it around shared counters- Run several independent formulas at the same time with
parallel
User rights as a bitmask
An application stores the rights of a role in one integer, one bit per right. The script grants rights with OR, toggles them with XOR, decodes a mask with AND in a loop, and answers the two checks the services ask: can delete, is admin.
#1. Rights of an application stored as bits in one integer; -> "[names]" "read,write,delete,export,admin"; -> "[READ]" 1; -> "[WRITE]" 2; -> "[DELETE]" 4; -> "[EXPORT]" 8; -> "[ADMIN]" 16; #2. Grant rights with OR: an editor reads, writes and exports; -> "[editor]" (math bit_or (math bit_or [READ] [WRITE]) [EXPORT]); #3. Toggle a right with XOR: export removed, delete added; -> "[revoked]" (math bit_xor [editor] [EXPORT]); -> "[mod]" (math bit_or [revoked] [DELETE]); #4. Decode a mask: AND with each bit, the bit value is 2 power position; -> "[decode]" ""; for (-> "[i]" 1) (<= [i] (atom size [names] ",")) (++ "[i]") { -> "[bit]" (math power 2 (- [i] 1)); if (!= (math bit_and [mod] [bit]) 0) { -> "[decode]" (concat [decode] (atom get [names] [i] ",") " "); }; }; #5. Checks used by the services; -> "[can_delete]" (== (math bit_and [mod] [DELETE]) [DELETE]); -> "[is_admin]" (== (math bit_and [mod] [ADMIN]) [ADMIN]); concat "editor = " [editor] " = " (string lpad (string bin [editor]) "0" 5) [_n_] "export revoked = " [revoked] " = " (string lpad (string bin [revoked]) "0" 5) [_n_] "moderator = " [mod] " = " (string lpad (string bin [mod]) "0" 5) " = " (string trim [decode]) [_n_] "can delete: " [can_delete] ", is admin: " [is_admin] ", rights in common with the editor: " (string bin (math bit_and [mod] [editor]));
editor = 11 = 01011 export revoked = 3 = 00011 moderator = 7 = 00111 = read write delete can delete: 1, is admin: 0, rights in common with the editor: 11
- Store the mask in a column of the user table: one INT holds up to 31 rights.
string lpad (string bin [mask]) "0" 8prints the mask on a fixed width for a log line.
Breadcrumb links from a page path
Build the HTML breadcrumb of a shop page from its path. Each level links to the path up to itself, built with atom before_includ and slugs; the current page is not a link and its label is cut; the back button goes to the parent.
#1. The path of the page asked by the visitor; -> "[path]" "Home/Shop/Books/Science Fiction/Dune: Deluxe Edition"; -> "[n]" (atom size [path] "/"); -> "[crumbs]" ""; #2. Each crumb links to the path up to its own level, level included; for (-> "[i]" 1) (<= [i] [n]) (++ "[i]") { -> "[label]" (atom get [path] [i] "/"); -> "[upto]" (atom before_includ [path] [i] "/"); -> "[url]" ""; for (-> "[j]" 2) (<= [j] [i]) (++ "[j]") { -> "[url]" (concat [url] "/" (string slugify (atom get [upto] [j] "/"))); }; if (is empty [url]) {-> "[url]" "/";}; #3. The last level is the current page: no link; if (== [i] [n]) { -> "[crumbs]" (concat [crumbs] "<li class=\"active\">" (string html_escape (string truncate [label] 18)) "</li>"); } { -> "[crumbs]" (concat [crumbs] "<li><a href=\"" [url] "\">" (string html_escape [label]) "</a></li>" [_n_]); }; }; #4. The parent page and the depth for the back button; concat "<ol class=\"breadcrumb\">" [_n_] [crumbs] [_n_] "</ol>" [_n_] "back to: " (atom get_last (atom before_exclud [path] [n] "/") "/") ", depth " (- [n] 1);
<ol class="breadcrumb"> <li><a href="/">Home</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/shop/books">Books</a></li> <li><a href="/shop/books/science-fiction">Science Fiction</a></li> <li class="active">Dune: Deluxe Ed...</li> </ol> back to: Science Fiction, depth 4
- Return the crumbs as JSON for a single-page app instead of HTML, with
json iarray. - Add the schema.org BreadcrumbList attributes for the search engines.
Sort CSV columns: text sort versus numeric sorts
Columns of a CSV export arrive as text. A text sort puts invoice 1001 before 98; the script sorts each column with the numeric sort of its type (int, long epochs, double amounts, float rates), finds the duplicated invoice and the period covered.
#1. Columns of an export, read as text from a CSV file; -> "[invoices]" "1002,98,1001,250,98,1100,7"; -> "[amounts]" "19.9,120,5.25,1200.5,99.99"; -> "[epochs]" "1793520000000,1761984000000,1793433600000"; -> "[rates]" "0.2,0.055,0.1,0.021"; #2. A text sort compares character by character: 1001 comes before 98; -> "[as_text]" (string order (concat "[\"" (string replace [invoices] "," "\",\"") "\"]")); #3. Numeric sorts by type: int, long for epoch ms, double, float; -> "[as_int]" (math order_int (concat "[" [invoices] "]")); -> "[as_long]" (math order_long (concat "[" [epochs] "]")); -> "[as_double]" (math order_double (concat "[" [amounts] "]")); -> "[as_float]" (math order_float (concat "[" [rates] "]")); #4. Duplicates: the most frequent invoice number and the unique list; -> "[dup]" (stat mode (concat "[" [invoices] "]")); -> "[unique]" (atom distinct [invoices] ","); #5. Oldest and newest dates from the sorted epochs; json load "rx_p4l_ep" [as_long]; -> "[oldest]" (date long_to_ts (json select "rx_p4l_ep" "/[0]")); -> "[newest]" (date long_to_ts (json select "rx_p4l_ep" (concat "/[" (- (json count "rx_p4l_ep" "/") 1) "]"))); json unload "rx_p4l_ep"; concat "text sort: " [as_text] [_n_] "int sort: " [as_int] [_n_] "long sort: " [as_long] [_n_] "double sort: " [as_double] [_n_] "float sort: " [as_float] [_n_] "duplicate invoice: " [dup] ", unique: " [unique] " (" (atom size [unique] ",") " of " (atom size [invoices] ",") ")" [_n_] "period: " [oldest] " to " [newest];
text sort: ["1001","1002","1100","250","7","98","98"] int sort: [7,98,98,250,1001,1002,1100] long sort: [1761984000000,1793433600000,1793520000000] double sort: [5.25,19.9,99.99,120.0,1200.5] float sort: [0.021,0.055,0.1,0.2] duplicate invoice: [98], unique: 1002,98,1001,250,1100,7 (6 of 7) period: 2025-11-01 12:00:00 to 2026-11-01 12:00:00
math order_floatworks the same for rates; usejson sortto sort objects by one of their fields.- Convert the sorted epochs with
date long_to_tsto show the first and last dates of the file.
Stable A/B test buckets from a hash
Split the users of a checkout test into control (10 %), A and B (45 % each) without storing anything: the SHA-256 of the test name and the user id gives a slot from 0 to 99, so a user always lands in the same bucket.
#1. An A/B test on the checkout page: 10 percent control, 45 A, 45 B; -> "[test]" "checkout-2026-11"; -> "[users]" "u1001,u1002,u1003,u1004,u1005,u1006,u1007,u1008,u1009,u1010,u1011,u1012"; json load "rx_p4l_ab" "{\"control\": [], \"A\": [], \"B\": []}"; -> "[n]" (atom size [users] ","); for (-> "[i]" 1) (<= [i] [n]) (++ "[i]") { -> "[u]" (atom get [users] [i] ","); #2. Same user and same test always give the same hash, so the same bucket; -> "[h]" (string hex_to_int (string left (string sha_256 (concat [test] ":" [u])) 6)); -> "[slot]" (math mod [h] 100); -> "[bucket]" (case (< [slot] 10) {"control"} (< [slot] 55) {"A"} {"B"}); json iarray "rx_p4l_ab" (concat "/" [bucket]) (concat [u] ":" [slot]) STR; }; #3. Check that a user keeps the bucket at the next visit; -> "[again]" (math mod (string hex_to_int (string left (string sha_256 (concat [test] ":u1005")) 6)) 100); #4. Sizes of the buckets and the members; -> "[out]" (concat "control " (json count "rx_p4l_ab" "/control") ", A " (json count "rx_p4l_ab" "/A") ", B " (json count "rx_p4l_ab" "/B") " of " [n] " users" [_n_] "u1005 slot at the next visit: " [again] [_n_] (string replace (string replace (json doc "rx_p4l_ab") [_n_] "") " " "")); json unload "rx_p4l_ab"; [out];
control 1, A 5, B 6 of 12 users
u1005 slot at the next visit: 57
{"A": ["u1001:20","u1004:38","u1006:38","u1007:21","u1008:11"],"B": ["u1002:93","u1003:77","u1005:57","u1009:88","u1010:78","u1012:69"],"control": ["u1011:5"]}- Change the test name and the users are shuffled again: buckets of two tests are independent.
- Log the bucket with
log writeat each order to compare the conversion of A and B later withstat ttest.
Cache a slow result in a node with a TTL
A slow computation (counting primes) is cached in a server node with its timestamp. The first call computes and stores it, the second reads the fresh value from the node; the cache is then invalidated.
#1. A slow computation: count the primes below a limit; -> "[limit]" 3000; -> "[key]" (concat "tuto_p4l_cache_primes_" [limit]); -> "[ttl_s]" 60; -> "[log]" ""; #2. Cache-aside: read the node if it is fresh, else compute and store it; for (-> "[call]" 1) (<= [call] 2) (++ "[call]") { -> "[t0]" (date current_ms); -> "[hit]" 0; if (node exist [key]) { -> "[age_s]" (/ (- (date current_ms) (node select [key] "/stored_ms")) 1000); if (< [age_s] [ttl_s]) {-> "[hit]" 1;}; }; if (== [hit] 1) { -> "[primes]" (node select [key] "/value"); } { -> "[primes]" 0; for (-> "[x]" 2) (< [x] [limit]) (++ "[x]") { -> "[p]" 1; for (-> "[d]" 2) (and [p] (<= (* [d] [d]) [x])) (++ "[d]") { if (== (math mod [x] [d]) 0) {-> "[p]" 0;}; }; -> "[primes]" (+ [primes] [p]); }; if (node exist [key]) {node delete [key];}; node create [key]; node iobject [key] "/" "value" [primes] INT; node iobject [key] "/" "stored_ms" (date current_ms) NUM; node iobject [key] "/" "stored_at" (date now) STR; }; -> "[log]" (concat [log] "call " [call] ": " [primes] " primes, " (if (== [hit] 1) {"cache hit"} {"computed"}) ", " (- (date current_ms) [t0]) " ms" [_n_]); }; #3. What the cache node holds, then invalidate it; -> "[fields]" (string replace (string replace (node fields [key] "/") [_n_] "") " " ""); node delete [key]; concat [log] "node fields: " [fields] [_n_] "after invalidation, cached: " (node exist [key]);
call 1: 430 primes, computed, 75 ms call 2: 430 primes, cache hit, 0 ms node fields: ["stored_ms","value","key","stored_at"] after invalidation, cached: 0
- Nodes persist on the server: other sessions and other scripts read the same cache.
- Wrap the write in
synchronized ("cache_primes") {...}when several threads may compute it at the same time.
