Skip to content

Commit

Permalink
Merge pull request #352 from alephium/var_decl
Browse files Browse the repository at this point in the history
`VariableDeclarationParser` & `AssignmentSpec`
  • Loading branch information
simerplaha authored Jan 14, 2025
2 parents 11c92e9 + 58b47fa commit c3ccf99
Show file tree
Hide file tree
Showing 13 changed files with 388 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,26 @@ 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 ~
AssignmentAccessModifierParser.parseOrFail.rep ~
IdentifierParser.parseOrFail ~
ExpressionParser.parseOrFailSelective(parseInfix = true, parseMethodCall = true, parseAssignment = false) ~
SpaceParser.parseOrFail.? ~
TokenParser.parseOrFail(Token.Equal) ~
SpaceParser.parseOrFail.? ~
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,
expressionLeft = identifier,
postIdentifierSpace = postIdentifierSpace,
equalToken = equalToken,
postEqualSpace = postEqualSpace,
expression = expression
expressionRight = expression
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
| }
| }
|
| 🚀
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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), _) =>
Expand All @@ -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
Expand All @@ -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
}
Expand All @@ -77,7 +92,7 @@ private object ExpressionParser {
ReturnStatementParser.parseOrFail |
ForLoopParser.parseOrFail |
WhileLoopParser.parseOrFail |
AssignmentParser.parseOrFail |
VariableDeclarationParser.parseOrFail |
TypeAssignmentParser.parseOrFail |
BlockParser.clause(required = false) |
ReferenceCallParser.parseOrFail |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.? ~
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -368,12 +368,11 @@ object SoftAST {

case class Assignment(
index: SourceIndex,
modifiers: Seq[SoftAST.AssignmentAccessModifier],
identifier: Identifier,
expressionLeft: ExpressionAST,
postIdentifierSpace: Option[Space],
equalToken: TokenDocumented[Token.Equal.type],
postEqualSpace: Option[Space],
expression: ExpressionAST)
expressionRight: ExpressionAST)
extends ExpressionAST

case class TypeAssignment(
Expand All @@ -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(
Expand All @@ -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],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// 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 {

"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<<"),
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")
)
)
}
}
}

"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 =>><<"))
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Loading

0 comments on commit c3ccf99

Please sign in to comment.