From 48afa2a21ea70598bf9c6acd5e48d298cd8f5fce Mon Sep 17 00:00:00 2001 From: simerplaha Date: Tue, 14 Jan 2025 12:49:40 +1100 Subject: [PATCH 1/2] `VariableDeclaration` and `AssignmentSpec` --- .../soft/AssignmentAccessModifierParser.scala | 2 +- .../parser/soft/AssignmentParser.scala | 4 +- .../parser/soft/ExpressionParser.scala | 1 + .../parser/soft/UnresolvedParser.scala | 4 +- .../soft/VariableDeclarationParser.scala | 26 ++++ .../compiler/parser/soft/ast/SoftAST.scala | 9 +- .../compiler/parser/soft/AssignmentSpec.scala | 84 +++++++++++++ .../compiler/parser/soft/TestParser.scala | 6 + .../parser/soft/VariableDeclarationSpec.scala | 112 ++++++++++++++++++ .../parser/soft/ast/TestSoftAST.scala | 24 ++++ 10 files changed, 264 insertions(+), 8 deletions(-) create mode 100644 compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/VariableDeclarationParser.scala create mode 100644 compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentSpec.scala create mode 100644 compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/VariableDeclarationSpec.scala diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentAccessModifierParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentAccessModifierParser.scala index 7833e9f60..200406664 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentAccessModifierParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentAccessModifierParser.scala @@ -27,7 +27,7 @@ private object AssignmentAccessModifierParser { P { Index ~ (TokenParser.parseOrFail(Token.Let) | TokenParser.parseOrFail(Token.Mut)) ~ - SpaceParser.parseOrFail.? ~ + SpaceParser.parseOrFail ~ Index } map { case (from, dataAssignment, space, to) => 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 f863dcb12..e67bd3db0 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 @@ -10,7 +10,6 @@ private object AssignmentParser { def parseOrFail[Unknown: P]: P[SoftAST.Assignment] = P { Index ~ - AssignmentAccessModifierParser.parseOrFail.rep ~ IdentifierParser.parseOrFail ~ SpaceParser.parseOrFail.? ~ TokenParser.parseOrFail(Token.Equal) ~ @@ -18,10 +17,9 @@ private object AssignmentParser { ExpressionParser.parse ~ Index } map { - case (from, control, identifier, postIdentifierSpace, equalToken, postEqualSpace, expression, to) => + case (from, identifier, postIdentifierSpace, equalToken, postEqualSpace, expression, to) => SoftAST.Assignment( index = range(from, to), - modifiers = control, identifier = identifier, postIdentifierSpace = postIdentifierSpace, equalToken = equalToken, 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 6454741af..2f1719dc2 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 @@ -77,6 +77,7 @@ private object ExpressionParser { ReturnStatementParser.parseOrFail | ForLoopParser.parseOrFail | WhileLoopParser.parseOrFail | + VariableDeclarationParser.parseOrFail | AssignmentParser.parseOrFail | TypeAssignmentParser.parseOrFail | BlockParser.clause(required = false) | diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/UnresolvedParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/UnresolvedParser.scala index 8021c4135..0962b971d 100644 --- a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/UnresolvedParser.scala +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/UnresolvedParser.scala @@ -33,8 +33,8 @@ private object UnresolvedParser { case (from, text, tailComment, to) => SoftAST.Unresolved( index = range(from, to), - code = text, - documentation = tailComment + documentation = tailComment, + code = text ) } diff --git a/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/VariableDeclarationParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/VariableDeclarationParser.scala new file mode 100644 index 000000000..549273d2e --- /dev/null +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/VariableDeclarationParser.scala @@ -0,0 +1,26 @@ +package org.alephium.ralph.lsp.access.compiler.parser.soft + +import fastparse._ +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 + +private object VariableDeclarationParser { + + /** Syntax: let mut variable = some_expression */ + def parseOrFail[Unknown: P]: P[SoftAST.VariableDeclaration] = + P { + Index ~ + AssignmentAccessModifierParser.parseOrFail.rep(1) ~ + AssignmentParser.parseOrFail ~ + Index + } map { + case (from, modifier, assignment, to) => + SoftAST.VariableDeclaration( + index = range(from, to), + modifiers = modifier, + assignment = assignment + ) + } + +} 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 9a43f08b1..cb371e3e6 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 @@ -368,7 +368,6 @@ object SoftAST { case class Assignment( index: SourceIndex, - modifiers: Seq[SoftAST.AssignmentAccessModifier], identifier: Identifier, postIdentifierSpace: Option[Space], equalToken: TokenDocumented[Token.Equal.type], @@ -389,7 +388,7 @@ object SoftAST { case class AssignmentAccessModifier( index: SourceIndex, token: TokenDocumented[Token.DataDefinition], - postTokenSpace: Option[Space]) + postTokenSpace: Space) extends ExpressionAST case class AccessModifier( @@ -398,6 +397,12 @@ object SoftAST { postTokenSpace: Option[Space]) extends ExpressionAST + case class VariableDeclaration( + index: SourceIndex, + modifiers: Seq[AssignmentAccessModifier], + assignment: Assignment) + extends ExpressionAST + case class Annotation( index: SourceIndex, at: TokenDocumented[Token.At.type], 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 new file mode 100644 index 000000000..d36a02a9d --- /dev/null +++ b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/AssignmentSpec.scala @@ -0,0 +1,84 @@ +// Copyright 2024 The Alephium Authors +// This file is part of the alephium project. +// +// The library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the library. If not, see http://www.gnu.org/licenses/. + +package org.alephium.ralph.lsp.access.compiler.parser.soft + +import org.alephium.ralph.lsp.access.compiler.parser.soft.TestParser._ +import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.SoftAST +import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.TestSoftAST._ +import org.alephium.ralph.lsp.access.util.TestCodeUtil._ +import org.scalatest.matchers.should.Matchers +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 =>><<")) + ) + } + } + + "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") + ) + } + + "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") + ) + ) + } + } + +} diff --git a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TestParser.scala b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TestParser.scala index ef7cd3201..b4c880653 100644 --- a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TestParser.scala +++ b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/TestParser.scala @@ -30,6 +30,12 @@ object TestParser { def parseAnnotation(code: String): SoftAST.Annotation = runSoftParser(AnnotationParser.parseOrFail(_))(code) + def parseVariableDeclaration(code: String): SoftAST.VariableDeclaration = + runSoftParser(VariableDeclarationParser.parseOrFail(_))(code) + + def parseAssignment(code: String): SoftAST.Assignment = + runSoftParser(AssignmentParser.parseOrFail(_))(code) + def parseTemplate(code: String): SoftAST.Template = runSoftParser(TemplateParser.parseOrFail(_))(code) 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 new file mode 100644 index 000000000..7572f6f92 --- /dev/null +++ b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/VariableDeclarationSpec.scala @@ -0,0 +1,112 @@ +// Copyright 2024 The Alephium Authors +// This file is part of the alephium project. +// +// The library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the library. If not, see http://www.gnu.org/licenses/. + +package org.alephium.ralph.lsp.access.compiler.parser.soft + +import org.alephium.ralph.error.CompilerError +import org.alephium.ralph.lsp.access.compiler.parser.soft.TestParser._ +import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.SoftAST +import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.TestSoftAST._ +import org.alephium.ralph.lsp.access.util.TestCodeUtil._ +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import org.scalatest.TryValues.convertTryToSuccessOrFailure + +import scala.util.Try + +class VariableDeclarationSpec extends AnyWordSpec with Matchers { + + "succeed" when { + "full valid variable declaration is defined" in { + val assigment = + parseVariableDeclaration("let mut variable = 1") + + assigment shouldBe + SoftAST.VariableDeclaration( + index = indexOf(">>let mut variable = 1<<"), + modifiers = Seq( + SoftAST.AssignmentAccessModifier( + index = indexOf(">>let <>let<< mut variable = 1")), + postTokenSpace = SpaceOne(indexOf("let>> <>mut <>mut<< variable = 1")), + postTokenSpace = SpaceOne(indexOf("let mut>> <>variable = 1<<"), + identifier = 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") + ) + ) + } + } + + "error" when { + "expression is missing" in { + val assigment = + parseVariableDeclaration("let mut variable = ") + + assigment shouldBe + SoftAST.VariableDeclaration( + index = indexOf(">>let mut variable = <<"), + modifiers = Seq( + SoftAST.AssignmentAccessModifier( + index = indexOf(">>let <>let<< mut variable = ")), + postTokenSpace = SpaceOne(indexOf("let>> <>mut <>mut<< variable = ")), + postTokenSpace = SpaceOne(indexOf("let mut>> <>variable = <<"), + identifier = 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 = >><<")) + ) + ) + } + } + + "let" should { + "not be allowed as variable name" in { + Try(parseVariableDeclaration("let let = 1")) + .failure + .exception + .getCause shouldBe a[CompilerError.FastParseError] + } + + "allow letter as variable name" in { + val varDec = + parseVariableDeclaration("let letter = 1") + + varDec.assignment.identifier.code.text shouldBe "letter" + } + } + +} diff --git a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/TestSoftAST.scala b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/TestSoftAST.scala index 84bd5f3e6..c0b4a9589 100644 --- a/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/TestSoftAST.scala +++ b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/ast/TestSoftAST.scala @@ -102,6 +102,30 @@ object TestSoftAST { token = Token.AlphUppercase ) + def Let(index: SourceIndex): SoftAST.TokenDocumented[Token.Let.type] = + TokenDocumented( + index = index, + token = Token.Let + ) + + def Mut(index: SourceIndex): SoftAST.TokenDocumented[Token.Mut.type] = + TokenDocumented( + index = index, + token = Token.Mut + ) + + def Equal(index: SourceIndex): SoftAST.TokenDocumented[Token.Equal.type] = + TokenDocumented( + index = index, + token = Token.Equal + ) + + def Plus(index: SourceIndex): SoftAST.TokenDocumented[Token.Plus.type] = + TokenDocumented( + index = index, + token = Token.Plus + ) + def Identifier( index: SourceIndex, text: String): SoftAST.Identifier = From 58b47fad1e32651615c68b0b0f1c187f76bb1ecf Mon Sep 17 00:00:00 2001 From: simerplaha Date: Tue, 14 Jan 2025 13:41:35 +1100 Subject: [PATCH 2/2] allow assignments to be assigned to expressions --- .../parser/soft/AssignmentParser.scala | 8 +- .../access/compiler/parser/soft/Demo.scala | 8 + .../parser/soft/ExpressionParser.scala | 28 +++- .../parser/soft/InfixCallParser.scala | 2 +- .../parser/soft/MethodCallParser.scala | 4 +- .../compiler/parser/soft/ast/SoftAST.scala | 6 +- .../compiler/parser/soft/AssignmentSpec.scala | 155 ++++++++++++------ .../parser/soft/VariableDeclarationSpec.scala | 36 +++- 8 files changed, 177 insertions(+), 70 deletions(-) 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 e67bd3db0..fe8d602eb 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 613b0f931..3ee3f5c0d 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 2f1719dc2..a80b78365 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 8942fef75..57fdfaaab 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 0b43fbcfe..fd72082ea 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 cb371e3e6..7e7cb6f71 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 d36a02a9d..a8ee41fc9 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 7572f6f92..1d136b8c3 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" + ) } }