Crate laconic

Crate laconic 

Source
Expand description

§Laconic: the language

I got carried away, because I had fun.

I needed an embeddable concise expression interpreter, but Laconic nearly became a programming language:

besides numeric, boolean, string and date operators, it provides variables, tests, loops, routines, standard input & output, file I/O and error handling.

- Koen Bekx

§Introduction

Laconic is a Polish notation expression interpreter :

*+4 2 3
evaluates to 18.

The Laconic crate provides both

  • an executable that can be called from the command line:

...$ laconic '*+4 2 3'

Execute the laconic executable without any parameters to get help.

  • a library exposing struct Interpreter, which can be used by other applications:
    use laconic::Interpreter;
    let mut interpreter = Interpreter::new_stdio_filesys();

    let exe_result = interpreter.execute("*+4 2 3".to_string());

    assert!(exe_result.is_ok());
    assert_eq!(18_f64, exe_result.unwrap().numeric_value());

The many operators understood by the Laconic interpreter, together with the way it expects numbers and strings to be represented, constitute the Laconic language - see below.

Note that a struct Interpreter instance can interprete or execute several scripts or expressions after one another, and preserves Laconic variables and routines that were assigned or declared in previous executions.

This would allow some Laconic snippets in a specific file type to assign values to variables which can be used by subsequent snippets.

Practical example:

a PostScript image file that would contain Laconic expression snippets in double quotes.

An application could preprocess this file by replacing the snippets with the actual numerical value returned by their interpretation by the same Laconic Interpreter instance and, next, removing the single-snippet lines that aren’t valid PostScript commands.

§General syntax of the Laconic language

As said, Laconic interpretes “Polish” expressions: operators precede their operands.

As every operator has a default number of operands, no parentheses are needed if one doesn’t want to exceed this default number.

So, as both the multiplication and addition operators have two operands by default, *+4 2 3 first executes +4 2 and next *6 3
(So +4 2 really is the * operator’s first operand.)

There is a way to have operators process more operands by using parentheses grouping the operands: +(7 8 9) evaluates to 24.
Many operators, however, simply ignore excess operands: ~(4 25) evaluates to -4.

§Number precision

Laconic handles all numbers as f64: 64-bit float values. Mind that this might entail precision limitations.

These precision limitations can be mitigated by setting an orb or precision allowance for comparison operators using a Z#prec operation in the script - see that operation’s description below.

§Basic elements of the Laconic language

