Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Infix operators #347

Merged
merged 1 commit into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ case object InfixCallParser {
Index ~
ExpressionParser.parseOrFailSelective(parseInfix = false, parseMethodCall = true) ~
spaceOrFail.? ~
TokenParser.OperatorOrFail ~
TokenParser.InfixOperatorOrFail ~
spaceOrFail.? ~
ExpressionParser.parse ~
Index
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import fastparse._
import fastparse.NoWhitespace.noWhitespaceImplicit
import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra.{point, range}
import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token}
import CommonParser._
import org.alephium.ralph.lsp.access.compiler.parser.soft.CommonParser._
import org.alephium.ralph.lsp.access.util.ParserUtil

private object TokenParser {

Expand Down Expand Up @@ -358,52 +359,31 @@ private object TokenParser {
/**
* Parses all reserved tokens defined in [[Token.reserved]] and returns the first match.
*/
def Reserved[Unknown: P]: P[Token.Reserved] = {
val it =
Token.reserved.iterator

val head =
P(it.next().lexeme) map {
_ =>
Token.reserved.head
}

it.foldLeft(head) {
case (parser, keyword) =>
def nextParser =
P(keyword.lexeme) map {
_ =>
keyword
}

parser | nextParser
}
def Reserved[Unknown: P]: P[Token.Reserved] =
ParserUtil.orTokenCombinator(Token.reserved.iterator)

/**
* Parses all tokens of type [[Token.InfixOperator]] and also their comments.
*
* TODO: Restrict the output type to [[SoftAST.InfixOperator]]
*/
def InfixOperatorOrFail[Unknown: P]: P[SoftAST.Operator] = {
val infixOps =
ParserUtil
.orCombinator(
items = Token.infix.iterator.filter(_ != Token.ForwardSlash), // remove forward-slash
parser = buildInfixOperatorParser(_: Token.InfixOperator)
)

// Forward-slash followed by another forward-slash is not an Operator.
// `//` is reserved as a comment prefix.
def forwardSlashOperator =
P(buildInfixOperatorParser(Token.ForwardSlash) ~ !Token.ForwardSlash.lexeme)

infixOps | forwardSlashOperator
}

def OperatorOrFail[Unknown: P]: P[SoftAST.Operator] =
P {
buildOperatorParser(Token.Or) |
buildOperatorParser(Token.And) |
buildOperatorParser(Token.GreaterThanOrEqual) |
buildOperatorParser(Token.LessThanOrEqual) |
buildOperatorParser(Token.PlusEquals) |
buildOperatorParser(Token.MinusEquals) |
buildOperatorParser(Token.Equality) |
buildOperatorParser(Token.Asterisk) |
buildOperatorParser(Token.GreaterThan) |
buildOperatorParser(Token.LessThan) |
buildOperatorParser(Token.NotEqual) |
buildOperatorParser(Token.Bar) |
buildOperatorParser(Token.Ampersand) |
buildOperatorParser(Token.Caret) |
buildOperatorParser(Token.Percent) |
buildOperatorParser(Token.Minus) |
buildOperatorParser(Token.Plus) |
// Forward-slash followed by another forward-slash is not an Operator. `//` is reserved for comment prefix.
P(buildOperatorParser(Token.ForwardSlash) ~ !Token.ForwardSlash.lexeme)
}

private def buildOperatorParser[Unknown: P](operator: Token.Operator): P[SoftAST.Operator] =
private def buildInfixOperatorParser[Unknown: P](operator: Token.InfixOperator): P[SoftAST.Operator] =
P(Index ~ CommentParser.parseOrFail.? ~ toCodeOrFail(operator.lexeme.!) ~ Index) map {
case (from, documentation, text, to) =>
SoftAST.Operator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,25 @@ package org.alephium.ralph.lsp.access.compiler.parser.soft.ast

import org.alephium.macros.EnumerationMacros

sealed trait Token { self =>
sealed trait Token extends Ordered[Token] { self =>

def lexeme: String

/**
* Order such that tokens such as:
* - `||` comes before `|`
* - `+=` comes before `+` and `=`
*/
override def compare(that: Token): Int = {
val lengthCompare =
that.lexeme.length compare this.lexeme.length

if (lengthCompare == 0)
that.lexeme compare this.lexeme
else
lengthCompare
}

}

object Token {
Expand All @@ -31,30 +46,31 @@ object Token {
*
* These tokens cannot be used as identifier [[SoftAST.Identifier]].
*/
sealed trait Reserved extends Token
sealed trait Reserved extends Token
sealed trait InfixOperator extends Token

sealed abstract class Operator(override val lexeme: String) extends Token
case object Equality extends Operator("==") with Reserved
case object GreaterThanOrEqual extends Operator(">=") with Reserved
case object PlusEquals extends Operator("+=") with Reserved
case object MinusEquals extends Operator("-=") with Reserved
case object LessThanOrEqual extends Operator("<=") with Reserved
case object NotEqual extends Operator("!=") with Reserved
case object Equality extends Operator("==") with Reserved with InfixOperator
case object GreaterThanOrEqual extends Operator(">=") with Reserved with InfixOperator
case object PlusEquals extends Operator("+=") with Reserved with InfixOperator
case object MinusEquals extends Operator("-=") with Reserved with InfixOperator
case object LessThanOrEqual extends Operator("<=") with Reserved with InfixOperator
case object NotEqual extends Operator("!=") with Reserved with InfixOperator
case object ForwardArrow extends Operator("->") with Reserved
case object Or extends Operator("||") with Reserved
case object And extends Operator("&&") with Reserved
case object Minus extends Operator("-") with Reserved
case object Plus extends Operator("+") with Reserved
case object Asterisk extends Operator("*") with Reserved
case object ForwardSlash extends Operator("/") with Reserved
case object GreaterThan extends Operator(">") with Reserved
case object LessThan extends Operator("<") with Reserved
case object Or extends Operator("||") with Reserved with InfixOperator
case object And extends Operator("&&") with Reserved with InfixOperator
case object Minus extends Operator("-") with Reserved with InfixOperator
case object Plus extends Operator("+") with Reserved with InfixOperator
case object Asterisk extends Operator("*") with Reserved with InfixOperator
case object ForwardSlash extends Operator("/") with Reserved with InfixOperator
case object GreaterThan extends Operator(">") with Reserved with InfixOperator
case object LessThan extends Operator("<") with Reserved with InfixOperator
case object Equal extends Operator("=") with Reserved
case object Exclamation extends Operator("!") with Reserved
case object Bar extends Operator("|") with Reserved
case object Ampersand extends Operator("&") with Reserved
case object Caret extends Operator("^") with Reserved
case object Percent extends Operator("%") with Reserved
case object Bar extends Operator("|") with Reserved with InfixOperator
case object Ampersand extends Operator("&") with Reserved with InfixOperator
case object Caret extends Operator("^") with Reserved with InfixOperator
case object Percent extends Operator("%") with Reserved with InfixOperator

sealed abstract class Delimiter(override val lexeme: String) extends Token
case object OpenParen extends Delimiter("(") with Reserved
Expand Down Expand Up @@ -125,26 +141,16 @@ object Token {
case class NumberLiteral(lexeme: String) extends Term
case class StringLiteral(lexeme: String) extends Term

/**
* Order such that tokens such as:
* - `||` come before `|`
* - `+=` come before `+` and `=`
*/
implicit val reservedDescendingOrdering: Ordering[Reserved] = {
case (x: Reserved, y: Reserved) =>
val lengthCompare =
y.lexeme.length compare x.lexeme.length

if (lengthCompare == 0)
y.lexeme compare x.lexeme
else
lengthCompare
}

val reserved: Array[Reserved] =
EnumerationMacros
.sealedInstancesOf[Reserved]
.toArray
.sorted

val infix: Array[InfixOperator] =
EnumerationMacros
.sealedInstancesOf[InfixOperator]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OMG didn't know we had this macro, I have few places where I could use this in the explorer-backend

.toArray
.sorted

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.alephium.ralph.lsp.access.util

import fastparse._
import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.Token

object ParserUtil {

/**
* Combines all items using the "or" combinator with the given parser.
*
* <b>Prerequisite</b>: The `items` iterator must not be empty.
*
* @param items The items to be parsed.
* @param parser The parser to execute.
* @tparam Unknown The current fastparse context.
* @tparam I The type of input items.
* @tparam O The type of output produced by the parser.
* @return A combined parser all input items using the "or" combinator.
*/
def orCombinator[Unknown: P, I, O](
items: Iterator[I],
parser: I => P[O]): P[O] =
items.nextOption() match {
case Some(head) =>
items.foldLeft(parser(head))(_ | parser(_))

case None =>
Fail("Expected nonempty items in orCombinator")
}

def orTokenCombinator[Unknown: P, T <: Token](tokens: Iterator[T]): P[T] =
orCombinator(
items = tokens,
parser = createParser(_: T)
)

@inline private def createParser[Unknown: P, T <: Token](item: T): P[T] =
P(item.lexeme) map {
_ =>
item
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ object TestParser {
def parseReservedToken(code: String): Token.Reserved =
runAnyParser(TokenParser.Reserved(_))(code)

def parseInfixOperatorOrFail(code: String): SoftAST.Operator =
runAnyParser(TokenParser.InfixOperatorOrFail(_))(code)

def parseReservedTokenOrError(code: String): Either[Parsed.Failure, Token.Reserved] =
runAnyParserOrError(TokenParser.Reserved(_))(code)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.alephium.ralph.lsp.access.compiler.parser.soft

import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra._
import org.alephium.ralph.lsp.access.compiler.parser.soft.TestParser._
import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.TestSoftAST._
import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.Token
import org.scalatest.matchers.should.Matchers._
import org.scalatest.wordspec.AnyWordSpec

class TokenParserSpec extends AnyWordSpec {

"InfixOperatorOrFail" when {
"ForwardSlash" should {
"not parse double forward slash as it is reserved for comments" in {
assertThrows[Exception](
parseInfixOperatorOrFail("//")
)
}
}

"all infix operators" should {
"succeed" in {
val infixOperators = Token.infix
infixOperators should not be empty

infixOperators foreach {
infix =>
parseInfixOperatorOrFail(infix.lexeme) shouldBe
InfixOperator(
index = range(0, infix.lexeme.length),
token = infix
)
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ object TestSoftAST {
)
)

def InfixOperator(
index: SourceIndex,
token: Token.InfixOperator): SoftAST.Operator =
SoftAST.Operator(
index = index,
documentation = None,
code = Code(
index = index,
token = token
)
)

def Contract(index: SourceIndex): SoftAST.Contract =
SoftAST.Contract(
index = index,
Expand Down
Loading