Precedence and associativity of the @ operator is undocumented
The @ symbol is permitted as the first character of a custom operator.
let (@+) a b = a + b
1 @+ 2
https://github.com/fsharp/fslang-spec/blob/a3b1d61ae54eee380803cd6800c4d02c246bde92/spec/lexical-analysis.md?plain=1#L331
The precedence table, doesn't list the @ operator to define its precedence and associativity.
https://github.com/fsharp/fslang-spec/blob/a3b1d61ae54eee380803cd6800c4d02c246bde92/spec/basic-grammar-elements.md?plain=1#L246-L284
The categorization table doesn't list @ operator.
https://github.com/fsharp/fslang-spec/blob/a3b1d61ae54eee380803cd6800c4d02c246bde92/spec/basic-grammar-elements.md?plain=1#L190-L212
What would be the best way to determine this to update the spec?
Operators starting with @ or ^ (INFIX_AT_HAT_OP) are right-associative and are between the relational operators (&, |, <, >, =, $) and :: in precedence:
%right LARROW
%right COLON_EQUALS
%nonassoc pat_tuple expr_tuple
%left COMMA
%nonassoc open_range_expr
%left DOT_DOT /* for matrix.[1..2, 3..4] the ".." has higher precedence than expression "2 COMMA 3" */
%nonassoc interpolation_fill /* "...{3,N4}..." .NET style fill has higher precedence than "e COMMA e" */
%nonassoc paren_pat_colon
%nonassoc paren_pat_attribs
%left OR BAR_BAR JOIN_IN
%left AND
%left AND_BANG
%left AMP AMP_AMP
%nonassoc pat_conj
%nonassoc expr_not
%left COLON_GREATER COLON_QMARK_GREATER
%left INFIX_COMPARE_OP DOLLAR LESS GREATER EQUALS INFIX_BAR_OP INFIX_AMP_OP
%right INFIX_AT_HAT_OP
%right COLON_COLON
%nonassoc pat_isinst
%left COLON_QMARK
%left PLUS_MINUS_OP MINUS expr_prefix_plus_minus ADJACENT_PREFIX_OP
%left INFIX_STAR_DIV_MOD_OP STAR PERCENT_OP
%right INFIX_STAR_STAR_OP
%left QMARK_QMARK
%left head_expr_adjacent_minus
%left expr_app expr_assert expr_lazy LAZY ASSERT
%left arg_expr_adjacent_minus
%left expr_args
%right matching_bar
%left pat_app
%left pat_args
%left PREFIX_OP
%right dot_lambda
%left DOT QMARK
%left HIGH_PRECEDENCE_BRACK_APP
%left HIGH_PRECEDENCE_PAREN_APP
%left HIGH_PRECEDENCE_TYAPP
Thanks! looks like I can adapt that listing and put it straight into the spec
Looking a bit more into pars.fsy it seems there are many additional precedence rules introduced to resolve conflicts.
It is possible to eliminate conflicts by giving precedence to rules and tokens. ... Dummy terminals (like prec_type_prefix) can assign precedence to a rule.
Should these "dummy terminals" be added to the precedence table or are they an artifact of the way FsLexYacc processes things that is better described elsewhere in the spec document?
I also note the spec table lists precedence highest to lowest but pars.fsy defines precedence lowest to highest. Should the spec be reversed to also list precedence in lowest to highest order for easier cross-checking?
I had a go at putting everything into the table, and applied inline code formatting to the operators. What you you think?
| Operator or expression | Associativity | Comments |
|---|---|---|
| prec-interaction-empty | Not associative | TODO |
f<types> |
Left | High-precedence type application; see § |
f(x) |
Left | High-precedence paren application; see § |
f[x] |
Left | High-precedence bracket application; see § |
. ? |
Left | |
_.M(x) |
Right | |
| prefix-op | Left | Applies to prefix uses of these symbols |
| pat-args | Left | TODO |
| pat-app | Left | TODO |
| rule |
Right | Pattern matching rules |
f x |
Left | |
| arg-expr-adjacent-minus | Left | TODO |
expr-app expr-assert expr-lazy lazy assert |
Left | |
| head-expr-adjacent-minus | Left | TODO |
?? |
Left | |
**OP |
Right | |
*OP /OP mod * %OP |
Left | TODO mod? |
-OP +OP expr-prefix-plus-minus |
Left | Applies to infix uses of these symbols TODO |
:? |
Left | |
| pat-isinst | Not associative | TODO |
:: |
Right | |
^OP @OP |
Right | |
!=OP <OP >OP = |OP &OP $ |
Left | Applies to infix uses of these symbols |
:> :?> |
Left | |
| expr-not | Not associative | TODO |
| pat-conj | Not associative | TODO |
& && |
Left | |
and! |
Left | |
and |
Left | |
or || in |
Left | in when used in a join in a query expression |
| paren-pat-attribs | Not associative | TODO |
| paren-pat-colon | Not associative | TODO |
| interpolation-fill | Not associative | TODO ...{3,N4}... .NET style fill has higher precedence than e, e |
.. |
Left | for matrix.[1..2, 3..4] the .. has higher precedence than expression 2, 3 |
| open-range-expr | Not associative | TODO |
, |
Left | |
| pat-tuple expr-tuple | Not associative | TODO |
:= |
Right | |
<- |
Right | |
interface |
Not associative | |
| prec-interfaces-prefix | Not associative | TODO lower than INTERFACE |
open |
Not associative | |
| prec-no-more-attr-bindings | Not associative | TODO lower than AND |
| prec-atomtyp-get-path | Not associative | TODO lower than LESS |
| prec-atomtyp-path | Not associative | TODO lower than LESS |
else |
Not associative | |
if |
Not associative | lower than ELSE to disambiguate if _ then if _ then _ else _ |
| decl-match decl-do | Not associative | TODO |
function fun match try do |
Not associative | |
| decl-let | Not associative | |
| expr-let | Not associative | |
let new |
Not associative | LET, NEW higher than SEMICOLON |
| prec-opt-attributes-none | Not associative | TODO |
ident [ |
Not associative | TODO |
-> |
Right | |
| prec-toptuptyptail-prefix | Not associative | TODO lower than STAR, IDENT, RARROW etc. |
| prec-tuptyptail-prefix | Not associative | TODO lower than STAR, IDENT, RARROW etc. |
| prec-tuptyp-prefix | Not associative | TODO lower than STAR, IDENT, RARROW etc. |
| prec-typ-prefix | Not associative | TODO lower than STAR, IDENT, RARROW etc. |
true false _ null |
Not associative | |
( { [| |
Not associative | |
| Interpolated strings | Not associative | |
Numeric Literals string |
Not associative | The string keyword |
| prec-atompat-pathop | Not associative | TODO |
| prec-defn-sep | Right | TODO |
; prec-semiexpr-sep OBLOCKSEP |
Right | |
| |
Left | |
| null |
Not associative | TODO |
| prec-then-if | Not associative | TODO |
| prec-then-before | Left | a then b as an object constructor is very low precedence Lower than if a then b |
| prec-pat-pat-action | Not associative | TODO |
when |
Right | |
) RPAREN_COMING_SOON RPAREN_IS_HERE |
Not associative | TODO |
| prec-wheretyp-prefix | Not associative | TODO lower than WHEN and RPAREN |
as |
Right | |
| prec-atomexpr-lparen-error | Not associative | TODO lower than WHEN and RPAREN |
| prec-args-error | Not associative | TODO lower than WHEN and RPAREN |
Should these "dummy terminals" be added to the precedence table
I don't think so, although I guess it depends exactly what the goal of the updated spec is. If it's to be comprehensive enough to enable a wholesale reimplementation of the parser, then I guess many such details would need to be included. But if the main goal of updating this part of the spec is so that users of the language can more easily understand the grammar (and the precedence and associativity of custom operators, etc.), then I think keeping only the higher-level items (operators, keywords, separators) makes more sense.
My instinct is to go in the direction of "enable a wholesale reimplementation", but depends how much interest and effort it's going to take
My instinct is to go in the direction of "enable a wholesale reimplementation"
In principle, I agree. That's what a spec is for. It should specify the language completely and compilers can implement it.
But we must also be realistic. There is just a single F# compiler, and de facto it is in the lead and the spec has to follow.
Why do we still need a spec?
- For advanced users of the language to find details that the user documentation can not provide.
- To decide if an issue is "by design" or a bug.
- To help new contributors understand the compiler.
- To improve the quality of new features. By writing down precisely what the feature changes, you can avoid a lot of issues that otherwise go unnoticed.
For now, I see the following priorities for our work on the F# spec.
- Add the existing RFCs to the spec.
- Ensure that new RFCs are more explicit and correct about the necessary spec changes. And that all breaking changes have a RFC (we had quite a few in the last years that had not).
- Fix obvious mistakes in the spec (like the missing
@operator).
I believe we should avoid spreading ourselves still thinner by tackling other improvements of the spec at this point. And rather focus on the above items for now.