Skip to content

Commit

Permalink
Merge pull request #351 from alephium/number_parser
Browse files Browse the repository at this point in the history
`NumberParser`
  • Loading branch information
simerplaha authored Jan 14, 2025
2 parents 4f71bd9 + e78775b commit 11c92e9
Show file tree
Hide file tree
Showing 7 changed files with 302 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ private object ExpressionParser {
ReferenceCallParser.parseOrFail |
AnnotationParser.parseOrFail |
TupleParser.parseOrFail |
NumberParser.parseOrFail |
BooleanParser.parseOrFail |
IdentifierParser.parseOrFail
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.alephium.ralph.lsp.access.compiler.parser.soft

import fastparse._
import fastparse.NoWhitespace.noWhitespaceImplicit
import org.alephium.ralph.lsp.access.compiler.message.SourceIndexExtra._
import org.alephium.ralph.lsp.access.compiler.parser.soft.ast.{SoftAST, Token}

object NumberParser {

def parseOrFail[Unknown: P]: P[SoftAST.Number] =
P {
Index ~
CommentParser.parseOrFail.? ~
number ~
SpaceParser.parseOrFail.? ~
TokenParser.parseOrFail(Token.AlphLowercase).? ~
Index
} map {
case (from, documentation, digits, postDigitSpace, unit, to) =>
SoftAST.Number(
index = range(from, to),
documentation = documentation,
number = digits,
space = postDigitSpace,
unit = unit
)
}

private def number[Unknown: P]: P[SoftAST.CodeString] =
P {
Index ~
isNumber ~
(!Token.AlphLowercase.lexeme ~ CharIn("[0-9a-zA-Z]._+\\-")).rep(1).! ~
Index
} map {
case (from, number, to) =>
SoftAST.CodeString(
index = range(from, to),
text = number
)
}

private def isNumber[Unknown: P]: P[Unit] =
P(&(CharIn("+\\-").? ~ CharIn("[0-9]")))

}
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,14 @@ object SoftAST {
postTupleSpace: Option[Space])
extends ExpressionAST

case class Number(
index: SourceIndex,
documentation: Option[Comments],
number: CodeString,
space: Option[Space],
unit: Option[TokenDocumented[Token.AlphLowercase.type]])
extends ExpressionAST

sealed trait SpaceAST extends SoftAST

case class Space(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ object Token {
case object False extends PrimitiveBoolean("false") with Reserved

sealed abstract class PrimitiveUnit(override val lexeme: String) extends Primitive(lexeme)
case object Alph_Small extends PrimitiveUnit("alph") with Reserved
case object Alph_Big extends PrimitiveUnit("ALPH") with Reserved
case object AlphLowercase extends PrimitiveUnit("alph") with Reserved
case object AlphUppercase extends PrimitiveUnit("ALPH") with Reserved

sealed trait Term extends Token
case class Name(lexeme: String) extends Term
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
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 NumberParserSpec extends AnyWordSpec with Matchers {

def assertSimpleNumber(number: String) =
parseNumber(number) shouldBe
Number(
index = indexOf(s">>$number<<"),
text = number
)

"integer" in {
assertSimpleNumber("10")
assertSimpleNumber("-10")
assertSimpleNumber("+10")
}

"typed" in {
assertSimpleNumber("10u")
assertSimpleNumber("10i")
assertSimpleNumber("-10u")
assertSimpleNumber("-10i")
assertSimpleNumber("+10u")
assertSimpleNumber("+10i")
}

"scientific" in {
assertSimpleNumber("1e18")
assertSimpleNumber("-1e18")
assertSimpleNumber("+1e18")
assertSimpleNumber("5.12e18")
assertSimpleNumber("-5.12e18")
assertSimpleNumber("+5.12e18")
}

"underscored" in {
assertSimpleNumber("1_000_000_000")
assertSimpleNumber("-1_000_000_000")
assertSimpleNumber("+1_000_000_000")
}

"hex" in {
assertSimpleNumber("0x12")
assertSimpleNumber("-0x12")
assertSimpleNumber("+0x12")
}

"unit" when {
"invalid" in {
assertSimpleNumber("1alp")
assertSimpleNumber("-1alp")
assertSimpleNumber("+1alp")

assertSimpleNumber("1lph")
assertSimpleNumber("-1lph")
assertSimpleNumber("+1lph")

assertSimpleNumber("1hpla")
assertSimpleNumber("-1hpla")
assertSimpleNumber("+1hpla")
}

"valid" when {
"no spaces" when {
def testWithUnit(numberOnly: String) =
parseNumber(s"${numberOnly}alph") shouldBe
SoftAST.Number(
index = indexOf(s">>${numberOnly}alph<<"),
documentation = None,
number = SoftAST.CodeString(
index = indexOf(s">>$numberOnly<<alph"),
text = numberOnly
),
space = None,
unit = Some(AlphLowercase(indexOf(s"$numberOnly>>alph<<")))
)

"no sign" in {
testWithUnit("1")
testWithUnit("5.12e18")
testWithUnit("0x12")
}

"positive" in {
testWithUnit("+1")
testWithUnit("+5.12e18")
testWithUnit("+0x12")
}

"negative" in {
testWithUnit("-1")
testWithUnit("-5.12e18")
testWithUnit("-0x12")
}
}

"with space" when {
def testWithUnit(numberOnly: String) =
parseNumber(s"$numberOnly alph") shouldBe
SoftAST.Number(
index = indexOf(s">>$numberOnly alph<<"),
documentation = None,
number = SoftAST.CodeString(
index = indexOf(s">>$numberOnly<< alph"),
text = numberOnly
),
space = Some(SpaceOne(indexOf(s"$numberOnly>> <<alph"))),
unit = Some(AlphLowercase(indexOf(s"$numberOnly >>alph<<")))
)

"no sign" in {
testWithUnit("1")
testWithUnit("5.12e18")
testWithUnit("0x12")
}

"positive" in {
testWithUnit("+1")
testWithUnit("+5.12e18")
testWithUnit("+0x12")
}

"negative" in {
testWithUnit("-1")
testWithUnit("-5.12e18")
testWithUnit("-0x12")
}
}

"scientific number" when {
"without space" when {
"valid unit" in {
parseNumber("1e-18alph") shouldBe
SoftAST.Number(
index = indexOf(s">>1e-18alph<<"),
documentation = None,
number = SoftAST.CodeString(
index = indexOf(s">>1e-18<<alph"),
text = "1e-18"
),
space = None,
unit = Some(AlphLowercase(indexOf("1e-18>>alph<<")))
)
}

"invalid unit - 'alp' is typo" in {
parseNumber("1e-18alp") shouldBe
SoftAST.Number(
index = indexOf(s">>1e-18alp<<"),
documentation = None,
number = SoftAST.CodeString(
index = indexOf(s">>1e-18alp<<"),
text = "1e-18alp"
),
space = None,
unit = None
)
}
}

"with space" when {
"valid unit" in {
parseNumber("1e-18 alph") shouldBe
SoftAST.Number(
index = indexOf(s">>1e-18 alph<<"),
documentation = None,
number = SoftAST.CodeString(
index = indexOf(s">>1e-18<< alph"),
text = "1e-18"
),
space = Some(SpaceOne(indexOf("1e-18>> <<alph"))),
unit = Some(AlphLowercase(indexOf("1e-18 >>alph<<")))
)
}

"invalid unit - 'alp' is typo" in {
val body = parseSoft("1e-18 alp")
body.parts should have size 2

val number = body.parts.head.part
val alp = body.parts.last.part

// Note: alp is not a unit. So it's not parsed as part of the number.
number shouldBe
SoftAST.Number(
index = indexOf(s">>1e-18 << alp"),
documentation = None,
number = SoftAST.CodeString(
index = indexOf(s">>1e-18<< alp"),
text = "1e-18"
),
space = Some(SpaceOne(indexOf("1e-18>> <<alp"))),
unit = None
)

// alp is stored as an identifier
alp shouldBe
Identifier(
index = indexOf(s"1e-18 >>alp<<"),
text = "alp"
)
}
}

}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ object TestParser {
def parseBoolean(code: String): SoftAST.TokenExpression[Token.PrimitiveBoolean] =
runSoftParser(BooleanParser.parseOrFail(_))(code)

def parseNumber(code: String): SoftAST.Number =
runSoftParser(NumberParser.parseOrFail(_))(code)

def findAnnotation(identifier: String)(code: String): Option[SoftAST.Annotation] =
findAnnotation(
identifier = identifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,18 @@ object TestSoftAST {
token = Token.False
)

def AlphLowercase(index: SourceIndex): SoftAST.TokenDocumented[Token.AlphLowercase.type] =
TokenDocumented(
index = index,
token = Token.AlphLowercase
)

def AlphUppercase(index: SourceIndex): SoftAST.TokenDocumented[Token.AlphUppercase.type] =
TokenDocumented(
index = index,
token = Token.AlphUppercase
)

def Identifier(
index: SourceIndex,
text: String): SoftAST.Identifier =
Expand All @@ -102,6 +114,20 @@ object TestSoftAST {
)
)

def Number(
index: SourceIndex,
text: String): SoftAST.Number =
SoftAST.Number(
index = index,
documentation = None,
number = SoftAST.CodeString(
index = index,
text = text
),
space = None,
unit = None
)

def Unresolved(
index: SourceIndex,
text: String): SoftAST.Unresolved =
Expand Down

0 comments on commit 11c92e9

Please sign in to comment.