Skip to content

Commit

Permalink
allow assignments to be assigned to expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
simerplaha committed Jan 14, 2025
1 parent 48afa2a commit 58b47fa
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.? ~
Expand All @@ -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
)
}

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 @@ -78,7 +93,6 @@ private object ExpressionParser {
ForLoopParser.parseOrFail |
WhileLoopParser.parseOrFail |
VariableDeclarationParser.parseOrFail |
AssignmentParser.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 @@ -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,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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>><<"))
}
}
}

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

Expand Down

0 comments on commit 58b47fa

Please sign in to comment.