Enhanced C#
Language of your choice: library documentation
Public static fields | List of all members
Loyc.Syntax.Les.LesPrecedence Class Reference

Contains Precedence objects that represent the precedence levels of LES. More...

Source file:


Contains Precedence objects that represent the precedence levels of LES.

In LES, the precedence of an operator is decided based simply on the text of the operator. The precedence of each one-character operator is predefined; the precedence of any operator with two or more characters is decided based on the first and last characters (according to the rules below). Other characters, if any, do not affect precedence.

The LES precedence table mostly matches popular programming languages, i.e. those in the C family.

An operator consists of a sequence of the following characters:

   ~ ! % ^ * - + = | < > / ? : . &

In addition, the $ character is allowed as the first character and, if present, it forces the operator to be interpreted as a prefix operator.

LESv3 also has operators that start with a single quote, which can include both letters and punctuation (e.g. '|foo|). The quote itself is ignored for the purpose of choosing precedence. LESv2 has backquoted operators instead, whereas in LESv3 backquoted strings are simply identifiers.

It is notable that the following punctuation cannot be used in operators:

The following table shows all the precedence levels and associativities of the "built-in" LES operators, except a couple of special operators such as the "lambda" operator =>, whose precedence is different on the left side than on the right side. Each precedence level has a name, which corresponds to a static field of this class. All binary operators are left-associative unless otherwise specified.

  1. Substitute: prefix $ . :
  2. Primary: binary . =:, generic arguments List!(int), suffix ++ –, method calls f(x), indexers a[i]
  3. NullDot: binary ?. ::
  4. DoubleBang: binary right-associative !!
  5. Prefix: prefix ~ ! % ^ * / - + & backtick (LESv2 only)
  6. Power: binary **
  7. Multiply: binary * / % \ >> <<
  8. Add: binary + -
  9. Arrow: binary right-associative -> <-
  10. PrefixDots: prefix ..
  11. Range: binary right-associative ..
  12. Compare: binary != == >= > < <=
  13. And: binary &&
  14. Or: binary || ^^
  15. IfElse: binary right-associative ? :
  16. LowerKeyword: a lowercase keyword
  17. PrefixOr: |

Not listed in table: binary => ~ = ?? >> ^ | & << ; prefix ? = > <; non-lowercase keywords.

Notice that the precedence of an operator depends on how it is used. The prefix operator - has higher precedence than the binary operator -, so for example - y * z is parsed as (- y) * z, while x - y * z is parsed as x - (y * z).

Programmers often use the shift operators >> and << in place of multiplication or division, so their natural precedence is the same as * and /. However, traditionally the C family of languages confusingly give the shift operators a precedence below +. Therefore, LES does not allow mixing of shift operators with + - * /; a >> b + c should produce a parse error. This is called immiscibility as explained in the documentation of Precedence. Parsing may still complete, but the exact output tree is unspecified (may be (a >> b) + c or a >> (b + c)).

Likewise, the bitwise ^ | & operators cannot be mixed with comparison operators as in a | 1 == 3.

The Lambda operator =>, which is right-associative, has a precedence level above Multiply on the left side, but below Assign on the right side. For example, the expression a = b => c = d is parsed as a = (b => (c = d)), and similarly a + b => c + d is parsed as a + (b => (c + d)), but a ** b => c ** d is parsed (a ** b) => (c ** d). The idea of two different precedences on the two sides of an operator may seem strange; see the documentation of Precedence for more explanation.

Similarly, all assignment operators (including compound assignments like +=) have a high precedence on the left side and low precedence on the right. This decision was made for WebAssembly, in which an expression like 2 * i32_store[$f(),4] = 3 * $g() is best parsed as 2 * (i32_store[$f(),4] = (3 * $g())) (NOTE: this example will surely be wrong by the time Wasm is released).

As a nod to functional languages, the arrow operator "->" is right- associative and has a precedence below '*' so that int * int -> int parses as (int * int) -> int rather than int * (int -> int) as in the C family of languages.

Some operators like 'this-one do not begin with punctuation. These "keyword operators" must be used as binary operators. They either start with a lowercase letter or they don't. If they do start with a lowercase letter, their precedence is LowerKeyword, which is very low, below assignment, so that a = b 'then x = y parses like (a = b) 'then (x = y).

If they do not start with a lowercase letter (as in 'Foo or '123) then they have an indeterminate precedence, below power (**) but above comparison (==). This means that an operator like 'XOR or 'Mod cannot be mixed with operators of precedence Multiply, Add, Arrow, AndBits, OrBits, OrIfNull, PrefixDots, and Range operators. Mixing operators illegally (e.g. x 'Mod y + z) will produce a parse error.

After constructing an initial table based on common operators from other languages, I noticed that

I also wanted to have a little "room to grow"–to defer the precedence decision to a future time for some operators. So the precedence of the binary operator ~ has a range of operators with which it cannot be mixed, the same range as for uppercase operators without punctuation; for example, x ~ y + z is invalid but x ~ y == z is allowed.

The operators \ ? = > < cannot be used as prefix operators.

The way that low-precedence prefix operators are parsed deserves some discussion... TODO.

Most operators can have two roles. Most operators can either be binary operators or prefix operators; for example, !*! is a binary operator in x !*! y but a prefix operator in x + !*! y.

The operators ++ – also have two roles, but different roles: they can be either prefix or suffix operators, but not binary operators. For example, -*- is a suffix operator in x -*- + y and a prefix operator in x + -*- y. Please note that x -*- y is ambiguous (it could be parsed as either of two superexpressions, (x -*-) (y) or (x) (-*- y)) and it is illegal.

Operators that start with $ can only be prefix operators (not binary or suffix). Having only a single role makes these operators unambiguous inside superexpressions (LESv2) or with juxtaposition (LESv3).

An operator cannot have all three roles (suffix, prefix and binary); that would be overly ambiguous. For example, if "-" could also be a suffix operator then x - + y could be parsed as (x -) + y as well as x - (+ y). More subtly, LES does not define any operators that could take binary or suffix roles, because that would also be ambiguous. For example, suppose |?| is a binary or suffix operator, but not a prefix operator. Clearly x |?| y and x |?| |?| y are unambiguous, but x |?| + y is ambiguous: it could be parsed as (x |?|) + y or x |?| (+ y). It turns out that a computer language can contain operators that serve as binary and prefix operators, OR it can contain operators that serve as binary and suffix operators, but a language is ambiguous if it has both kinds of operators at the same time.

How to detect an operator's precedence

To determine the precedence of any given operator, first you must decide, mainly based on the context in which the operator appears and the text of the operator, whether it is a prefix, binary, or suffix operator. Suffix operators can only be derived from the operators ++, – ("derived" means that you can add additional operator characters in the middle, e.g. +++ and -%- are can be prefix or suffix operators.)

If an operator starts with a single quote in LESv3 ('), the quote is not considered for the purpose of choosing precedence (rather, it is used to allow letters and digits in the operator name).

Next, if the operator is only one character, simply find it in the list of operators in the previous section to learn its precedence. If the operator is two or more characters, take the first character A and the and the last character Z, and of the following rules, use the first rule that applies:

  1. If AZ is "!=" or "==", or if the operator is exactly two characters long (ignoring the initial single quote) and equal to ">=", or "&lt;=", its precedence is Compare. This rule separates comparison operators from assignment operators, so that ">>=" is different from ">=", and "===" counts as a comparison operator.
  2. If it's an infix operator and Z is '=', the precedence is Assign.
  3. Look for an operator named AZ from the section above. If it is defined, the operator will have the same precedence. For example, binary =|> has the same precedence as binary "=>".
  4. Otherwise, look for an entry in the table for A. For example, binary "%+" has the same precedence as binary "%" and unary "-*" has the same precedence as unary "-".
  5. If the operator is not an infix operator, it is illegal (e.g. prefix ?? doesn't exist).
  6. If A is a lowercase letter, the precedence is LowerKeyword.
  7. Otherwise, the operator's precedence is Other.

The double-colon :: has the "wrong" precedence according to C# and C++ rules; a.b::c.d is parsed (a.b)::(c.d) although it would be parsed ((a.b)::c).d in C# and C++. The change in precedence allows double colon to be used for variable declarations in LeMP, as in x::System.Drawing.Point. The lower precedence allows this to be parsed properly, but it sacrifices full fidelity with C#/C++.

There are no ternary operators in LES. '?' and ':' are right-associative binary operators, so c ? a : b is parsed as c ? (a : b). The lack of an official ternary operator reduces the complexity of the parser.

LES represents Loyc trees, which do not distinguish operators and functions except by name; x += y is equivalent to the function call `'+=`(x, y) in LESv3 (@'+=(x, y) in LESv2), and the actual name of the function is '+=. Operators that do not start with a single quote in LES do start with a single quote in the final output (e.g. 2 + 2 is equivalent to 2 '+ 2). There is an exception: While prefix ++ and – are named '++ and '–, the suffix versions are named '++suf and '–suf in the output tree. For LESv2 operators surrounded by backquotes, the backquotes are not included in the output tree (e.g. sqrt x is equivalent to sqrt(x)).

See also

Public static fields

static readonly Precedence Substitute = new Precedence(106, 105)
static readonly Precedence Primary = new Precedence(100)
static readonly Precedence NullDot = new Precedence(95)
static readonly Precedence DoubleBang = new Precedence(91, 90)
static readonly Precedence Prefix = new Precedence(85)
static readonly Precedence Power = new Precedence(80)
static readonly Precedence Juxtaposition = new Precedence(76, 75)
static readonly Precedence Multiply = new Precedence(70)
static readonly Precedence Add = new Precedence(60)
static readonly Precedence Shift = new Precedence(65, 65, 60, 70)
static readonly Precedence Arrow = new Precedence(55)
static readonly Precedence PrefixDots = new Precedence(50)
static readonly Precedence Range = new Precedence(45)
static readonly Precedence OrIfNull = new Precedence(40)
static readonly Precedence Other = new Precedence(40, 40, 40, 75)
static readonly Precedence Compare = new Precedence(35)
static readonly Precedence AndBits = new Precedence(30, 30, 25, 50)
static readonly Precedence OrBits = new Precedence(25, 25, 25, 50)
static readonly Precedence And = new Precedence(20)
static readonly Precedence Or = new Precedence(15)
static readonly Precedence IfElse = new Precedence(10, 0, 0, 0)
static readonly Precedence Assign = new Precedence(72, 5, 5, 5)
static readonly Precedence LowerKeyword = new Precedence(1, 0, 0, 0)
static readonly Precedence Lambda = new Precedence(77, -5, -5, -5)
static readonly Precedence PrefixOr = new Precedence(-10)
static readonly Precedence SuperExpr = new Precedence(-15)