diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentParser.scala index e67bd3db..fe8d602e 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentParser.scala @@ -5,12 +5,12 @@ import fastparse.NoWhitespace.noWhitespaceImplicit import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.range import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token} -private object AssignmentParser { +private case object AssignmentParser { def parseOrFail[Unknown: P]: P[SoftAST.Assignment] = P { Index ~ - IdentifierParser.parseOrFail ~ + ExpressionParser.parseOrFailSelective(parseInfix = true, parseMethodCall = true, parseAssignment = false) ~ SpaceParser.parseOrFail.? ~ TokenParser.parseOrFail(Token.Equal) ~ SpaceParser.parseOrFail.? ~ @@ -20,11 +20,11 @@ private object AssignmentParser { case (from, identifier, postIdentifierSpace, equalToken, postEqualSpace, expression, to) => SoftAST.Assignment( index = range(from, to), - identifier = identifier, + expressionLeft = identifier, postIdentifierSpace = postIdentifierSpace, equalToken = equalToken, postEqualSpace = postEqualSpace, - expression = expression + expressionRight = expression ) } diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/Demo.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/Demo.scala index 613b0f93..3ee3f5c0 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/Demo.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/Demo.scala @@ -33,6 +33,14 @@ object Demo extends App { | // infix assignment | let sum = 1 + 2 | } + | + | // complex assignment + | object.function(1).value = cache.getValue() + | + | // complex equality check + | while(cache.getValue() == objectB.read().value) { + | // do something + | } | } | | 🚀 diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ExpressionParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ExpressionParser.scala index 2f1719dc..a80b7836 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ExpressionParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ExpressionParser.scala @@ -26,21 +26,28 @@ private object ExpressionParser { def parse[Unknown: P]: P[SoftAST.ExpressionAST] = parseSelective( parseInfix = true, - parseMethodCall = true + parseMethodCall = true, + parseAssignment = true ) def parseOrFail[Unknown: P]: P[SoftAST.ExpressionAST] = parseOrFailSelective( parseInfix = true, - parseMethodCall = true + parseMethodCall = true, + parseAssignment = true ) def parseSelective[Unknown: P]( parseInfix: Boolean, - parseMethodCall: Boolean): P[SoftAST.ExpressionAST] = + parseMethodCall: Boolean, + parseAssignment: Boolean): P[SoftAST.ExpressionAST] = P { Index ~ - parseOrFailSelective(parseInfix = parseInfix, parseMethodCall = parseMethodCall).? ~ + parseOrFailSelective( + parseInfix = parseInfix, + parseMethodCall = parseMethodCall, + parseAssignment = parseAssignment + ).? ~ Index } map { case (_, Some(expression), _) => @@ -52,7 +59,8 @@ private object ExpressionParser { def parseOrFailSelective[Unknown: P]( parseInfix: Boolean, - parseMethodCall: Boolean): P[SoftAST.ExpressionAST] = { + parseMethodCall: Boolean, + parseAssignment: Boolean): P[SoftAST.ExpressionAST] = { def infixOrFail() = if (parseInfix) InfixCallParser.parseOrFail @@ -65,8 +73,15 @@ private object ExpressionParser { else Fail(s"${MethodCallParser.productPrefix} ignored") + def assignmentOrFail() = + if (parseAssignment) + AssignmentParser.parseOrFail + else + Fail(s"${AssignmentParser.productPrefix} ignored") + P { - infixOrFail() | + assignmentOrFail() | + infixOrFail() | methodCallOrFail() | common } @@ -78,7 +93,6 @@ private object ExpressionParser { ForLoopParser.parseOrFail | WhileLoopParser.parseOrFail | VariableDeclarationParser.parseOrFail | - AssignmentParser.parseOrFail | TypeAssignmentParser.parseOrFail | BlockParser.clause(required = false) | ReferenceCallParser.parseOrFail | diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/InfixCallParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/InfixCallParser.scala index 8942fef7..57fdfaaa 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/InfixCallParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/InfixCallParser.scala @@ -10,7 +10,7 @@ case object InfixCallParser { def parseOrFail[Unknown: P]: P[SoftAST.InfixExpression] = P { Index ~ - ExpressionParser.parseOrFailSelective(parseInfix = false, parseMethodCall = true) ~ + ExpressionParser.parseOrFailSelective(parseInfix = false, parseMethodCall = true, parseAssignment = false) ~ SpaceParser.parseOrFail.? ~ TokenParser.InfixOperatorOrFail ~ SpaceParser.parseOrFail.? ~ diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/MethodCallParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/MethodCallParser.scala index 0b43fbcf..fd72082e 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/MethodCallParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/MethodCallParser.scala @@ -10,7 +10,7 @@ case object MethodCallParser { def parseOrFail[Unknown: P]: P[SoftAST.MethodCall] = P { Index ~ - ExpressionParser.parseOrFailSelective(parseInfix = false, parseMethodCall = false) ~ + ExpressionParser.parseOrFailSelective(parseInfix = false, parseMethodCall = false, parseAssignment = false) ~ SpaceParser.parseOrFail.? ~ dotCall.rep(1) ~ Index @@ -29,7 +29,7 @@ case object MethodCallParser { Index ~ TokenParser.parseOrFail(Token.Dot) ~ SpaceParser.parseOrFail.? ~ - ReferenceCallParser.parse ~ + (ReferenceCallParser.parseOrFail | IdentifierParser.parse) ~ Index } map { case (from, dot, postDotSpace, rightExpression, to) => diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/SoftAST.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/SoftAST.scala index cb371e3e..7e7cb6f7 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/SoftAST.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/SoftAST.scala @@ -322,7 +322,7 @@ object SoftAST { index: SourceIndex, dot: TokenDocumented[Token.Dot.type], postDotSpace: Option[Space], - rightExpression: ReferenceCall) + rightExpression: ReferenceCallOrIdentifier) extends SoftAST case class ReturnStatement( @@ -368,11 +368,11 @@ object SoftAST { case class Assignment( index: SourceIndex, - identifier: Identifier, + expressionLeft: ExpressionAST, postIdentifierSpace: Option[Space], equalToken: TokenDocumented[Token.Equal.type], postEqualSpace: Option[Space], - expression: ExpressionAST) + expressionRight: ExpressionAST) extends ExpressionAST case class TypeAssignment( diff --git a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentSpec.scala b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentSpec.scala index d36a02a9..a8ee41fc 100644 --- a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentSpec.scala +++ b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentSpec.scala @@ -25,59 +25,118 @@ import org.scalatest.wordspec.AnyWordSpec class AssignmentSpec extends AnyWordSpec with Matchers { - "report ExpressionExpected" when { - "a variable is assigned without initialisation" in { - val annotation = - parseAssignment("variable =") - - annotation shouldBe - SoftAST.Assignment( - index = indexOf(">>variable =<<"), - identifier = Identifier(indexOf(">>variable<<="), "variable"), - postIdentifierSpace = Some(SpaceOne(indexOf("variable>> <<="))), - equalToken = Equal(indexOf("variable >>=<<")), - postEqualSpace = None, - expression = SoftAST.ExpressionExpected(indexOf("variable =>><<")) - ) + "assignments to an identifier" should { + "report ExpressionExpected" when { + "a variable is assigned without initialisation" in { + val assignment = + parseAssignment("variable =") + + assignment shouldBe + SoftAST.Assignment( + index = indexOf(">>variable =<<"), + expressionLeft = Identifier(indexOf(">>variable<<="), "variable"), + postIdentifierSpace = Some(SpaceOne(indexOf("variable>> <<="))), + equalToken = Equal(indexOf("variable >>=<<")), + postEqualSpace = None, + expressionRight = SoftAST.ExpressionExpected(indexOf("variable =>><<")) + ) + } } - } - "succeed" when { - "full assignment syntax is defined" in { - val assigment = - parseAssignment("variable = 1") - - assigment shouldBe - SoftAST.Assignment( - index = indexOf(">>variable = 1<<"), - identifier = Identifier(indexOf(">>variable<< = 1"), "variable"), - postIdentifierSpace = Some(SpaceOne(indexOf("variable>> <<= 1"))), - equalToken = Equal(indexOf("variable >>=<< 1")), - postEqualSpace = Some(SpaceOne(indexOf("variable =>> <<1"))), - expression = Number(indexOf("variable = >>1<<"), "1") - ) + "succeed" when { + "full assignment syntax is defined" in { + val assigment = + parseAssignment("variable = 1") + + assigment shouldBe + SoftAST.Assignment( + index = indexOf(">>variable = 1<<"), + expressionLeft = Identifier(indexOf(">>variable<< = 1"), "variable"), + postIdentifierSpace = Some(SpaceOne(indexOf("variable>> <<= 1"))), + equalToken = Equal(indexOf("variable >>=<< 1")), + postEqualSpace = Some(SpaceOne(indexOf("variable =>> <<1"))), + expressionRight = Number(indexOf("variable = >>1<<"), "1") + ) + } + + "expression is another expression" in { + val assigment = + parseAssignment("variable = variable + 1") + + assigment shouldBe + SoftAST.Assignment( + index = indexOf(">>variable = variable + 1<<"), + expressionLeft = Identifier(indexOf(">>variable<< = variable + 1"), "variable"), + postIdentifierSpace = Some(SpaceOne(indexOf("variable>> <<= variable + 1"))), + equalToken = Equal(indexOf("variable >>=<< variable + 1")), + postEqualSpace = Some(SpaceOne(indexOf("variable =>> << variable + 1"))), + expressionRight = SoftAST.InfixExpression( + index = indexOf("variable = >>variable + 1<<"), + leftExpression = Identifier(indexOf("variable = >>variable<< + 1"), "variable"), + preOperatorSpace = Some(SpaceOne(indexOf("variable = variable>> <<+ 1"))), + operator = Plus(indexOf("variable = variable >>+<< 1")), + postOperatorSpace = Some(SpaceOne(indexOf("variable = variable +>> <<1"))), + rightExpression = Number(indexOf("variable = variable + >>1<<"), "1") + ) + ) + } } + } - "expression is another expression" in { - val assigment = - parseAssignment("variable = variable + 1") - - assigment shouldBe - SoftAST.Assignment( - index = indexOf(">>variable = variable + 1<<"), - identifier = Identifier(indexOf(">>variable<< = variable + 1"), "variable"), - postIdentifierSpace = Some(SpaceOne(indexOf("variable>> <<= variable + 1"))), - equalToken = Equal(indexOf("variable >>=<< variable + 1")), - postEqualSpace = Some(SpaceOne(indexOf("variable =>> << variable + 1"))), - expression = SoftAST.InfixExpression( - index = indexOf("variable = >>variable + 1<<"), - leftExpression = Identifier(indexOf("variable = >>variable<< + 1"), "variable"), - preOperatorSpace = Some(SpaceOne(indexOf("variable = variable>> <<+ 1"))), - operator = Plus(indexOf("variable = variable >>+<< 1")), - postOperatorSpace = Some(SpaceOne(indexOf("variable = variable +>> <<1"))), - rightExpression = Number(indexOf("variable = variable + >>1<<"), "1") + "assignments to an expression" should { + "succeed" when { + "left expression is a method call" in { + val assignment = + parseAssignment("obj.func(param).counter = 0") + + // left expression is a method call + val methodCall = assignment.expressionLeft.asInstanceOf[SoftAST.MethodCall] + methodCall.index shouldBe indexOf(">>obj.func(param).counter<< = 0") + val objectName = methodCall.leftExpression.asInstanceOf[SoftAST.Identifier] + objectName.code.text shouldBe "obj" + + // right expression is a number + val number = assignment.expressionRight.asInstanceOf[SoftAST.Number] + number shouldBe + Number( + index = indexOf("obj.func(param).counter = >>0<<"), + text = "0" ) - ) + } + + "left & right expressions both are method call" in { + val assignment = + parseAssignment("obj.func(param).counter = cache.getNumber()") + + // left expression is a method call + val left = assignment.expressionLeft.asInstanceOf[SoftAST.MethodCall] + left.index shouldBe indexOf(">>obj.func(param).counter<< = cache.getNumber()") + val objectName = left.leftExpression.asInstanceOf[SoftAST.Identifier] + objectName.code.text shouldBe "obj" + + // right expression is also a method call + val right = assignment.expressionRight.asInstanceOf[SoftAST.MethodCall] + right.index shouldBe indexOf("obj.func(param).counter = >>cache.getNumber()<<") + val cacheObject = right.leftExpression.asInstanceOf[SoftAST.Identifier] + cacheObject.code.text shouldBe "cache" + } + } + + "report missing expression" when { + "left expression is a method call and right expression is missing" in { + val assignment = + parseAssignment("obj.func(param).counter =") + + // left expression is a method call + val methodCall = assignment.expressionLeft.asInstanceOf[SoftAST.MethodCall] + methodCall.index shouldBe indexOf(">>obj.func(param).counter<< = 0") + val objectName = methodCall.leftExpression.asInstanceOf[SoftAST.Identifier] + objectName.code.text shouldBe "obj" + + // right expression is a number + assignment.expressionRight shouldBe + SoftAST.ExpressionExpected(indexOf("obj.func(param).counter =>><<")) + } } } diff --git a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/VariableDeclarationSpec.scala b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/VariableDeclarationSpec.scala index 7572f6f9..1d136b8c 100644 --- a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/VariableDeclarationSpec.scala +++ b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/VariableDeclarationSpec.scala @@ -51,11 +51,11 @@ class VariableDeclarationSpec extends AnyWordSpec with Matchers { ), assignment = SoftAST.Assignment( index = indexOf("let mut >>variable = 1<<"), - identifier = Identifier(indexOf("let mut >>variable<< = 1"), "variable"), + expressionLeft = Identifier(indexOf("let mut >>variable<< = 1"), "variable"), postIdentifierSpace = Some(SpaceOne(indexOf("let mut variable>> <<= 1"))), equalToken = Equal(indexOf("let mut variable >>=<< 1")), postEqualSpace = Some(SpaceOne(indexOf("let mut variable =>> <<1"))), - expression = Number(indexOf("let mut variable = >>1<<"), "1") + expressionRight = Number(indexOf("let mut variable = >>1<<"), "1") ) ) } @@ -83,11 +83,11 @@ class VariableDeclarationSpec extends AnyWordSpec with Matchers { ), assignment = SoftAST.Assignment( index = indexOf("let mut >>variable = <<"), - identifier = Identifier(indexOf("let mut >>variable<< = "), "variable"), + expressionLeft = Identifier(indexOf("let mut >>variable<< = "), "variable"), postIdentifierSpace = Some(SpaceOne(indexOf("let mut variable>> <<= "))), equalToken = Equal(indexOf("let mut variable >>=<< ")), postEqualSpace = Some(SpaceOne(indexOf("let mut variable =>> << "))), - expression = SoftAST.ExpressionExpected(indexOf("let mut variable = >><<")) + expressionRight = SoftAST.ExpressionExpected(indexOf("let mut variable = >><<")) ) ) } @@ -105,7 +105,33 @@ class VariableDeclarationSpec extends AnyWordSpec with Matchers { val varDec = parseVariableDeclaration("let letter = 1") - varDec.assignment.identifier.code.text shouldBe "letter" + varDec.assignment.expressionLeft.asInstanceOf[SoftAST.Identifier].code.text shouldBe "letter" + } + } + + "allow expressions as assignment identifiers" when { + "the identifier is a tuple" in { + val tupleDecl = + parseVariableDeclaration("let (a, b, c) = blah") + + tupleDecl.modifiers should contain only + SoftAST.AssignmentAccessModifier( + indexOf(">>let <<(a, b, c) = blah"), + Let(indexOf(">>let<< (a, b, c) = blah")), + SpaceOne(indexOf("let>> <<(a, b, c) = blah")) + ) + + // left is a tuple + val left = tupleDecl.assignment.expressionLeft.asInstanceOf[SoftAST.Tuple] + left.index shouldBe indexOf("let >>(a, b, c)<< = blah") + left.toCode() shouldBe "(a, b, c)" + + // right is an assignment + tupleDecl.assignment.expressionRight shouldBe + Identifier( + index = indexOf("let (a, b, c) = >>blah<<"), + text = "blah" + ) } }