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 3ee3f5c0..86fd7cfe 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 @@ -41,6 +41,9 @@ object Demo extends App { | while(cache.getValue() == objectB.read().value) { | // do something | } + | + | // mutable binding + | let (a, mut b, _) = (1, 2, 3) | } | | 🚀 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 a80b7836..c4cc59d5 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 @@ -93,6 +93,7 @@ private object ExpressionParser { ForLoopParser.parseOrFail | WhileLoopParser.parseOrFail | VariableDeclarationParser.parseOrFail | + MutableBindingParser.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/MutableBindingParser.scala b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/MutableBindingParser.scala new file mode 100644 index 00000000..55c17078 --- /dev/null +++ b/compiler-access/src/main/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/MutableBindingParser.scala @@ -0,0 +1,28 @@ +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, Token} + +private object MutableBindingParser { + + /** Syntax: mut [identifier] */ + def parseOrFail[Unknown: P]: P[SoftAST.MutableBinding] = + P { + Index ~ + TokenParser.parseOrFail(Token.Mut) ~ + SpaceParser.parseOrFail ~ + IdentifierParser.parseOrFail ~ + Index + } map { + case (from, mut, space, identifier, to) => + SoftAST.MutableBinding( + index = range(from, to), + mut = mut, + space = space, + identifier = identifier + ) + } + +} 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 7e7cb6f7..84dbe42e 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 @@ -403,6 +403,14 @@ object SoftAST { assignment: Assignment) extends ExpressionAST + /** Syntax: mut [identifier] */ + case class MutableBinding( + index: SourceIndex, + mut: TokenDocumented[Token.Mut.type], + space: Space, + identifier: Identifier) + 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/MutableBindingSpec.scala b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/MutableBindingSpec.scala new file mode 100644 index 00000000..fa7e2cd7 --- /dev/null +++ b/compiler-access/src/test/scala/org/alephium/ralph/lsp/access/compiler/parser/soft/MutableBindingSpec.scala @@ -0,0 +1,92 @@ +// 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 +import org.scalatest.OptionValues._ + +class MutableBindingSpec extends AnyWordSpec with Matchers { + + "succeed" when { + "an identifier is set a mut" in { + val annotation = + parseMutableBinding("mut variable") + + annotation shouldBe + SoftAST.MutableBinding( + index = indexOf(">>mut variable<<"), + mut = Mut(indexOf(">>mut<< variable")), + space = SpaceOne(indexOf("mut>> <>variable<<"), "variable") + ) + } + + "an identifier in a tuple is set a mut" in { + val body = + parseSoft("(a, b, mut variable)") + + body.parts should have size 1 + val tuple = body.parts.head.part.asInstanceOf[SoftAST.Tuple] + + tuple.headExpression shouldBe defined + tuple.tailExpressions should have size 2 // there are two tail expressions + val lastExpression = tuple.tailExpressions.last.expression.asInstanceOf[SoftAST.MutableBinding] // test the last expression i.e. `mut variable` + + lastExpression shouldBe + SoftAST.MutableBinding( + index = indexOf("(a, b, >>mut variable<<)"), + mut = Mut(indexOf("(a, b, >>mut<< variable)")), + space = SpaceOne(indexOf("(a, b, mut>> <>variable<<)"), "variable") + ) + } + + "the binding is documented" when { + "tuple" in { + val body = + parseSoft { + """( + |a, + |b, + |// documentation line 1 + |// documentation line 2 + |mut variable + |) + |""".stripMargin + } + + body.parts should have size 1 + val tuple = body.parts.head.part.asInstanceOf[SoftAST.Tuple] + + tuple.headExpression shouldBe defined + tuple.tailExpressions should have size 2 // there are two tail expressions + val lastExpression = tuple.tailExpressions.last.expression.asInstanceOf[SoftAST.MutableBinding] // test the last expression i.e. `mut variable` + + lastExpression.mut.documentation.value.toCode() shouldBe + """// documentation line 1 + |// documentation line 2 + |""".stripMargin + } + } + } + +} 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 b4c88065..3e162d76 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 @@ -36,6 +36,9 @@ object TestParser { def parseAssignment(code: String): SoftAST.Assignment = runSoftParser(AssignmentParser.parseOrFail(_))(code) + def parseMutableBinding(code: String): SoftAST.MutableBinding = + runSoftParser(MutableBindingParser.parseOrFail(_))(code) + def parseTemplate(code: String): SoftAST.Template = runSoftParser(TemplateParser.parseOrFail(_))(code)