langur

given expressions

Langur's "switch" is the given expression. Given expressions are very flexible. You would not likely use every feature available within a single expression.

In the following descriptions, the parts of the tests listed after the given keyword are called "test expressions" and the parts listed after the case keyword are called "conditions."

Here's an example of a given expression with 3 test expressions. More examples are given at the end of this page.

given example 1

given .x, .y, .z { case true: ... # all are true case true, false, null: ... # .x == true and .y == false and .z == null case _, 123, < 7: ... # .y == 123 and .z < 7 case 10 >=, _: ... # 10 >= .x case _, re/abc+/: ... # .y matches regex pattern abc+ default: ... }

test expressions

You can use 1 or more expressions to test against, as a comma-delimited list preceding the opening curly brace (such as given .x, .y, .z ...).

As the name implies, test expressions are not limited to variables and constants.

As of 0.7.0, complex test expressions are evaluated once, instead of being done on every case condition test.

conditions

Conditions are comma-delimited lists after the case keyword, preceding an opening colon (such as case 123, .abc, 890: ...). An action is placed after the colon.

You do not have to list a condition for every test expression.

A default may be specified with the default keyword.

one test expression with multiple conditions

Using one test expression with multiple conditions will test all conditions against the expression.

To make this work like "switch" in other languages, use "case or" since the default operator for multiple conditions is "and".

one condition with multiple test expressions

A single test condition, without no-ops, is a special condition meaning to test all the expressions against the condition.

no-op

A test condition may use an underscore to indicate no-op (no comparison, such as case _, 2: ... to test only the second expression or case 2, _: ... to test only the first expression).

alternate comparison operators

By default, tests and conditions are compared as operands of the == operator. Alternate default comparison operators may be specified at the beginning or end of each test expression (such as given .x >, != .y, .z ...).

An alternate comparison operator may also be specified as part of a condition, preceding or following the value (such as case != 100, 5 <=: ...).

Comparison operators specified in conditions override operators specified in test expressions.

alternate logical operators

By default, logical and is used between test conditions in a case statement. An alternate logical operator may be specified immediately after the case keyword (such as case or 1, 2, 3: ...).

An alternate logical operator may also be specified immediately after a semicolon in a case test containing alternate conditions (such as case 1, 2; xor .x > .y: ...).

regex in given

A condition or test expression may be a regex, which will match the other against the regex pattern (such as case re/abc+/:). If both are a regex pattern, they will be treated the same as other test conditions.

Non-string values are converted to strings using auto-stringification before being compared against the regex.

alternate conditions

Alternate test conditions may be specified as a comma-delimited list after a semicolon within a case test. This allows you to insert other test conditions without having to use an if/else expression.

A given may also have no test expressions. In this case, alternate test conditions are used without a semicolon.

fallthrough

A fallthrough statement is allowed anywhere within a given expression body (but not in the default section, of course).

A case with an empty body has an implicit fallthrough (case alternate). Otherwise, there is no implicit fallthrough in langur.

shortened form given expressions

A shortened form given expression uses parentheses, colons, and semicolons, and no extra keywords.

The shortened form expects a single expression per action section rather than an implicit block.

You may still use multiple test expressions and conditions, as comma-delimited lists.

given .x { case 100: 1; case 200: 2; case 300: 3; default: 4} # long form given expression given(.x; 100: 1; 200: 2; 300: 3; 4) # shortened form given expression

This is a semantic convenience and the result is the same as the long form.

The shortened form is more limited, as you cannot use alternate test conditions, nor alternate logical operators, nor explicit fallthrough.

expression value

A given expression always returns something, even if it's nothing (null).

given example 2

Using a given on a catch can be convenient....

catch given _err["cat"], _err["msg"] { case "math", _: writeln("math error!") ... default: throw # rethrow }

given example 3

val .y = "abcd" given .x { case 100: ... # .x == 100 case 200; len(.y) > 3: ... # .x == 200 and len(.y) > 3 }

given example 4

val .z = "abcd" given <= .x, .y > { case 100: ... # 100 <= .x and .y > 100 case or 200, 300; len(.z) > 3: ... # (200 <= .x or .y > 300) and len(.z) > 3 case 200, 300 >=; or len(.z) > 3: ... # 200 <= .x and 300 >= .y or len(.z) > 3 case ; len(.z) > 3: ... # len(.z) > 3 }

given example 5

given <= .x { case 200, > 300: ... # 200 <= .x and .x > 300 case or 200, > 300: ... # 200 <= .x or .x > 300 }

breakdown

The following gives a semi-technical general idea for the syntax of a given expression.

given [[comparison op] expr1 | expr1 [comparison op] [, ... [comparison op] exprn | [comparison op] exprn]] { case [[logical op] [[comparison op] expr1 | expr1 [comparison op] [, ... [comparison op] exprn | [comparison op] exprn]]] [; [logical op] alt1 [, ... altn]] : ... default: ... }

scope within given expressions

Declarations are not allowed within case statements.

Blocks within given expressions are scoped, as illustrated below.

given ... { case 1: val .x = 123; .x + .y case 2: val .x = 789; .x + .y } # each .x different, and not seen after given expression

val .y = 789 given .x { case > 100: val .y = 7 ... return .x ^ .y } # .y == 789 still

Declarations and assignments are moved from test expressions and placed ahead of the given expression in a scoped block. This ensures the declaration and assignment happens once, rather than on each test.

given val .x = len(.y) { ... } # ... is the same as ... { val .x = len(.y) given .x { ... } }