eclipse-langium / langium

Next-gen language engineering / DSL framework
https://langium.org/
MIT License
754 stars 68 forks source link

Add a special syntax for binary operators #1065

Open pluralia opened 1 year ago

pluralia commented 1 year ago

Motivation is in the discussion #751.

The proposal is to add the ability to define binary operations shorter with the following syntax:

@binary_op (left_operand=left, right_operand=right, operator=op)
BinaryExpression infers Expression:
    , infixl ("*" | "/") Primary
    , infixl ('+' | '-') Primary
    , infixl "&&" Primary
    , infixl "||" Primary
    , infixn  ".." Primary // range operator; no associativity
    , infixr '=' Primary
;

The syntax above must be semantically equivalent to the current syntax:

Expression:
    Assignment;

Assignment infers Expression:
    Range ({infer BinaryExpression.left=current} operator='=' right=Assignment)?;

Range infers Expression:
    Or ({infer BinaryExpression.left=current} operator='..' right=Or)?;

Or infers Expression:
    And ({infer BinaryExpression.left=current} operator='||' right=And)*;

And infers Expression:
    Addition ({infer BinaryExpression.left=current} operator='&&' right=Addition)*;

Addition infers Expression:
    Multiplication ({infer BinaryExpression.left=current} operator=('+' | '-') right=Multiplication)*;

Multiplication infers Expression:
    Primary ({infer BinaryExpression.left=current} operator=('*' | '/') right=Primary)*;

Unary operators must be defined using the current syntax, for example:

Primary infers Expression:
      '(' Expression ')'
    | {infer UnaryPrefix} op=('+' | '-') value=Expression
    | {infer NumberLiteral} value=NUMBER 
;

Both syntaxes have to generate the same types for the ast.ts:

export type Expression = BinaryExpression | NumberLiteral | UnaryPrefix;

export interface BinaryExpression extends AstNode {
    left: Expression
    operator: '&&' | '*' | '+' | '-' | '..' | '/' | '=' | '||'
    right: Expression
}

export interface UnaryPrefix extends AstNode {
    op: '+' | '-'
    value: Expression
}

export interface NumberLiteral extends AstNode {
    value: number
}
Lotes commented 1 year ago

Cool, that you have done something. For me this topic is a hot potato that I would avoid to touch, since it is very subjective. There are already some solutions out there with different syntaxes.

What I like:

What could be better:

Other proposal:

Expression binaryOperators Primary: // same schema: Rule Verb Rule
   {left} (‚+‘|‘-‚) //recycles actions, terminals and alternatives
 | {left} ‚*‘ // order of alternatives defines the priority
 | {right} ‚^‘
…
;

Instead of the equal alternative operator | we could use a ordered alternative operator like |>…

Expression …: {right} ‚^‘ |> {left} ‚*‘ |> {left} ‚+‘;

Instead of Expression binaryOperators Primary: I first thought of a template expansion like if you would call a template library function… ˋExpression expands BinaryOperationTemplate(Primary)ˋ, but that would introduce a new concept of templates, with which we should be careful as well.

msujew commented 1 year ago

I agree with @Lotes here. I actually had a very similar syntax in mind:

// Using `|` for normal alternatives, `,` for splitting operators
operator BinaryExpression on Primary:
  '+' | '-', '*' | '/', '^', '..', '=';

// Use `<` and `>` for associativity
operator BinaryExpression on Primary:
  <= '+' | '-', // left associative by default
  <= '*' | '/',
  => '^', // right associative
  <> '..', // no associativity
  => '='; // right associative again

To explain my reasoning here a bit: Using {right} and {left} for associativity is ambiguous with the action syntax. I would use a rather lightweight solution such as < and >. Even though I prefer |> over , for differentiating between operator precedence. However, that is difficult to read with < and > in there.

I would also remove the infix information, since this style of operator usage is only useful in case you have an infix notation. Prefix notation can easily be parsed in a different way (think Addition: '+' Primary Primary), while nobody uses postfix notations.

Lotes commented 1 year ago

Add a rename operator as code actions {§left=“lhs”} {§right=“rhs”} {§operator=“op”}

I used § because it states "what is the law" for generator and other parts of Langium :D