Skip to content

Commit

Permalink
ScalametaParser: parse numbers with unary applied
Browse files Browse the repository at this point in the history
Just like we did earlier with minus for computing negative values, lets
"absorb" any unary op into the numeric or logical constants.
  • Loading branch information
kitbellew authored and albertikm committed Mar 10, 2024
1 parent a9a23d6 commit 702942d
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 47 deletions.
Expand Up @@ -544,7 +544,6 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
case Ident(x) => pred(x)
case _ => false
}
def isUnaryOp: Boolean = isIdentAnd(token, _.isUnaryOp)
def isIdentExcept(except: String) = isIdentAnd(token, _ != except)
def isIdentOf(tok: Token, name: String) = isIdentAnd(tok, _ == name)
@inline def isStar: Boolean = isStar(token)
Expand Down Expand Up @@ -1113,8 +1112,8 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
case _: Literal =>
if (dialect.allowLiteralTypes) literal()
else syntaxError(s"$dialect doesn't support literal types", at = path())
case Ident("-") if dialect.allowLiteralTypes && tryAhead[NumericConstant[_]] =>
numericLiteral(prevTokenPos, isNegated = true)
case Unary.Numeric(unary) if dialect.allowLiteralTypes && tryAhead[NumericConstant[_]] =>
numericLiteral(prevTokenPos, unary)
case _ => pathSimpleType()
}
simpleTypeRest(autoEndPosOpt(startPos)(res), startPos)
Expand Down Expand Up @@ -1332,29 +1331,30 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
if (acceptOpt[Dot]) selectors(name) else name
}

