Skip to content

Add a special syntax for binary operators #1065

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
pluralia opened this issue May 26, 2023 · 3 comments · May be fixed by #1836
Open

Add a special syntax for binary operators #1065

pluralia opened this issue May 26, 2023 · 3 comments · May be fixed by #1836
Assignees
Labels
grammar Grammar language related issue parser Parser related issue

Comments

@pluralia
Copy link
Contributor

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
;
  • @binary_op -- an annotation to defined a rule with the special syntax
  • left_operand, right_operand, operator -- arguments of the annotation that reflect property names in the generated interface BinaryExpression
  • arguments can be omitted -- then default names can be used: left_operand, right_operand, operator
  • the rule with the special annotation @binary_op consists of comma-separated operator declarations; the order of these declarations reflects the operator precedence
  • each operator declaration consists of 3 parts:
    • associativity keyword infix(r|l|n)
    • operators -- usual syntax with pipes
    • the name of the rule responsible for parsing the primary expression

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
}
@pluralia pluralia added parser Parser related issue grammar Grammar language related issue labels May 26, 2023
@pluralia pluralia added this to the v2.0.0 milestone May 26, 2023
@pluralia pluralia self-assigned this May 26, 2023
@Lotes
Copy link
Contributor

Lotes commented May 27, 2023

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:

  • all operators are closed in one kind of rule. Good because you are able to define operators on different concepts, like on value and type level for example
  • the idea about naming parts of the rule, like left, right, op. This could be tricky (example NameProvider) if you have multiple levels of expressions, like said in the first point
  • the syntax of writing a table, because even if it is not enforced by syntax, it would be a great help for the user to read this information

What could be better:

  • we have no annotations yet, I think we should be careful here not to introduce something that opens the longings of others to have it somewhere else as well
  • the „infix“ mentioned there is not used, it is always infix, so you could remove it (postfix {1 2 +} and prefix {+ 1 2} could be used here for example, but I cannot imagine that someone would need it for binary operations)
  • as mentioned in Slack, you need to decide between left or right associativity. Saying no should default to left.

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
Copy link
Member

msujew commented May 31, 2023

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
Copy link
Contributor

Lotes commented May 31, 2023

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

@msujew msujew removed this from the v2.0.0 milestone Jul 21, 2023
@msujew msujew linked a pull request Mar 11, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
grammar Grammar language related issue parser Parser related issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants