- Shopping cart total: variables and arithmetic
- Grade a class: for loop, list and case
- Savings plan: while, repeat, break and continue
- Check a sign-up form with boolean operators
- Your first try and exception: a bank withdrawal
- Split a bill: functions inside functions
- Loop over the lines of a JSON order
- Your first script with typed parameters
- Check typed quantities: try inside a loop
- Your first web service, called with execute
Shopping cart total: variables and arithmetic
Your first MQL script: a cart holds 3 items at 19.90 with a 10% discount and 4.99 of shipping. The script stores the values in variables, computes the total with the four operators, shows why money needs the exact bd operators, decides with if whether shipping is free, and uses the bi operators for integers of any size.
#1. Store the values in variables; -> "[price]" 19.90; -> "[qty]" 3; -> "[shipping]" 4.99; #2. The four operators take two numbers each; -> "[subtotal]" (* [price] [qty]); -> "[discount]" (/ [subtotal] 10); -> "[total]" (+ (- [subtotal] [discount]) [shipping]); #3. Same thing with exact decimal operators; -> "[bd_subtotal]" (bd* [price] [qty]); -> "[bd_total]" (bd+ (bd- [bd_subtotal] (bd/ [bd_subtotal] 10)) [shipping]); #4. Free shipping from 50; if (>= [bd_subtotal] 50) {-> "[note]" "free shipping: remove the 4.99";} {-> "[note]" "add items to reach 50";}; #5. Integers of any size with bi operators; -> "[big]" (bi* 987654321987 123456789123); -> "[back]" (bi/ (bi- (bi+ [big] 7) 7) 123456789123); concat "Subtotal with *: " [subtotal] [_n_] "Subtotal with bd*: " [bd_subtotal] [_n_] "Total with + - /: " [total] [_n_] "Total with bd+ bd- bd/: " [bd_total] [_n_] "Note: " [note] [_n_] "bi*: " [big] ", then bi+ bi- bi/ give back " [back];
Subtotal with *: 59.699999999999996 Subtotal with bd*: 59.70 Total with + - /: 58.72 Total with bd+ bd- bd/: 58.72 Note: free shipping: remove the 4.99 bi*: 121932631355968601347401, then bi+ bi- bi/ give back 987654321987
- Very large integers:
bi*andbi+never overflow, trybi* 123456789123 987654321987 - Round a double for display with
math round
Grade a class: for loop, list and case
A teacher has five scores out of 20 in a comma-separated list. The script loops over the list with for, reads each score with atom get, gives a mention with case and computes the class average.
#1. A list of scores separated by commas; -> "[scores]" "15,8,12,19,10"; -> "[n]" (atom size [scores] ","); -> "[sum]" 0; -> "[report]" ""; #2. Loop from 1 to n: init, condition, increment, action; for (-> "[i]" 1) (<= [i] [n]) (++ "[i]") { -> "[s]" (atom get [scores] [i] ","); -> "[sum]" (+ [sum] [s]); #3. case returns the action of the first true condition; -> "[mention]" (case (>= [s] 16) {"Very good"} (>= [s] 12) {"Good"} (>= [s] 10) {"Pass"} {"Fail"}); -> "[report]" (concat [report] "Student " [i] ": " [s] "/20 " [mention] [_n_]); }; concat [report] "Class average: " (/ [sum] [n]) "/20";
Student 1: 15/20 Good Student 2: 8/20 Fail Student 3: 12/20 Good Student 4: 19/20 Very good Student 5: 10/20 Pass Class average: 12.8/20
- Keep only the failed students with
continuewhen the score is >= 10 - Return the report as JSON with
json loadandjson iarray
Savings plan: while, repeat, break and continue
You save 120 a month on an account paying 1% a month, but skip August, and you want 1200. The script loops with while until the goal, skips a month with continue, stops at 24 months with break, then shows that repeat runs its block at least once even when the condition is false.
#1. Start empty, goal 1200; -> "[balance]" 0; -> "[month]" 0; -> "[skipped]" 0; while (< [balance] 1200) { ++ "[month]"; #2. Safety guard: never loop forever; if (> [month] 24) {break;}; -> "[balance]" (math round (* [balance] 1.01) 2); #3. Month 8 is August: no deposit; if (== [month] 8) {++ "[skipped]"; continue;}; -> "[balance]" (+ [balance] 120); }; #4. while checks first, repeat checks after the block; -> "[w]" 0; while (< [w] 0) {++ "[w]";}; -> "[r]" 0; repeat (< [r] 0) {++ "[r]";}; concat "Goal reached in month " [month] " with " [balance] ", " [skipped] " month skipped." [_n_] "while with a false condition ran " [w] " time, repeat ran " [r] " time.";
Goal reached in month 11 with 1264.38, 1 month skipped. while with a false condition ran 0 time, repeat ran 1 time.
Check a sign-up form with boolean operators
A sign-up form sends an e-mail, a nickname, an age, a country and an optional coupon. The script checks each field with the null and empty tests, compares strings and numbers, and combines the checks with and, or, not and xor.
#1. The values sent by the form; -> "[email]" "ana@example.com"; -> "[nickname]" ""; -> "[age]" "17"; -> "[country]" "FR"; -> "[coupon]" null; #2. Null and empty tests return 1 or 0; -> "[has_email]" (is not empty [email]); -> "[no_nick]" (is empty [nickname]); -> "[no_coupon]" (is null [coupon]); -> "[coupon_ok]" (is not null [coupon]); -> "[nick_missing]" (is null or empty [nickname]); #3. Compare strings with equal, numbers with == != < >; -> "[adult]" (>= [age] 18); -> "[france]" (equal [country] "FR"); -> "[not_us]" (not equal [country] "US"); -> "[age_17]" (and (== [age] 17.0) (!= [age] 18)); #4. Combine the rules; -> "[can_buy]" (and [has_email] (or [adult] (not [france]))); -> "[one_of]" (xor [no_coupon] [coupon_ok]); concat "email given: " [has_email] ", nickname null or empty: " [nick_missing] ", nickname empty: " [no_nick] [_n_] "adult: " [adult] ", age is 17: " [age_17] ", from France: " [france] ", not US: " [not_us] [_n_] "coupon: none " [no_coupon] ", given " [coupon_ok] ", exactly one is true: " [one_of] [_n_] "can buy alcohol online: " [can_buy] ", minor in France: " (and (< [age] 18) [france]);
email given: 1, nickname null or empty: 1, nickname empty: 1 adult: 0, age is 17: 1, from France: 1, not US: 1 coupon: none 1, given 0, exactly one is true: 1 can buy alcohol online: 0, minor in France: 1
- Check the e-mail format with
string matchesand a regular expression - Stop at the first error with
exceptionand catch it withtry
Your first try and exception: a bank withdrawal
A customer with 120 on the account asks for 200. The script raises an exception with a code when the funds are too low, catches it with try, reads the message from the error variable and shows that the balance did not change.
#1. Account and request; -> "[balance]" 120; -> "[amount]" 200; try { #2. Raise an error with a code and a message; if (> [amount] [balance]) { exception (1001) (concat "Insufficient funds: balance " [balance] ", asked " [amount]); }; -> "[balance]" (- [balance] [amount]); -> "[status]" "Withdrawal accepted"; } { #3. The error text ends with code: message; -> "[status]" (concat "Refused, error " (string regexp_extract [err] "([0-9]+): [^\n]*$" 1) " - " (string regexp_extract [err] "[0-9]+: ([^\n]*)$" 1)); } "[err]"; concat [status] [_n_] "Balance still " [balance];
Refused, error 1001 - Insufficient funds: balance 120, asked 200 Balance still 120
- Change the amount to 50: no error, the balance becomes 70
- Without try, the same exception stops the script and the editor shows the error
Split a bill: functions inside functions
Four friends share a restaurant bill of 187.45. The script first works step by step with one function per line, then nests the calls in one line, and makes the last friend pay the rounding gap so the total stays exact.
#1. A restaurant bill shared by 4 friends; -> "[bill]" 187.45; -> "[guests]" 4; #2. Step by step: one function per line, each result in a variable; -> "[share]" (/ [bill] [guests]); -> "[euros]" (math trunc [share]); -> "[cents]" (math frac_part [share]); #3. The same work in one line: the innermost parenthesis runs first; -> "[each]" (math round (/ [bill] [guests]) 2); #4. The last friend pays what is left, so the total is exact; -> "[last]" (bd- [bill] (bd* [each] (- [guests] 1))); -> "[gap]" (bd- [last] [each]); #5. A function result can be the parameter of another function; concat "Share: " [share] " = " [euros] " euros + " [cents] [_n_] (- [guests] 1) " friends pay " [each] ", the last one pays " [last] [_n_] "Gap sign: " (math sign [gap]) ", gap in cents: " (string lpad (math round (* [gap] 100) 0) "0" 2) [_n_] (string upper (concat "total checked: " (bd+ (bd* [each] (- [guests] 1)) [last])));
Share: 46.8625 = 46 euros + 0.8625 3 friends pay 46.86, the last one pays 46.87 Gap sign: 1, gap in cents: 01 TOTAL CHECKED: 187.45
- Read a nested call from the inside out:
(math round (/ [bill] [guests]) 2)divides first, then rounds. - Replace the last
concatbystring lpadandstring rpadto print aligned columns of a receipt.
Loop over the lines of a JSON order
An order arrives as JSON with an array of lines. The script loads it in memory, loops over the array with a for and json select, prints a small receipt, visits the fields of the first line with json parse_obj, writes the total back into the order and frees the memory.
#1. Load an order in memory: a JSON object with an array of lines; json load "rx_p4l_order" "{\"id\": \"A-102\", \"customer\": \"Ana\", \"lines\": [{\"item\": \"Pen\", \"qty\": 3, \"price\": 1.5}, {\"item\": \"Notebook\", \"qty\": 2, \"price\": 4.9}, {\"item\": \"Bag\", \"qty\": 1, \"price\": 24}]}"; -> "[n]" (json count "rx_p4l_order" "/lines"); #2. A for loop on the array: positions start at 0; -> "[total]" 0; -> "[prices]" ""; -> "[receipt]" ""; for (-> "[i]" 0) (< [i] [n]) (++ "[i]") { -> "[path]" (concat "/lines[" [i] "]"); -> "[item]" (json select "rx_p4l_order" (concat [path] "/item")); -> "[amount]" (* (json select "rx_p4l_order" (concat [path] "/qty")) (json select "rx_p4l_order" (concat [path] "/price"))); -> "[total]" (+ [total] [amount]); -> "[prices]" (concat [prices] (if (> [i] 0) {", "} {""}) (json select "rx_p4l_order" (concat [path] "/price"))); -> "[receipt]" (concat [receipt] (string rpad [item] "." 12) " " [amount] [_n_]); }; #3. json parse_obj visits each field of an object: key and value; -> "[first]" ""; json parse_obj "rx_p4l_order" "/lines[0]" "[k]" "[v]" { -> "[first]" (concat [first] [k] "=" [v] " "); }; #4. Write the total into the order, read it back, free the memory; json iobject "rx_p4l_order" "/" "total" [total] NUM; -> "[saved]" (json select "rx_p4l_order" "/total"); -> "[fields]" (json fields "rx_p4l_order" "/"); json unload "rx_p4l_order"; concat [receipt] "Total: " [saved] " for " [n] " lines" [_n_] "Average unit price: " (math round (math avg (concat "[" [prices] "]")) 2) [_n_] "First line: " [first] [_n_] "Order fields: " (string replace (string replace [fields] [_n_] "") " " "");
Pen......... 4.5 Notebook.... 9.8 Bag......... 24 Total: 38.3 for 3 lines Average unit price: 10.13 First line: item=Pen price=1.5 qty=3 Order fields: ["total","id","lines","customer"]
json parse_array "rx_key" "/lines" "[line]" {...}gives each element of an array in[line], with no index to manage.- Always end with
json unloadon your key: the document stays in the server memory otherwise.
Your first script with typed parameters
Create a small greeting script that takes a first name and the hour of the day, each checked by a type rule. Execute it twice, ask the server for its parameters, then delete it.
#1. Create a script: method post, name, activity log off, 1 attempt in the stack; script create post "tuto.p4l.hello.greet" false 1 (param (var "[name]" {type is_varchar [name] 30} "First name, 30 chars max" is_null:false is_empty:false "ana") (var "[hour]" {type is_integer [hour] 2} "Hour of the day, 0 to 23" is_null:false is_empty:false "9") ;) "Say hello at the right time of the day" { -> "[word]" (case (< [hour] 12) {"Good morning"} (< [hour] 18) {"Good afternoon"} {"Good evening"}); concat [word] ", " (string capitalize_words [name]) "!"; } "A greeting sentence"; #2. Run it: the method is now the end of its name; -> "[r1]" (execute "tuto.p4l.hello.greet.post" "[name]" "ana" "[hour]" 9); #The parameters are given in the order of the definition; -> "[r2]" (execute "tuto.p4l.hello.greet.post" "[name]" "léo martin" "[hour]" 20); #3. The server knows the script and its parameters; -> "[exists]" (script exist "tuto.p4l.hello.greet.post"); json load "rx_p4l_params" (script show params "tuto.p4l.hello.greet.post"); -> "[p1]" (concat (json select "rx_p4l_params" "/[0]/name") ": " (json select "rx_p4l_params" "/[0]/desc") ", checked by " (json select "rx_p4l_params" "/[0]/type")); -> "[p2]" (concat (json select "rx_p4l_params" "/[1]/name") ": " (json select "rx_p4l_params" "/[1]/desc") ", checked by " (json select "rx_p4l_params" "/[1]/type")); json unload "rx_p4l_params"; #4. A tutorial script is deleted at the end; script delete "tuto.p4l.hello.greet.post"; concat [r1] [_n_] [r2] [_n_] "exists: " [exists] ", parameters: " [p1] " | " [p2] [_n_] "after delete, exists: " (script exist "tuto.p4l.hello.greet.post");
Good morning, Ana! Good evening, Léo Martin! exists: 1, parameters: [name]: First name, 30 chars max, checked by type is_varchar [name] 30; | [hour]: Hour of the day, 0 to 23, checked by type is_integer [hour] 2; after delete, exists: 0
- The values must be given in the order of the definition:
[name]then[hour], or the server refuses the call. - Try
execute "tuto.p4l.hello.greet.post" "[name]" "ana" "[hour]" "nine"before the delete: the type ruletype is_integerrejects it.
Check typed quantities: try inside a loop
A stock form sends five quantities, some of them wrong. Each value is checked inside its own try, so one bad value raises a coded exception without stopping the loop; a server error (a text given to math round) is caught the same way.
#1. Quantities typed in a stock form, one per product; -> "[inputs]" "12|abc|-4|7.5|30"; -> "[n]" (atom size [inputs] "|"); -> "[stock]" 0; -> "[ok]" 0; -> "[report]" ""; for (-> "[i]" 1) (<= [i] [n]) (++ "[i]") { -> "[q]" (atom get [inputs] [i] "|"); #2. One try per value: an error stops this value only, the loop goes on; try { if (not (type is_number [q])) {exception (2001) (concat "'" [q] "' is not a number");}; if (< (math sign [q]) 1) {exception (2002) (concat [q] " must be positive");}; if (not (type is_integer [q] 4)) {exception (2003) (concat [q] " is not a whole number");}; -> "[stock]" (+ [stock] [q]); ++ "[ok]"; -> "[report]" (concat [report] "line " [i] ": +" [q] [_n_]); } { #3. The catch block reads the code and the message at the end of the error; -> "[code]" (string regexp_extract [err] "([0-9]+): [^\n]*$" 1); -> "[msg]" (string regexp_extract [err] "[0-9]+: ([^\n]*)$" 1); -> "[report]" (concat [report] "line " [i] ": error " [code] ", " [msg] [_n_]); } "[err]"; }; #4. An error raised by the server itself is caught the same way; try { -> "[x]" (math round "twelve" 2); } { -> "[server]" (string trim (string regexp_extract [err] "\[\[\[ (.*) \]\]\]" 1)); } "[err]"; concat [report] [ok] "/" [n] " lines accepted, stock +" [stock] [_n_] "Server error caught: " [server];
line 1: +12 line 2: error 2001, 'abc' is not a number line 3: error 2002, -4 must be positive line 4: error 2003, 7.5 is not a whole number line 5: +30 2/5 lines accepted, stock +42 Server error caught: Sorry, an error appears (For input string: "twelve").
- Put the try around the whole loop instead: the first bad value then stops everything, compare both outputs.
- Use the code in the catch with
switchto answer 400 for an input error and 500 for a server error.
Your first web service, called with execute
Write a GET web service that returns the weather of a city as JSON, call it from MQL with execute exactly as the web server would, read one field of the answer, and print how a browser or another program calls it over HTTP.
#1. A web service is a script with a method: get to read data; script create get "tuto.p4l.weather.city" false 1 (param (var "[city]" {type is_enum [city] "paris,lyon,saint-denis"} "City code" is_null:false is_empty:false "paris") ;) "Weather of a city, as a JSON object" { -> "[temp]" (switch ([city]) ("paris") {12.5} ("lyon") {10} {27.8}); json load "rx_p4l_ws" "{}"; json iobject "rx_p4l_ws" "/" "city" (string capitalize_words [city]) STR; json iobject "rx_p4l_ws" "/" "celsius" [temp] NUM; json iobject "rx_p4l_ws" "/" "fahrenheit" (math round (+ (* [temp] 1.8) 32) 1) NUM; json iobject "rx_p4l_ws" "/" "date" (date sysdate) STR; -> "[answer]" (json doc "rx_p4l_ws"); json unload "rx_p4l_ws"; [answer]; } "JSON with city, celsius, fahrenheit and date"; #2. Call it from MQL with execute, like the web server does; -> "[json]" (execute "tuto.p4l.weather.city.get" "[city]" "saint-denis"); #3. The answer is a JSON string: load it to read one field; json load "rx_p4l_answer" [json]; -> "[f]" (json select "rx_p4l_answer" "/fahrenheit"); -> "[c]" (json select "rx_p4l_answer" "/city"); json unload "rx_p4l_answer"; #4. How a browser or another program calls the same service over HTTP; -> "[http]" (script generate url "tuto.p4l.weather.city.get"); script delete "tuto.p4l.weather.city.get"; concat "Answer: " (string replace (string replace [json] [_n_] "") " " "") [_n_] [c] " is at " [f] " F" [_n_] "HTTP call:" [_n_] [http];
Answer: {"date": "2026-11-01","celsius": 27.8,"city": "Saint-Denis","fahrenheit": 82.0}
Saint-Denis is at 82.0 F
HTTP call:
Method
get
Header
x-token = (empty or not define the first time)
x-user = admin (required the first time)
x-password = ***** (required the first time)
URL
https://localhost:9999/api/tuto/p4l/weather/city?x-token=&x-user=&city=- The URL path is the script name:
tuto.p4l.weather.city.getis served at/api/tuto/p4l/weather/citywith the GET method. - Call it with a city outside the enum,
"nice", and catch the refusal withtry.