private def numericLiteral(startPos: Int, isNegated: Boolean): Lit = {
private def numericLiteral(startPos: Int, unary: Unary.Numeric): Lit = {
val number = token.asInstanceOf[NumericConstant[_]]
next()
autoEndPos(startPos)(numericLiteralAt(number, isNegated))
autoEndPos(startPos)(numericLiteralAt(number, unary))
}

private def numericLiteralAt(token: NumericConstant[_], isNegated: Boolean): Lit = {
private def numericLiteralAt(token: NumericConstant[_], unary: Unary.Numeric): Lit = {
def getBigInt(tok: NumericConstant[BigInt], dec: BigInt, hex: BigInt, typ: String) = {
// decimal never starts with `0` as octal was removed in 2.11; "hex" includes `0x` or `0b`
// non-decimal literals allow signed overflow within unsigned range
val max = if (tok.text(0) != '0') dec else hex
// token value is always positive as it doesn't take into account a sign
val value = tok.value
if (isNegated) {
if (value > max) syntaxError(s"integer number too small for $typ", at = token)
-value
val result = unary(value)
if (result.signum < 0) {
if (value > max) syntaxError(s"integer number too small for $typ", at = tok)
} else {
if (value >= max) syntaxError(s"integer number too large for $typ", at = token)
value
if (value >= max) syntaxError(s"integer number too large for $typ", at = tok)
}
result
}
def getBigDecimal(tok: NumericConstant[BigDecimal]) =
if (isNegated) -tok.value else tok.value
unary(tok.value)
.getOrElse(syntaxError(s"bad unary op `${unary.op}` for floating-point", at = tok))
token match {
case tok: Constant.Int =>
Lit.Int(getBigInt(tok, bigIntMaxInt, bigIntMaxUInt, "Int").intValue)
Expand All @@ -1369,7 +1369,7 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
}

def literal(): Lit = atCurPosNext(token match {
case number: NumericConstant[_] => numericLiteralAt(number, false)
case number: NumericConstant[_] => numericLiteralAt(number, Unary.Noop)
case Constant.Char(value) => Lit.Char(value)
case Constant.String(value) => Lit.String(value)
case t: Constant.Symbol =>
Expand Down Expand Up @@ -2228,25 +2228,30 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
}
}

def prefixExpr(allowRepeated: Boolean): Term =
if (!isUnaryOp) simpleExpr(allowRepeated)
else {
def prefixExpr(allowRepeated: Boolean): Term = token match {
case Unary((ident, unary)) =>
val startPos = tokenPos
val op = termName()
next()
def op = atPos(startPos)(Term.Name(ident))
def addPos(tree: Term) = autoEndPos(startPos)(tree)
def rest(tree: Term) = simpleExprRest(tree, canApply = true, startPos = startPos)
if (op.value == "-" && token.is[NumericConstant[_]])
rest(numericLiteral(startPos, isNegated = true))
else {
def otherwise =
simpleExpr0(allowRepeated = true) match {
case Success(result) => addPos(Term.ApplyUnary(op, result))
case Failure(_) =>
// maybe it is not unary operator but simply an ident `trait - {...}`
// we would fail here anyway, let's try to treat it as ident
rest(op)
}
(token, unary) match {
case (tok: NumericConstant[_], unary: Unary.Numeric) =>
next(); rest(addPos(numericLiteralAt(tok, unary)))
case (tok: BooleanConstant, unary: Unary.Logical) =>
next(); rest(addPos(Lit.Boolean(unary(tok.value))))
case _ => otherwise
}
}
case _ => simpleExpr(allowRepeated)
}

def simpleExpr(allowRepeated: Boolean): Term = simpleExpr0(allowRepeated).get

Expand Down Expand Up @@ -2910,11 +2915,10 @@ class ScalametaParser(input: Input)(implicit dialect: Dialect) { parser =>
autoEndPos(startPos)(token match {
case sidToken @ (_: Ident | _: KwThis | _: Unquote) =>
val sid = stableId()
if (token.is[NumericConstant[_]]) {
sid match {
case Term.Name("-") => return numericLiteral(startPos, isNegated = true)
case _ =>
}
(token, sidToken) match {
case (_: NumericConstant[_], Unary.Numeric(unary)) if prevTokenPos == startPos =>
return numericLiteral(startPos, unary)
case _ =>
}
val targs = if (token.is[LeftBracket]) Some(super.patternTypeArgs()) else None
if (token.is[LeftParen]) {
Expand Down
39 changes: 21 additions & 18 deletions tests/shared/src/test/scala/scala/meta/tests/parsers/LitSuite.scala
Expand Up @@ -271,9 +271,9 @@ class LitSuite extends ParseSuite {
}

test("unary: +1") {
runTestAssert[Stat]("+1")(Term.ApplyUnary(tname("+"), lit(1)))
val tree = Term.ApplyUnary(tname("+"), Term.Apply(lit(1), List(lit(0))))
runTestAssert[Stat]("+1(0)")(tree)
runTestAssert[Stat]("+1", "1")(lit(1))
val tree = Term.Apply(lit(1), List(lit(0)))
runTestAssert[Stat]("+1(0)", "1(0)")(tree)
}

test("unary: -1") {
Expand All @@ -283,9 +283,9 @@ class LitSuite extends ParseSuite {
}

test("unary: ~1") {
runTestAssert[Stat]("~1")(Term.ApplyUnary(tname("~"), lit(1)))
val tree = Term.ApplyUnary(tname("~"), Term.Apply(lit(1), List(lit(0))))
runTestAssert[Stat]("~1(0)")(tree)
runTestAssert[Stat]("~1", "-2")(lit(-2))
val tree = Term.Apply(lit(-2), List(lit(0)))
runTestAssert[Stat]("~1(0)", "-2(0)")(tree)
}

test("unary: !1") {
Expand All @@ -295,9 +295,9 @@ class LitSuite extends ParseSuite {
}

test("unary: +1.0") {
runTestAssert[Stat]("+1.0", "+1.0d")(Term.ApplyUnary(tname("+"), lit(1d)))
val tree = Term.ApplyUnary(tname("+"), Term.Apply(lit(1d), List(lit(0))))
runTestAssert[Stat]("+1.0(0)", "+1.0d(0)")(tree)
runTestAssert[Stat]("+1.0", "1.0d")(lit(1d))
val tree = Term.Apply(lit(1d), List(lit(0)))
runTestAssert[Stat]("+1.0(0)", "1.0d(0)")(tree)
}

test("unary: -1.0") {
Expand All @@ -307,9 +307,12 @@ class LitSuite extends ParseSuite {
}

test("unary: ~1.0") {
runTestAssert[Stat]("~1.0", "~1.0d")(Term.ApplyUnary(tname("~"), lit(1d)))
val tree = Term.ApplyUnary(tname("~"), Term.Apply(lit(1d), List(lit(0))))
runTestAssert[Stat]("~1.0(0)", "~1.0d(0)")(tree)
def error(code: String) =
s"""|<input>:1: error: bad unary op `~` for floating-point
|~$code
| ^""".stripMargin
runTestError[Stat]("~1.0", error("1.0"))
runTestError[Stat]("~1.0(0)", error("1.0(0)"))
}

test("unary: !1.0") {
Expand All @@ -319,15 +322,15 @@ class LitSuite extends ParseSuite {
}

test("unary: !true") {
runTestAssert[Stat]("!true")(Term.ApplyUnary(tname("!"), lit(true)))
val tree = Term.ApplyUnary(tname("!"), Term.Apply(lit(true), List(lit(0))))
runTestAssert[Stat]("!true(0)")(tree)
runTestAssert[Stat]("!true", "false")(lit(false))
val tree = Term.Apply(lit(false), List(lit(0)))
runTestAssert[Stat]("!true(0)", "false(0)")(tree)
}

test("unary: !false") {
runTestAssert[Stat]("!false")(Term.ApplyUnary(tname("!"), lit(false)))
val tree = Term.ApplyUnary(tname("!"), Term.Apply(lit(false), List(lit(0))))
runTestAssert[Stat]("!false(0)")(tree)
runTestAssert[Stat]("!false", "true")(lit(true))
val tree = Term.Apply(lit(true), List(lit(0)))
runTestAssert[Stat]("!false(0)", "true(0)")(tree)
}

test("scalatest-like infix without literal") {
Expand Down
Expand Up @@ -76,13 +76,13 @@ class TreeSyntaxSuite extends scala.meta.tests.parsers.ParseSuite {
testBlockAddNL("Foo.bar")
testBlockAddNL("10")
testBlockAddNL("-10")
testBlockAddNL("~10")
testBlockAddNL("~10", "-11")
testBlockAddNL("10.0d")
testBlockAddNL("-10.0d")
testBlockAddNL("true")
testBlockAddNL("false")
testBlockAddNL("!true")
testBlockAddNL("!false")
testBlockAddNL("!true", "false")
testBlockAddNL("!false", "true")
testBlockNoNL("-{10}", "-{\n 10\n}")
testBlockAddNL("foo(bar)")
testBlockAddNL("foo[Bar]")
Expand Down

0 comments on commit 702942d

Please sign in to comment.