chharvey / counterpoint

A robust programming language.
GNU Affero General Public License v3.0
2 stars 0 forks source link

Comparison Expression vs Generic Function Call #40

Open chharvey opened 4 years ago

chharvey commented 4 years ago

Ideas for resolving ambiguity between chained relational expressions a < B > (c) and generic function calls: a<B>(c). Inspired by #38, #39, and #21.

Option 1

Disallow chaining of relational expressions altogether.

-ExpressionComparative ::= (ExpressionComparative ("<" | ">" | "<=" | ">=" | "!<" | "!>"))? ExpressionAdditive;
+ExpressionComparative ::= (ExpressionAdditive    ("<" | ">" | "<=" | ">=" | "!<" | "!>"))? ExpressionAdditive;

Then, in v0.7.0:

ExpressionCompound ::=
    | ExpressionUnit
    | ExpressionCompound FunctionCall
;
FunctionCall
    ::= ("<" ","? Type# ","? ">")? "(" Expression# ")";

Note that this would make the syntax a < b < c ill-formed. It also doesn’t handle the comma-separated expression pattern:

[a<b, c>(d)];

Will this be parsed as

[ (a<b, c>(d)) ];

(one item a generic function call)? Or will it be parsed as

[ (a < b), (c > (d)) ];

(two items, comparison expressions)?

Option 2

Require parentheses inside of angle brackets. I.e., tokenize <( and )> as delimiters. Thus x<Y>(z) would be unambiguously parsed as a comparison expression, and a<b, c>(d) as two comma-separated comparison expressions. To make them generic function calls you would need to write x<(Y)>(z) and a<(b, c)>(d) respectively. Nice and symmetrical, but a little heavy to read.

FunctionCall
    ::= ("<(" ","? Type# ","? ")>")? "(" Expression# ")";
x<y>(z);        % parsed as `(x < y) > (z);` (chained comparison expressions)
x<(y)>(z);      % parsed as `(x<(y)>(z));` (generic function call)
[a<b, c>(d)];   % parsed as `[ (a < b), (c > (d)) ];` (two comparison expression items)
[a<(b, c)>(d)]; % parsed as `[ (a<(b, c)>(d)) ];` (one generic function call item)

If applying to all type arguments:

[NS.map<T, Array<T>>(d)];     % tried to parse as `[ (NS.map < T), (Array < T), >>(d) ]`, but `>>(d)` is not well-formed
[NS.map<T, Array<(T)>>(d)];   % tried to parse as `[ (NS.map < T), (Array<(T)> > (d)) ]`, but `Array<(T)>` is not a well-formed expression
[NS.map<(T, Array<(T)>)>(d)]; % parsed as expected

Option 3

Require a trailing comma in the generic argument list: x<Y,>(z) cannot possibly be interpreted as a comparison expression, nor a<b, c,>(d) as three expressions (>(w) is not a well-formed expression). Not particularly pretty, but the most readable and easiest to implement.

FunctionCall
    ::= ("<" ","? Type# "," ">")? "(" Expression# ")";
x<y>(z);       % parsed as `(x < y) > (z);` (chained comparison expressions)
x<y,>(z);      % parsed as `(x<y,>(z));` (generic function call)
[a<b, c>(d)];  % parsed as `[ (a < b), (c > (d)) ];` (two comparison expression items)
[a<b, c,>(d)]; % parsed as `[ (a<b, c,>(d)) ];` (one generic function call item)

Update: Option 3 will not work since the shift-reduce parser always shifts as many tokens as possible. E.g., parsing x < Y > (z) will reduce x to ExpressionCompound, and then shift <, on track for a generic argument, and then see an unexpected >. It will never get a chance to reduce x to ExpressionComparative.

Option 4

Call all functions with a dot . before all arguments. x < Y > (z) is always a comparative expression while x.<Y>(z) is always a function call.

FunctionCall
    ::= "." ("<" ","? Type# "," ">")? "(" Expression# ")";
x<y>(z);       % parsed as `(x < y) > (z);` (chained comparison expressions)
x.<y>(z);      % parsed as `(x.<y>(z));` (generic function call)
[a<b, c>(d)];  % parsed as `[ (a < b), (c > (d)) ];` (two comparison expression items)
[a.<b, c>(d)]; % parsed as `[ (a.<b, c>(d)) ];` (one generic function call item)

We already see this pattern with member access: a.b and c.[d]. Might as well be consistent with function calls: e.(f), g.<H>(i).

j.k.<L, M>(n, o.(p)).q.()~~.r.<S>()

Dot is only needed for function calls, so not needed for type arguments in general.

((a: int, b: int) => a + b).(3, 5);
(<T>(a: T, b: Array<T>): T | Array<T> => a || b).<int>(3, [5]);
class Foo<T> extends Bar<T> {}
func qux<T>(t: T): void {}
let f: Foo<int> = Foo.<int>();
qux.<int>(5);