Laconic expressions or scripts, which are technically the same, consist of basic elements that can either be

  • whitespace
  • operators
  • numbers
  • simple strings (#...)
  • string bracket contents ([s...])
  • number bracket contents ([n...])
  • comment bracket contents ([c...])

All of these are case-sensitive.

If there are more elements than expected by the preceding operators, the preceding results are discarded and the value of the remaining expression is returned.

However, this allows for a long sequence of expressions to precede the final one, the value of which will be returned or printed. This way, Laconic is a script interpreter instead of a single expression interpreter.

E.g.:

*+4 2 3 25
will return 25, just like
*+4 2 3 + 19 6

Note: the characters backspace, double and single quote are nowhere used in Laconic, as they interfere with command line syntax.

§Whitespace

The characters [blank], ‘\t’, ‘\n’, and ‘\r’ are considered whitespace.

They can be used

  • to separate number operands if necessary,
  • to terminate a simple string (see below)
  • or to enhance readability.

§Operators and operations: general

All operators consist of a single character that precedes its operands.

Note: there are also “named operations” - see the O and o operators.

See below for a detailed explanation of each operator.

As all operators consist of only one character, there’s nearly never a need to separate an operator from preceding or following elements using a whitespace, but it’s permitted for readability.

The only instance where an operator needs to be separated by whitespace from a previous element, is when that previous element is a simple string - without preceding whitespace, the operator would be interpreted as a continuation of that simple string.

Every operator has a default number of operands it expects. E.g., the + operator expects two operands :

+ 1 2 yields 3.

However, many operators can operate on more operands, too, but in order to deviate from the default number, parentheses have to be used:

+(1 2 3) yields 6, just like
++1 2 3

Many operators have a different behavior when followed by one or more variant operators : ,. E.g.:

°1 yields one radian as degrees (ca. 57.295779).
°,1 yields one degree as radians (ca. 0.017452).

Note: whenever used below, the term “operation” designates an operator with all its operands, that may be operators with operands too. In fact, an operation has a tree structure that can be visualized by passing a -b and/or a -a parameter to the laconic executable. This can be a help for debugging an expression or script. For example, the command

... $ laconic '$0 200 ?,(+v0 7 ;w++[sProblem: ] V ¶ 0 ;w+[sAddition succeeded] ¶ V)' -a

will display the below tree:

	Tree after execution :
	;
	│	$
	│	│	0
	│	│	200
	│	└─> 200
	│	?,(
	│	│	+
	│	│	│	v
	│	│	│	│	0
	│	│	│	└─> 200
	│	│	│	7
	│	│	└─> 207
	│	│	;
	│	│	│	w
	│	│	│	│	+
	│	│	│	│	│	+
	│	│	│	│	│	│	Problem:
	│	│	│	│	│	│	V
	│	│	│	│	│	│	└─> (no_value)
	│	│	│	│	│	└─> (no_value)
	│	│	│	│	│	¶
	│	│	│	│	│	└─> (no_value)
	│	│	│	│	└─> (no_value)
	│	│	│	└─> (no_value)
	│	│	│	0
	│	│	└─> (no_value)
	│	│	;
	│	│	│	w
	│	│	│	│	+
	│	│	│	│	│	Addition succeeded
	│	│	│	│	│	¶
	│	│	│	│	│	└─>
	│	│	│	│	│
	│	│	│	│	└─> Addition succeeded
	│	│	│	│
	│	│	│	└─> 19
	│	│	│	V
	│	│	│	└─> 207
	│	│	└─> 207
	│	│)
	│	└─> 207
	└─> 207

§Named operations

The number of viable characters to represent operators is limited as, for many people, non-Latin characters are difficult to enter using a keyboard.

So when Laconic wants to express every operator using only one character, it’s a bit short of characters.

That’s why there are also operators that are designated by an operation name; these are called “named operations”.

The o and O operators take that name as their first operand in order to perform the operation of a named operation.

The difference between both operators is that

  • the lower case o operator takes 2 operands:
  • the operation name
  • and 1 operation operand;
  • the upper case O operator takes 3 operands:
  • the operation name
  • and 2 operation operands.

Furthermore, adding the variant operator (,) to both the O and o operands increases their expected number of operands by (2 * number_of_variants), so

  • the o, operator takes 4 operands;
  • the O, operator takes 5 operands;
  • the o,, operator takes 6 operands;
  • the O,, operator takes 7 operands;
  • etc.

Both operators, however, can have their number of operands overridden by parentheses, in which case they can be used interchangeably and following variant operators don’t affect the number of expected operands.

The operation name, which is the first operand of the o and O operators, can either be a number or a string. For readability, however, strings have been chosen for the implemented operators.

As stated, this string is the first operand and can be written in any valid way a first-operand-string can follow an operator:
o #name
o#name
o [sname]
o[sname]
or even on a new line:

o
  #name

See the Named Operations List section below for the implemented named operations; see the Strings section for the ways strings can be represented in Laconic code.

§Value types

Every element, except whitespace and comments, has a value. Likewise, every operator returns a value - it’s the operator’s value. The value of elements or operators can be operands of other operators.

There are 4 types of values, each having a numeric id:

  • 0: empty
  • 1: number
  • 2: string
  • 90: error

The empty value is returned by unassigned variables, the operator and some other operators when nothing useful can be returned. Many operators return an error value when processing operands having an empty value: e.g.: +20 €

Number values are returned by number elements and many numeric operators.

String values are returned by string elements and many string-related operators.

Error values are returned by the U (user error) operator and many other operators when they find incompatible operands or other problems.

The type of any element or any operator’s return value can be tested using the t operator, e.g.:

t€ yields 0: empty
t/9 3 yields 1: number
t[sI am a string] yields 2: string
Z#ign 1 t/33 0 yields 90: error (because of division by 0; without the Z#ign 1 operation execution would have stopped immediately after the division-by-zero and the t operation would never have been executed completely.)

§Error handling

A script’s author or Laconic executable user can choose how Laconic handles errors: either by halting or ignoring.

Halting: whenever an operator returns an error value, script execution is stopped immediately and an error value is returned. The string representation of this error value can be displayed;

Ignoring: Laconic treats the error outcome as just another operand value and continues execution, having operators using the error value as operand return the same error value. However, this allows the very script to handle the error.

Halting is the default way of error handling.

In scripts:

The error handling mode can be set (and changed again) in scripts using the Z#ign or ?, operators:

Z#ign 1 has Laconic ignore any errors until a Z#ign 0 operation is met:

ta€ type of absolute value of empty should return value 90, because absolute value of empty returns an error. However, the t operator is never executed fully, as the a absolute value operator halts the script execution on finding an empty operand.

Z#ign 1 ta€ does yield 90: after Z#ign 1 has Laconic ignore errors, the a€ operation returns an error and the t type operator can evaluate it to find it has type 90: error.

The ?, operator takes two operands and tries and evaluates the first one. If this first operand has a non-error value, ?, returns that one. Otherwise, the value of the second operand is returned.

?,a€ #Oops! yields the string “Oops!”, as the ?, operator tries to evaluate its first operand (a€: absolute value of empty), gets an error value, and therefore returns the value of its second operand: the string element #Oops!.

By the way, if the ?, operator has a third operand, this operand’s value is returned on successful execution of the first one:

?,(a72 #Oops! #Ok) yields the string “Ok”.

Using the laconic executable, one can add the -I parameter to a command to have the Laconic interpreter ignore errors instead of halting immediately. E.g.:

...$ laconic -I '+[sOutcome: ] /15 0'
will output
Outcome: DivideByZero('/')
which means that the + (concatenation) operator still could operate after the zero-division error occurred.

§Numbers: base 10

Base 10 numbers consist of characters 0 up to 9, and may contain exactly one period as fractal separator.

(Unary minus, ~, is an operator.)

If the number is between 1 and -1 exclusive, the period may be the first character of the number.

For readability, underscores _ may be inserted anywhere in a number - in fact, prior to the interpretion of an expression, all underscores are discarded from it:

.000_001 = 0.000001
1_000_000 = 1000000

A number is allowed to end with a period :

40. = 40

And a lonely period is interpreted as zero :

. = 0

Surplus periods in a number will be ignored:

1.0.0.2

is interpreted as

1.002

§Numbers: other bases

The default number base is 10.

However, using the b and b, operators, one can switch to other bases for input and output, respectively.

As for literal number presentation, Laconic handles 3 number base ranges in a different way:

Digits in bases from 2 up to 10:

These numbers can be expressed directly, e.g.:

  • base 2 : 0-1
  • base 3 : 0-2
  • base 8 : 0-7
  • base 10 : 0-9
    E.g.: b2 101.01 yields 5.25

Digits in bases from 11 up to 36:

Just like hexadecimal numbers, these numbers use (case-insensitive) letters for digits above 10:

  • base 11 : 0-9 and A
  • base 12 : 0-9 and A-B
  • base 16 : 0-9 and A-F
  • base 17 : 0-9 and A-G
  • base 36 : 0-9 and A-Z

If these number contain letters, the should be written as number bracket contents:
b16 11.8 yields 17.5(base10)
b16 [n1A.C] yields 26.75(base10)
b16 1A.C has Laconic consider A and C as operators and complain about insufficient operands for the C operator.

Digits in bases from 37 and higher:

The “digits” in these bases should be written as decimal numbers. If the number contains more than one digit, they should be separated by whitespace and be put inside number brackets:

  • base 37 : 0 - 9 and 10 - 36
  • base 38 : 0 - 9 and 10 - 37
  • base 200 : 0 - 9 and 10 - 199
  • etc.

If the number contains more than one digit, they should be separated by whitespace and be put inside number brackets:

E.g.: base80:
b80 4.8 yields 4.1(base10)
b80 [n1 4] yields 84(base10)
E.g.: base50:
b50 [n1 4.25] yields 54.5(base10)
b50 [n1 4.0 16] yields 54.0064(base10)
b50 [n1 4 . 25] yields 54.5(base10) too - in number brackets, spaces are allowed around the fractal separator

All of this reduces calculating hours, minutes and seconds to calculating seconds in base 60:

b,60 b60 /[n5 20 8] 4 yields [1 20 2(base60)] or 4802(base10):
a fourth of 5 hours, 20 minutes and 8 seconds is 4802 seconds or 1 hour, 20 minutes and 2 seconds.

Combinations of b, and b commands allow conversion of numbers between bases. In order to avoid mistakes in the specification of the output base, it’s best to put the b, (output base) operator before the b (input base) operator.

It’s even possible to process numbers of different bases together:

*17 ;b16 21 yields 561(base10).

If a number has a digit which is too high for its base, that digit is reduced to the highest one:

e.g.:

b 2 104
yields the same as
b 2 101
to wit 5(base10).

§Strings

Strings can be encoded in two ways: either

  • enclosed between [s and ] : string bracket contents
  • simple strings: enclosed between any of

# and whitespace
# and [
# and (
# and )

Note that the # character doesn’t terminate a simple string, so it can be part of one.

A blank can be expressed by
[s ]

An empty string can be expressed by

  • [s]
  • # on itself: followed by whitespace, [, (, ) or at the end of a script.

Eg.:

[sKunji Namparshespa] yields the string “Kunji Namparshespa”.
#Petrov yields the string “Petrov”.
#易經 yields the string “易經”, just like b16 o(#uni 6613 [n7D93]).

If any of their operands is a string, some numerical operators will perform string-related operations :

+ and +, Concatenation, converting any numeric operand to a string:

+, [sTotal: ] 353
yields
“Total: 353”

Other numerical operators having string operands, or other combinations of number and string operands, might throw errors. Please refer to the individual operators’ descriptions below.

String brackets support nesting, so

+ #!!! [s [s...]]
yields
“!!! [s…]”

A newline can be expressed by one of both newline constant operators:

  • c#n

E.g.:

Z#quiet 1 $#sum 500 w+,(#Total: c#n v#sum c#n)
will write “Total:” and “500” on two consecutive lines.

§Date and time.

§Date

Laconic offers several date-related named operations, the names of which begin with greg, as Laconic only handles dates in the Gregorian Calendar that started at Friday, October 15, 1582.

The o,#greg year month day named operation calculates a sequence number for any given date in the Gregorian era:

o,#greg 1582 10 15 yields 1
o,#greg 1582 10 18 yields 4
o,#greg 1970 1 1 yields 141418

These sequence numbers allow calculation of the number of days between dates.

Day-of-week: the o,#dow year month day named operation yields a number from 0 to 6 for Saturday to Friday:

o,#dow 1582 10 15 yields 6: Friday
o,#dow 1582 10 18 yields 2: Monday

(This numbering is consistent with Greek tradition: Monday is δευτέρα, the “second”, Tuesday is τρίτη, the “third”, etc.)

The o#dow sequenceNumber named operation also yields a weekday number from 0 to 6:

o#dow 1 yields 6: Friday
o#dow 4 yields 2: Monday

For other date-related operations, see the o#greg... operations in the Named operations list section.

§Time

Laconic handles time as a number of seconds since the start of the Unix Epoch at January 1, 1970, 00h00m00s UTC.

Laconic doesn’t know about time zones: all hour, minute or second values derived from this number of seconds express Universal Time.

The c#utc operation returns the number of seconds elapsed since the start of the Unix Epoch, as reported by your system.

Several other operations convert that number of seconds to a date, or hour, minute and seconds-in-minute values - see the o#time... operations in the Named operations list section.

§Comments

Comments are enclosed between [c and ].

They are completely ignored by Laconic: removed from expressions or scripts before their very evaluation.

Eg.:

$20 100[c Let's assign 100 to variable 20.]v20[c This entire expression should yield 100.]

Just like string brackets, comment brackets support nesting, so one can have a string bracket in a comment:

[c Just some more comment content: [sMystring]]

§Variables

Laconic’s variables live in a HashMap dictionary that maps variable identifiers to Laconic values.

Variable identifiers can be strings as well as numbers.

A variable can be assigned using the $ operator:

$#tau *2p assigns the value of twice Pi to a variable having identifier “tau”.

$0 *2p assigns the value of twice Pi to a variable having identifier 0 - a number.

Identifier 0 is not the same as identifier “0”:

$#0 *2p assigns the value of twice Pi to a variable having identifier “0” - a string.

One can even have spaces in variable identifiers:

$[sMax value] 200 assigns 200 to a variable having identifier “Max value”.

The value of a variable can be read by the v operator:

$
    #diapason
    440
$
    #halftone
    ^2 /1 12
*
    v#diapason
    ^
        v#halftone
        2

yields the frequency of tone B4 according to ISO 16.

One can supply a default value when reading a variable, in case it’s uninitialized or holds the empty value: the v, operator takes two operands:

  • a variable identifier
  • a default value

When the variable referred to by the first operand is empty or uninitialized, the value of the second operand is assigned to it and returned.

E.g.:

[cVariable 5 is uninitialized] v,5 1 yields 1
[cVariable 5 is uninitialized] v,5 1 v5 yields 1
$5 240 v,5 1 yields 240

Variables can also hold pointers to other variables:

$#pointer 5 vv#pointer returns the value of variable having identifier 5.

Variable identifiers can also be calculated:

$#month 1 $+,#daysInMonth v#month 31 assigns the value 31 to variable having identifier “daysInMonth1”.

Expressions like these, together with variables holding a pointer like v#month, allow for array-like constructs.

Assigning the empty value ( or an empty operation outcome) to a variable, removes it from the variables collection.

§Reading & assigning a variable

There is a shorthand way of making an operator assign its return value to a variable that’s one of its operands: replace the v operator with the : read-and-assign operator:

Say you have a counter variable “counter” that needs to be increased every time something specific occurs.

Your script initializes that counter variable using a
$#counter 0
operation. Next, your script executes a loop. In every iteration of that loop, a condition is tested and, if true, your counter needs to be increased by 1.

Your script could perform that increase using the below operation:

$
    #counter
    +
        v #counter
        1

This would work all right, but there’s a shorter way:

+
    : #counter
    1

The : read-and-assign operator has one operand and does two things:

  • it reads the value of the variable designed by its operand;
  • it tells the parent operator to assign its outcome to that variable.

So, if variable “counter” holds value 4, operation

+ :#counter 1

where + is the parent operator of :, would process as follows:

  • :#counter returns 4
  • + 4 1 returns 5
  • this value 5 is assigned to variable “counter”
  • and the entire operation returns 5 again, which may or may not be used by encapsulating parent operators.

Note that the two below operations are identical, as the + operator is commutative:

+ :#counter 1
+ 1 :#counter

It’s even possible to assign a calculation outcome to more than one variable at once:

    $0 21
    $1 5
    * :0 :1

This script would result in both variables 0 and 1 having the value 105.

There is also a variant :, operator, that takes two arguments

  • the identifier of the variable to be read and assigned;
  • a default value if this variable is empty. E.g.:
  $
    #count
    €
  +
    :,
      #count
      0
    1
  v#count

yields 1 - if the non-variant version of the : operator would have been used, the + operator would have raised an error about an empty operand.

One might wonder if, using this : operator, the $ (assignment) operator becomes redundant.

Functionally, one could indeed replace all
$ #myVar #value
expressions with
; :#myVar #value
as the ; (combination) operator would read, but not use, the value of variable “myVar”, and then assign the value of its second operand to it due to the : operator used to read variable “myVar”.

However, technically, the $ (assignment) operator entails less processing, as evidenced by the expression trees:

$ laconic -aq '$ #myVar 8'

Tree after execution :
$
│	myVar
│	8
└─> 8

Use of the ;: way involves two instead of one operators, so more processing:

$ laconic -aq '; :#myVar 8'

Tree after execution :
;
│	:
│	│	myVar
│	└─> (no_value)
│	8
└─> 8

§Serial assignment of variables

It is possible to assign a series of values to a series of variables using one $ operation:

$(100 30 20 10) assigns
30 to variable 100
20 to variable 101
10 to variable 102.

When the given variable identifier is a string, the assigned variables will have as identifier the original string + a counter starting from 0:

$(rate 30 20 10) assigns
30 to variable “rate0”
20 to variable “rate1”
10 to variable “rate2”.

§Stack

Laconic’s execution environment provides one Last-In-First-Out stack, the main use of which is to pass values to routines that don’t share the calling operator’s memory.

One can push values onto the stack using the K operator:

K 40 pushes the value 40 onto the stack.

One can pop and read items from the stack using the k operator:

K40 k yields 40.

For easier passing arguments to routines, one can push operands in reverse order on the stack using the K, operator:

K,(9 7 5 3) >(kkkk) yields 1 (the larger numbers were on top of the stack and popped first)

The k, operator returns the height of the stack: its number of values:

K(#A #B 25) k, yields 3.

For clearing the stack, one can use the K,, operator, which would do the same as:

Wk,k : While (W) the stack has items (k,), pop the topmost item (k).

§Routines

In order to reuse Laconic script code, it is possible to write routines that can be called repeatedly by other code.

Laconic offers two kinds of routines:

  • routines that share - i.e., can read and write - the variables of the calling operator’s environment;
  • routines that have an isolated set of variables and can’t access the ones of the calling environment.

These two kinds of routines are declared using two different operator variants:

  • R declares routines that will run in an isolated environment;
  • R, declares routines that will share the caller’s variables.

Note: routines will always share the stack and routines known to their caller, and the caller will always have access to stack items pushed by a called routine or to other routines declared by a called routine.

In other words, the stack and collection of routines are global.

The R and R, operators expect two operands:

  • the routine’s name or identifier, which can be a number or a string;
  • the operation to be performed.

If more than two arguments are given (using parentheses), all operands after the routine name will be executed when the routine is run.

Note: another way to have more operators executed is by combining them by the appropriate number of preceding ; operators - see this operator’s description.

E.g., the below script declares a routine that calculates an average of the stack items, ignoring non-numeric values:

R(
	#average
	$#count k,
	$#total 0
	W
		k,
		;
			$#next k
			?
				=1 tv#next
				+:#total v#next 
				-:#count 1
	?
		=0 v#count
		0
		/v#total v#count
)

A routine can be called using the X (eXecute) operator. This operator expects only one operand: the routine’s name.

Arguments can be passed to the routine by using the K operator to push them on the stack. E.g., given the above “average” routine being declared,

K(1 2 3 2) X#average yields 2.

Alternatively, if more operands are given to the X operator, these are pushed on the stack for the routine to pop them (or not) as arguments. So the “average” routine can also be called as follows:

X(#average 1 2 3 2)

Note: as the stack is a last-in-first-out stack, stack items will be read by a routine in the reverse order they were pushed. One can avoid this using the K, or X, operators instead.

As a routine’s name is just another expression value, it’s possible to use variables and stack items as pointers to routines.

A routine’s code has access to its own name or identifier using the c#rtn operation. When this operation is used outside of any routine, it returns “main”.

Routines can be stored in script files, which are UTF-8 text files containing Laconic code. These routintes can be imported to a main script in two ways:

  • either by preceding calling code in the script by an Evaluate-after-reading operation:
    Er,#myRoutines.lac X[sRoutine from file]
  • or else by including the script file by preceding the main code by an -i parameter when running the command-line Laconic interpreter:
    ...$ laconic -i myRoutines.lac 'X[sRoutine from file]'

§Unit tests on routines

Routines are mainly written in order to reuse code. So they tend to contain important code you want to rely on.

A popular way to ensure you really can rely on your code is submitting it to unit tests: little test programs that use a routine and compare its outcome to an expected result.

These unit tests should test both normal, common cases as well as boundary cases (lowest and highest input values, erroneous input, negative input where only positive numbers are expected, etc.)

It is entirely possible to use Laconic scripts to write unit tests on routines in Laconic scripts:

In scripts/unitTestUtils.lac, you’ll find two routines that will help you in adding unit tests to your “production” routines:

  • assert_eq
  • executeUnitTests

An example of their usage can be found in scripts/dowName.lac, which contains

  • routine dowName, which converts a weekday number to the English name of the day of the week;
  • several individual unit test routines;
  • routine dowNameUnitTests, which executes all the unit tests and reports on their outcome.

In order to use the dowName routine, include it using the -i parameter:

... $ laconic -i /home/user/path_to_laconic_scripts/dowName.lac 'X(#downName 5)'

In order to execute the unit tests on routine dowName, be sure to cd to the directory where unitTestUtils.lac resides, and execute:

... $ cd path_where_unitTestUtils.lac_is_stored
... $ laconic -i /home/user/path_to_other_laconic_scripts/dowName.lac 'X#downNameUnitTests'

§Arithmetic operators

OperatorDescriptionRequired
operands
Excess
operands
Returns
~unary
minus
1ignorednumber
+addition
if no string
operands
2addednumber
+concatenation
if at least 1
string
operand
2addedstring;
numbers are
formatted
as per
o,#fmt command
+,concatenation
if at least 1
string
operand
2addedstring;
numbers are
truncated
to integers
-subtraction2subtractednumber
*multiplication2multipliednumber
/division2new
division
number
/,integer
division;
remainder
is pushed
to stack
2ignorednumber:
quotient
%modulo2modulo
from
modulo…
number
^exponen-
tiation
2new
exponen-
tiation
number
iinteger1ignorednumber
truncated
towards
zero
i,ceiling1ignorednumber
filled
towards
nearest
integer
away from
zero
@rounding1ignorednumber
truncated
or filled
towards
nearest
integer
aabs1ignorednumber
absolute
value
llogarithm
from 2nd
operand
in base
1st operand
2ignorednumber
ssign1tested also1 if all
operands are
positive,
-1 if all are
negative,
0 if mixed.
binput
number
base
1ignoredthe new
base
b,output
number
base
1ignoredthe new
base
nparse
number
from
string
1ignorednumber

~ Unary minus

required operands: 1
excess operands: ignored
returns: the negative value of its first operand

e.g:

+1~4 yields -3
-1~4 yields 5

+ Addition or concatenation

required operands: 2
excess operands: added also
returns: if all operands are numeric, the sum of these operands.

If at least one operand is a string, the concatenation of all operands, converted to strings.
Numbers will be formatted according to the formatting settings requested using the o,#fmt operator or default settings.

e.g:

+5 6 yields 11
$0 50 +([sPrice: ] v0 [s EUR]) yields “Price: 50.000000 EUR”

+, Addition or concatenation with different formatting

required operands: 2
excess operands: added also
returns: if all operands are numeric, the sum of these operands (same as +).

If at least one operand is a string, the concatenation of all operands, converted to strings.
Numbers will be truncated towards zero.

e.g:

+,5 6 yields 11
$0 50 +,([sPrice: ] v0 [s EUR]) yields “Price: 50 EUR”

- Subtraction

required operands: 2
excess operands: subtracted also
returns: the difference of the first operand and the sum of the subsequent ones.

e.g:

-80 20 yields 60
-(80 20 10) yields 20

* Multiplication

required operands: 2
excess operands: multiplied also
returns: the product of all its operands

e.g:

*1.1 5 yields 5.5
*(1.1 5 2) yields 11

/ Division

required operands: 2
excess operands: further division
returns: the quotient of the first operand divided by the product of the subsequent ones

e.g:

/100 4 yields 25
/(100 4 5) yields 5

/, Integer division and remainder

required operands: 2
excess operands: ignored
returns: the integer quotient of the first operand divided by the second one; the remainder is put on the stack.

e.g:

/,100 7 yields 14
/(100 7 2) yields 14 also
$#quotient /,100 7 k yields 2 : the division’s remainder, and assigns 14 to variable “quotient”.

(The k operator pops an item from the stack.)

% Modulo (remainder)

required operands: 2
excess operands: ignored
returns: the remainder of the integer division of the first operand by the second operand

e.g:

%7 3 yields 1
%7.1 3.1 yields 0.9

^ Exponent (power)

required operands: 2
excess operands: further exponentiation
returns: the first operand raised to the power of the next operands

e.g:

^2 3 yields 8
^(2 3 /1 2) yields 2.828426, just like ^^2 3 /1 2

Note: a non-integer exponent is not supported on negative numbers:
^~10 .5 yields an error.

l Logarithm (lower case L)

required operands: 2
excess operands: ignored
returns: the logarithm of the second operand in a base given by the first operand

e.g:

l10 1000 yields 3
le 10 yields the natural logarithm of 10, having base e

Note: zero or negative operands are not supported

i Integer value

required operands: 1
excess operands: ignored
returns: the first operand truncated towards zero

e.g:

i4.7 yields 4
i~3.8 yields -3
i15 yields 15
i~27 yields -27

i, Ceiling

required operands: 1
excess operands: ignored
returns: the first operand inflated away from zero so as to be integer

e.g:

i,4.7 yields 5
i,~3.8 yields -4
i,15 yields 15
i,~27 yields -27

@ Round

required operands: 1
excess operands: ignored
returns: the first operand truncated or filled towards nearest integer
If a number is halfway two integers, rounds away from 0.

e.g:

@ 13.2 yields 13
@ 13.7 yields 14
@ 13.5 yields 14
@ ~13.2 yields -13
@ ~13.6 yields -14
@ ~13.5 yields -14

a Absolute value

required operands: 1
excess operands: ignored
returns: the absolute value of its first operand

e.g:

a~3 yields 3
a15.9 yields 15.9

s Sign

required operands: 1
excess operands: considered also
returns:

1 if all operands are > 0,
-1 if all are < 0,
otherwise 0

e.g:

s~14 yields -1.
s300 yields 1.
s(3 5 20) yields 1.
s(~3 5) yields 0.
s(~3 +10~20) yields -1.

b Input number base

required operands: 1
excess operands: ignored
returns: the new base

The b operator sets the number base for subsequent numbers in script or read from standard input or files.

As base 1 only allows to express 0, it’s useless and raises an error,
so only bases from 2 up to any large integer are supported.

If the specified base has a fractal part, it’s truncated.

Caution: in order to return to base 10 after a first b command,
the 10 has to be expressed in the previously established base.

e.g:

b8 [c Using octal numbersfor input] b12 [c using base ten again, as "12" is ten in base 8.]

b2 +101 11 yields 8 and really prints “8.000000”, as we only changed the input base, not the output base.

b, Output number base for subsequent output

required operands: 1
excess operands: ignored
returns: the new base

The b, operator sets the number base for output to standard output or files.

In order to avoid confusion, when changing both the input and output base, it’s less confusing to change the output base first: e.g.:

b16 b,16 first sets the input base to 16 and next the output base to … 22(base10)!
b,16 b16 first sets the output base to 16 and next the input base to 16 also.

e.g:

n Parses string to number

required operands: 1
excess operands: ignored
returns: the parsed numeric value or error

If the first operand is a string, the n operator converts it to a a number, if possible.
If the first operand is empty, 0 will be returned.
If the first operand is already a number, it will be returned as is.
If the first operand is an error value, that error will be returned or thrown depending on Z#ign.

The n operator takes the current input number base into account.

If the string to be parsed is to contain a negative number, it can be preceded by either - or ~.

e.g:

n[s28] yields 28.
n#28 yields 28.
n28 yields 28 (easy, the first operand is already a number)
n#-28 yields -28.
n#~28 yields -28.
b16 n#2E yields 46(base10).
b60 n[s2 1 0] yields 7260(base10).
b60 n[s210] yields an error: NumberParsingFailure(“Digit value too high for base of input number”).
n€ yields 0

§Trigonometric operators

OperatorDescriptionRequired
operands
Excess
operands
ReturnsExampleExample
yields
pπ or Pi0ignored3.1415 etc.Cp-1 (= cosine(π)
°degrees
from
radians
1ignorednumber°p180
°,radians
from
degrees
1ignorednumber°,1803.1415 etc.
Ssine
from
radians
1ignorednumberS/p21 (=sine(π/2)
S,arcsine
in
radians
1ignorednumberS,11.570795 etc.
(= π/2)
S,,hyperbolic
sine
1ignorednumberS,,11.7520 etc.
S,,,inverse
hyperbolic
sine
1ignorednumberS,,,10.88137 etc.
Ccosine
from
radians
1ignorednumberC01
C,arccosine
in
radians
1ignorednumberC,01.570795 etc.
(= π/2)
C,,hyperbolic
cosine
1ignorednumberC,,11.54308 etc.
C,,,inverse
hyperbolic
cosine
1ignorednumberC,,,10
Ttangent
from
radians
1ignorednumberT°,451
T,arctangent
in
radians
1ignorednumber°T,145
T,,hyperbolic
tangent
1ignorednumberT,,10.761594 etc.
T,,,inverse
hyperbolic
tangent
1ignorednumberT,,,.50.549306 etc.
Afour quadrant
arctangent
in radians
of 1st arg (y)
and 2nd arg (x)
2ignorednumber°A 4 ~4135

§Logical operators

Laconic has no boolean value type, however

as return value,

  • 0 is false and
  • 1 is true.

As input value, falsy are:

  • 0,
  • empty string,
  • empty value and
  • any error.

All other values are truthy:

  • any non-zero number, negative ones included,
  • any non-empty string.
OperatorDescriptionExpected
operands
Excess
operands
ReturnsExampleExample
yields
!not1are
checked
also
1 if all
operands
are falsy;
else 0
!0
!5
!(0 € -4 4 #)
!(5 0 1)
1
0
1
0
&and2are
checked
also
1 if all
operands
are truthy;
else 0
&29 #Hello
&(45 1 ~7)
&86 0
1
1
0
|or2are
checked
also
1 if 1 or more
operands
are truthy;
else 0
|1 0
|0 0
|(0 #Ghent €)
1
0
1
xxor2are
checked
also
1 if only 1
operand
is truthy;
else 0
x(0 1 0)
x0 €
x74 ~12
1
0
1

§Comparison operators

For comparison’s sake, all possible Laconic values live in one large continuum:
empty < any number < any string < any error

The < and > operators can be combined with the negation operator:
!< and !>
so as to obtain greater-or-equal or less-or-equal expressions with 2 operands.

The = (equals) operator takes a precision margin into account in order to mitigate the underlying f64 number type’s precision limitations.

This precision margin is 0.000_000_01 by default, but can be set and changed again using the Z#prec operation. E.g.:

Z#prec .1 = .11 .12 yields 1: true.

Note: = is always a comparison operator (“equals”), and never assigns. For assignments, see the $ and : operators.

OperatorDescriptionRequired
operands
Excess
operands
ReturnsExampleExample
yields
=equals2are
compared
also
1 if all
operands
are equal;
else 0
=27 ^3 3
=#Καλημέρα [sΚαλημέρα]
=(256 ^2 8 ^16 2)
=€ 0
$0T°,45 =1v0
1
1
1
0
1
<is less2are
compared
also
1 if all
operands
are an
increasing
series;
else 0
Z#ign 1 <(€ ~33 0 [sA] [sa] /5 0)
<0 €
1
0
>is greater2are
compared
also
1 if all
operands
are a
decreasing
series;
else 0
>(#Woof! 38 2)
> +12.1 .3 13
>#a #A
>[sZorro y Perro] #Zorro
1
0
1
1
mminimum2usedvalue of
the smallest
operand
m Sp Cp
m(38 77 3)
m(#z #York 8)
-1
3
8
Mmaximum2usedvalue of
the greatest
operand
M Sp Cp
M(45 ~3 1_252)
M## ###
0
1252
“##”

Note: ### yields “##” as the first # is the simple string prefix.

§Constant operators

OperatorDescriptionRequired
operands
Excess
operands
ReturnsExampleExample
yields
ppi0ignored3.141592 etc.Sp-1 (=sine(π))
eEuler’s
number
0ignored2.718281 etc.
newline
character
=
c#n
0ignored“\n”, U+000A+,(72 ¶ 99)“72
99”
empty
value
=
c#empty
0ignoredempty value?=v#crit 0 B1 €If
variable
“crit” is 0,
break
the loop,
else
do nothing.
cnamed
constant:
1ignoredseveral,
see below
c#goldgolden
ratio,
calculated as
(1 + sqrt(5))/2
0ignored1.618033 etc.$#height *v#width c#goldvariable
“height”
has var. “width”
x golden
ratio
c#cogoldconjugate
of golden
ratio,
calculated as
(1 - sqrt(5))/2
0ignored-0.618033 etc.
c#nnewline
character
=
0ignored“\n”, U+000A+($Decem- c#n #ber)“Decem-
ber”
c#emptyempty
value
=
0ignoredempty value
c#rtnthe running
routine’s
name
0ignored“main” if not
in routine;
else the running
routine’s
name
R(
#percent
$0k
$1k
?
!v1
U
+
c#rtn
[s: zero div.]
/*v0 100 v1
)
The “percent”
routine
includes
its own name
in an error
message.
c#utcUTC
system
time
0ignoredNumber
of seconds
elapsed
since
1970-1-1
00:00:00
c#utc1755978527
.526352
OperatorDescriptionRequired
operands
Excess
operands
ReturnsExampleExample
yields
$assignment
of 2nd
operand
to variable
having 1st
operand
as name
(number or text)
2assigned to
subsequent
variables
assigned
value
$4 8 v4
$#count 0 v#count

$(#tariff 3 10 25)
+,(v#tariff0 #; v#tariff2)
8
0


3;25
vvalue of
variable
having 1st
operand
as name
(number or text)
1ignoredany type of
expression
value
(including
empty and
error)
See above
v,value of
variable
having 1st
operand
as name
(number or text);
if empty,
assigns and
second operand
2ignoredany type of
expression
value
(including
empty and
error)
See above
:like v
but has result
of parent operator
assigned to that
variable
1ignoredlike v$#count 0
+:#count 1
v#count


1
:,like :
but assigns
and returns
the second
operand if the
variable
is empty
2ignoredlike :$#count €
+:,#count 100 1
v#count


101
OperatorDescriptionRequired
operands
Excess
operands
ReturnsExampleExample
yields
Kpush to
LIFO
stack
1pushed
also
value of
last
operand
K155 K30 k
K(155 30) k
30
30
K,pushes its
operands
to LIFO
stack
in reverse
order
1pushed
also
value of
last
operand
K,(155 30) k
K,155 K,30 k
155
30
K,,clears
the
stack
0ignorednumber
of stack
items
cleared
K,, k
(empty
value)
kpop from
LIFO
stack
0ignoredvalue of
top stack
item;
empty
if none
K(#A 33) k k“A”
k,stack
height
0ignorednumber
of stack
items
K,, K(10 20 30) k,3
OperatorDescriptionRequired
operands
Excess
operands
ReturnsExampleExample
yields
Zsetting:
assign value
of 2nd
operand
to setting
designated
by 1st
operand
2ignoredsetting
value
(2nd operand)
Z#preccomparison
precision
setting
(Default=
.000_000_01)
1ignoredsetting
value
(2nd operand)
Z
#prec
.01
=
.001
.002





1
Z#loopsmaximum
number
of loop
iterations
(Default=
10,000)
1ignoredsetting
value
(2nd operand)
Z#loops 1_000_000
Z#ignif not 0 ignore
errors,
else stop
script
execution
on errors
1ignoredsetting
value
(2nd operand)
Z#ign 1

§Named operations list

The below named operations have been implemented:

OperatorDescriptionRequired
operands,
including
operation
name
Excess
operands
ReturnsExamplesExample
yields
o#rrounding
(Same as @)
2ignorednumber
truncated
or filled
towards
nearest
integer
o#r 2.1
o#r 2.5
~2.5
2
3
-3
o#versionversion1A 2nd
operand
chooses the
version part
(1-3).
0 chooses
entire version
again.
The
Laconic
interpreter’s
version
o#version 0
o#version 1
o#version 2
o#version 3
“1.0.2”
1
0
2
o#fibFibonacci
number
2ignoredFibonacci
number
chosen
by index (2nd
operand)
o#fib 0
o#fib 1
o#fib 2
o#fib 3
o#fib 4
o#fib 5
0
1
1
2
3
5
o#uniUnicode
character
2converted
also
string
having
Unicode
characters
o#uni 65
o(#uni 65 66 67)
o#uni 936
b16 o#uni [n3A8]
b16 o(#uni [n3A8]
[n3C5][n3C7]
[n3AE])
“A”
“ABC”
“Ψ”
“Ψ”


“Ψυχή”
o#ucvUnicode
value
23rd operand
is used as
character
position.
If absent,
0 is used.
Unicode
code point of
character
o#ucv #Dinant
O#ucv #Dinant 1
68
105
o#lenlength in
characters
(not in
bytes)
2length is
added
total length
of operands
o#len #Dinant
O#len #Dinant #Mons
o#len #易經
6
10
2
o#lowerlower
case
2ignoredlower case
of 2nd
operand
o#lower [sThe Hague]“the hague”
o#upperupper
case
2ignoredupper case
of 2nd
operand
o#upper [sThe Hague]“THE HAGUE”
o#properproper
case
2ignoredproper case
of 2nd
operand
o#proper [sADDIS ABEBA]“Addis Abeba”
o,#findfind 3rd operand
as part of
2nd operand
starting from
position passed
as 4th operand
34th operand
is start position.
If missing,
0 is used.
If found,
start position
of found
substring.
Else, empty.
see below
o,#subsubstring3;
see
below
4th operand
will be used
as length
the substringsee below
O,,#replstring
replacement
4 or 7;
see
below
ignoredresulting
string
see below
o,#splitsplit string4;
see
below
ignorednumber of
segments
see below
o,#fmtset number
format
2 or 4;
see
below
ignoredemptysee below
o#leapleap
year
2ignored1 if number
in 2st operand
is a leap year,
else 0
o#leap 2004
o#leap 2000
o#leap 1900
o#leap 2025
1
1
0
0
o,#dowday of
week
4 or 2:
#dow,
year,
month
and day
–or–
#dow,
sequence
number.
ignored0 for
saturday,
1-6 for
following
days
o,#dow 2025 1 1
o(#dow 2025 1 4)
o#dow 161517
4 (wed.)
0 (sat.)
4 (wed.)
o,#gregGregorian
day’s
sequence
number
4:
#greg,
year,
month
and day.
ignored1 for
October
15, 1582,
etc.
o,#greg 2025 7 1161698
o#gregyyear from
Gregorian
day’s
sequence
number
2:
#gregy
and seq.nr.
ignoredyearo#gregy 1616982025
o#gregmmonth from
Gregorian
day’s
sequence
number
2:
#gregm
and seq.nr.
ignoredmonth (1-12)o#gregm 1616987
o#gregdday from
Gregorian
day’s
sequence
number
2:
#gregd
and seq.nr.
ignoredday (1-31)o#gregd 1616981
o#gregtdate text
from
Gregorian
day’s
sequence
number
2:
#gregt
and seq.nr.
A 3rd.
operand
is used as
separator
A YYYYsMMsDD
string where
s is a
separator,
if any
o#gregt 161698
O#gregt 161698 #-
 
20250701TUE
2025-07-01-TUE
o#timedtime
to
Gregorian
day
sequence
number
2:
#timed
and UTC
seconds
ignoreddate
sequence
number
like the
ones
returned
by o,greg
o#timed c#utc161751
o#timehtime
to
hours in
day
2:
#timeh
and UTC
seconds
ignoredThe hour
(0-23)
in
Universal
Time
o#timeh c#utc20
o#timemtime
to
minutes in
hour
2:
#timem
and UTC
seconds
ignoredThe minutes
(0-59)
in
Universal
Time
o#timem c#utc9
o#timestime
to
seconds in
minute
2:
#times
and UTC
seconds
ignoredThe seconds
(0-59)
in
Universal
Time
o#times c#utc26
o#timeftime
to
seconds
fraction in
second
2:
#timef
and UTC
seconds
ignoredThe fraction
of the second
(0.nnn…)
in
Universal
Time
o#timef c#utc0.682822
o#timettime
to
text
2:
#timet
and UTC
seconds
A third
operand
is used
as a
separator
A “time-
stamp”
text
in
Universal
Time
o#timet c#utc
O#timet c#utc #:

o#fmt 2
O#timet
c#utc [s ]
201927.139002UTC
20:20:15.635714:UTC



20 22 59.23 UTC

 

o#find Find substring in a string

required operands: 3

  • operation name: #find
  • source string
  • substring to find in source string

excess operands: a 4th operand is used as zero-based starting position; if absent, 0 is assumed
returns: if found, the 0-based start position of the substring, otherwise the empty value ()

e.g:

O#find #Samovar #a yields 1
o,#find #Samovar #a 3 yields 5
o(#find #Samovar #a 3) yields 5
O#find [sNo hay remedio] #mejor yields €
tO#find [sNo hay remedio] #mejor yields 0 (= the type id of )
tO#find [sNo hay remedio] #hay yields 1
$#pos O#find #Brussels #s ?tv#pos [sHas s] [sHas no s] yields “Has s”

o,#sub Substring

required operands: 3

  • #sub: the operator’s name;
  • the source string;
  • the start position of the substring;
  • (optional 4th) the length of the substring. If ommitted, the substring will be taken from the start position until the end of the source string.

excess operands after 4th: ignored
returns: the substring

E.g.:

O#sub [sLa Roche-en-Ardenne] 12 yields “Ardenne”
o,#sub [sLa Roche-en-Ardenne] 3 5 yields “Roche”

If the source string argument is not a string, but a number, then that number is first converted to a string according to the current o#fmt settings.

o,#repl and O,,#repl

required operands: 4

  • #repl : the operator’s name;
  • the source string;
  • the substring to be replaced;
  • the string to replace it with

excess operands: 3 more are used:

  • the number or name of the position variable;
  • the number or name of the sequence variable;
  • the number or name of the routine that can use both variables to decide if a replacement should happen.

returns: the source string with the requested replacements applied.

In other words,
O,,#repl sourceString substring replacement varPos varSeq conditionRoutineName

replaces an occurrence of substring in sourceString with replacement if the condition evaluates to a value that is truthy (not 0 (zero), empty string or € (the Empty value)).

The condition has to be passed as the name of a routine defined previously using the R, operator - the variant that declares routines that can accesses the calling code’s memory.

The condition routine can make use of two variables, the identifiers (string or number) of which are passed in the varPos and varSeq operands.

(The names varSeq and varPos are examples. You can choose any name or number.)

If the condition routine hasn’t been defined before, no replacement is performed.

varPos :
When the condition routine is called, varPos will hold the (zero-based) start of the occurrence of substring found in the original string.

varSeq:
When the condition routine is called, varSeq will hold the (zero-based) sequence number of the occurrence.

If the varPos, varNum and condition operands are ommitted, all occurrences of substring will be replaced.

The operator returns a string in which the requested replacements have been performed.

E.g.

$#replaced o,#repl #philadelphia #ph #f<br/> puts “filadelfia” in variable #replaced.

R,#replCond =v#seq 0

$#replaced O,,#repl #philadelphia #ph #f #pos #seq #replCond
puts “filadelphia” in variable #replaced.

R,#replCond >v#seq 0
$#replaced O,,#repl #philadelphia #ph #f #pos #seq #replCond
puts “philadelfia” in variable #replaced.

R,#replCond >v#seq 5
$#replaced O,,#repl #philadelphia #ph #f #pos #seq #replCond
puts “philadelphia” in variable #replaced.

R,#replCond =v#pos 0
$#replaced O,,#repl #philadelphia #ph #f #pos #seq #replCond
puts “filadelphia” in variable #replaced.

R,#replCond >v#pos 4
$#replaced O,,#repl #philadelphia #ph #f #pos #seq #replCond
puts “philadelfia” in variable #replaced.

R,#replCond 1
$#replaced O,,#repl #philadelphia #ph #f #pos $seq #replCond
puts “filadelfia” in variable #replaced.

R,#replCond 0
$#replaced O,,#repl #philadelphia #ph #f #pos #seq #replCond
puts “philadelphia” in variable #replaced.

R,#replCond =v#pos 0
$#myCity #philadelphia
O,,#repl :#myCity #p #P #pos #seq #replCond
puts “Philadelphia” in variable #myCity (due to the : operator).

(Note that all string operations are case-sensitive.)

Note: as the R, operator returns the name of the routine itself, it can be used “in place” as the routine-name operand:

    $
        #replaced
        O,,
            #repl
            #philadelphia
            #ph
            #f
            #pos
            #seq
            R,
                #replCond
                =v#pos 0

o,#split Split a string

required operands: 4
excess operands: ignored
returns: the number of segments found

The o,#split operator takes 4 arguments:

  • #split : the operation’s name;
  • the source string;
  • the segment separator, like #;
  • the prefix of the names of the variables the split fragments will be assigned to.

E.g.:

    $
        #nrCities
        o,
            #split
            [sBrugge,Arlon,Liège]
            #,
            #city
    F
        0
        v
            #nrCities
        1
        -
            #count
            1
        w
            +
                v
                    +,
                        #city
                        v#count
                ¶

will first store the three city names in variables city0, city1 and city2 and next write the three city names in the original string on separate lines to standard output.

If the separator is an empty string (# or [s]), every character of the source string will be copied to a variable.

Note that the numbers after the varNamePrefix will have no leading zeroes, fractal part or interpunction.

In combination with the r, operator, this allows for awk-like file processing as well as reading of CSV files.

o,#fmt Set number format for output

required operands: 2

  • #fmt : the operation’s name;
  • the number of fractal digits required

excess operands: 2 more operands can be given:

  • the fractal separator (default = .);
  • the thousands grouping separator (default: no grouping).

returns: the empty value.

This number format is only used for

  • the final output of a script,
  • the output by the w operator
  • or the + (concatenation) operator.

The +, (concatenation) operator variant does not use the settings established by an o,#fmt operation or its defaults!

Default is o,#fmt 6 #. #

E.g.:

o,#fmt 2 #, #. requests numbers to be output in European continental style and two fractal digits after the comma.
o(#fmt 2 #, #.) does the same
o#fmt 3 requests output of numbers with 3 fractal digits, but no change of fractals or thousands separators.

OperatorDescriptionRequired
operands
Excess
operands
ReturnsExampleExample
yields
;combines
expressions
2usedvalue of
last one
;158 28
;;+52 8 °2.41 r52.1
;(#A #B #C)
28
52
“C”
?if2:
1. condition
2. operator
executed
if true
3. operator
executed
if false
ignoredvalue of
executed
operator
see below
?,try
operand 1;
if error
return
operand 2
2if a 3rd
operand
is given,
it’s returned
when no error.
see prev.
columns
see below
Vvalue of
1st operand
of last ?,
operator
0ignoredError value
if operand1
failed, else
any value
see ?,
below
Wwhile2:
1. condition
2. operator
to be
executed
executed
also
value of
last
executed
operator
see below
Ffor5:
1. start count
2. end count
3. increment
4. counter
variable
number
or name
5. operator
to be
executed
in iterations
executed
also
value of
last
executed
operator
see below
Bbreak
while
or for
loop
1:
1 = current
loop;
higher = nth-1
nesting
loop
ignored1st
operand
see below

; Operation combinator

required operands: 1, expected: 2
excess operands: used also
returns: the value of its last operand

The ; operator, or a sequence of them, can be used to group multiple operations where only one is expected, as in the second operand of the W (while) operator, the 2nd or 3rd operands of the ? (if) operator, and others.

e.g:

;$0 ~18 v0 groups 2 operations as 1 and yields -18
;;K7 K35 X#myRoutine groups 3 operations as 1
;(+:#count 1 +:#sum v#count ?=v#count 20 B1 €) groups 3 operations as 1

? if

required operands: 3
excess operands: ignored
returns: if the first operand has a truthy value, the second operand, otherwise the third.

Depending on the first operand, either the second or the third operand is executed, and the resulting value is returned, thus making this operator a real if-then-else construct.

e.g:

    $#crit 10
    ?
        >v#crit 5
        ;
            /:#crit 2
            +#divs 1
        €
    v#crit

assigns ($) 10 to variable “crit”; if (?) variable “crit” exceeds (>) 5, it’s divided by 2 and (;) variable “divs” is increased, otherwise, nothing (€) happens. Afterwards, the value of variable “crit” is returned.

switch ... case ... case ... default - like constructs can also be coded using the ? - operator:

    R(
        #thanksWord

        $#language k

        ?=v#language #en      [c Test ? and first operand: the condition]
        [sthank you]          [c Second operand: return value if condition is true]
        ?=v#language #fr      [c Third operand: a subsequent test]
        #merci
        ?=v#language #de
        [sdanke schön]
        ?=v#language #nl
        [sdank u]
        ?=v#language #el
        #ευχαριστώ
        [sthank you]          [c Default value]
    )

defines (R) a routine thanksWord that reads a language abbreviation string from the stack (k) and assigns ($) it to the language variable. Next, it tests (?) several known abbreviations; if a match is found, the return value of the ? operators is given as their second argument. Otherwise, a subsequent test is performed in the test’s third operand. The last test operator (?) has a default value in its third operand.

?, try … catch …

required operands: 2
excess operands: a 3rd operand will be used to produce the return value if operand 1 executes without errors.
returns: if the 1st operand executes without errors, its result value, else the value of the 2nd operand’s execution.

In other words:
?, opr1 oprIfError optional:oprIfOk

If operator opr1 is successful (no error),
then its outcome is returned,
else oprIfError’s outcome.

If a third operator oprIfOk is present, then that operator’s outcome is returned if opr1 was successful.

Both the operators oprIfOk and oprIfError can make use of a V operator, which will yield the result of the successful operation if called by oprIfOk, or the error value if called by oprIfError.

e.g:

    ?,
        +€ 7
        ;
            w
                +
                    +[sProblem: ] V
                    ¶
            0

As this script tries (?,) to add to an empty operand (+€ 7), an error is raised. Therefore, the entire ?, operation yields 0 as a fallback result after (;) having written (w) “Problem: EmptyOperand(‘+’)\n” to standard output.

    $0 200
    ?,(
        +v0 7
        ;
            w++[sProblem: ] V ¶
            0
        ;
            w+[sAddition succeeded] ¶
            V
    )

This script first assigns 200 to variable 0. Next it tries (?,) and adds (+) this variable’s value (v0) and 7. As this operation succeeds, and the ?,operator has a third operand, “Addition succeeded\n” is written to standard output (w) and (;) the 1st operand’s value (V) is returned - to wit, 207.

W while

required operands: 2
excess operands: executed also
returns: the value of the last executed operator

While operand 1 is truthy, operands 2 up to the last one are executed in loop iterations.

(“Truthy” means: not

  • empty ()
  • 0
  • empty string ([s])
  • error value.)

If more than one operation is needed in operand 2, use the ; combinator operator or enclose the W operator’s operands in parentheses.

Besides finding a falsy value when re-evaluation its 1st operand, the W operator will also stop repeating iterations if a B operation in the current or a nested loop requests a loop break.

If the maximum number of iterations set by the Z#loops operation have been run (default = 10,000), the W operator will raise an error - this is a protection against vicious loops.
e.g:

    $0 10
    $1 0
    W
        v0
        ;
            +:1v0
            -:0 1
    v1

yields the summation of 10, to wit 55.

Note: a summation can be calculated more easily using $#source 10 /* v#source + v#source 1 2

    R(
        #factorial

        $#fact k
        $#res 1
        W
            >v#fact 0
            ;
                *:#res v#fact
                -:#fact 1
        v#res
    )

    X(#factorial 6)

defines a routine “factorial” that calculates factorials, calls it while passing value 6 on the stack and returns 720.

    Z#loops 500
    $#count 0
    W
        1
        +:#count 1
    v#count

raises an error: as the 1st operand (1) of the W operator is always truthy, the maximal number of iteration set by the Z#loops 500 operation is looped through, and an extra iteration will be attempted. As a result, an error will be raised.

If you want your script to simply stop looping without raising an error when exceeding the maximum number of iterations, have it preceeded by a Z#ign 1 operation.

Breaking from a loop: see the B operator.

F for

required operands: 5
excess operands: executed also in iterations
returns: the value of its last executed operand

F startValue endValue increment counterVariable iteration_operands

The F operator takes 5 operands :

  • the starting value of the loop counter
  • the end value of the loop counter
  • the increment step: the number the loop counter is incremented with after every iteration
  • the variable identifier (number or string) that will hold the current loop counter
  • an operator executed in each iteration; if there are more than 5 operands, these are executed too in each iteration.

The F operator

  1. starts by initializing its loop counter with its 1st operand,
  2. executes its 5th and subsequent operands
  3. increases or decreases its loop counter with its 3rd operand and stores it in the variable indicated in the 4th operand
  4. checks if the loop counter is still in the range between its 1st and 2nd operand, inclusive,
  5. checks if no break has been set on itself using the B operator,
  6. checks if the maximal number of iterations has not been reached,
  7. if steps 4, 5 and 6 are positive, repeats from step 2.

If you want to execute more than one operator, either

  • use the ; operator
  • or use parentheses.

e.g:

    $
        0
        1
    F
        3
        11
        2
        1
        *
            :0
            v1
    v0

calculates and returns the product of all odd numbers between 3 and 11, inclusive.

The F operator can also count down: just have the 1st operand greater than the 2nd. The step operator should remain positive. E.g.:

    R(
        #revertChars

        $#orig k
        $#nrFrag o,#split v#orig # #fragment
        $#reverse #
        F
            - v#nrFrag 1    [c First index]
            0               [c Last index]
            1               [c Step value; positive]
            #index          [c Id of variable that will hold loop index]
            +               [c Concatenate character in var. "fragment"index
                                with var. "reverse"]
                :#reverse
                v
                    +,
                        #fragment
                        v#index
        v#reverse
    )

    X(#revertChars #ABCDE)  [c Call routine "revertChars")

This script creates a routine “revertChars” that reverses the characters of a string popped from the stack.
Next, the script calls it and yields the reverted string (as the call is the last operator).

Breaking from a loop: see the B operator.

Just like the W operator, the number of loops an F operator can iterate through is limited by the settings of the Z#loops operation, or its default: 10,000. If your script raises an error about the maximum number of iterations being exceeded, you might need to set the allowed number of iterations using this Z#loops operation. E.g.:

    $#iterationsNeeded 1_000_000
    Z#loops v#iterationsNeeded
    F
        1
        v#iterationsNeeded
        1
        #count
        [c Do something a million times.]

B break from a loop

required operands: 1
excess operands: ignored
returns: its first operand

The B operator breaks both W (while) and F (for) loops.

The B operator has been designed to both break a current loop as well as a parent loop of nested loops. Therefore, it takes 1 operand: the level of parent loops (if any) to break, where 1 is the current loop, 2 is the immediate parent loop, etc. E.g.:

    Z#loops 10
    Z#ign 1
    $#iters 0
    W
        1
        W
            1
            +:#iters 1
    v#iters

Has no break operations, and as the maximal number of iterations is set to 10, the script will execute 10 times 10 iterations of a nested loop, so variable “iters” will hold 100 at the end.
(Note that the script doesn’t raise any errors about the maximal number of iterations being exceeded due to the Z#ign 1 operation.)

    Z#loops 10
    Z#ign 1
    $#iters 0
    W
        1
        W
            1
            ;
                +:#iters 1
                B1
    v#iters

Has a break operation in its nested loop, so these nested loops will only execute once. Therefore, this script will execute 10 times 1 iteration of a nested loop, so variable “iters” will hold 10 at the end.

    Z#loops 10
    Z#ign 1
    $#iters 0
    W
        1
        W
            1
            ;
                +:#iters 1
                B2
    v#iters

Has a break operation in its nested loop that breaks the parent loop, so both the nested and parent loops will only execute once. Therefore, this script will execute 1 time 1 iteration of a nested loop, so variable “iters” will hold 1 at the end.

    Z#loops 10
    Z#ign 1
    $#iters 0
    W
        1
        W
            1
            ;(
                +:#iters 1
                B2
                B0
            )
    v#iters

Has a break operation in its nested loop that breaks the parent loop. However the next operation is B0, which desactivates any breaks, so this script repeats both the parent and nested loop 10 times anyway. Therefore, variable “iters” will hold 100 at the end.

For examples, see the section about routines above.

OperatorDescriptionRequired
operands
Excess
operands
Returns
Rdefinition
of routine
with new
scope
2:
1. routine
name or
number;
2. operator
to be
executed
included
in definition
name or
number
R,definition
of routine
using scope
of caller
2:
1. routine
name or
number;
2. operator
to be
executed
included
in definition
name or
number
Xexecute
routine
1:
routine
name or
number
pushed
on stack as
arguments
result of
routine’s
last
top-level
operator
X,execute
routine
1:
routine
name or
number
pushed
on stack as
arguments
in reverse
order
result of
routine’s
last
top-level
operator

§I/O operators

OperatorDescriptionRequired
operands
Excess
operands
ReturnsExampleExample
yields
rread
from
stdin
0ignoredif input
is a valid
number,
a number;
else a
string
see below
r,read
from
file
1:
file
path
ignoredif file
content
is a valid
number,
a number;
else a
string
see below
wwrite
to
stdout
1written
also
number
of bytes
written
see below
w,write
to
file,
overwriting
it
2ignorednr. of
characters
written
see below

r read input from standard input

required operands: 0
excess operands: ignored
returns: if the input is a valid number, a number, otherwise a string. For input of a negative number, both the standard minus sign - as Laconic’s unary minus operator ~ may be used.

e.g:

w[sEnter a number: ] r yields 45 if the user entered “45”.
w[sEnter a number: ] r yields -45 if the user entered “-45”.
w[sEnter a number: ] r yields -45 if the user entered “~45”.
w[sEnter a number: ] r yields “Ouagadougou” if the user entered “Ouagadougou”.
w[sEnter degrees: ]°,r yields the number input understood as degrees and converted to radians (°,).
w[sEnter your name: ] $#name r stores the name entered by the user to variable “name” - next, input check can be performed by the code.
w[sEnter a Laconic expression: ] Er evaluates and executes (E) a string entered (r) as Laconic code, so if the user entered “+(22 8 12 7)”, 49 would be returned.

r, read input from a file

required operands: 1: the file path
excess operands: ignored
returns: the file’s content as a string, even if that represents a valid number. To convert it to a number, use the E operator.

The file path given may be absolute or relative to the user shell’s current working directory.

As the file path is a string, it can be composed and calculated like any other string.

The file should consist entirely of valid UTF-8 characters. If not, an error is thrown.

e.g:

* 2 Er, [s/home/linda/constants/lemniscate.txt] would yield the double of the Lemniscate constant if file /home/linda/constants/lemniscate.txt would contain “.8346268”.

Er, #routines.txt would import all routines coded in file ./routines.txt using R or R, operators. These routines would be immediately callable from subsequent code.

Note: another way of having Laconic code in a file executed is issuing the -i parameter when using the laconic executable.

w write to standard output

required operands: 1
excess operands: written also
returns: the number of bytes written

Note that no newline or CR characters are inserted automatically. You can, however, easily add a newline to any text using the + and ¶ or c#n operators - see the example below.

If a script’s last operation is a w, the entire script’s return value will be the number of bytes that write operation did write. To avoid this often useless output, insert a Z#quiet 1 operation in the script, or else have the script end with an empty string: [s] or #.

e.g:

    Z#quiet 1
    w[sEnter radians: ]
    $#input r 
    ?
        =tv#input 1
        w([sDegrees: ] °v#input ¶)
        w([sHey, enter a number!] ¶)

This script

  • suppresses the output of the entire script’s last operation’s value (Z#quiet 1),
  • writes a prompt for an input of radians (w),
  • assigns the user’s input (r) to variable “input” ($),
  • tests (?) if the type (t) of that input (v#input) is numeric (1),
  • and if so, outputs (w) this input converted to degrees (°)
  • or else, displays an error message (w).

w, write to a file, overwriting it.

required operands: 2

  • file path
  • content to be written

excess operands: ignored
returns: the number of bytes written

The file path given may be absolute or relative to the user shell’s current working directory.

As the file path is a string, it can be composed and calculated like any other string.

If the write operation fails, the w, operator returns an error message containing the file system’s error message.

e.g:

    Z#quiet 1
    $#index 5
    $#currentData [sJust a file write test]
    ?,(
        w,
            +,
                #calcData
                v#index 
            v#currentData
        w(V ¶)
        w(q,V [s bytes written] ¶)
    )

This script tries (?,) and writes (w,) data in variable (v) “currentData” to a file having as path the concatenation (+,) of “calcData” and a number or string in variable “index”.
If the write operation fails, the error message (V) is written (w) to standard output.
If it succeeds, the number of bytes written (V) is written to standard output. (The q, operator converts that number to a string without decimal digits.)

§Other operators

OperatorDescriptionRequired
operands
Excess
operands
ReturnsExampleExample
yields
qquote:
convert
to string.
Same as
+# …
1ignoredstring;
numbers are
formatted
according to
o,#fmt settings
q21
q[sA string]
q€
q/1 0
“21.000000”
“A string”
“”
“DivideByZero(‘/’)”
q,quote:
convert
to string.
Same as
+,# …
1ignoredstring;
fractal parts
of numbers are
truncated
towards zero
q,21
q,[sA string]
q,€
q,/1 0
“21”
“A string”
“”
“DivideByZero(‘/’)”
ttype1ignored0 for empty,
1 for number,
2 for text,
90 for error.
t€
t~55
t+,[sRoom ] 24
t/1 0
0
1
2
90
Nnumber
of operands
of preceding
same-level
operator.
If that was
a loop (W or F),
iterations
executed.
0ignorednumber*56.77 21 N

$
10
;
F1 5 1 0 €
N
v10
2






5
Eevaluates
and
executes
the expression
in 1st
operand
(should
be string)
1ignoredevaluation
result
E[s -70 8]

E[sR#double *2 k]
X(#double 11)
62


22
Uuser-
coded
error
1:
error
message
ignorederrorU[sInput
should be
a number!]
“UserDefinedError(
"Input should be a number!")”

Modules§

input
The Laconic language offers a r (read) operator, that’s meant to read from standard input.
output
The Laconic language offers a w (write) operator, that’s meant to write to standard output.

Structs§

Interpreter
struct Interpreter interpretes strings containing Laconic expressions or scripts.
NumberFormat
A NumberFormat object contains information about the formatting of output numbers requested by the executed Laconic script - it’s the type of the format_info field of the ExecutionOutcome::Number variant.

Enums§

ExecutionOutcome
Whenever a laconic::Interpreter executes a Laconic script without halting on any errors, it returns an ExecutionOutcome variant, depending on the type of the value of the last top-level operator in the parsed script’s operator tree.
ScriptError
Whenever an Interpreter halts a script’s execution due to an error condition, an Err<ScriptError> is returned.