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 committed Mar 7, 2024
1 parent a6fe036 commit 8883669
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 34 deletions.
Expand Up @@ -1096,8 +1096,9 @@ 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 @@ -1315,28 +1316,24 @@ 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 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))
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
} else {
if (value >= max) syntaxError(s"integer number too large for $typ", at = token)
value
}
val value = unary(tok.value)
if (value >= max) syntaxError(s"integer number too large for $typ", at = tok)
if (value < -max) syntaxError(s"integer number too small for $typ", at = tok)
value
}
token match {
case tok: Constant.Int =>
Expand All @@ -1353,7 +1350,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 @@ -2212,24 +2209,35 @@ 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()
def op = atPos(startPos)(Term.Name(ident))
next()
def rest(tree: Term) = simpleExprRest(tree, canApply = true, startPos = startPos)
if (op.value == "-" && token.is[NumericConstant[_]])
rest(numericLiteral(startPos, isNegated = true))
else {
val withUnary = unary match {
case unary: Unary.Numeric =>
token match {
case x: NumericConstant[_] => next(); Some(numericLiteralAt(x, unary))
case _ => None
}
case unary: Unary.Logical =>
token match {
case x: BooleanConstant => next(); Some(Lit.Boolean(unary(x.value)))
case _ => None
}
}
withUnary.fold {
simpleExpr0(allowRepeated = true) match {
case Success(result) => autoEndPos(startPos)(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)
}
}
}
}(lit => rest(autoEndPos(startPos)(lit)))
case _ => simpleExpr(allowRepeated)
}

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

Expand Down Expand Up @@ -2893,9 +2901,9 @@ 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)
if (prevTokenPos == startPos && token.is[NumericConstant[_]]) {
sidToken match {
case Unary.Numeric((_, unary)) => return numericLiteral(startPos, unary)
case _ =>
}
}
Expand Down
Expand Up @@ -271,43 +271,47 @@ class LitSuite extends ParseSuite {
}

test("unary: +1") {
runTestAssert[Stat]("+1")(Term.ApplyUnary(tname("+"), lit(1)))
runTestAssert[Stat]("+1", "1")(lit(1))
}

test("unary: -1") {
runTestAssert[Stat]("-1")(int(-1))
}

test("unary: ~1") {
runTestAssert[Stat]("~1")(Term.ApplyUnary(tname("~"), lit(1)))
runTestAssert[Stat]("~1", "-2")(lit(-2))
}

test("unary: !1") {
runTestAssert[Stat]("!1")(Term.ApplyUnary(tname("!"), lit(1)))
}

test("unary: +1.0") {
runTestAssert[Stat]("+1.0", "+1.0d")(Term.ApplyUnary(tname("+"), lit(1d)))
runTestAssert[Stat]("+1.0", "1.0d")(lit(1d))
}

test("unary: -1.0") {
runTestAssert[Stat]("-1.0", "-1.0d")(lit(-1d))
}

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

test("unary: !1.0") {
runTestAssert[Stat]("!1.0", "!1.0d")(Term.ApplyUnary(tname("!"), lit(1d)))
}

test("unary: !true") {
runTestAssert[Stat]("!true")(Term.ApplyUnary(tname("!"), lit(true)))
runTestAssert[Stat]("!true", "false")(lit(false))
}

test("unary: !false") {
runTestAssert[Stat]("!false")(Term.ApplyUnary(tname("!"), lit(false)))
runTestAssert[Stat]("!false", "true")(lit(true))
}

}

0 comments on commit 8883669

Please sign in to comment.