scala.meta.internal.parsers.ScalametaParser.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of parsers_native0.5_2.13 Show documentation
Show all versions of parsers_native0.5_2.13 Show documentation
Scalameta APIs for parsing and their baseline implementation
package scala.meta
package internal
package parsers
import org.scalameta._
import org.scalameta.invariants._
import scala.meta.classifiers._
import scala.meta.inputs._
import scala.meta.internal.parsers.Absolutize._
import scala.meta.internal.parsers.Location._
import scala.meta.internal.trees._
import scala.meta.parsers._
import scala.meta.prettyprinters._
import scala.meta.tokens.Token._
import scala.meta.tokens._
import scala.meta.trees.Origin
import scala.annotation.tailrec
import scala.collection.immutable._
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.language.implicitConversions
import scala.reflect.ClassTag
import scala.reflect.classTag
import scala.util.Failure
import scala.util.Success
import scala.util.Try
class ScalametaParser(input: Input)(implicit dialect: Dialect) {
parser =>
import ScalametaParser._
private val scannerTokens: ScannerTokens = ScannerTokens(input)
import scannerTokens._
/* ------------- NESTED CONTEXT OBJECTS ----------------------------------------- */
// must all be parser-specific, to avoid sharing state with other parsers
private object QuotedSpliceContext extends NestedContext
private object QuotedPatternContext extends NestedContext
private object ReturnTypeContext extends NestedContext
private object TypeBracketsContext extends NestedContext
private object PatternTypeContext extends NestedContext
private object ExtensionSigContext extends NestedContext
private object GivenSigContext extends NestedContext
/* ------------- PARSER ENTRY POINTS -------------------------------------------- */
def parseRule[T <: Tree](rule: this.type => T): T = parseRule(rule(this))
def parseRule[T <: Tree](rule: => T): T = {
// NOTE: can't require in.tokenPos to be at -1, because TokIterator auto-rewinds when created
// require(in.tokenPos == -1 && debug(in.tokenPos))
accept[BOF]
parseRuleAfterBOF(rule)
}
private def parseRuleAfterBOF[T <: Tree](rule: => T): T = {
val start = prevIndex
val t = rule
// NOTE: can't have prevTokenPos here
// because we need to subsume all the trailing trivia
val end = currIndex
accept[EOF]
atPos(start, end)(t)
}
// Entry points for Parse[T]
def parseStat(): Stat =
parseRule(if (dialect.allowUnquotes) quasiquoteStat() else entrypointStat())
def parseTerm(): Term =
parseRule(if (dialect.allowUnquotes) quasiquoteExpr() else entrypointExpr())
def parseUnquoteTerm(): Term = parseRule(unquoteExpr())
def parseTermParam(): Term.Param =
parseRule(if (dialect.allowUnquotes) quasiquoteTermParam() else entrypointTermParam())
def parseType(): Type =
parseRule(if (dialect.allowUnquotes) quasiquoteType() else entrypointType())
def parseTypeParam(): Type.Param =
parseRule(if (dialect.allowUnquotes) quasiquoteTypeParam() else entrypointTypeParam())
def parsePat(): Pat =
parseRule(if (dialect.allowUnquotes) quasiquotePattern() else entrypointPattern())
def parseUnquotePat(): Pat = parseRule(unquotePattern())
def parseCase(): Case =
parseRule(if (dialect.allowUnquotes) quasiquoteCase() else entrypointCase())
def parseCtor(): Ctor =
parseRule(if (dialect.allowUnquotes) quasiquoteCtor() else entrypointCtor())
def parseInit(): Init =
parseRule(if (dialect.allowUnquotes) quasiquoteInit() else entrypointInit())
def parseSelf(): Self =
parseRule(if (dialect.allowUnquotes) quasiquoteSelf() else entrypointSelf())
def parseTemplate(): Template =
parseRule(if (dialect.allowUnquotes) quasiquoteTemplate() else entrypointTemplate())
def parseMod(): Mod =
parseRule(if (dialect.allowUnquotes) quasiquoteModifier() else entrypointModifier())
def parseEnumerator(): Enumerator =
parseRule(if (dialect.allowUnquotes) quasiquoteEnumerator() else entrypointEnumerator())
def parseImporter(): Importer =
parseRule(if (dialect.allowUnquotes) quasiquoteImporter() else entrypointImporter())
def parseImportee(): Importee =
parseRule(if (dialect.allowUnquotes) quasiquoteImportee() else entrypointImportee())
def parseSource(): Source = parseRule(parseSourceImpl())
private def parseSourceImpl(): Source =
if (dialect.allowUnquotes) quasiquoteSource() else entrypointSource()
def parseAmmonite(): MultiSource = parseRule(entryPointAmmonite())
def entryPointAmmonite(): MultiSource = {
assert(input.isInstanceOf[Input.Ammonite], s"Expected Input.Ammonite, not ${input.getClass}")
val builder = List.newBuilder[Source]
doWhile(builder += parseRuleAfterBOF(parseSourceImpl())) {
currToken match {
case t: Token.EOF if t.end < input.chars.length =>
in.next()
accept[Token.At]
accept[Token.BOF]
true
case _ => false
}
}
MultiSource(builder.result())
}
/* ------------- TOKEN STREAM HELPERS -------------------------------------------- */
@inline
private def nextIfColonIndent(): Boolean = at[Colon] && nextIfIndentAhead()
@inline
private def nextIfIndentAhead(): Boolean = tryAhead[Indentation.Indent]
/* ------------- PARSER-SPECIFIC TOKENS -------------------------------------------- */
var in: TokenIterator = LazyTokenIterator(scannerTokens)
@inline
def currIndex = in.currIndex
@inline
def prevIndex = in.prevIndex
@inline
def currToken = in.currToken
@inline
def prevToken = in.prevToken
@inline
def peekIndex = in.peekIndex
@inline
def peekToken = in.peekToken
def next() = {
in.next()
currToken match {
case t: Token.Invalid => reporter.syntaxError(t.error, at = t)
case _ =>
}
}
def nextTwice() = { next(); next() }
@inline
private def nextIf(cond: Boolean): Boolean = {
if (cond) next()
cond
}
/* ------------- PARSER COMMON -------------------------------------------- */
/**
* Scoping operator used to temporarily look into the future. Backs up token iterator before
* evaluating a block and restores it after.
*/
@inline
final def ahead[T](body: => T): T = {
val forked = in.fork
try next(body)
finally in = forked
}
@inline
private def tryAhead[T: ClassTag]: Boolean = nextIf(peek[T])
@inline
private def tryAheadNot[T: ClassTag]: Boolean = nextIf(!peek[T])
private def unreachable(debuggees: Map[String, Any]): Nothing = UnreachableError.raise(debuggees)
private def unreachable(tok: Token): Nothing = unreachable(Map("token" -> tok))
private def unreachable: Nothing = unreachable(Map.empty[String, Any])
private def tryAhead(cond: => Boolean): Boolean = {
val forked = in.fork
next()
val ok = cond
if (!ok) in = forked
ok
}
private def tryParse[A](bodyFunc: => Option[A]): Option[A] = {
val forked = in.fork
val body = bodyFunc
if (body.isEmpty) in = forked
body
}
private def tryAhead[A](bodyFunc: => Option[A]): Option[A] = tryParse(next(bodyFunc))
/** evaluate block after shifting next */
@inline
private def next[T](body: => T): T = {
next()
body
}
@inline
final def inParens[T](body: => T): T = {
accept[LeftParen]
inParensAfterOpen(body)
}
@inline
final def inParensOr[T](body: => T)(ifEmpty: => T): T = {
accept[LeftParen]
inParensAfterOpenOr(body)(ifEmpty)
}
@inline
private def inParensOnOpen[T](body: => T): T = {
next()
inParensAfterOpen(body)
}
@inline
private def inParensOnOpenOr[T](body: => T)(ifEmpty: => T): T = {
next()
inParensAfterOpenOr(body)(ifEmpty)
}
@inline
private def inParensAfterOpen[T](body: T): T = {
acceptAfterOptNL[RightParen]
body
}
@inline
private def inParensAfterOpenOr[T](body: => T)(ifEmpty: => T): T =
if (acceptOpt[RightParen]) ifEmpty else inParensAfterOpen(body)
@inline
final def inBraces[T](body: => T): T = inBracesOr(body)(syntaxErrorExpected[LeftBrace])
@inline
final def inBracesOr[T](body: => T)(ifEmpty: => T): T = {
newLineOpt()
if (acceptOpt[LeftBrace]) inBracesAfterOpen(body) else ifEmpty
}
@inline
private def inBracesOnOpen[T](body: => T): T = {
next()
inBracesAfterOpen(body)
}
@inline
private def inBracesAfterOpen[T](body: T): T = {
acceptAfterOptNL[RightBrace]
body
}
@inline
final def indented[T](body: => T): T = {
accept[Indentation.Indent]
indentedAfterOpen(body)
}
@inline
private def indentedOnOpen[T](body: => T): T = {
next()
indentedAfterOpen(body)
}
@inline
private def indentedAfterOpen[T](body: T): T = {
acceptAfterOptNL[Indentation.Outdent]
body
}
private def indentedOr[T](body: => T)(orElse: => T): T =
if (acceptOpt[Indentation.Indent]) indentedAfterOpen(body) else orElse
@inline
private def maybeIndented[T](body: => T): T = indentedOr(body)(body)
@inline
final def inBracesOrNil[T](body: => List[T]): List[T] = inBracesOr(body)(Nil)
@inline
final def dropAnyBraces[T](body: => T): T = inBracesOr(body)(body)
@inline
final def inBrackets[T](body: => T): T = {
accept[LeftBracket]
inBracketsAfterOpen(body)
}
@inline
private def inBracketsOnOpen[T](body: => T): T = {
next()
inBracketsAfterOpen(body)
}
@inline
private def inBracketsAfterOpen[T](body: T): T = {
accept[RightBracket]
body
}
/* ------------- POSITION HANDLING ------------------------------------------- */
case object AutoPos extends Pos {
def begIndex = currIndex
def endIndex = prevIndex
}
implicit def intToIndexPos(index: Int): Pos = new IndexPos(index)
implicit def treeToTreePos(tree: Tree): Pos = new TreePos(tree)
implicit def optionTreeToPos(tree: Option[Tree]): Pos = tree.fold[Pos](AutoPos)(treeToTreePos)
implicit def modsToPos(mods: List[Mod]): Pos = mods.headOption
def atPos[T <: Tree](start: StartPos, end: EndPos)(body: => T): T =
atPos(start.begIndex, end)(body)
def atPosIf[T <: Tree](start: StartPos, end: EndPos)(body: => Option[T]): Option[T] =
atPosIf(start.begIndex, end)(body)
def atPosOpt[T <: Tree](start: StartPos, end: EndPos)(body: => T): T =
atPosOpt(start.begIndex, end)(body)
@inline
def atPos[T <: Tree](start: Int, end: EndPos)(body: T): T =
atPosWithBody(start, body, end.endIndex)
def atPosIf[T <: Tree](start: Int, end: EndPos)(body: Option[T]): Option[T] = body
.map(atPos(start, end))
def atPosOpt[T <: Tree](start: Int, end: EndPos)(body: T): T = body.origin match {
case o: Origin.Parsed if o.source eq originSource => body
case _ => atPos(start, end)(body)
}
def atPos[T <: Tree](pos: Int)(body: => T): T = atPosWithBody(pos, body, pos)
def atCurPos[T <: Tree](body: => T): T = atPos(currIndex)(body)
def atCurPosNext[T <: Tree](body: => T): T =
try atCurPos(body)
finally next()
def atPosEmpty[T <: Tree](pos: Int)(body: => T): T = atPosWithBody(pos, body, pos - 1)
def atPosEmpty[T <: Tree](pos: StartPos)(body: => T): T = atPosEmpty(pos.begIndex)(body)
def atCurPosEmpty[T <: Tree](body: => T): T = atPosEmpty(currIndex)(body)
def atCurPosEmptyNext[T <: Tree](body: => T): T =
try atCurPosEmpty(body)
finally next()
private val originSource = new Origin.ParsedSource(input)
def atPosWithBody[T <: Tree](startPos: Int, body: T, endPos: Int): T = {
def getPosRange(): (Int, Int) = { // uses "return"
if (endPos < startPos) return (startPos, startPos)
val endExcl = endPos + 1
val nonSpaceEnd = tokens.rskipIf(_.is[Whitespace], endPos, startPos - 1)
if (nonSpaceEnd < startPos) return (startPos, if (endPos == startPos) endPos else endExcl)
val start = tokens.skipIf(_.is[Trivia], startPos, endExcl)
if (start > endPos) return (startPos, nonSpaceEnd + 1)
if (!tokens(nonSpaceEnd).is[Comment]) return (start, nonSpaceEnd + 1)
val end = tokens.rskipIf(_.is[HTrivia], nonSpaceEnd - 1, start)
(start, (if (tokens(end).is[AtEOLorF]) nonSpaceEnd else end) + 1)
}
val (start, endExcl) = getPosRange()
body.withOrigin(Origin.Parsed(originSource, start, endExcl))
}
def atPosTry[T <: Tree](start: StartPos, end: EndPos)(body: => Try[T]): Try[T] = {
val startTokenPos = start.begIndex
body.map(atPos(startTokenPos, end))
}
def atPosTryOpt[T <: Tree](start: StartPos, end: EndPos)(body: => Try[T]): Try[T] = {
val startTokenPos = start.begIndex
body.map(atPosOpt(startTokenPos, end))
}
def autoPos[T <: Tree](body: => T): T = atPos(start = AutoPos, end = AutoPos)(body)
def autoPosIf[T <: Tree](body: => Option[T]): Option[T] =
atPosIf(start = AutoPos, end = AutoPos)(body)
def autoPosOpt[T <: Tree](body: => T): T = atPosOpt(start = AutoPos, end = AutoPos)(body)
@inline
def autoEndPos[T <: Tree](start: Int)(body: => T): T = atPos(start = start, end = AutoPos)(body)
@inline
def autoEndPosOpt[T <: Tree](start: Int)(body: => T): T =
atPosOpt(start = start, end = AutoPos)(body)
@inline
def autoEndPos[T <: Tree](start: StartPos)(body: => T): T = autoEndPos(start.begIndex)(body)
@inline
def autoEndPosOpt[T <: Tree](start: StartPos)(body: => T): T = autoEndPosOpt(start.begIndex)(body)
@inline
def autoPrevPos[T <: Tree](body: => T) = autoEndPos(prevIndex)(body)
def autoPosTry[T <: Tree](body: => Try[T]): Try[T] = atPosTry(start = AutoPos, end = AutoPos)(body)
/* ------------- ERROR HANDLING ------------------------------------------- */
final lazy val reporter = Reporter()
import this.reporter._
implicit class XtensionToken(tok: Token) {
def is[T: ClassTag] = classTag[T].runtimeClass.isAssignableFrom(tok.getClass())
def as[T <: AnyRef: ClassTag]: T = (if (is[T]) tok else null).asInstanceOf[T]
def isAny[A: ClassTag, B: ClassTag] = is[A] || is[B]
def isAny[A: ClassTag, B: ClassTag, C: ClassTag] = is[A] || is[B] || is[C]
}
@inline
private def at[T: ClassTag]: Boolean = currToken.is[T]
@inline
private def at[A: ClassTag, B: ClassTag]: Boolean = currToken.isAny[A, B]
@inline
private def at[A: ClassTag, B: ClassTag, C: ClassTag]: Boolean = currToken.isAny[A, B, C]
@inline
private def peek[T: ClassTag]: Boolean = peekToken.is[T]
@inline
private def peek[A: ClassTag, B: ClassTag]: Boolean = peekToken.isAny[A, B]
@inline
private def prev[T: ClassTag]: Boolean = prevToken.is[T]
private def syntaxErrorExpected[T <: Token: ClassTag]: Nothing = syntaxErrorExpected[T](currToken)
private def syntaxErrorExpected[T <: Token: ClassTag](tok: Token): Nothing =
syntaxError(syntaxExpectedMessage[T](tok), at = tok)
private def expectAt[T <: Token: ClassTag](
tok: Token,
exists: Boolean
)(msg: => String, prefix: => String): Unit = if (tok.is[T] != exists)
syntaxError(Option(prefix).filter(_.nonEmpty).fold(msg)(p => s"$p: $msg"), at = tok)
@inline
private def expectAt[T <: Token: ClassTag](tok: Token, prefix: => String = ""): Unit =
expectAt[T](tok, exists = true)(msg = syntaxExpectedMessage[T](tok), prefix = prefix)
@inline
private def expect[T <: Token: ClassTag]: Unit = expectAt[T](currToken)
@inline
private def expect[T <: Token: ClassTag](prefix: => String): Unit =
expectAt[T](currToken, prefix = prefix)
@inline
private def expectNotAt[T <: Token: ClassTag](tok: Token, prefix: => String = ""): Unit =
expectAt[T](tok, exists = false)(msg = syntaxNotExpectedMessage[T], prefix = prefix)
@inline
private def expectNot[T <: Token: ClassTag]: Unit = expectNotAt[T](currToken)
@inline
private def expectNot[T <: Token: ClassTag](prefix: => String): Unit =
expectNotAt[T](currToken, prefix = prefix)
/** Consume one token of the specified type, or signal an error if it is not there. */
def accept[T <: Token: ClassTag]: Unit = {
expect[T]
if (!at[EOF]) next()
}
/** If current token is T consume it. */
@inline
private def acceptOpt[T: ClassTag]: Boolean = nextIf(at[T])
private def acceptAs[T <: AnyRef: ClassTag]: T = {
val res = currToken.as[T]
if (res ne null) next()
res
}
/** If current token is T consume it. */
@inline
private def acceptIf(unapply: Token => Boolean): Boolean = nextIf(unapply(currToken))
private def acceptIfAfterOpt(unapplyA: Token => Boolean, unapplyB: Token => Boolean): Boolean =
if (unapplyA(currToken)) { next(); true }
else if (unapplyB(currToken) && unapplyA(peekToken)) { nextTwice(); true }
else false
private def acceptIfAfterOpt[A <: Token: ClassTag](unapply: Token => Boolean): Boolean =
acceptIfAfterOpt(_.is[A], unapply)
private def acceptIfAfterOpt[A <: Token: ClassTag, B <: Token: ClassTag]: Boolean =
acceptIfAfterOpt[A](_.is[B])
@inline
private def acceptIfAfterOptNL[T <: Token: ClassTag]: Boolean = acceptIfAfterOpt[T, AtEOL]
@inline
private def acceptAfterOpt[A <: Token: ClassTag, B <: Token: ClassTag]: Unit = {
if (at[B]) next()
accept[A]
}
@inline
private def acceptAfterOptNL[T <: Token: ClassTag]: Unit = acceptAfterOpt[T, AtEOL]
def isAtEndMarker(): Boolean = isEndMarkerIntro(currToken, peekIndex)
@inline
def acceptIfStatSep(): Boolean = acceptIf(StatSep)
@inline
def isImplicitStatSep(): Boolean = prev[Indentation.Outdent]
def acceptStatSep(): Boolean = {
val ok = acceptIfStatSep() || isAtEndMarker() || isImplicitStatSep() && !at[Indentation.Outdent]
if (!ok) syntaxErrorExpected[Semicolon]
ok
}
def acceptStatSepOpt() = !StatSeqEnd(currToken) && acceptStatSep()
def skipAllStatSep(): Boolean = {
val ok = acceptIfStatSep()
while (acceptIfStatSep()) {}
ok
}
/* -------------- TOKEN CLASSES ------------------------------------------- */
@inline
def isStar: Boolean = isStar(currToken)
def isStar(tok: Token): Boolean = Keywords.Star(tok)
private trait MacroIdent {
protected def ident(tok: Token): Option[String]
final def unapply(tok: Token): Option[String] =
if (dialect.allowSpliceAndQuote && QuotedSpliceContext.isInside()) ident(tok) else None
}
private object MacroSplicedIdent extends MacroIdent {
protected def ident(tok: Token): Option[String] = tok match {
case Keywords(value) if value.length > 1 && value.charAt(0) == '$' => Some(value.substring(1))
case _ => None
}
}
private object MacroQuotedIdent extends MacroIdent {
protected def ident(tok: Token): Option[String] = tok match {
case Constant.Symbol(value) => Some(value.name)
case _ => None
}
}
private object InfixTypeIdent {
def unapply(tok: Token.Ident): Boolean = tok.text match {
case soft.KwPureFunctionLikeArrow() => false
case "*" => // we assume that this is a type specification for a vararg parameter
peekToken match {
case _: RightParen | _: Comma | _: Equals | _: RightBrace | _: EOF => false
case _ => true
}
case _ => true
}
}
/* ---------- TREE CONSTRUCTION ------------------------------------------- */
private def listBy[T](f: ListBuffer[T] => Unit): List[T] = {
val buf = new ListBuffer[T]
f(buf)
buf.toList
}
def ellipsis[T <: Tree: AstInfo: ClassTag](ell: Ellipsis, rank: Int, extraSkip: => Unit = {}): T = {
if (ell.rank != rank) syntaxError(Messages.QuasiquoteRankMismatch(ell.rank, rank), at = ell)
ellipsis(ell, extraSkip)
}
def ellipsis[T <: Tree: AstInfo: ClassTag](ell: Ellipsis): T with Quasi = ellipsis(ell, {})
def ellipsis[T <: Tree: AstInfo: ClassTag](ell: Ellipsis, extraSkip: => Unit): T with Quasi =
autoPos {
if (!dialect.allowUnquotes) syntaxError(s"$dialect doesn't support ellipses", at = ell)
next()
extraSkip
// unquote returns a rank=0 quasi tree
val tree = currToken match {
case _: LeftParen => inParensOnOpen(unquote[T])
case _: LeftBrace => inBracesOnOpen(unquote[T])
case t: Unquote => unquote[T](t)
case t => syntaxError(s"$$, ( or { expected but ${t.name} found", at = t)
}
// NOTE: In the case of an unquote nested directly under ellipsis, we get a bit of a mixup.
// Unquote's pt may not be directly equal unwrapped ellipsis's pt, but be its refinement instead.
// For example, in `new { ..$stats }`, ellipsis's pt is List[Stat], but quasi's pt is Term.
// This is an artifact of the current implementation, so we just need to keep it mind and work around it.
assert(
classTag[T].runtimeClass.isAssignableFrom(tree.pt),
s"ellipsis: $ell,\ntree: $tree,\nstructure: ${tree.structure}"
)
quasi[T](ell.rank, tree)
}
private def unquote[T <: Tree: AstInfo](unquote: Token): T with Quasi = {
assert(unquote.input.chars(unquote.start + 1) != '$', "Expected unquote to start with $")
val unquoteDialect = dialect.unquoteParentDialect
if (null eq unquoteDialect) syntaxError(s"$dialect doesn't support unquotes", at = unquote)
// NOTE: I considered having Input.Slice produce absolute positions from the get-go,
// but then such positions wouldn't be usable with Input.Slice.chars.
val unquotedTree = atCurPosNext {
try {
val unquoteInput = Input.Slice(input, unquote.start + 1, unquote.end)
val unquoteParser = new ScalametaParser(unquoteInput)(unquoteDialect)
if (dialect.allowTermUnquotes) unquoteParser.parseUnquoteTerm()
else if (dialect.allowPatUnquotes) unquoteParser.parseUnquotePat()
else unreachable
} catch { case ex: Exception => throw ex.absolutize }
}
copyPos(unquotedTree)(quasi[T](0, unquotedTree))
}
def unquote[T <: Tree: AstInfo]: T with Quasi = currToken match {
case t: Unquote => unquote[T](t)
case t => unreachable(t)
}
def unquoteOpt[T <: Tree: AstInfo]: Option[T with Quasi] = currToken match {
case t: Unquote => Some(unquote[T](t))
case _ => None
}
def unquoteOpt[T <: Tree: AstInfo](pred: => Boolean): Option[T with Quasi] = currToken match {
case t: Unquote if pred => Some(unquote[T](t))
case _ => None
}
final def tokenSeparated[Sep: ClassTag, T <: Tree: AstInfo: ClassTag](
sepFirst: Boolean,
part: Int => T
): List[T] = listBy[T] { ts =>
@tailrec
def iter(sep: Boolean): Unit = currToken match {
case t: Ellipsis =>
ts += ellipsis[T](t, 1)
iter(false)
case _ if sep =>
ts += part(ts.length)
iter(false)
case _ if acceptOpt[Sep] => iter(true)
case _ =>
}
iter(!sepFirst)
}
@inline
final def commaSeparated[T <: Tree: AstInfo: ClassTag](part: => T): List[T] =
commaSeparatedWithIndex(_ => part)
@inline
final def commaSeparatedWithIndex[T <: Tree: AstInfo: ClassTag](part: Int => T): List[T] =
tokenSeparated[Comma, T](sepFirst = false, part)
private def makeTuple[A <: Tree](lpPos: Int, body: List[A], zero: => A, tuple: List[A] => A)(
single: A => Either[List[A], A]
): A = body match {
case Nil => autoEndPos(lpPos)(zero)
case (q: Quasi) :: Nil if q.rank == 1 => copyPos(q)(tuple(body))
case t :: Nil => single(t) match {
case Right(x) => x
case Left(x) => autoEndPos(lpPos)(tuple(x))
}
case _ => autoEndPos(lpPos)(tuple(body))
}
private def makeTupleTerm(
single: Term => Either[List[Term], Term]
)(lpPos: Int, body: List[Term]): Term =
makeTuple(lpPos, body, Lit.Unit(), Term.Tuple.apply)(single)
private def makeTupleTerm(lpPos: Int, body: List[Term]): Term =
makeTupleTerm(Right(_))(lpPos, body)
private def makeTupleType(lpPos: Int, body: List[Type], zero: => Type, wrap: Boolean): Type =
makeTuple(lpPos, body, zero, Type.Tuple.apply) {
maybeAnonymousLambda(_) match {
case t: Type.Tuple if wrap && t.args.lengthCompare(1) > 0 => Left(t :: Nil)
case t => Right(t)
}
}
private def makeTupleType(lpPos: Int, body: List[Type]): Type = {
def invalidLiteralUnitType =
syntaxError("illegal literal type (), use Unit instead", at = currToken.pos)
makeTupleType(lpPos, body, invalidLiteralUnitType, wrap = false)
}
private def inParensOrTupleOrUnitExpr(allowRepeated: Boolean): Term = {
val lpPos = currIndex
val maybeTupleArgs = inParensOnOpenOr(
commaSeparated(expr(location = PostfixStat, allowRepeated = allowRepeated))
)(Nil)
if (maybeTupleArgs.lengthCompare(1) > 0) maybeTupleArgs.foreach {
case arg: Term.Repeated =>
syntaxError("repeated argument not allowed here", at = arg.tokens.last)
case _ =>
}
makeTupleTerm(x => Right(maybeAnonymousFunction(x)))(lpPos, maybeTupleArgs)
}
/* -------- IDENTIFIERS AND LITERALS ------------------------------------------- */
/**
* Methods which implicitly propagate the context in which they were called: either in a pattern
* context or not. Formerly, this was threaded through numerous methods as boolean isPattern.
*/
trait PatternContextSensitive {
private def tupleInfixType(allowFunctionType: Boolean = true): Type = autoPosOpt {
// NOTE: This is a really hardcore disambiguation caused by introduction of Type.Method.
// We need to accept `(T, U) => W`, `(x: T): x.U` and also support unquoting.
var hasImplicits = false
def modOrType(modsBuf: mutable.Builder[Mod, List[Mod]]): Type = currToken match {
case _: KwImplicit if !hasImplicits && allowFunctionType =>
next()
hasImplicits = true
null
case soft.KwErased() if allowFunctionType =>
modsBuf += atCurPosNext(Mod.Erased())
null
case _ =>
val mods = modsBuf.result()
val tpe = maybeParamType(allowFunctionType)
if (mods.isEmpty) tpe else autoEndPos(mods.head)(Type.FunctionArg(mods, tpe))
}
@tailrec
def paramOrType(modsBuf: mutable.Builder[Mod, List[Mod]]): Type =
namedTypeOpt(modsBuf.result(), allowFunctionType = allowFunctionType) match {
case Some(x) => x
case None =>
val x = modOrType(modsBuf)
if (x ne null) x else paramOrType(modsBuf)
}
val openParenPos = currIndex
// NOTE: can't have this, because otherwise we run into #312
// newLineOptWhenFollowedBy[LeftParen]
def makeTuple(ts: List[Type]) = makeTupleType(openParenPos, ts)
val ts = {
val ts = inParensOr(commaSeparated(paramOrType(List.newBuilder[Mod])))(Nil)
var hasTypes = false
var hasParams = false
ts.foreach {
case _: Quasi =>
case _: Type.TypedParam => hasParams = true
case _ => hasTypes = true
}
if (hasTypes && hasParams)
syntaxError("can't mix function type and dependent function type syntaxes", at = currToken)
if (hasParams && !dialect.allowDependentFunctionTypes)
syntaxError("dependent function types are not supported", at = currToken)
if (!hasTypes && !hasImplicits && at[LeftParen]) {
val message = "can't have multiple parameter lists in function types"
syntaxError(message, at = currToken)
}
withTypeCapturesOpt(openParenPos, needCaret = true)(makeTuple(ts)).fold(ts)(_ :: Nil)
}
def maybeFunc = getAfterOptNewLine(typeFuncOnArrowOpt(openParenPos, ts))
(if (allowFunctionType) maybeFunc else None).getOrElse {
val simple = simpleTypeRest(makeTuple(ts), openParenPos)
val compound = compoundTypeRest(annotTypeRest(simple, openParenPos), openParenPos)
infixTypeRest(compound) match {
case `compound` => compound
case t => autoEndPos(openParenPos)(t)
}
}
}
private def typeLambdaOrPoly(): Type = {
val quants = typeParamClauseOpt(ownerIsType = true)
newLineOpt()
currToken match {
case _: TypeLambdaArrow => next(); Type.Lambda(quants, typeIndentedOpt())
case t: RightArrow =>
next()
typeIndentedOpt() match {
case tpe: Type.FunctionType => Type.PolyFunction(quants, tpe)
case _ => syntaxError("polymorphic function types must have a value parameter", at = t)
}
case t => syntaxError("expected =>> or =>", at = t)
}
}
def typeIndentedOpt(): Type = maybeIndented(typ())
def typ(inParam: Boolean = false): Type = autoPosOpt {
val startPos = currIndex
val t: Type =
if (at[LeftBracket] && dialect.allowTypeLambdas) typeLambdaOrPoly() else infixTypeOrTuple()
getAfterOptNewLine(currToken match {
case _: KwForsome => Some(existentialTypeOnForSome(t))
case _: KwMatch if dialect.allowTypeMatch => next(); Some(Type.Match(t, typeCaseClauses()))
case _ => typeFuncOnArrowOpt(startPos, t :: Nil, inParam = inParam)
}).getOrElse(t)
}
private def paramValueType(allowRepeated: Boolean = false): Type = {
val startPos = currIndex
def withRepeated(t: Type) =
if (allowRepeated && isStar) { next(); autoEndPos(startPos)(Type.Repeated(t)) }
else t
def atInto() = {
val intoPos = currIndex
val mod = atPos(intoPos)(Mod.Into())
next()
autoEndPos(intoPos)(Type.FunctionArg(mod :: Nil, typ(inParam = true)))
}
currToken match {
case soft.KwInto() => atInto()
case _: LeftParen if nextIf(soft.KwInto.matches(peekToken)) =>
withRepeated(inParensAfterOpen(atInto()))
case _ => withRepeated(typ(inParam = true))
}
}
def paramType(): Type = {
def byNameParamValueType =
paramValueType(allowRepeated = dialect.allowByNameRepeatedParameters)
currToken match {
case _: RightArrow => autoPos { next(); Type.ByName(byNameParamValueType) }
case soft.KwPureFunctionArrow() =>
val startPos = currIndex
next()
withTypeCaptures(startPos)(autoEndPos(startPos)(Type.PureByName(byNameParamValueType)))
case _ => paramValueType(allowRepeated = true)
}
}
private def maybeParamType(allowFunctionType: Boolean): Type =
if (allowFunctionType) paramType() else typ()
private def namedTypeOpt(
fmods: => List[Mod],
allowFunctionType: Boolean
): Option[Type.TypedParam] = currToken match {
case t: Ident if peek[Colon] =>
val startPos = currIndex
nextTwice() // skip ident and colon
val mods = fmods
val modPos = if (mods.isEmpty) startPos else mods.head.begIndex
val name = atPos(startPos)(Type.Name(t.value))
val tpe = maybeParamType(allowFunctionType)
Some(autoEndPos(modPos)(Type.TypedParam(name, tpe, mods)))
case _ => None
}
def typeBlock(): Type =
// TypeBlock, https://dotty.epfl.ch/docs/internals/syntax.html#expressions-3
if (dialect.allowQuotedTypeVariables && at[KwType]) autoPos {
val typeDefs = listBy[Stat.TypeDef] { buf =>
doWhile(buf += typeDefOrDcl(Nil))(acceptIfStatSep() && at[KwType])
}
Type.Block(typeDefs, typ())
}
else typ()
private def typeCaptures(needCaret: Boolean, allowCaptures: Boolean): Option[List[Term.Ref]] =
if (allowCaptures && dialect.allowCaptureChecking) {
def captures(): Option[List[Term.Ref]] = Some(commaSeparated(path()))
if (needCaret) currToken match {
case Token.Ident("^") => next(); inBracesOr(captures())(Some(Nil))
case _ => None
}
else inBracesOr(captures())(None)
} else None
private def withTypeCapturesOpt(startPos: Int, needCaret: Boolean, allowCaptures: Boolean = true)(
capturedType: => Type
): Option[Type] = typeCaptures(needCaret = needCaret, allowCaptures = allowCaptures)
.map(captures => autoEndPos(startPos)(Type.Capturing(capturedType, captures)))
private def withTypeCaptures(
startPos: Int,
needCaret: Boolean = false,
allowCaptures: Boolean = true
)(capturedType: => Type): Type =
withTypeCapturesOpt(startPos, needCaret = needCaret, allowCaptures = allowCaptures)(
capturedType
).getOrElse(capturedType)
private def typeFuncOnArrow(
paramPos: Int,
params: List[Type],
allowCaptures: Boolean = false,
inParam: Boolean = false
)(ctor: (Type.FuncParamClause, Type) => Type): Type = {
val funcParams = autoEndPos(paramPos)(params.reduceWith(Type.FuncParamClause.apply))
next()
withTypeCaptures(paramPos, allowCaptures = allowCaptures) {
ctor(funcParams, maybeIndented(if (inParam) paramValueType() else typ()))
}
}
private def typeFuncOnArrowOpt(
paramPos: Int,
params: List[Type],
inParam: Boolean = false
): Option[Type] = {
def func(caps: Boolean = true)(ctor: (Type.FuncParamClause, Type) => Type): Option[Type] =
Some(typeFuncOnArrow(paramPos, params, inParam = inParam, allowCaptures = caps)(ctor))
currToken match {
case _: RightArrow => func(caps = false)(Type.Function(_, _))
case _: ContextArrow => func(caps = false)(Type.ContextFunction(_, _))
case t: Ident => t.text match {
case soft.KwPureFunctionArrow() => func()(Type.PureFunction(_, _))
case soft.KwPureContextFunctionArrow() => func()(Type.PureContextFunction(_, _))
case _ => None
}
case _ => None
}
}
private def typeCaseClauses(): Type.CasesBlock = autoPos {
def cases() = listBy[TypeCase] { allCases =>
while (at[KwCase]) {
allCases += autoPos {
next()
val pat = infixTypeOrTuple(inMatchType = true)
accept[RightArrow]
TypeCase(pat, typeIndentedOpt())
}
newLinesOpt()
}
}
(if (at[Indentation.Indent]) indentedOnOpen(cases()) else inBraces(cases()))
.reduceWith(Type.CasesBlock.apply)
}
def quasiquoteType(): Type = entrypointType()
def entrypointType(): Type = paramType()
private def typeArg(): Type = currToken match {
case t: Ident if peek[Equals] =>
val startPos = currIndex
nextTwice() // skip ident and equals
val name = atPos(startPos)(Type.Name(t.value))
autoEndPos(startPos)(Type.Assign(name, typ()))
case _ => typ()
}
def typeArgsInBrackets(): Type.ArgClause = TypeBracketsContext
.within(autoPos(inBrackets(commaSeparated(typeArg())).reduceWith(Type.ArgClause.apply)))
def infixTypeOrTuple(inMatchType: Boolean = false): Type =
if (at[LeftParen]) tupleInfixType(allowFunctionType = !inMatchType)
else infixType(inMatchType = inMatchType)
@inline
def infixType(inMatchType: Boolean = false, inGivenSig: Boolean = false): Type =
maybeAnonymousLambda(infixTypeRest(
compoundType(inMatchType = inMatchType, inGivenSig = inGivenSig),
inMatchType = inMatchType,
inGivenSig = inGivenSig
))
@inline
private def infixTypeRest(
t: Type,
inMatchType: Boolean = false,
inGivenSig: Boolean = false
): Type =
if (dialect.useInfixTypePrecedence)
infixTypeRestWithPrecedence(t, inMatchType = inMatchType, inGivenSig = inGivenSig)
else infixTypeRestWithMode(t, identity, inMatchType = inMatchType, inGivenSig = inGivenSig)(
InfixMode.FirstOp,
t.begIndex
)
@tailrec
private final def infixTypeRestWithMode(
t: Type,
f: Type => Type,
inMatchType: Boolean = false,
inGivenSig: Boolean = false
)(mode: InfixMode.Value, startPos: Int): Type = {
val ok = currToken match {
case _: Unquote | InfixTypeIdent() => true
case _ => false
}
if (ok) {
val op = typeName()
val leftAssoc = op.isLeftAssoc
if (mode != InfixMode.FirstOp) checkAssoc(op, leftAssoc, mode == InfixMode.LeftOp)
newLineOptWhenFollowedBy(TypeIntro)
val typ = compoundType(inMatchType = inMatchType, inGivenSig = inGivenSig)
def mkOp(t1: Type) = atPos(startPos, t1)(Type.ApplyInfix(t, op, t1))
if (leftAssoc) infixTypeRestWithMode(
mkOp(typ),
f,
inMatchType = inMatchType,
inGivenSig = inGivenSig
)(InfixMode.LeftOp, startPos)
else infixTypeRestWithMode(
typ,
f.compose(mkOp),
inMatchType = inMatchType,
inGivenSig = inGivenSig
)(InfixMode.RightOp, typ.begIndex)
} else f(t)
}
private final def infixTypeRestWithPrecedence(
t: Type,
inMatchType: Boolean = false,
inGivenSig: Boolean = false
): Type = {
val ctx = TypeInfixContext
val base = ctx.stack
@inline
def reduce(rhs: ctx.Typ, op: Option[ctx.Op]): ctx.Typ = ctx.reduceStack(base, rhs, rhs, op)
def getNextRhs(rhs: ctx.Typ)(op: ctx.Op): ctx.Typ = {
newLineOptWhenFollowedBy(TypeIntro)
ctx.push(ctx.UnfinishedInfix(reduce(rhs, Some(op)), op))
compoundType(inMatchType = inMatchType, inGivenSig = inGivenSig)
}
@tailrec
def loop(rhs: ctx.Typ): ctx.Typ = (currToken match {
case lf: InfixLF => getLeadingInfix(lf)(Type.Name.apply)(getNextRhs(rhs))
case _: Unquote | InfixTypeIdent() => Some(getNextRhs(rhs)(typeName()))
case _ => None
}) match {
case Some(x) => loop(x)
case None => reduce(rhs, None)
}
loop(t)
}
def compoundType(inMatchType: Boolean = false, inGivenSig: Boolean = false): Type =
refinement(innerType = None).getOrElse {
val startPos = currIndex
val annot = annotType(startPos, inMatchType = inMatchType)
if (inGivenSig) annot else compoundTypeRest(annot, startPos)
}
def compoundTypeRest(typ: Type, startPos: Int): Type = {
@tailrec
def gatherWithTypes(previousType: Type): Type = refinement(Some(previousType)) match {
/* Indentation means a refinement and we cannot join
* refinements this way so stop looping.
*/
case None | Some(`previousType`) =>
if (acceptOpt[KwWith]) {
val rhs = annotType()
val t = autoEndPos(startPos)(Type.With(previousType, rhs))
gatherWithTypes(t)
} else previousType
case Some(t) => t
}
gatherWithTypes(typ)
}
def annotType(inMatchType: Boolean = false): Type =
annotType(currIndex, inMatchType = inMatchType)
private def annotType(startPos: Int, inMatchType: Boolean): Type =
annotTypeRest(simpleType(inMatchType = inMatchType), startPos)
def annotTypeRest(t: Type, startPos: Int): Type = {
val annots = ScalametaParser.this.annots(skipNewLines = false)
if (annots.isEmpty) t else autoEndPos(startPos)(Type.Annotate(t, annots))
}
def simpleType(inMatchType: Boolean = false): Type = {
val startPos = currIndex
def wildcardType(): Type = {
next()
Type.Wildcard(typeBounds())
}
def anonymousParamWithVariant(modOpt: Option[Mod.Variant]): Type = Type
.AnonymousParam(modOpt.map(v => atPos(startPos)(v)))
def pathSimpleType(): Type = {
val ref = path()
if (acceptOpt[Dot]) {
accept[KwType]
Type.Singleton(ref.become[Term.Ref])
} else ref match {
case q: Quasi => q.become[Type]
case Term.Select(qual: Term.Quasi, name: Term.Name.Quasi) =>
val newQual = qual.become[Term.Ref]
val newName = name.become[Type.Name]
Type.Select(newQual, newName)
case Term.Select(qual: Term.Ref, name) =>
val newName = name.becomeOr(x => copyPos(x)(Type.Name(x.value)))
Type.Select(qual, newName)
case name: Term.Name => Type.Name(name.value)
case _ => syntaxError("identifier expected", at = ref)
}
}
val res = currToken match {
case _: Ident if peek[Dot] => pathSimpleType()
case _: LeftParen => makeTupleType(startPos, typesInParens())
case MacroSplicedIdent(ident) => Type.Macro(macroSplicedIdent(ident))
case _: MacroSplice => Type.Macro(macroSplice())
case _: Underscore if inMatchType => next(); Type.PatWildcard()
case _: Underscore
if dialect.allowUnderscoreAsTypePlaceholder ||
dialect.allowTypeLambdas && !PatternTypeContext.isInside() &&
TypeBracketsContext.isDeeper(1) && !peek[Supertype, Subtype] =>
next(); Type.AnonymousParam(None)
case _: Underscore => wildcardType()
case _: Literal =>
if (dialect.allowLiteralTypes) literal()
else syntaxError(s"$dialect doesn't support literal types", at = path())
case Unary.Numeric(unary) if dialect.allowLiteralTypes && tryAhead[NumericConstant[_]] =>
autoEndPos(prevIndex)(numericLiteral(unary))
case t: Token.Ident if !inMatchType =>
t.text match {
case soft.QuestionMarkAsTypeWildcard() => wildcardType()
case soft.StarAsTypePlaceholder(value) => next(); anonymousParamWithVariant(value)
case value @ TParamVariantStr(mod)
if (dialect.allowPlusMinusUnderscoreAsIdent ||
dialect.allowUnderscoreAsTypePlaceholder) && tryAhead[Underscore] =>
next() // Ident and Underscore
if (dialect.allowUnderscoreAsTypePlaceholder) anonymousParamWithVariant(Some(mod))
else Type.Name(s"${value}_")
case _ => pathSimpleType()
}
case _ => pathSimpleType()
}
val rest = simpleTypeRest(autoEndPosOpt(startPos)(res), startPos)
withTypeCaptures(startPos, needCaret = true)(rest)
}
@tailrec
private final def simpleTypeRest(t: Type, startPos: Int): Type = currToken match {
case _: Hash =>
next()
simpleTypeRest(autoEndPos(startPos)(Type.Project(t, typeName())), startPos)
case _: LeftBracket =>
simpleTypeRest(autoEndPos(startPos)(Type.Apply(t, typeArgsInBrackets())), startPos)
case _ => t
}
def typesInParens(): List[Type] =
inParensOnOpen(commaSeparated(namedTypeOpt(Nil, allowFunctionType = false).getOrElse(typ())))
def patternTyp(allowInfix: Boolean, allowImmediateTypevars: Boolean): Type = {
def setPos(outerTpe: Type)(innerTpe: Type): Type =
if (innerTpe eq outerTpe) outerTpe else copyPos(outerTpe)(innerTpe)
def copyType(t: Type): Type = loop(t, convertTypevars = false)
def convertType(t: Type): Type = loop(t, convertTypevars = true)
def convertFuncParamClause(t: Type.FuncParamClause): Type.FuncParamClause = t match {
case t: Quasi => t
case t => copyPos(t)(Type.FuncParamClause(t.values.map(convertType)))
}
def convertFunc[A <: Type.ParamFunctionType](t: A)(f: (Type.FuncParamClause, Type) => A): A =
f(convertFuncParamClause(t.paramClause), copyType(t.res))
def convertByName[A <: Type.ByNameType](t: A)(f: Type => A): A = f(copyType(t.tpe))
def loop(outerTpe: Type, convertTypevars: Boolean): Type = setPos(outerTpe)(outerTpe match {
case q: Quasi => q
case tpe @ Type.Name(value) if convertTypevars && value(0).isLower => Type.Var(tpe)
case tpe: Type.Name => tpe
case tpe: Type.Select => tpe
case Type.Project(qual, name) => Type.Project(copyType(qual), name)
case tpe: Type.Singleton => tpe
case t: Type.Apply =>
val args1 = t.argClause match {
case q: Type.ArgClause.Quasi => q
case x: Type.ArgClause => copyPos(x)(Type.ArgClause(x.values.map(convertType)))
}
Type.Apply(copyType(t.tpe), args1)
case Type.ApplyInfix(lhs, op, rhs) => Type.ApplyInfix(copyType(lhs), op, copyType(rhs))
case t: Type.ByName => convertByName(t)(Type.ByName(_))
case t: Type.PureByName => convertByName(t)(Type.PureByName(_))
case t: Type.Function => convertFunc(t)(Type.Function(_, _))
case t: Type.PureFunction => convertFunc(t)(Type.PureFunction(_, _))
case t: Type.ContextFunction => convertFunc(t)(Type.ContextFunction(_, _))
case t: Type.PureContextFunction => convertFunc(t)(Type.PureContextFunction(_, _))
case t: Type.PolyFunction => Type.PolyFunction(t.tparamClause, copyType(t.tpe))
case Type.Tuple(elements) => Type.Tuple(elements.map(convertType))
case Type.With(lhs, rhs) => Type.With(copyType(lhs), copyType(rhs))
case Type.Refine(tpe, stats) => Type.Refine(tpe.map(copyType), stats)
case Type.Existential(underlying, stats) => Type.Existential(copyType(underlying), stats)
case Type.Annotate(underlying, annots) => Type.Annotate(copyType(underlying), annots)
case t: Type.Wildcard => Type.Wildcard(t.bounds)
case t: Type.AnonymousLambda => Type.AnonymousLambda(copyType(t.tpe))
case t: Type.AnonymousParam => Type.AnonymousParam(t.variant)
case t: Type.Placeholder => Type.Placeholder(t.bounds)
case t: Type.Block => Type.Block(t.typeDefs, copyType(t.tpe))
case t: Type.Capturing => Type.Capturing(convertType(t.tpe), t.caps)
case tpe: Lit => tpe
})
val t: Type = PatternTypeContext.within {
if (allowInfix) {
val t = if (at[LeftParen]) tupleInfixType() else compoundType()
currToken match {
case _: KwForsome => existentialTypeOnForSome(t)
case _: Unquote | Keywords.NotPatAlt() => infixTypeRest(t)
case _ => t
}
} else compoundType()
}
loop(t, convertTypevars = allowImmediateTypevars)
}
def patternTypeArgs() = autoPos(
inBrackets(commaSeparated(patternTyp(allowInfix = true, allowImmediateTypevars = true)))
.reduceWith(Type.ArgClause.apply)
)
}
private trait AllowedName[T]
private object AllowedName {
implicit object AllowedTermName extends AllowedName[Term.Name]
implicit object AllowedTypeName extends AllowedName[Type.Name]
}
private def identName[T <: Tree](ident: Ident, ctor: String => T): T =
atCurPosNext(ctor(ident.value))
private def name[T <: Tree: AllowedName: AstInfo](ctor: String => T): T = currToken match {
case t: Ident => identName(t, ctor)
case t: Unquote => unquote[T](t)
case _ => syntaxErrorExpected[Ident]
}
def termName(): Term.Name = name(Term.Name(_))
def typeName(): Type.Name = name(Type.Name(_))
private def termName(t: Ident): Term.Name = identName(t, Term.Name.apply)
private def typeName(t: Ident): Type.Name = identName(t, Type.Name.apply)
@inline
private def anonNameRaw(): Name.Anonymous = Name.Anonymous()
@inline
private def anonName(): Name.Anonymous = atCurPosEmpty(anonNameRaw())
@inline
private def anonNameAt(pos: StartPos): Name.Anonymous = atPosEmpty(pos)(anonNameRaw())
@inline
private def nameThis(): Name.This = atCurPosNext(Name.This())
@inline
private def namePlaceholder(): Name.Placeholder = atCurPosNext(Name.Placeholder())
private def anonThis(): Term.This = atCurPosNext(Term.This(anonName()))
def path(thisOK: Boolean = true): Term.Ref = {
val startsAtBof = prev[BOF]
def afterDot[A <: Term.Ref](ref: A)(f: PartialFunction[Token, A => Term.Ref]): Term.Ref =
if (!at[Dot]) ref
else f.lift(peekToken) match { case Some(f) => next(); f(ref); case _ => ref }
@inline
def maybeSelectors(ref: Term.Ref): Term.Ref = afterDot(ref) { case _: Ident | _: Unquote =>
selectors
}
def getThis(name: Name): Term.Ref = {
val thisp = autoEndPos(name)(Term.This(name))
if (thisOK) maybeSelectors(thisp)
else {
accept[Dot]
selectors(thisp)
}
}
def getSuper(name: Name): Term.Ref = {
val superp = autoEndPos(name)(Term.Super(name, mixinQualifier()))
if (startsAtBof && dialect.allowUnquotes && at[EOF]) superp
else {
accept[Dot]
maybeSelectors(autoEndPos(name)(Term.Select(superp, termName())))
}
}
def getAnonQual(): Name =
try anonName()
finally next()
def getQual(name: Term.Name): Name = {
next()
name.becomeOr[Name](x => copyPos(x)(Name.Indeterminate(x.value)))
}
currToken match {
case _: KwThis => getThis(getAnonQual())
case _: KwSuper => getSuper(getAnonQual())
case _ => afterDot(termName()) {
case _: KwThis => x => getThis(getQual(x))
case _: KwSuper => x => getSuper(getQual(x))
case _: Ident | _: Unquote => x => selectors(x.become[Term])
}
}
}
def selector(t: Term, startPos: Int): Term.Select =
autoEndPos(startPos)(Term.Select(t, termName()))
@tailrec
private final def selectors(t: Term, startPos: Int): Term.Ref = {
val t1 = selector(t, startPos)
if (at[Dot] && tryAhead[Ident]) selectors(t1, startPos) else t1
}
private final def selectors(t: Term): Term.Ref = selectors(t, t.begIndex)
def mixinQualifier(): Name =
if (acceptOpt[LeftBracket]) inBracketsAfterOpen {
typeName().becomeOr[Name](x => copyPos(x)(Name.Indeterminate(x.value)))
}
else anonName()
def stableId(): Term.Ref = path(thisOK = false)
def qualId(): Term.Ref = {
val name = termName().become[Term.Ref]
if (acceptOpt[Dot]) selectors(name) else name
}
private def numericLiteral(unary: Unary.Numeric): Lit = {
val number = currToken.asInstanceOf[NumericConstant[_]]
next()
numericLiteralWithUnaryAt(number, unary)
}
private def numericLiteralMaybeWithUnaryAt(
tok: NumericConstant[_],
unary: Unary.Numeric
): Either[Lit, 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
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 = tok)
result
}
def getBigDecimal(tok: NumericConstant[BigDecimal], f: BigDecimal => Lit) = {
val number = tok.value
unary(number).fold[Either[Lit, Lit]](Left(f(number)))(x => Right(f(x)))
}
tok match {
case tok: Constant.Int =>
Right(Lit.Int(getBigInt(tok, bigIntMaxInt, bigIntMaxUInt, "Int").intValue))
case tok: Constant.Long =>
Right(Lit.Long(getBigInt(tok, bigIntMaxLong, bigIntMaxULong, "Long").longValue))
case tok: Constant.Float => getBigDecimal(tok, Lit.Float.apply)
case tok: Constant.Double => getBigDecimal(tok, Lit.Double.apply)
}
}
private def numericLiteralWithUnaryAt(tok: NumericConstant[_], unary: Unary.Numeric): Lit =
numericLiteralMaybeWithUnaryAt(tok, unary) match {
case Right(x) => x
case _ => syntaxError(s"bad unary op `${unary.op}` for floating-point", at = tok)
}
def literal(): Lit = atCurPosNext(currToken match {
case number: NumericConstant[_] => numericLiteralWithUnaryAt(number, Unary.Noop)
case Constant.Char(value) => Lit.Char(value)
case Constant.String(value) => Lit.String(value)
case t: Constant.Symbol =>
if (dialect.allowSymbolLiterals) Lit.Symbol(t.value)
else syntaxError("Symbol literals are no longer allowed", at = t)
case x: BooleanConstant => Lit.Boolean(x.value)
case _: KwNull => Lit.Null()
case t => unreachable(t)
})
private def interpolateWith[Ctx, Ret <: Tree](
arg: => Ctx,
result: (Term.Name, List[Lit], List[Ctx]) => Ret
): Ret = autoPos {
val partsBuf = new ListBuffer[Lit]
val argsBuf = new ListBuffer[Ctx]
@tailrec
def loop(): Unit = currToken match {
case Interpolation.Part(value) =>
partsBuf += atCurPos(Lit.String(value))
next()
loop()
case _: Interpolation.SpliceStart =>
next()
argsBuf += arg
accept[Interpolation.SpliceEnd]
loop()
case _ =>
}
val interpolator = atCurPos(currToken match {
case Interpolation.Id(value) => next(); Term.Name(value)
case _ => syntaxErrorExpected[Interpolation.Id]
})
accept[Interpolation.Start]
loop()
accept[Interpolation.End]
result(interpolator, partsBuf.toList, argsBuf.toList)
}
private def xmlWith[Ctx, Ret <: Tree](arg: => Ctx, result: (List[Lit], List[Ctx]) => Ret): Ret =
autoPos {
val partsBuf = new ListBuffer[Lit]
val argsBuf = new ListBuffer[Ctx]
@tailrec
def loop(): Unit = currToken match {
case Xml.Part(value) =>
partsBuf += atCurPos(Lit.String(value))
next()
loop()
case _: Xml.SpliceStart =>
next()
argsBuf += arg
accept[Xml.SpliceEnd]
loop()
case _ =>
}
accept[Xml.Start]
loop()
accept[Xml.End]
result(partsBuf.toList, argsBuf.toList)
}
def interpolateTerm(): Term.Interpolate = interpolateWith(unquoteExpr(), Term.Interpolate.apply)
def xmlTerm(): Term.Xml = xmlWith(unquoteExpr(), Term.Xml.apply)
def interpolatePat(): Pat.Interpolate = interpolateWith(unquotePattern(), Pat.Interpolate.apply)
def xmlPat(): Pat.Xml = xmlWith(unquoteSeqPattern(), Pat.Xml.apply)
/* ------------- NEW LINES ------------------------------------------------- */
@inline
def newLineOpt(): Unit = if (at[EOL]) next()
@inline
def newLinesOpt(): Unit = if (at[AtEOL]) next()
def nextIfPair[A: ClassTag, B: ClassTag]: Boolean = at[A] && tryAhead[B]
def nextIfPair[A: ClassTag](pred: Token => Boolean): Boolean = at[A] && tryAhead(pred(currToken))
def newLineOptWhenFollowedBy(pred: Token => Boolean): Boolean = nextIfPair[EOL](pred)
def newLineOptWhenFollowedBy[T: ClassTag]: Boolean = nextIfPair[EOL, T]
def isIndentingOrEOL(nonOptBracesOK: Boolean): Boolean =
if (dialect.allowSignificantIndentation) in.indenting else nonOptBracesOK && at[EOL]
def isAfterOpt[A: ClassTag, B: ClassTag]: Boolean = if (at[B]) tryAhead[A] else at[A]
def isAfterOptNewLine[T: ClassTag]: Boolean = isAfterOpt[T, EOL]
def atOrPeekAfterEOL(body: Token => Boolean): Boolean =
if (at[EOL]) nextIf(body(peekToken)) else body(currToken)
def getAfterOptNewLine[A](body: => Option[A]): Option[A] = if (at[EOL]) tryAhead(body) else body
/* ------------- TYPES ---------------------------------------------------- */
def typedOpt(): Option[Type] =
if (acceptOpt[Colon]) Some {
if (at[At] && peek[Ident]) {
val startPos = currIndex
outPattern.annotTypeRest(autoEndPos(startPos)(Type.AnonymousName()), startPos)
} else typ()
}
else None
private def getDeclTpeOpt(fullTypeOK: Boolean): Option[Type] =
if (acceptOpt[Colon]) Some(typeOrInfixType(fullTypeOK)) else None
private def typeOrInfixType(fullTypeOK: Boolean): Type =
if (fullTypeOK) typ() else startInfixType()
@inline
private def typeOrInfixType(location: Location): Type = typeOrInfixType(location.fullTypeOK)
/* ----------- EXPRESSIONS ------------------------------------------------ */
def condExpr(): Term = inParens(expr())
def expr(): Term = expr(location = NoStat, allowRepeated = false)
def quasiquoteExpr(): Term = expr(location = NoStat, allowRepeated = true)
def entrypointExpr(): Term = expr(location = NoStat, allowRepeated = false)
def unquoteExpr(): Term = currToken match {
case t: Ident => termName(t)
case _: LeftBrace => expr(location = UnquoteStat, allowRepeated = true)
case _: KwThis => anonThis()
case _ => syntaxError(
"error in interpolated string: identifier, `this' or block expected",
at = currToken
)
}
/**
* Deals with Scala 3 concept of {{{inline x match { ...}}}. Since matches can also be chained in
* Scala 3 we need to create the Match first and only then add the the inline modifier.
*/
def inlineMatchClause(inlineMods: List[Mod]) =
autoEndPos(inlineMods)(postfixExpr(allowRepeated = false)) match {
case t: Term.Match => copyPos(t)(t.fullCopy(mods = inlineMods))
case other => syntaxError("`inline` must be followed by an `if` or a `match`", at = other.pos)
}
private def matchClause(t: Term, startPos: Int, isSelect: Boolean = false) = {
val cases = autoPos {
if (at[Indentation.Indent]) indentedOnOpen(casesBlock()) else inBraces(casesBlock())
}
autoEndPos(startPos)(if (isSelect) Term.SelectMatch(t, cases) else Term.Match(t, cases))
}
def ifClause(mods: List[Mod] = Nil) = autoEndPos(mods) {
accept[KwIf]
val (cond, thenp) = condExprWithBody[KwThen]
val elsep = if (acceptIfAfterOpt[KwElse](StatSep)) expr() else atCurPosEmpty(Lit.Unit())
Term.If(cond, thenp, elsep, mods)
}
private def condExprWithBody[T <: Token: ClassTag]: (Term, Term) = {
val (cond, bodyOpt) =
if (!dialect.allowQuietSyntax) {
val cond = condExpr()
newLinesOpt()
(cond, None)
} else if (!at[LeftParen]) {
val cond = expr()
acceptAfterOptNL[T]
(cond, None)
} else {
val startPos = currIndex
val simpleExpr = condExpr()
if (acceptIfAfterOptNL[T]) (simpleExpr, None)
else {
// let's consider case when something can continue cond or start body
val argsOrInitBody = currToken match {
case _: LeftParen => Some(inParensOrTupleOrUnitExpr(allowRepeated = false))
case _: LeftBrace => Some(blockExprOnBrace())
case _ => None
}
val complexExpr = tryParse {
val simpleExprWithArgs = argsOrInitBody.fold(simpleExpr) { t =>
val args = copyPos(t)(termInfixContext.toArgClause(t))
autoEndPos(startPos)(Term.Apply(simpleExpr, args))
}
val simpleRest = simpleExprRest(simpleExprWithArgs, canApply = true, startPos = startPos)
Try(postfixExpr(startPos, simpleRest, allowRepeated = false)).toOption.flatMap { x =>
val exprCond = exprOtherRest(startPos, x, location = NoStat, allowRepeated = false)
if (acceptIfAfterOptNL[T]) Some(exprCond -> None) else None
}
}
complexExpr.getOrElse {
if (argsOrInitBody.isEmpty) newLinesOpt()
simpleExpr -> argsOrInitBody.map { t =>
val startPos = t.begIndex
val rest = simpleExprRest(t, canApply = true, startPos = startPos)
val init = postfixExpr(startPos, rest, allowRepeated = false)
exprOtherRest(startPos, init, NoStat, allowRepeated = false)
}
}
}
}
val body = bodyOpt.getOrElse(expr())
(cond, body)
}
// FIXME: when parsing `(2 + 3)`, do we want the ApplyInfix's position to include parentheses?
// if yes, then nothing has to change here
// if no, we need eschew autoPos here, because it forces those parentheses on the result of calling prefixExpr
// see https://github.com/scalameta/scalameta/issues/1083 and https://github.com/scalameta/scalameta/issues/1223
def expr(location: Location, allowRepeated: Boolean): Term = {
def inlineMod() = autoPos {
accept[Ident]
Mod.Inline()
}
val res = autoPosOpt(currToken match {
case soft.KwInline() if peek[KwIf] => ifClause(List(inlineMod()))
case _ if isInlineMatchMod(currIndex) => inlineMatchClause(List(inlineMod()))
case _: KwIf => ifClause()
case _: KwTry =>
next()
val body: Term = currToken match {
case _ if dialect.allowTryWithAnyExpr => expr()
case _: LeftParen => inParensOnOpen(expr())
case _: LeftBrace => blockOnBrace()
case _: Indentation.Indent => blockOnIndent()
case _ => expr()
}
def finallyopt = if (acceptIfAfterOptNL[KwFinally]) Some(expr()) else None
def tryWithCases(cases: Option[Term.CasesBlock]) = Term.Try(body, cases, finallyopt)
def tryWithHandler(handler: Term) = Term.TryWithHandler(body, handler, finallyopt)
def tryInDelims(
f: (=> Either[Term, Term.CasesBlock]) => Either[Term, Term.CasesBlock]
): Term = {
val catchPos = currIndex
f(casesBlockIfAny().toRight(blockRaw())).fold(
x => tryWithHandler(autoEndPos(catchPos)(x)),
x => tryWithCases(Some(autoEndPos(catchPos)(x)))
)
}
if (acceptIfAfterOptNL[KwCatch]) currToken match {
case _: KwCase =>
tryWithCases(Some(autoPos { next(); toCasesBlock(caseClause(true) :: Nil) }))
case _: Indentation.Indent => tryInDelims(indentedOnOpen)
case _: LeftBrace => tryInDelims(inBracesOnOpen)
case _ => tryWithHandler(expr())
}
else tryWithCases(None)
case _: KwWhile =>
next()
val (cond, body) = condExprWithBody[KwDo]
Term.While(cond, body)
case _: KwDo if dialect.allowDoWhile =>
next()
val body = expr()
skipAllStatSep()
accept[KwWhile]
val cond = condExpr()
Term.Do(body, cond)
case _: KwDo =>
syntaxError("do {...} while (...) syntax is no longer supported", at = currToken)
case _: KwFor =>
next()
def enumList =
if (acceptOpt[LeftBrace]) inBracesAfterOpen(enumerators())
else if (at[LeftParen]) {
def parseInParens() = inParensOnOpen(enumerators())
if (dialect.allowQuietSyntax)
// Dotty retry in case of `for (a,b) <- list1.zip(list2) yield (a, b)`
tryParse(Try(parseInParens()).toOption).getOrElse(enumerators())
else parseInParens()
} else maybeIndented(enumerators())
val enums = autoPos(enumList.reduceWith(Term.EnumeratorsBlock.apply))
newLinesOpt()
if (acceptOpt[KwDo]) Term.For(enums, expr())
else if (acceptOpt[KwYield]) Term.ForYield(enums, expr())
else Term.For(enums, expr())
case _: KwReturn =>
next()
if (isExprIntro(currToken, currIndex)) Term.Return(expr())
else Term.Return(atCurPosEmpty(Lit.Unit()))
case _: KwThrow =>
next()
Term.Throw(expr())
case _: KwImplicit =>
next()
implicitClosure(location)
case _ =>
val startPos = currIndex
val t: Term = postfixExpr(allowRepeated)
exprOtherRest(startPos, t, location, allowRepeated)
})
if (location.anonFuncOK) maybeAnonymousFunction(res) else res
}
private def exprOtherRest(
startPos: Int,
prefix: Term,
location: Location,
allowRepeated: Boolean
): Term = {
@inline
def addPos[T <: Tree](body: T) = autoEndPos(startPos)(body)
def repeatedTerm(t: Term, nextTokens: () => Unit): Term =
if (allowRepeated) addPos { nextTokens(); Term.Repeated(t) }
else syntaxError("repeated argument not allowed here", at = currToken)
@tailrec
def iter(t: Term): Term = currToken match {
case _: Equals => t match {
case _: Term.Ref | _: Term.Apply | _: Quasi =>
next()
addPos(Term.Assign(t, expr(location = NoStat, allowRepeated = true)))
case _ => t
}
case _: Colon => getFewerBracesApplyOnColon(t, startPos) match {
case Some(x) => x
case _ =>
next()
if (at[At] || (at[Ellipsis] && peek[At]))
iter(addPos(Term.Annotate(t, annots(skipNewLines = false))))
else if (at[Underscore] && isStar(peekToken)) repeatedTerm(t, nextTwice)
else
// this does not necessarily correspond to syntax, but is necessary to accept lambdas
// check out the `if (token.is[RightArrow]) { ... }` block below
iter(addPos(Term.Ascribe(t, typeOrInfixType(location))))
}
case soft.StarSplice() if allowRepeated && peek[RightParen] => repeatedTerm(t, next)
case _: KwMatch =>
next()
matchClause(t, startPos)
case _ => t
}
val res: Term = iter(prefix)
// Note the absense of `else if` here!!
if (at[FunctionArrow])
// This is a tricky one. In order to parse lambdas, we need to recognize token sequences
// like `(...) => ...`, `id | _ => ...` and `implicit id | _ => ...`.
//
// If we exclude Implicit (which is parsed elsewhere anyway), then we can see that
// these sequences are non-trivially ambiguous with tuples and self-type annotations
// (i.e. are not resolvable with static lookahead).
//
// Therefore, when we encounter RightArrow, the part in parentheses is already parsed into a Term,
// and we need to figure out whether that term represents what we expect from a lambda's param list
// in order to disambiguate. The term that we have at hand might wildly differ from the param list that one would expect.
// For example, when parsing `() => x`, we arrive at RightArrow having `Lit.Unit` as the parsed term.
// That's why we later need `convertToParams` to make sense of what the parser has produced.
//
// Rules:
// 1. `() => ...` means lambda
// 2. `x => ...` means self-type annotation, but only in template position
// 3. `(x) => ...` means self-type annotation, but only in template position
// 4a. `x: Int => ...` means self-type annotation in template position
// 4b. `x: Int => ...` means lambda in block position
// 4c. `x: Int => ...` means ascription, i.e. `x: (Int => ...)`, in expression position
// 5a. `(x: Int) => ...` means lambda
// 5b. `(using x: Int) => ...` means lambda for dotty
// 6. `(x, y) => ...` or `(x: Int, y: Int) => ...` or with more entries means lambda
//
// A funny thing is that scalac's parser tries to disambiguate between self-type annotations and lambdas
// even if it's not parsing the first statement in the template. E.g. `class C { foo; x => x }` will be
// a parse error, because `x => x` will be deemed a self-type annotation, which ends up being inapplicable there.
convertToParamClause(res)(
isNameAllowed = location != TemplateStat,
isParamAllowed = location.funcParamOK || tokens(startPos).is[LeftParen] && prev[RightParen]
).fold(res) { pc =>
val contextFunction = at[ContextArrow]
val params = addPos(pc)
next()
val trm = termFunctionBody(location)
addPos {
if (contextFunction) Term.ContextFunction(params, trm) else Term.Function(params, trm)
}
}
// if couldn't convert to params:
// do nothing, which will either allow self-type annotation parsing to kick in
// or will trigger an unexpected token error down the line
else res
}
private def termFunctionBody(location: Location): Term =
if (location != BlockStat) expr()
else (currToken match {
case _: LeftBrace => blockExprOnBrace(isOptional = true)
case _: Indentation.Indent => blockExprOnIndent()
case _ => blockOnOther()
}) match {
case t: Term.PartialFunction => getDeclTpeOpt(fullTypeOK = false)
.fold[Term](t)(tpe => autoEndPos(t)(Term.Ascribe(t, tpe)))
case t => t
}
private def convertToParam(tree: Tree): Option[Term.Param] = {
def getModFromName(name: Name): Option[Mod] = name.value match {
case soft.KwUsing() => Some(copyPos(name)(Mod.Using()))
case soft.KwErased() => Some(copyPos(name)(Mod.Erased()))
case _ => None
}
@tailrec
def getMod(t: Tree, mods: List[Mod] = Nil): Option[List[Mod]] = t match {
case t: Term.Name => getModFromName(t).map(_ :: mods)
case t: Term.SelectPostfix => getModFromName(t.name) match {
case Some(mod) => getMod(t.qual, mod :: mods)
case _ => None
}
case t: Term.ApplyInfix => t.argClause.values match {
case (n: Name) :: Nil if t.targClause.values.isEmpty =>
val mOpt = for {
m1 <- getModFromName(t.op)
m2 <- getModFromName(n)
} yield m1 :: m2 :: mods
mOpt match {
case Some(m) => getMod(t.lhs, m)
case _ => None
}
case _ => None
}
case _ => None
}
def getNameAndMod(t: Tree): Option[(Name, List[Mod])] = t match {
case t: Name => Some((t, Nil))
case t: Quasi => Some((t.become[Term.Name], Nil))
case t: Term.SelectPostfix => getMod(t.qual).map((t.name, _))
case t: Term.ApplyInfix => t.argClause.values match {
case arg :: Nil if t.targClause.values.isEmpty =>
for {
mod <- getModFromName(t.op)
name <- arg match {
case n: Term.Placeholder => Some(copyPos(n)(Name.Placeholder()))
case n: Name => Some(n)
case _ => None
}
mods <- getMod(t.lhs, mod :: Nil)
} yield (name, mods)
case _ => None
}
case t: Term.Placeholder => Some((copyPos(t)(Name.Placeholder()), Nil))
case t: Term.Eta => getMod(t.expr).map((atPos(t.endIndex)(Name.Placeholder()), _))
case _ => None
}
tree match {
case q: Quasi => Some(q.become[Term.Param])
case _: Lit.Unit => None
case t: Term.Ascribe => getNameAndMod(t.expr).map { case (name, mod) =>
copyPos(t)(Term.Param(mod, name, Some(t.tpe), None))
}
case t => getNameAndMod(t).map { case (name, mod) =>
copyPos(t)(Term.Param(mod, name, None, None))
}
}
}
private def convertToParamClause(
tree: Term
)(isNameAllowed: => Boolean, isParamAllowed: => Boolean): Option[Term.ParamClause] = (tree match {
case _: Lit.Unit => Some(Nil)
case q: Quasi => q.rank match {
case 0 if isNameAllowed => Some(q.become[Term.Param] :: Nil)
case 1 if isParamAllowed => Some(q.become[Term.Param] :: Nil)
case _ => None
}
case t: Term.Tuple =>
val params = new ListBuffer[Term.Param]
@tailrec
def iter(ts: List[Term]): Option[List[Term.Param]] = ts match {
case Nil => Some(params.toList)
case head :: tail => convertToParam(head) match {
case None => None
case Some(p) => params += p; iter(tail)
}
}
iter(t.args)
case t => convertToParam(t)
.filter(p => if (p.decltpe.isEmpty && p.mods.isEmpty) isNameAllowed else isParamAllowed)
.map(_ :: Nil)
}).map(_.reduceWith(toParamClause(None)))
private def implicitClosure(location: Location): Term.Function = {
val implicitPos = prevIndex
val paramName = termName()
val paramTpt = getDeclTpeOpt(fullTypeOK = false)
val mod = atPos(implicitPos)(Mod.Implicit())
val param = autoEndPos(implicitPos)(Term.Param(mod :: Nil, paramName, paramTpt, None))
val params = copyPos(param)(Term.ParamClause(param :: Nil, Some(mod)))
accept[RightArrow]
autoEndPos(implicitPos)(Term.Function(params, termFunctionBody(location)))
}
// Encapsulates state and behavior of parsing infix syntax.
// See `postfixExpr` for an involved usage example.
// Another, much less involved usage, lives in `pattern3`.
sealed abstract class InfixContext {
// (Lhs, op and targs form UnfinishedInfix).
// FinishedInfix is the type of an infix expression.
// The conversions are necessary to push the output of finishInfixExpr on stack.
type Typ
type Op <: Name
type UnfinishedInfix <: Unfinished
// Represents an unfinished infix expression, e.g. [a * b +] in `a * b + c`.
protected trait Unfinished {
def lhs: Typ
def op: Op
final def precedence = op.precedence
override def toString = s"[$lhs $op]"
}
// The stack of unfinished infix expressions, e.g. Stack([a + ]) in `a + b [*] c`.
// `push` takes `b`, reads `*`, checks for type arguments and adds [b *] on the top of the stack.
// Other methods working on the stack are self-explanatory.
var stack: List[UnfinishedInfix] = Nil
@inline
def isDone(base: List[UnfinishedInfix]): Boolean = this.stack == base
def head = stack.head
def push(unfinishedInfix: UnfinishedInfix): Unit = stack ::= unfinishedInfix
def pop(): UnfinishedInfix =
try head
finally stack = stack.tail
def reduceStack(stack: List[UnfinishedInfix], curr: Typ, currEnd: EndPos, op: Option[Op]): Typ =
if (isDone(stack)) curr
else {
val opPrecedence = op.fold(0)(_.precedence)
val leftAssoc = op.forall(_.isLeftAssoc)
def lowerPrecedence = opPrecedence < this.head.precedence
def samePrecedence = opPrecedence == this.head.precedence
def canReduce = lowerPrecedence || leftAssoc && samePrecedence
if (samePrecedence) checkAssoc(this.head.op, leftAssoc)
// Pop off an unfinished infix expression off the stack and finish it with the rhs.
// Then convert the result, so that it can become someone else's rhs.
// Repeat while precedence and associativity allow.
@tailrec
def loop(rhs: Typ): Typ =
if (!canReduce) rhs
else {
val lhs = pop()
val fin = finishInfixExpr(lhs, rhs, currEnd)
if (isDone(stack)) fin else loop(fin)
}
loop(curr)
}
// Takes the unfinished infix expression, e.g. `[x +]`,
// then takes the right-hand side (which can have multiple args), e.g. ` (y, z)`,
// and creates `x + (y, z)`.
// We need to carry endPos explicitly because its extent may be bigger than rhs because of parent of whatnot.
protected def finishInfixExpr(unf: UnfinishedInfix, rhs: Typ, rhsEnd: EndPos): Typ
}
// Infix syntax in terms is borderline crazy.
//
// For example, did you know that `a * b + (c, d) * (f, g: _*)` means:
// a.$times(b).$plus(scala.Tuple2(c, d).$times(f, g: _*))?!
//
// Actually there's even crazier stuff in scala-compiler.jar.
// Apparently you can parse and typecheck `a + (bs: _*) * c`,
// however I'm going to error out on this.
object termInfixContext extends InfixContext {
type Typ = Term
type Op = Term.Name
// We need to carry lhsStart/lhsEnd separately from lhs.pos
// because their extent may be bigger than lhs because of parentheses or whatnot.
case class UnfinishedInfix(lhs: Typ, op: Op, targs: Type.ArgClause) extends Unfinished {
override def toString = s"[$lhs $op$targs]"
}
def toArgClause(rhs: Typ): Term.ArgClause = copyPos(rhs)(
(rhs match {
case _: Lit.Unit if dialect.allowEmptyInfixArgs => Nil
case t: Term.Tuple => t.args
case _ => rhs :: Nil
}).reduceWith(Term.ArgClause(_))
)
protected def finishInfixExpr(unf: UnfinishedInfix, rhs: Typ, rhsEnd: EndPos): Typ = {
val UnfinishedInfix(lhsExt, op, targs) = unf
val lhs = lhsExt match {
case Term.Tuple(arg :: Nil) => arg
case x => x
}
if (lhs.is[Term.Repeated])
syntaxError("repeated argument not allowed here", at = lhs.tokens.last)
atPos(lhsExt, rhsEnd)(Term.ApplyInfix(lhs, op, targs, toArgClause(rhs)))
}
}
// In comparison with terms, patterns are trivial.
implicit object patInfixContext extends InfixContext {
type Typ = Pat
type Op = Term.Name
case class UnfinishedInfix(lhs: Typ, op: Op) extends Unfinished
protected def finishInfixExpr(unf: UnfinishedInfix, rhs: Typ, rhsEnd: EndPos): Typ = {
val UnfinishedInfix(lhsExt, op) = unf
val lhs = lhsExt match {
case Pat.Tuple(arg :: Nil) => arg
case x => x
}
val args = copyPos(rhs)(
(rhs match {
case _: Lit.Unit if dialect.allowEmptyInfixArgs => Nil
case t: Pat.Tuple => t.args
case _ => rhs :: Nil
}).reduceWith(Pat.ArgClause.apply)
)
atPos(lhsExt, rhsEnd)(Pat.ExtractInfix(lhs, op, args))
}
}
private object TypeInfixContext extends InfixContext {
type Typ = Type
type Op = Type.Name
case class UnfinishedInfix(lhs: Typ, op: Op) extends Unfinished
protected def finishInfixExpr(unf: UnfinishedInfix, rhs: Typ, rhsEnd: EndPos): Typ = {
val UnfinishedInfix(lhs, op) = unf
atPos(lhs, rhsEnd)(Type.ApplyInfix(lhs, op, rhs))
}
}
@inline
def checkAssoc(op: Name, leftAssoc: Boolean): Unit = checkAssoc(op, op.isLeftAssoc, leftAssoc)
@inline
private def checkAssoc(at: Tree, opLeftAssoc: Boolean, leftAssoc: Boolean): Unit =
if (opLeftAssoc != leftAssoc) syntaxError(
"left- and right-associative operators with same precedence may not be mixed",
at = at
)
private def getLeadingInfix[A <: Name, B](lf: InfixLF)(f: String => A)(g: A => B): Option[B] =
peekToken match {
case op: Ident =>
def res = Some(g(atCurPos { next(); newLineOpt(); f(op.value) }))
tryAhead(if (peek[Indentation]) None else lf.invalid.fold(res)(syntaxError(_, at = op)))
case _ => None
}
def postfixExpr(allowRepeated: Boolean): Term = {
val startPos = currIndex
// Start the infix chain.
// We'll use `a + b` as our running example.
val rhs0 = prefixExpr(allowRepeated)
postfixExpr(startPos, rhs0, allowRepeated)
}
private def postfixExpr(startPos: Int, rhs0: Term, allowRepeated: Boolean): Term = {
val ctx = termInfixContext
val base = ctx.stack
def getLhsStartPos(lhs: ctx.Typ): Int = if (lhs eq rhs0) startPos else lhs.begIndex
// Skip to later in the `postfixExpr` method to start mental debugging.
// rhsStartK/rhsEndK may be bigger than then extent of rhsK,
// so we really have to track them separately.
@tailrec
def loop(rhsK: ctx.Typ): ctx.Typ = {
val rhsEndK = prevIndex
def getPrevLhs(op: Term.Name): Term = ctx.reduceStack(base, rhsK, rhsEndK, Some(op))
def getNextRhs(targs: => Type.ArgClause)(op: Term.Name) =
getNextRhsWith(op, targs, argumentExprsOrPrefixExpr(PostfixStat))
def getNextRhsWith(op: Term.Name, targs: Type.ArgClause, rhs: Term) = {
val lhs = getPrevLhs(op)
val wrap = (lhs eq rhs0) && lhs.begIndex != startPos
val lhsExt = if (wrap) atPosWithBody(startPos, Term.Tuple(lhs :: Nil), rhsEndK) else lhs
ctx.push(ctx.UnfinishedInfix(lhsExt, op, targs))
Right(rhs)
}
def getPostfix(op: Term.Name, targs: Type.ArgClause) = {
// Infix chain has ended with a postfix expression.
// This never happens in the running example.
if (targs.nonEmpty)
syntaxError("type application is not allowed for postfix operators", at = currToken)
val finQual = getPrevLhs(op)
val term: Term = atPos(getLhsStartPos(finQual), op)(Term.SelectPostfix(finQual, op))
Left(term)
}
def emptyTypeArgs = atCurPosEmpty(Type.ArgClause(Nil))
def getPostfixOrNextRhs(op: Term.Name): Either[Term, Term] = {
// Infix chain continues.
// In the running example, we're at `a [+] b`.
val targs = if (at[LeftBracket]) exprTypeArgs() else emptyTypeArgs
// Check whether we're still infix or already postfix by testing the current token.
// In the running example, we're at `a + [b]` (infix).
// If we were parsing `val c = a b`, then we'd be at `val c = a b[]` (postfix).
if (if (at[EOL]) nextIf(isExprIntro(peekToken, peekIndex))
else isIdentOrExprIntro(currToken))
// Infix chain continues, so we need to reduce the stack.
// In the running example, base = List(), rhsK = [a].
getNextRhs(targs)(op) // [a]
// afterwards, ctx.stack = List([a +])
else {
val argPos = currIndex
(currToken match {
case _: Colon => getFewerBracesArgOnColon()
case _ => None
}) match {
case None => getPostfix(op, targs)
case Some(x) => getNextRhsWith(op, targs, autoEndPos(argPos)(Term.Tuple(x :: Nil)))
}
}
}
val resOpt = currToken match {
case lf: InfixLF => getLeadingInfix(lf)(Term.Name.apply)(getNextRhs(emptyTypeArgs))
case _ if prev[Indentation.Outdent] => None
case t: Unquote =>
val op = unquote[Term.Name](t)
Some(getPostfixOrNextRhs(op))
case t: Ident if !(allowRepeated && soft.StarSplice(t) && peek[RightParen]) =>
val op = atCurPosNext(Term.Name(t.value))
Some(getPostfixOrNextRhs(op))
case _: KwMatch if dialect.allowMatchAsOperator =>
val op = atCurPosNext(Term.Name("match"))
val lhs = getPrevLhs(op)
Some(Right(matchClause(lhs, getLhsStartPos(lhs))))
case _ => None
}
resOpt match {
case Some(Left(x)) => x
case Some(Right(x)) =>
// Try to continue the infix chain.
loop(x)
case None =>
// Infix chain has ended.
// In the running example, we're at `a + b[]`
// with base = List([a +]), rhsK = List([b]).
rhsK
}
}
// Iteratively read the infix chain via `loop`.
// rhs0 is now [a]
// If the next token is not an ident or an unquote, the infix chain ends immediately,
// and `postfixExpr` becomes a fallthrough.
val rhsN = loop(rhs0)
// Infix chain has ended.
// base contains pending UnfinishedInfix parts and rhsN is the final rhs.
// For our running example, this'll be List([a +]) and [b].
// Afterwards, lhsResult will be List([a + b]).
if (rhs0 == rhsN && ctx.isDone(base)) rhs0
else {
val endPos = prevIndex
atPosWithBody(startPos, ctx.reduceStack(base, rhsN, endPos, None), endPos)
}
}
def prefixExpr(allowRepeated: Boolean): Term = currToken match {
case Unary((ident, unary)) =>
val startPos = currIndex
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)
def applyUnary(term: Term) = addPos(Term.ApplyUnary(op, term))
def otherwise = simpleExpr0(allowRepeated = true) match {
case Success(result) => applyUnary(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)
}
(currToken, unary) match {
case (tok: NumericConstant[_], unary: Unary.Numeric) =>
next(); rest(numericLiteralMaybeWithUnaryAt(tok, unary).fold(applyUnary, addPos))
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
private def simpleExpr0(allowRepeated: Boolean): Try[Term] = {
var canApply = true
val startPos = currIndex
(currToken match {
case _: MacroQuote => Success(macroQuote())
case _: MacroSplice => Success(macroSplice())
case MacroQuotedIdent(ident) => Success(macroQuotedIdent(ident))
case MacroSplicedIdent(ident) => Success(macroSplicedIdent(ident))
case _: Literal => Success(literal())
case _: Interpolation.Id => Success(interpolateTerm())
case _: Xml.Start => Success(xmlTerm())
case _: Ident | _: KwThis | _: KwSuper | _: Unquote => Success(path().become[Term])
case _: Underscore => Success(atCurPosNext(Term.Placeholder()))
case _: LeftParen => Success(inParensOrTupleOrUnitExpr(allowRepeated = allowRepeated))
case _: LeftBrace => canApply = false; Success(blockExprOnBrace())
case _: Indentation.Indent => canApply = false; Success(blockExprOnIndent())
case _: KwNew =>
canApply = false
Success(autoPos {
next()
val tpl = template(OwnedByTrait)
tpl.inits match {
case init :: Nil if !prev[RightBrace] && tpl.earlyClause.isEmpty && tpl.body.isEmpty =>
Term.New(init)
case _ => Term.NewAnonymous(tpl)
}
})
case _: LeftBracket if dialect.allowPolymorphicFunctions => Success(polyFunction())
case _ => Failure(ParseException(currToken.pos, "illegal start of simple expression"))
}) match {
case Success(x) => Success(simpleExprRest(x, canApply = canApply, startPos = startPos))
case x: Failure[_] => x
}
}
def polyFunction() = autoPos {
val quants = typeParamClauseOpt(ownerIsType = true)
accept[RightArrow]
val term = expr()
Term.PolyFunction(quants, term)
}
private def macroSplice(): Term = autoPos(QuotedSpliceContext.within {
next()
if (QuotedPatternContext.isInside()) Term.SplicedMacroPat(autoPos(inBraces(pattern())))
else Term.SplicedMacroExpr(autoPos(inBraces(blockRaw())))
})
private def macroQuote(): Term = autoPos(QuotedSpliceContext.within {
next()
currToken match {
case _: LeftBrace => Term.QuotedMacroExpr(autoPos(inBracesOnOpen(blockRaw())))
case _: LeftBracket => Term.QuotedMacroType(inBracketsOnOpen(typeBlock()))
case t => syntaxError("Quotation only works for expressions and types", at = t)
}
})
@inline
private def macroQuotedIdent(ident: String): Term = macroIdent(ident, Term.QuotedMacroExpr.apply)
@inline
private def macroSplicedIdent(ident: String): Term = macroIdent(ident, Term.SplicedMacroExpr.apply)
private def macroIdent(ident: String, f: Term.Name => Term): Term = {
val curpos = currIndex
next()
autoEndPos(curpos)(f(atPos(curpos)(Term.Name(ident))))
}
@tailrec
private def simpleExprRest(t: Term, canApply: Boolean, startPos: Int): Term = {
@inline
def addPos(body: Term): Term = autoEndPos(startPos)(body)
currToken match {
case _: AtEOL if (peekToken match {
case _: Dot => true
case _: LeftBrace if canApply => isIndentingOrEOL(true)
case _: LeftParen if canApply => isIndentingOrEOL(false)
case _ => false
}) => next(); simpleExprRest(t, canApply, startPos)
case _: Dot =>
next()
val isMatch = dialect.allowMatchAsOperator && acceptOpt[KwMatch]
val clause =
if (isMatch) matchClause(t, startPos, isSelect = true) else selector(t, startPos)
simpleExprRest(clause, canApply = !isMatch, startPos = startPos)
case _: LeftBracket =>
@tailrec
def isOk(tree: Tree): Boolean = tree match {
case _: Quasi | _: Term.Name | _: Term.Select | _: Term.Apply | _: Term.ApplyInfix |
_: Term.ApplyUnary | _: Term.New | _: Term.Placeholder | _: Term.ApplyUsing |
_: Term.Interpolate | _: Term.SplicedMacroExpr | _: Term.PolyFunction => true
case Term.Block(t :: Nil) => isOk(t)
case _ => false
}
if (isOk(t)) {
var app: Term = t
while (at[LeftBracket]) app = addPos(Term.ApplyType(app, exprTypeArgs()))
simpleExprRest(app, canApply = true, startPos = startPos)
} else addPos(t)
case tok @ (_: LeftParen | _: LeftBrace) if canApply =>
def argClause = if (tok.is[LeftBrace]) getArgClauseOnBrace() else getArgClauseOnParen()
val arguments = addPos(Term.Apply(t, argClause))
simpleExprRest(arguments, canApply = true, startPos = startPos)
case _: Colon if canApply => getFewerBracesApplyOnColon(t, startPos).getOrElse(t)
case _: Underscore if canApply => next(); addPos(Term.Eta(t))
case _ => t
}
}
private def getFewerBracesArgOnColon(): Option[Term] =
if (!dialect.allowFewerBraces) None
else {
val colonPos = currIndex
def addPos(term: Term) = autoEndPos(colonPos)(term)
def tryGetArgAsLambdaBlock(postCheck: => Boolean) = tryGetArgAsLambda().flatMap { arg =>
if (postCheck) Some(addPos(toBlockRaw(arg :: Nil))) else None
}
peekToken match {
case _: Indentation.Indent =>
next()
tryAhead(tryGetArgAsLambdaBlock(acceptIfAfterOptNL[Indentation.Outdent])).orElse {
Some(addPos(blockExprOnIndent(keepBlock = true)))
}
case _: Indentation => syntaxError("expected fewer-braces method body", currToken)
case _: AtEOL =>
val colon = currToken
nextTwice()
val argOpt = tryGetArgAsLambdaBlock(true)
if (argOpt.isEmpty) syntaxError("expected fewer-braces method body", colon)
argOpt
case _ => tryAhead(tryGetArgAsLambdaBlock(true))
}
}
private def tryGetArgAsLambda(): Option[Term.FunctionTerm] = Try {
val paramPos = currIndex
/**
* We need to handle param and then open indented region, otherwise only the block will be
* handles and any `.` will be accepted into the block:
* ```
* .map: a =>
* a+1
* .filter: x =>
* x > 2
* ```
* Without manual handling here, filter would be included for `(a+1).filter`
*/
@tailrec
def getParamClause(pt: Option[Mod.ParamsType], mods: List[Mod]): Term.ParamClause =
currToken match {
case _: KwImplicit if pt.isEmpty =>
val mod = atCurPosNext(Mod.Implicit())
getParamClause(Some(mod), mod :: mods)
case _ => commaSeparated(getParamWithPos(mods, fullTypeOK = true))
.reduceWith(toParamClause(pt))
}
def getParamAsClause(pt: Option[Mod.ParamsType], mods: List[Mod]): Option[Term.ParamClause] = {
val param = getParamWithPos(mods, fullTypeOK = false)
Some(copyPos(param)(reduceAs(param :: Nil, toParamClause(pt))))
}
def getParamWithPos(mods: List[Mod], fullTypeOK: Boolean) = autoPos(getParam(mods, fullTypeOK))
@tailrec
def getParam(mods: List[Mod], fullTypeOK: Boolean): Term.Param = {
def afterName(name: Name) = {
val tpe = if (fullTypeOK || mods.nonEmpty) getDeclTpeOpt(fullTypeOK = fullTypeOK) else None
Term.Param(mods, name, tpe, None)
}
currToken match {
case t: Ellipsis => ellipsis[Term.Param](t, 1)
case t: Ident =>
val mod =
if (peek[Ident]) t.text match {
case soft.KwErased() => atCurPosNext(Mod.Erased())
case soft.KwUsing() => atCurPosNext(Mod.Using())
case _ => null
}
else null
if (mod eq null) afterName(termName(t)) else getParam(mod :: mods, fullTypeOK)
case _: Underscore => afterName(namePlaceholder())
case _ => syntaxErrorExpected[Ident]
}
}
val paramClauseOpt = currToken match {
case _: LeftParen =>
Some(autoPos(inParensOnOpenOr(getParamClause(None, Nil))(Term.ParamClause(Nil))))
case t: Ellipsis if t.rank == 2 => Some(ellipsis[Term.ParamClause](t))
case _: Ident | _: Underscore | _: Ellipsis => getParamAsClause(None, Nil)
case _: KwImplicit =>
val mod = atCurPosNext(Mod.Implicit())
getParamAsClause(Some(mod), mod :: Nil)
case _ => None
}
paramClauseOpt.flatMap { params =>
if (at[FunctionArrow] && nextIfIndentAhead()) Some {
val contextFunction = prev[ContextArrow]
val trm = blockExprOnIndent()
autoEndPos(paramPos) {
if (contextFunction) Term.ContextFunction(params, trm) else Term.Function(params, trm)
}
}
else None
}
}.getOrElse(None)
private def getFewerBracesApplyOnColon(fun: Term, startPos: Int): Option[Term] = {
val colonPos = currIndex
getFewerBracesArgOnColon().map { arg =>
val endPos = AutoPos.endIndex
val argClause = atPos(colonPos, endPos)(Term.ArgClause(arg :: Nil))
val arguments = atPos(startPos, endPos)(Term.Apply(fun, argClause))
simpleExprRest(arguments, canApply = true, startPos = startPos)
}
}
private def argumentExprsOrPrefixExpr(location: Location): Term = {
val isBrace = at[LeftBrace]
if (!isBrace && !at[LeftParen]) prefixExpr(allowRepeated = false)
else {
def findRep(args: List[Term]): Option[Term.Repeated] = args.collectFirst {
case Term.Assign(_, rep: Term.Repeated) => rep
case rep: Term.Repeated => rep
}
val lpPos = currIndex
val args =
if (isBrace) checkNoTripleDot(blockExprOnBrace(allowRepeated = true)) :: Nil
else inParensOnOpenOr(argumentExprsInParens(location))(Nil)
def getRest() = {
findRep(args).foreach(x => syntaxError("repeated argument not allowed here", at = x))
simpleExprRest(makeTupleTerm(lpPos, args), canApply = true, startPos = lpPos)
}
currToken match {
case _: Dot | _: OpenDelim | _: Underscore => getRest()
// see ArgumentExprs in:
// https://scala-lang.org/files/archive/spec/2.13/13-syntax-summary.html#context-free-syntax
case _: EOL if !isBrace && !dialect.allowSignificantIndentation && tryAhead[LeftBrace] =>
getRest()
case _ => makeTupleTerm { arg =>
val res = maybeAnonymousFunction(arg)
if (isBrace) Right(res) else Left(res :: Nil)
}(lpPos, args)
}
}
}
private def argumentExpr(location: Location): Term = currToken match {
case t @ Ellipsis(2) => syntaxError(Messages.QuasiquoteRankMismatch(2, 1), at = t)
case _ => expr(location = location, allowRepeated = true)
}
private def getArgClauseOnBrace(): Term.ArgClause = autoPos {
val arg = blockExprOnBrace(allowRepeated = true)
Term.ArgClause(arg :: Nil)
}
private def getArgClauseOnParen(location: Location = NoStat): Term.ArgClause = autoPos(
inParensOnOpenOr(currToken match {
case t @ Ellipsis(2) => (ellipsis[Term](t) :: Nil).reduceWith(Term.ArgClause(_))
case x =>
val using = x.text == soft.KwUsing.name && mightStartStat(peekToken, closeDelimOK = false)
val mod = if (using) Some(atCurPosNext(Mod.Using())) else None
argumentExprsInParens(location).reduceWith(Term.ArgClause(_, mod))
})(Term.ArgClause(Nil))
)
private def argumentExprsInParens(location: Location = NoStat): List[Term] = {
@tailrec
def checkRep(exprsLeft: List[Term]): Unit = exprsLeft match {
case head :: tail =>
if (!head.is[Term.Repeated]) checkRep(tail)
else if (tail.nonEmpty) syntaxError("repeated argument not allowed here", at = head)
case _ =>
}
val exprs = commaSeparated(argumentExpr(location))
checkRep(exprs)
exprs
}
private def checkNoTripleDot[T <: Tree](tree: T): T = tree match {
case q: Quasi if q.rank == 2 => syntaxError(Messages.QuasiquoteRankMismatch(q.rank, 1), at = q)
case t => t
}
private def isCaseIntro(): Boolean = at[KwCase] && isCaseIntroOnKwCase()
// call it only if token is KwCase
private def isCaseIntroOnKwCase(): Boolean = !peekToken.isClassOrObject
private def blockExprPartial[T <: Token: ClassTag](orElse: => Term): Term = {
val isPartial = peekToken match {
case _: KwCase => ahead(isCaseIntroOnKwCase())
case _: Ellipsis => ahead(peek[KwCase])
case _ => false
}
if (isPartial) autoPos(next {
try Term.PartialFunction(caseClauses())
finally acceptAfterOptNL[T]
})
else orElse
}
private def blockRaw(allowRepeated: Boolean = false): Term.Block =
toBlockRaw(blockStatSeq(allowRepeated = allowRepeated))
private def blockOnIndent(keepBlock: Boolean = false): Term = autoPosOpt {
indentedOnOpen(blockStatSeq() match {
case (t: Term) :: Nil if !(keepBlock || isPrecededByDetachedComment(currIndex, t.endIndex)) =>
t
case stats => toBlockRaw(stats)
})
}
private def blockExprOnIndent(keepBlock: Boolean = false): Term =
blockExprPartial[Indentation.Outdent](blockOnIndent(keepBlock))
private def blockOnBrace(fstats: => List[Stat]): Term = autoPos(toBlockRaw(inBracesOnOpen(fstats)))
private def blockOnBrace(allowRepeated: Boolean = false): Term =
blockOnBrace(blockStatSeq(allowRepeated = allowRepeated))
private def blockExprOnBrace(allowRepeated: Boolean = false, isOptional: Boolean = false): Term =
blockExprPartial[RightBrace] {
if (isOptional) blockOnOther(allowRepeated) else blockOnBrace(allowRepeated)
}
private def blockOnOther(allowRepeated: Boolean = false): Term = autoPosOpt {
blockStatSeq(allowRepeated = allowRepeated) match {
case (term: Term) :: Nil => term
case stats => toBlockRaw(stats)
}
}
def caseClause(forceSingleExpr: Boolean = false): Case = {
expectNot[KwCase]
autoEndPos(prevIndex) {
def caseBody() = {
accept[RightArrow]
val start = currIndex
def parseStatSeq() = blockStatSeq() match {
case List(q: Quasi) => q.become[Term]
case List(term: Term) => term
case other => autoEndPos(start)(Term.Block(other))
}
indentedOr(parseStatSeq()) {
if (forceSingleExpr) expr(location = BlockStat, allowRepeated = false) else parseStatSeq()
}
}
@inline
def guard(): Option[Term] = if (at[KwIf]) Some(guardOnIf()) else None
Case(pattern(), guard(), caseBody())
}
}
def quasiquoteCase(): Case = entrypointCase()
def entrypointCase(): Case = {
accept[KwCase]
caseClause()
}
def toCasesBlock(cases: List[Case]): Term.CasesBlock = cases.reduceWith(Term.CasesBlock.apply)
def casesBlock(): Term.CasesBlock = casesBlockIfAny().getOrElse(syntaxErrorExpected[KwCase])
def casesBlockIfAny(): Option[Term.CasesBlock] = caseClausesIfAny().map(toCasesBlock)
def caseClauses(): List[Case] = caseClausesIfAny().getOrElse(syntaxErrorExpected[KwCase])
def caseClausesIfAny(): Option[List[Case]] = {
val cases = new ListBuffer[Case]
@tailrec
def iter(): Unit = currToken match {
case t: Ellipsis =>
cases += ellipsis[Case](t, 1, accept[KwCase])
skipAllStatSep()
iter()
case _: KwCase if isCaseIntroOnKwCase() =>
next()
val quasiCase = unquoteOpt[Case]
cases += quasiCase.getOrElse(caseClause())
if (quasiCase.nonEmpty) skipAllStatSep()
else if (StatSep(currToken)) tryAhead(isCaseIntro())
iter()
case _ =>
}
iter()
if (cases.isEmpty) None else Some(cases.toList)
}
private def guardOnIf(): Term = {
next()
autoPos(postfixExpr(allowRepeated = false))
}
private def enumeratorGuardOnIf() = autoPos(Enumerator.Guard(guardOnIf()))
def enumerators(): List[Enumerator] = listBy[Enumerator] { enums =>
def notEnumsEnd(token: Token): Boolean = token match {
case _: Indentation.Outdent | _: CloseDelim | _: KwDo | _: KwYield => false
case _ => true
}
doWhile {
enums += enumerator(isFirst = enums.isEmpty)
while (at[Token.KwIf]) enums += enumeratorGuardOnIf()
} {
if (StatSep(currToken)) nextIf(notEnumsEnd(peekToken))
else isImplicitStatSep() && notEnumsEnd(currToken)
}
}
private def enumerator(isFirst: Boolean = false): Enumerator = currToken match {
case _: KwIf if !isFirst => enumeratorGuardOnIf()
case t: Ellipsis => ellipsis[Enumerator](t, 1)
case t: Unquote if !peek[Equals, LeftArrow] => unquote[Enumerator](t) // support for q"for ($enum1; ..$enums; $enum2)"
case _ => generator()
}
def quasiquoteEnumerator(): Enumerator = entrypointEnumerator()
def entrypointEnumerator(): Enumerator = enumerator()
private def generator(): Enumerator with Tree.WithBody = {
val startPos = currIndex
val hasVal = acceptOpt[KwVal]
val isCase = acceptOpt[KwCase]
val pat = noSeq.pattern1(isForComprehension = true)
val hasEq = at[Equals]
if (hasVal)
if (hasEq)
deprecationWarning("val keyword in for comprehension is deprecated", at = currToken)
else syntaxError("val in for comprehension must be followed by assignment", at = currToken)
if (hasEq) next() else accept[LeftArrow]
val rhs = expr()
autoEndPos(startPos) {
if (hasEq) Enumerator.Val(pat, rhs)
else if (isCase) Enumerator.CaseGenerator(pat, rhs)
else Enumerator.Generator(pat, rhs)
}
}
/* -------- PATTERNS ------------------------------------------- */
/**
* Methods which implicitly propagate whether the initial call took place in a context where
* sequences are allowed. Formerly, this was threaded through methods as boolean seqOK.
*/
trait SeqContextSensitive extends PatternContextSensitive {
// is a sequence pattern _* allowed?
def isSequenceOK: Boolean
def isNamedTupleOk: Boolean = false
def patterns(): List[Pat] = commaSeparated(pattern())
def pattern(): Pat = patternAlternatives(Nil)
@tailrec
private def patternAlternatives(pats: List[Pat]): Pat = {
val pat = pattern1()
checkNoTripleDot(pat)
if (Keywords.PatAlt(currToken)) {
next()
patternAlternatives(pat :: pats)
} else if (pats.isEmpty) pat
else {
val endPos = pat.endIndex
pats.foldLeft(pat) { case (rtAll, ltOne) =>
atPos(ltOne.begIndex, endPos)(Pat.Alternative(ltOne, rtAll))
}
}
}
def quasiquotePattern(): Pat = {
// NOTE: As per quasiquotes.md
// * p"x" => Pat.Var (ok)
// * p"X" => Pat.Var (needs postprocessing, parsed as Term.Name)
// * p"`x`" => Term.Name (ok)
// * p"`X`" => Term.Name (ok)
val nonbqIdent = at[Ident] && !currToken.isBackquoted
argumentPattern() match {
case pat: Term.Name if nonbqIdent => copyPos(pat)(Pat.Var(pat))
case pat => pat
}
}
def entrypointPattern(): Pat = pattern()
def unquotePattern(): Pat = dropAnyBraces(pattern())
private def getSeqWildcard(isEnabled: Boolean, elseF: => Pat, mapF: Pat => Pat = identity) =
if (isEnabled && at[Underscore]) autoPosIf(getSeqWildcardAtUnderscore()).fold(elseF)(mapF)
else elseF
private def getSeqWildcardAtUnderscore() =
if (isSequenceOK && isStar(peekToken)) tryParse {
nextTwice() // skip underscore and star
newLinesOpt()
val isArgListEnd = at[RightParen, RightBrace, EOF]
if (isArgListEnd) Some(Pat.SeqWildcard()) else None
}
else None
def pattern1(isForComprehension: Boolean = false): Pat = {
val p = pattern2(isForComprehension)
@inline
def typed() = Pat
.Typed(p, super.patternTyp(allowInfix = false, allowImmediateTypevars = false))
val pat = p match {
case _ if !at[Colon] => p
case _: Quasi =>
next()
typed()
case _: Pat.Var =>
next()
if (!dialect.allowColonForExtractorVarargs && at[Underscore] && isStar(peekToken))
syntaxError(s"$dialect does not support var: _*", at = p)
getSeqWildcard(dialect.allowColonForExtractorVarargs, typed(), Pat.Bind(p, _))
case _: Pat.Wildcard =>
next()
getSeqWildcard(dialect.allowColonForExtractorVarargs, typed())
case _: Pat if dialect.allowAllTypedPatterns =>
next()
typed()
case _: Pat => p
}
if (pat eq p) p else autoEndPos(p)(pat)
}
def pattern2(isForComprehension: Boolean = false): Pat = {
val p = pattern3(isForComprehension)
val pat = p match {
case _ if !at[At] => p
case _: Quasi =>
next()
Pat.Bind(p, pattern3())
case _: Term.Name =>
syntaxError("Pattern variables must start with a lower-case letter. (SLS 8.1.1.)", at = p)
case p: Pat.Var =>
next()
Pat.Bind(p, getSeqWildcard(dialect.allowAtForExtractorVarargs, pattern3()))
case _: Pat.Wildcard =>
next()
getSeqWildcard(dialect.allowAtForExtractorVarargs, pattern3())
case p => p
}
if (pat eq p) p else autoEndPos(p)(pat)
}
def pattern3(isForComprehension: Boolean = false): Pat = {
val ctx = patInfixContext
val lhs = simplePattern(badPattern3, isForComprehension = isForComprehension)
val base = ctx.stack
@tailrec
def loop(rhs: ctx.Typ): ctx.Typ = {
@inline
def lhs(opOpt: Option[Term.Name]) = ctx.reduceStack(base, rhs, rhs, opOpt)
currToken match {
case _: Unquote | Keywords.NotPatAlt() =>
val op = termName()
expectNot[LeftBracket]("infix patterns cannot have type arguments")
ctx.push(ctx.UnfinishedInfix(lhs(Some(op)), op))
loop(simplePattern(badPattern3, isRhs = true))
case _ => lhs(None)
}
}
loop(lhs)
}
def badPattern3(tok: Token): Nothing = {
import patInfixContext._
def isComma = tok.is[Comma]
def isDelimiter = tok.isAny[RightParen, RightBrace]
def isCommaOrDelimiter = isComma || isDelimiter
val (isUnderscore, isStar) = stack match {
case UnfinishedInfix(lhs, Term.Name("*")) :: _ => (lhs.is[Pat.Wildcard], true)
case _ => (false, false)
}
val preamble = "bad simple pattern:"
val subtext = (isUnderscore, isStar, isSequenceOK) match {
case (true, true, true) if isComma =>
"bad use of _* (a sequence pattern must be the last pattern)"
case (true, true, true) if isDelimiter => "bad brace or paren after _*"
case (true, true, false) if isDelimiter => "bad use of _* (sequence pattern not allowed)"
case (false, true, true) if isDelimiter => "use _* to match a sequence"
case (false, true, _) if isCommaOrDelimiter => "trailing * is not a valid pattern"
case _ => null
}
val msg = if (subtext != null) s"$preamble $subtext" else "illegal start of simple pattern"
syntaxError(msg, at = tok)
}
def simplePattern(
onError: Token => Nothing,
isRhs: Boolean = false,
isForComprehension: Boolean = false
): Pat = {
val startPos = currIndex
autoEndPos(startPos)(currToken match {
case Unary.Numeric(unary) if tryAhead[NumericConstant[_]] => numericLiteral(unary)
case sidToken @ (_: Ident | _: KwThis | _: Unquote) =>
val sid = stableId()
val targs = if (at[LeftBracket]) Some(super.patternTypeArgs()) else None
if (at[LeftParen]) {
val ref = sid.become[Term]
Pat.Extract(
targs.fold(ref)(x => autoEndPos(sid)(Term.ApplyType(ref, x))),
autoPos(argumentPatterns().reduceWith(Pat.ArgClause.apply))
)
} else {
targs.foreach { x =>
syntaxError(s"pattern must be a value or have parens: $sid$x", at = currToken)
}
sid match {
case name: Term.Name if isNamedTupleOk && acceptOpt[Equals] =>
Pat.Assign(name, noSeqWithNamed.pattern())
case name: Term.Name.Quasi => name.become[Pat]
case name: Term.Name =>
if (soft.StarSplice(currToken) && tryAheadNot[Ident]) Pat.Repeated(name)
else if (if (!isForComprehension && sidToken.isBackquoted) at[Colon, At]
else {
val first = name.value.head
first == '_' || Character.getType(first) == Character.LOWERCASE_LETTER ||
dialect.allowUpperCasePatternVarBinding && at[At]
}) Pat.Var(name)
else name
case select: Term.Select => select
case _ => unreachable(Map(
"token" -> currToken,
"tokenStructure" -> currToken.structure,
"sid" -> sid,
"sidStructure" -> sid.structure
))
}
}
case _: Underscore => getSeqWildcardAtUnderscore().getOrElse(next(Pat.Wildcard()))
case _: Literal => literal()
case _: Interpolation.Id => interpolatePat()
case _: Xml.Start => xmlPat()
case _: LeftParen =>
val lpPos = currIndex
val patterns = inParensOnOpenOr(noSeqWithNamed.patterns())(Nil)
makeTuple(lpPos, patterns, Lit.Unit(), Pat.Tuple.apply) {
case t if !isRhs => Right(t)
case t @ Pat.Tuple(_ :: Nil) => Right(t)
case t => Left(t :: Nil)
}
case _: MacroQuote => QuotedPatternContext.within(Pat.Macro(macroQuote()))
case MacroQuotedIdent(ident) => Pat.Macro(macroQuotedIdent(ident))
case _: KwGiven =>
next()
Pat.Given(super.patternTyp(allowInfix = false, allowImmediateTypevars = false))
case t => onError(t)
})
}
}
/** The implementation of the context sensitive methods for parsing outside of patterns. */
object outPattern extends PatternContextSensitive {}
/** The implementation for parsing inside of patterns at points where sequences are allowed. */
object seqOK extends SeqContextSensitive {
val isSequenceOK = true
}
/** The implementation for parsing inside of patterns at points where sequences are disallowed. */
object noSeq extends SeqContextSensitive {
val isSequenceOK = false
}
/* In addition to above named tuple patterns are allowed*/
object noSeqWithNamed extends SeqContextSensitive {
val isSequenceOK = false
override def isNamedTupleOk: Boolean = true
}
/**
* These are default entry points into the pattern context sensitive methods: they are all
* initiated from non-pattern context.
*/
def typ() = outPattern.typ()
def paramType() = outPattern.paramType()
private def typeBlock() = outPattern.typeBlock()
def typeIndentedOpt() = outPattern.typeIndentedOpt()
def quasiquoteType() = outPattern.quasiquoteType()
def entrypointType() = outPattern.entrypointType()
def startInfixType(inGivenSig: Boolean = false) = outPattern.infixType(inGivenSig = inGivenSig)
def startModType() = outPattern.annotType()
def exprTypeArgs() = outPattern.typeArgsInBrackets()
def exprSimpleType() = outPattern.simpleType()
/** Default entry points into some pattern contexts. */
def pattern(): Pat = noSeq.pattern()
def quasiquotePattern(): Pat = seqOK.quasiquotePattern()
def entrypointPattern(): Pat = seqOK.entrypointPattern()
def unquotePattern(): Pat = noSeq.unquotePattern()
def unquoteSeqPattern(): Pat = seqOK.unquotePattern()
def seqPatterns(): List[Pat] = seqOK.patterns()
def argumentPattern(): Pat = seqOK.pattern()
def argumentPatterns(): List[Pat] = inParens(if (at[RightParen]) Nil else seqPatterns())
def xmlLiteralPattern(): Pat = syntaxError("XML literals are not supported", at = currToken)
def patternTyp() = noSeq.patternTyp(allowInfix = true, allowImmediateTypevars = false)
def patternTypeArgs() = noSeq.patternTypeArgs()
/* -------- MODIFIERS and ANNOTATIONS ------------------------------------------- */
private def privateModifier(): Mod = accessModifier(Mod.Private(_))
private def protectedModifier(): Mod = accessModifier(Mod.Protected(_))
private def accessModifier(mod: Ref => Mod): Mod = autoPos {
next()
if (!acceptOpt[LeftBracket]) mod(anonName())
else {
val result = mod {
if (at[KwThis]) anonThis()
else termName().becomeOr[Ref](x => copyPos(x)(Name.Indeterminate(x.value)))
}
accept[RightBracket]
result
}
}
private def badModifier(tok: Token, isLocal: Boolean): Nothing = {
val local = if (isLocal) "local " else ""
syntaxError(s"${local}modifier expected but ${tok.text} found", at = tok)
}
private def modifier(tok: ModifierKeyword, isLocal: Boolean): Mod = tok match {
case _: KwAbstract => atCurPosNext(Mod.Abstract())
case _: KwFinal => atCurPosNext(Mod.Final())
case _: KwSealed => atCurPosNext(Mod.Sealed())
case _: KwImplicit => atCurPosNext(Mod.Implicit())
case _: KwLazy => atCurPosNext(Mod.Lazy())
case _: KwOverride if !isLocal => atCurPosNext(Mod.Override())
case _: KwPrivate if !isLocal => privateModifier()
case _: KwProtected if !isLocal => protectedModifier()
case _ => badModifier(tok, isLocal)
}
private def modifier(tok: Ident, isLocal: Boolean): Mod = tok.text match {
case soft.KwInline() => atCurPosNext(Mod.Inline())
case soft.KwInfix() => atCurPosNext(Mod.Infix())
case soft.KwOpen() if !isLocal => atCurPosNext(Mod.Open())
case soft.KwOpaque() => atCurPosNext(Mod.Opaque())
case soft.KwTransparent() => atCurPosNext(Mod.Transparent())
case soft.KwErased() => atCurPosNext(Mod.Erased())
case soft.KwTracked() => atCurPosNext(Mod.Tracked())
case _ => badModifier(tok, isLocal)
}
def quasiquoteModifier(): Mod = entrypointModifier()
def entrypointModifier(): Mod = {
def fail(t: Token, what: String = "modifier"): Nothing =
syntaxError(s"$what expected but ${t.name} found", at = t)
val mod = currToken match {
case t: Unquote => unquote[Mod](t)
case _: At => annots(skipNewLines = true) match {
case Nil => unreachable
case annot :: Nil => annot
case _ => fail(currToken, "end of file")
}
case _: KwPrivate => privateModifier()
case _: KwProtected => protectedModifier()
case _: KwImplicit => atCurPosNext(Mod.Implicit())
case _: KwFinal => atCurPosNext(Mod.Final())
case _: KwSealed => atCurPosNext(Mod.Sealed())
case _: KwOverride => atCurPosNext(Mod.Override())
case _: KwCase => atCurPosNext(Mod.Case())
case _: KwAbstract => atCurPosNext(Mod.Abstract())
case _: KwLazy => atCurPosNext(Mod.Lazy())
case _: KwVal if !dialect.allowUnquotes => atCurPosNext(Mod.ValParam())
case _: KwVar if !dialect.allowUnquotes => atCurPosNext(Mod.VarParam())
case t: Ident => t.text match {
case "+" => atCurPosNext(Mod.Covariant())
case "-" => atCurPosNext(Mod.Contravariant())
case "valparam" if dialect.allowUnquotes => atCurPosNext(Mod.ValParam())
case "varparam" if dialect.allowUnquotes => atCurPosNext(Mod.VarParam())
case soft.KwOpen() => atCurPosNext(Mod.Open())
case soft.KwTransparent() => atCurPosNext(Mod.Transparent())
case soft.KwInline() => atCurPosNext(Mod.Inline())
case soft.KwInfix() => atCurPosNext(Mod.Infix())
case soft.KwErased() => atCurPosNext(Mod.Erased())
case _ => fail(t)
}
case t => fail(t)
}
newLinesOpt()
mod
}
private def ctorModifiers(buf: ListBuffer[Mod]): Unit = currToken match {
case t: Unquote => if (peek[LeftParen]) buf += unquote[Mod](t)
case t: Ellipsis => buf += ellipsis[Mod](t, 1)
case _: KwPrivate => buf += privateModifier()
case _: KwProtected => buf += protectedModifier()
case _ =>
}
private def tparamModifiers(buf: ListBuffer[Mod]): Unit = currToken match {
case t: Unquote => if (peek[Ident, Unquote]) buf += unquote[Mod](t)
case t: Ellipsis => buf += ellipsis[Mod](t, 1)
case TParamVariant(mod) => buf += atCurPosNext(mod)
case _ =>
}
def modifiersBuf(
buf: ListBuffer[Mod],
isLocal: Boolean = false,
isParams: Boolean = false
): Unit = {
def append(mod: Mod): Unit = { buf += mod; newLinesOpt() }
// the only things that can come after $mod or $mods are either keywords or names; the former is easy,
// but in the case of the latter, we need to take care to not hastily parse those names as modifiers
def continueLoop = peekToken match {
case _: Colon | _: Equals | _: EOF | _: LeftBracket | _: Subtype | _: Supertype |
_: Viewbound => true
case _ => false
}
@tailrec
def loop: Unit = currToken match {
case _: AtEOL if !isLocal => next(); loop
case t: Unquote => if (!continueLoop) { append(unquote[Mod](t)); loop }
case t: Ellipsis => append(ellipsis[Mod](t, 1)); loop
case _: KwCase if peek[KwObject, KwClass] => buf += atCurPosNext(Mod.Case())
case t: ModifierKeyword => append(modifier(t, isLocal)); loop
case t: Ident if {
if (isParams) !peek[Colon] && ParamsModifier.matches(t.value)
else isSoftModifier(currIndex)
} => append(modifier(t, isLocal)); loop
case _ =>
}
loop
}
def annots(skipNewLines: Boolean, allowArgss: Boolean = true): List[Mod.Annot] =
listBy[Mod.Annot](annotsBuf(_, skipNewLines, allowArgss))
def annotsBuf[T >: Mod.Annot](
annots: ListBuffer[T],
skipNewLines: Boolean,
insidePrimaryCtorAnnot: Boolean = false,
allowArgss: Boolean = true
): Unit = while (currToken match {
case _: At =>
next()
annots += unquoteOpt[Mod.Annot].getOrElse(autoEndPos(prevIndex) {
Mod.Annot(initRest(exprSimpleType(), allowArgss, insidePrimaryCtorAnnot))
})
true
case t: Ellipsis if peek[At] =>
annots += ellipsis[Mod.Annot](t, 1, next())
true
case _ => false
}) if (skipNewLines) newLineOpt()
/* -------- PARAMETERS ------------------------------------------- */
@tailrec
private def onlyLastParameterCanBeRepeated(params: List[Term.Param]): Unit = params match {
case p :: tail if tail.nonEmpty =>
if (!p.is[Term.Param.Quasi] && p.decltpe.is[Type.Repeated])
syntaxError("*-parameter must come last", p)
onlyLastParameterCanBeRepeated(tail)
case _ =>
}
private def memberParamClauseGroupOnParen(
ownerIsType: Boolean,
ownerIsCase: Boolean = false
): Member.ParamClauseGroup = {
val tparams = emptyTypeParams
autoPos(
termParamClausesOnParen(ownerIsType, ownerIsCase = ownerIsCase, ellipsisMaxRank = 3) match {
case (x: Quasi) :: Nil if x.rank == 2 => reellipsis[Member.ParamClauseGroup](x, 1)
case x => Member.ParamClauseGroup(tparams, x)
}
)
}
private def memberParamClauseGroupOnBracket(
ownerIsType: Boolean,
ownerIsCase: Boolean = false,
allowUnderscore: Boolean = true
): Member.ParamClauseGroup = autoPos {
val tparamClause = typeParamClauseOnBracket(ownerIsType, allowUnderscore = allowUnderscore)
val paramClauses = termParamClauses(ownerIsType, ownerIsCase = ownerIsCase)
Member.ParamClauseGroup(tparamClause, paramClauses)
}
def memberParamClauseGroup(
isFirst: Boolean,
ownerIsType: Boolean,
ownerIsCase: Boolean = false,
allowUnderscore: Boolean = true
): Option[Member.ParamClauseGroup] = {
def onFirstParen = Some(memberParamClauseGroupOnParen(ownerIsType, ownerIsCase = ownerIsCase))
def onBracket = Some(memberParamClauseGroupOnBracket(
ownerIsType,
ownerIsCase = ownerIsCase && isFirst,
allowUnderscore = allowUnderscore
))
currToken match {
case _: LeftParen if isFirst => onFirstParen
case _: LeftBracket => onBracket
case _: EOL => peekToken match {
case _: LeftParen if isFirst => next(); onFirstParen
case _: LeftBracket => next(); onBracket
case _ => None
}
case _ => None
}
}
def memberParamClauseGroups(ownerIsType: Boolean): List[Member.ParamClauseGroup] =
listBy[Member.ParamClauseGroup] { buf =>
while ({
val pcgOpt = memberParamClauseGroup(isFirst = buf.isEmpty, ownerIsType = ownerIsType)
pcgOpt.exists { pcg =>
buf += pcg
// can't have consecutive type clauses (so params must be present)
// also, only the very last param may contain implicit
pcg.is[Quasi] ||
pcg.paramClauses.lastOption.exists(pc => pc.is[Quasi] || !pc.mod.is[Mod.Implicit])
}
}) {}
}
def termParamClauses(ownerIsType: Boolean, ownerIsCase: Boolean = false): List[Term.ParamClause] =
if (!isAfterOptNewLine[LeftParen]) Nil else termParamClausesOnParen(ownerIsType, ownerIsCase)
private def termParamClausesOnParen(
ownerIsType: Boolean,
ownerIsCase: Boolean = false,
first: Option[Term.ParamClause] = None,
ellipsisMaxRank: Int = 2
): List[Term.ParamClause] = listBy[Term.ParamClause] { paramss =>
first.foreach(paramss += _)
while ({
val clause = termParamClauseOnParen(
ownerIsType = ownerIsType,
ownerIsCase = ownerIsCase && paramss.isEmpty,
ellipsisMaxRank = ellipsisMaxRank
)
paramss += clause
val hasModImplicit = clause match {
case _: Quasi => false
case x => x.mod.exists(_.is[Mod.Implicit])
}
!hasModImplicit && isAfterOptNewLine[LeftParen]
}) {}
}
private def termParamClauseOnParen(
ownerIsType: Boolean,
ownerIsCase: Boolean = false,
ellipsisMaxRank: Int = 2
): Term.ParamClause = autoPos {
def reduceParams(params: List[Term.Param], mod: Option[Mod.ParamsType] = None) = params
.reduceWith { x =>
onlyLastParameterCanBeRepeated(x)
toParamClause(mod)(x)
}
def parseParams(
mod: Option[Mod.ParamsType] = None,
anonNameOK: Boolean = false,
ownerIsTypeOverride: => Boolean = false
) = {
val allowAnonName = anonNameOK || GivenSigContext.isInside()
val params = commaSeparatedWithIndex(termParam(
ownerIsCase = ownerIsCase,
ownerIsType = ownerIsType || ownerIsTypeOverride,
allowAnonName = allowAnonName,
mod = mod
))
reduceParams(params, mod)
}
inParensOnOpenOr(currToken match {
case t @ Ellipsis(rank) if rank >= 2 && rank <= ellipsisMaxRank =>
reduceParams(List(ellipsis[Term.Param](t)))
case _: KwImplicit => parseParams(Some(atCurPosNext(Mod.Implicit())))
case t: Ident if !peek[Colon] =>
t.text match {
case soft.KwUsing() => parseParams(
mod = Some(atCurPosNext(Mod.Using())),
anonNameOK = true,
ownerIsTypeOverride = ExtensionSigContext.isInside()
)
case _ => parseParams()
}
case _ => parseParams()
})(Term.ParamClause(Nil))
}
def termParam(
ownerIsCase: Boolean,
ownerIsType: Boolean,
mod: Option[Mod.ParamsType] = None,
allowAnonName: Boolean = false
)(paramIdx: Int): Term.Param = autoPos {
val mods = new ListBuffer[Mod]
def appendMod(mod: Mod) = { mods += atCurPosNext(mod); true }
annotsBuf(mods, skipNewLines = false)
val numAnnots = mods.length
if (ownerIsType) modifiersBuf(mods, isParams = true)
else while (currToken match {
case t: Ident if !peek[Colon] =>
t.text match {
case soft.KwInline() => appendMod(Mod.Inline())
case soft.KwErased() => appendMod(Mod.Erased())
case _ => false
}
case _ => false
}) {}
val hasExplicitMods = mods.view.drop(numAnnots).exists {
case _: Mod.Quasi | _: Mod.Erased => false
case m: Mod.Private => m.within.is[Name.Anonymous]
case m: Mod.Protected => m.within.is[Name.Anonymous]
case _ => true
}
mod.foreach { mod =>
val clazz = mod.getClass
mods.find(_.getClass eq clazz) match {
case None => mods += mod
case Some(x) => if (paramIdx == 0) syntaxError("repeated modifier", at = x)
}
}
val varOrVarParamMod = currToken match {
case _ if !ownerIsType => None
case _: KwVal => Some(atCurPosNext(Mod.ValParam()))
case _: KwVar => Some(atCurPosNext(Mod.VarParam()))
case _ => if (hasExplicitMods) syntaxErrorExpected[KwVal]; None
}
varOrVarParamMod.foreach(mods += _)
def endParamQuasi = at[RightParen, Comma]
def checkByNameParamType(tpt: Type): Unit = {
def mayNotBeByName(mod: Mod) =
syntaxError(s"`$mod' parameters may not be call-by-name", at = tpt)
val notLocalToThis: Boolean = ownerIsType && (ownerIsCase || varOrVarParamMod.nonEmpty) &&
mods.forall { case Mod.Private(_: Term.This) => false; case _ => true }
val badMod =
if (notLocalToThis) varOrVarParamMod
else if (dialect.allowImplicitByNameParameters) None
else mod.filter(_.is[Mod.Implicit])
badMod.foreach(mayNotBeByName)
}
def getParamType: Type = paramType() match {
case tpt: Type.ByName => checkByNameParamType(tpt); tpt
case tpt => tpt
}
def getParam(name: Name, tpt: Option[Type]) = {
val default = if (acceptOpt[Equals]) Some(expr()) else None
Term.Param(mods.toList, name, tpt, default)
}
mods.headOption.collect { case q: Mod.Quasi if endParamQuasi => q.become[Term.Param] }
.getOrElse(currToken match {
case t: Ellipsis => ellipsis[Term.Param](t, 1)
case _ if !peek[Colon] && (allowAnonName || GivenSigContext.isInside()) =>
getParam(anonName(), Some(getParamType))
case t: Unquote =>
val name = unquote[Name](t)
if (endParamQuasi) name.become[Term.Param]
else getParam(name, if (acceptOpt[Colon]) Some(getParamType) else None)
case t: Ident =>
val name = atCurPosNext(Term.Name(t.value))
accept[Colon]
getParam(name, Some(getParamType))
case _ => syntaxErrorExpected[Ident]
})
}
def quasiquoteTermParam(): Term.Param = entrypointTermParam()
def entrypointTermParam(): Term.Param = termParam(ownerIsCase = false, ownerIsType = true)(-1)
private def emptyTypeParamsRaw: Type.ParamClause = Type.ParamClause(Nil)
private def emptyTypeParams: Type.ParamClause = atCurPosEmpty(emptyTypeParamsRaw)
private def typeParamClauseOpt(
ownerIsType: Boolean,
allowUnderscore: Boolean = true
): Type.ParamClause =
if (!isAfterOptNewLine[LeftBracket]) emptyTypeParams
else typeParamClauseOnBracket(ownerIsType, allowUnderscore)
private def typeParamClauseOnBracket(
ownerIsType: Boolean,
allowUnderscore: Boolean = true
): Type.ParamClause = TypeBracketsContext.within(autoPos(inBrackets(
commaSeparated(typeParam(ownerIsType, allowUnderscore)).reduceWith(Type.ParamClause.apply)
)))
def typeParam(ownerIsType: Boolean, allowUnderscore: Boolean = true): Type.Param = autoPos {
val mods: List[Mod] = listBy[Mod] { buf =>
annotsBuf(buf, skipNewLines = false)
if (ownerIsType) tparamModifiers(buf)
}
def endTparamQuasi = at[RightBracket, Comma]
mods.headOption match {
case Some(q: Mod.Quasi) if endTparamQuasi => q.become[Type.Param]
case _ =>
val name = currToken match {
case t: Ident => typeName(t)
case t: Unquote => unquote[Name](t)
case _: Underscore if allowUnderscore => namePlaceholder()
case _ =>
if (allowUnderscore) syntaxError("identifier or `_' expected", at = currToken)
else syntaxError("identifier expected", at = currToken)
}
name match {
case q: Quasi if endTparamQuasi => q.become[Type.Param]
case _ =>
val tparams = typeParamClauseOpt(ownerIsType = true)
val tbounds = typeBounds()
val vbounds = new ListBuffer[Type]
val cbounds = new ListBuffer[Type]
@inline
def getBound(allowAlias: Boolean): Type = currToken match {
case t: Ellipsis => ellipsis[Type](t, 1)
case _ => contextBoundOrAlias(allowAlias)
}
while (acceptOpt[Viewbound]) vbounds += getBound(allowAlias = false)
if (acceptOpt[Colon]) {
def addContextBounds[T: ClassTag]: Unit = doWhile(
cbounds += getBound(allowAlias = dialect.allowImprovedTypeClassesSyntax)
)(acceptOpt[T])
if (acceptOpt[LeftBrace]) inBracesAfterOpen(addContextBounds[Comma])
else addContextBounds[Colon]
}
Type.Param(mods, name, tparams, tbounds, vbounds.toList, cbounds.toList)
}
}
}
def contextBoundOrAlias(allowAlias: Boolean) = typ() match {
case Type.ApplyInfix(nm: Type.Name, Type.Name("as"), alias: Type.Name)
if allowAlias && dialect.allowImprovedTypeClassesSyntax => Type.BoundsAlias(alias, nm)
case tpe => tpe
}
def quasiquoteTypeParam(): Type.Param = entrypointTypeParam()
def entrypointTypeParam(): Type.Param = typeParam(ownerIsType = true)
def typeBounds() = autoPos(Type.Bounds(bound[Supertype], bound[Subtype]))
def bound[T: ClassTag]: Option[Type] = if (acceptOpt[T]) Some(typ()) else None
/* -------- DEFS ------------------------------------------- */
def exportStmt(): Stat = autoPos {
accept[KwExport]
Export(commaSeparated(importer()))
}
def importStmt(): Import = autoPos {
accept[KwImport]
Import(commaSeparated(importer()))
}
def importer(): Importer = autoPos {
val sid = stableId() match {
case q: Quasi => q.become[Term.Ref]
case sid @ Term.Select(q: Quasi, name) => copyPos(sid)(Term.Select(q.become[Term.Ref], name))
case path => path
}
def dotselectors = Importer(sid, importees())
def name(tn: Term.Name) = copyPos(tn)(Name.Indeterminate(tn.value))
sid match {
case Term.Select(sid: Term.Ref, tn: Term.Name) if sid.isPath =>
if (acceptOpt[Dot]) dotselectors
else if (acceptIf(soft.KwAs)) Importer(sid, importeeRename(name(tn)) :: Nil)
else if (Wildcard.isStar(tn.tokens.head))
Importer(sid, copyPos(tn)(Importee.Wildcard()) :: Nil)
else Importer(sid, copyPos(tn)(Importee.Name(name(tn))) :: Nil)
case tn: Term.Name if acceptIf(soft.KwAs) =>
Importer(atCurPosEmpty(Term.Anonymous()), importeeRename(name(tn)) :: Nil)
case _ =>
accept[Dot]
dotselectors
}
}
def quasiquoteImporter(): Importer = entrypointImporter()
def entrypointImporter(): Importer = importer()
def importees(): List[Importee] =
if (!acceptOpt[LeftBrace]) List(importWildcardOrName())
else {
val importees = inBracesAfterOpen(commaSeparated(importee()))
if (dialect.allowGivenImports) importees.map {
case Importee.Name(nm) if nm.value == "given" && importees.exists {
case _: Importee.Wildcard => true
case _ => false
} => copyPos(nm)(Importee.GivenAll())
case i => i
}
else importees
}
def importWildcardOrName(): Importee = {
val startPos = currIndex
autoEndPos(startPos)(currToken match {
case Wildcard() => next(); Importee.Wildcard()
case _: KwGiven => next(); if (at[Ident]) Importee.Given(typ()) else Importee.GivenAll()
case t: Unquote => Importee.Name(unquote[Name.Quasi](t))
case t: Ident => next(); Importee.Name(atPos(startPos)(Name.Indeterminate(t.value)))
case _ => syntaxErrorExpected[Ident]
})
}
def importeeRename(from: Name) = autoEndPos(from) {
importWildcardOrName() match {
case to: Importee.Name => Importee.Rename(from, to.name)
case _: Importee.Wildcard => Importee.Unimport(from)
case other => unreachable(Map("importees" -> other, "importeesStructure" -> other.structure))
}
}
def importee(): Importee = autoPos {
importWildcardOrName() match {
case from: Importee.Name if at[RightArrow] || soft.KwAs(currToken) =>
next()
importeeRename(from.name)
// NOTE: this is completely nuts
case from: Importee.Wildcard
if (at[RightArrow] || soft.KwAs(currToken)) && nextIf(Wildcard.unapply(peekToken)) =>
next()
from
case other => other
}
}
def quasiquoteImportee(): Importee = entrypointImportee()
def entrypointImportee(): Importee = importee()
def nonLocalDefOrDcl(
enumCaseAllowed: Boolean = false,
secondaryConstructorAllowed: Boolean = false
): Stat = {
val mods = listBy[Mod] { buf =>
annotsBuf(buf, skipNewLines = true)
modifiersBuf(buf)
}
defOrDclOrSecondaryCtor(mods, enumCaseAllowed, secondaryConstructorAllowed) match {
case s if s.isTemplateStat => s
case other => syntaxError("is not a valid template statement", at = other)
}
}
def defOrDclOrSecondaryCtor(
mods: List[Mod],
enumCaseAllowed: Boolean = false,
secondaryConstructorAllowed: Boolean = false
): Stat = {
def onlyInline() = mods match {
case (_: Mod.Inline) :: Nil => true
case _ => false
}
currToken match {
case _: KwVal | _: KwVar => patDefOrDcl(mods)
case _: KwGiven => givenDecl(mods)
case t: KwDef =>
if (!secondaryConstructorAllowed && peek[KwThis])
syntaxError("Illegal secondary constructor", at = t)
funDefOrDclOrExtensionOrSecondaryCtor(mods)
case _: KwType => typeDefOrDcl(mods)
case t: KwCase if dialect.allowEnums && peek[Ident] =>
if (!enumCaseAllowed) syntaxError("Enum cases are only allowed in enums", at = t)
mods.find(mod => !mod.isAccessMod && !mod.is[Mod.Annot]) match {
case Some(mod) => syntaxError("Only access modifiers allowed on enum case", at = mod.pos)
case None => enumCaseDef(mods)
}
case _: KwIf if onlyInline() => ifClause(mods)
case _ if isExprIntro(currToken, currIndex) && onlyInline() => inlineMatchClause(mods)
case _ if isKwExtension(currIndex) => extensionGroupDecl(mods)
case _ => tmplDef(mods, okTopLevel = false)
}
}
def endMarker(): Stat = autoPos {
assert(currToken.text == "end")
next()
Term.EndMarker(atCurPosNext(Term.Name(currToken match {
case t: Ident => t.value
case t => t.text
})))
}
def patDefOrDcl(mods: List[Mod]): Stat = autoEndPos(mods) {
val isVal = at[KwVal]
next()
val lhs: List[Pat] = commaSeparated(noSeq.pattern2()).map {
case name: Term.Name => copyPos(name)(Pat.Var(name))
case pat => pat
}
val tpOpt: Option[Type] = typedOpt()
if (acceptOpt[Equals]) {
val rhs = expr()
if (rhs.is[Term.Placeholder] && (tpOpt.isEmpty || isVal || !lhs.forall(_.is[Pat.Var])))
syntaxError("unbound placeholder parameter", at = currToken)
if (isVal) Defn.Val(mods, lhs, tpOpt, rhs) else Defn.Var(mods, lhs, tpOpt, rhs)
} else {
lhs.foreach { x =>
if (!x.is[Quasi] && !x.is[Pat.Var])
syntaxError("pattern definition may not be abstract", at = x)
}
tpOpt.fold(syntaxError("declaration requires a type", at = currToken)) { tp =>
if (isVal) Decl.Val(mods, lhs, tp) else Decl.Var(mods, lhs, tp)
}
}
}
/**
* @param oldOrNewSyntax
* new only if positive, old only if negative, either if zero
*
* ```scala
* Given ::= 'given' (GivenDef | OldGivenDef)
* GivenDef ::= [id ':'] GivenSig
* GivenSig ::= GivenImpl
* | '(' ')' '=>' GivenImpl
* | GivenConditional '=>' GivenSig
* GivenImpl ::= GivenType ([‘=’ Expr] | TemplateBody)
* | ConstrApps TemplateBody
* GivenConditional ::= DefTypeParamClause
* | DefTermParamClause
* | '(' FunArgTypes ')'
* | GivenType
* GivenType ::= AnnotType1 {id [nl] AnnotType1}
*
* -- syntax up to Scala 3.5, to be deprecated in the future
* OldGivenDef ::= [OldGivenSig] (AnnotType [‘=’ Expr] | StructuralInstance)
* -- one of `id`, `DefTypeParamClause`, `UsingParamClause` must be present
* OldGivenSig ::= [id] [DefTypeParamClause] {UsingParamClause} ‘:’
* StructuralInstance ::= ConstrApp {‘with’ ConstrApp} [‘with’ WithTemplateBody]
*
* -- for reference
* ConstrApps ::= ConstrApp ({‘,’ ConstrApp} | {‘with’ ConstrApp})
* ConstrApp ::= SimpleType {Annotation} {ParArgumentExprs}
* TemplateBody ::= :<<< [SelfType] TemplateStat {semi TemplateStat} >>>
* WithTemplateBody ::= <<< [SelfType] TemplateStat {semi TemplateStat} >>>
* ```
*/
private def givenDecl(mods: List[Mod]): Stat = autoEndPos(mods) {
accept[KwGiven]
val sig = givenSig()
val newSyntaxOK = dialect.allowImprovedTypeClassesSyntax
val usedNewSyntax = newSyntaxOK && !prev[Colon]
val initPos = sig.declPos.getOrElse(sig.declType.fold(currIndex)(_.begIndex))
val decltype = sig.declType.getOrElse(refinement(None).getOrElse(startModType()))
def getDefnGiven() = {
val headInit =
if (!at[LeftParen]) initImpl(initPos, decltype, allowSingleton = false)(Nil)
else initRestAt(initPos, decltype)(allowArgss = true, allowSingleton = false)
val inits = templateParentsWithFirst(allowComma = newSyntaxOK, allowWithBody = true)(headInit)
val body =
if (acceptOpt[KwWith])
if (isAfterOptNewLine[LeftBrace]) templateBodyOnLeftBrace(OwnedByGiven)
else if (at[Indentation.Indent]) autoPos(templateBodyOnIndentRaw(OwnedByGiven))
else syntaxError("expected '{' or indentation", at = currToken)
else if (usedNewSyntax) templateBodyOpt(OwnedByGiven)
else if (inits.lengthCompare(1) > 0) syntaxError("expected 'with' ", at = prevToken)
else emptyTemplateBody()
val rhs = autoEndPos(initPos)(Template(None, inits, body, Nil))
Defn.Given(mods, sig.name, sig.pcg, rhs)
}
currToken match {
case _: Equals => next(); Defn.GivenAlias(mods, sig.name, sig.pcg, decltype, expr())
case _: KwWith | _: LeftParen => getDefnGiven()
case _: Comma | _: LeftBrace | _: Colon if newSyntaxOK => getDefnGiven()
case _ => sig.name match {
case n: Term.Name => Decl.Given(mods, n, sig.pcg, decltype)
case n => syntaxError("abstract givens cannot be anonymous", at = n)
}
}
}
/**
* We are first trying to parse non-anonymous given, which
* - requires `:` for type, if none is found it means it's anonymous
* - might fail in cases that anonymous type looks like type param
* {{{given Conversion[AppliedName, Expr.Apply[Id]] = ???}}}
*
* This will fail because type params cannot have `.`
*/
private def givenSig(): GivenSig = tryParse(GivenSigContext.within {
val ident = currToken.as[Ident]
def newSyntax = dialect.allowImprovedTypeClassesSyntax
if (ident ne null) { // possibly named typeclass
val identPos = currIndex
def name = atPos(identPos)(Term.Name(ident.value))
def anon = anonNameAt(identPos)
if (tryAhead[Colon])
if (!tryAheadNot[Indentation.Indent]) None
else if (newSyntax) givenSigAfterColon(name)
else Some(GivenSig(name))
else if (tryAhead[LeftBracket]) givenSigOnBracket(name)
else if (tryAhead[LeftParen]) givenSigOnParen(name) // only with old syntax
else if (tryAhead[EOL])
if (tryAhead[LeftBracket]) givenSigOnBracket(name)
else if (tryAhead[LeftParen]) givenSigOnParen(name) // only with old syntax
else None
else if (newSyntax)
if (!tryAhead[RightArrow]) givenSigOther(anon) // only with old syntax
else givenSigOnArrow(anon, atPos(identPos)(Type.Name(ident.value)))
else None
} else // anonymous typeclass or start of decltype
if (isAfterOptNewLine[LeftBracket]) givenSigOnBracket(anonName())
else if (isAfterOptNewLine[LeftParen]) Try(givenSigOnParen(anonName())).getOrElse(None)
else None
}).getOrElse(GivenSig(anonName()))
private def givenOldSyntaxColon(): Boolean = at[Colon] && tryAheadNot[Indentation.Indent]
private def givenTypeParamClause(): Type.ParamClause =
typeParamClauseOnBracket(ownerIsType = false, allowUnderscore = dialect.allowTypeParamUnderscore)
private def givenTermParamClause(): Term.ParamClause = termParamClauseOnParen(ownerIsType = false)
private def givenSigOnBracket(name: Name): Option[GivenSig] = Try(memberParamClauseGroupOnBracket(
ownerIsType = false,
allowUnderscore = dialect.allowTypeParamUnderscore
)).toOption.flatMap { pcg =>
if (givenOldSyntaxColon()) Some(GivenSig(name, pcg :: Nil))
else if (pcg.paramClauses.nonEmpty) None // too hard to convert to init, re-parse
else if (!name.isAnonymous) {
val typeName = copyPos(name)(Type.Name(name.value))
val tpe = convertNameTypeParamClauseToType(typeName, pcg.tparamClause)
val anon = anonNameAt(name)
if (dialect.allowImprovedTypeClassesSyntax && acceptOpt[RightArrow])
givenSigAfterArrow(anon, convertTypeToTermParamClause(tpe))
else Some(GivenSig(anon, declType = Some(tpe)))
} else if (dialect.allowImprovedTypeClassesSyntax && acceptOpt[RightArrow])
givenSigAfterArrow(name, pcg.tparamClause)
else None
}
private def givenSigOnParen(name: Name): Option[GivenSig] = {
// under the new syntax, this is possible only with anonymous name
val useNewSyntax = dialect.allowImprovedTypeClassesSyntax && name.isAnonymous
if (!useNewSyntax && !soft.KwUsing(peekToken)) None
else {
val pcg = memberParamClauseGroupOnParen(ownerIsType = false)
if (givenOldSyntaxColon()) Some(GivenSig(name, pcg :: Nil))
else pcg.paramClauses match {
case Seq(pc) if useNewSyntax && acceptOpt[RightArrow] =>
givenSigAfterArrow(name, pcg.tparamClause, pc)
case _ => None // too hard to convert to init, re-parse
}
}
}
private def givenSigAfterColon(name: Name): Option[GivenSig] =
if (isAfterOptNewLine[LeftParen]) {
val pc = givenTermParamClause()
if (acceptOpt[RightArrow]) givenSigAfterArrow(name, pc)
else Some(GivenSig(name, declType = Some(convertTermParamClauseToType(pc))))
} else if (isAfterOptNewLine[LeftBracket]) {
val tpc = givenTypeParamClause()
accept[RightArrow]
givenSigAfterArrow(name, tpc)
} else givenSigOther(name)
// with old syntax deprecated, fold into `givenSigAfterColon`
private def givenSigOther(name: => Name): Option[GivenSig] = {
val startPos = currIndex
val tpe = startInfixType(inGivenSig = true)
if (at[RightArrow]) givenSigOnArrow(name, tpe)
else Some(GivenSig(name, declType = Some(tpe), declPos = Some(startPos)))
}
private def givenSigOnArrow(name: Name, tpe: Type): Option[GivenSig] = {
next()
givenSigAfterArrow(name, convertTypeToTermParamClause(tpe))
}
private def givenSigAfterArrow(name: Name, pc: Term.ParamClause): Option[GivenSig] =
givenSigAfterArrow(name, atPosEmpty(pc)(emptyTypeParamsRaw), pc)
private def givenSigAfterArrow(
name: Name,
tpc: Type.ParamClause,
pc: Term.ParamClause = null
): Option[GivenSig] = {
val pcbuf = new ListBuffer[Term.ParamClause]
if (pc ne null) pcbuf += pc
implicit val pcgbuf = new ListBuffer[Member.ParamClauseGroup]
givenSigAfterArrow(name, tpc, pcbuf)
}
@tailrec
private def givenSigAfterArrow(
name: Name,
tpc: Type.ParamClause,
pcbuf: ListBuffer[Term.ParamClause]
)(implicit pcgbuf: ListBuffer[Member.ParamClauseGroup]): Option[GivenSig] = {
val arrowPos = prevIndex
def flushPcBuf(): Unit = {
val ok = pcbuf.nonEmpty || tpc.is[Quasi] || tpc.nonEmpty
if (ok) pcgbuf += atPos(tpc, arrowPos)(Member.ParamClauseGroup(tpc, pcbuf.toList))
pcbuf.clear()
}
if (pcbuf.lastOption.exists(!_.nonEmpty)) { // empty clause ends `GivenConditional`
flushPcBuf()
Some(GivenSig(name, pcgbuf.toList))
} else if (isAfterOptNewLine[LeftParen]) {
val pc = givenTermParamClause()
if (acceptOpt[RightArrow]) {
pcbuf += pc
givenSigAfterArrow(name, tpc, pcbuf)
} else {
flushPcBuf()
Some(GivenSig(name, pcgbuf.toList, Some(convertTermParamClauseToType(pc))))
}
} else if (isAfterOptNewLine[LeftBracket]) {
flushPcBuf()
val tpc = givenTypeParamClause()
accept[RightArrow]
givenSigAfterArrow(name, tpc, pcbuf)
} else {
val startPos = currIndex
val tpe = startInfixType(inGivenSig = true)
if (acceptOpt[RightArrow]) {
pcbuf += convertTypeToTermParamClause(tpe)
givenSigAfterArrow(name, tpc, pcbuf)
} else {
flushPcBuf()
Some(GivenSig(name, pcgbuf.toList, declType = Some(tpe), declPos = Some(startPos)))
}
}
}
private def convertTypeParamToType(param: Type.Param): Type = param.becomeOr[Type] { x =>
convertNameTypeParamClauseToType(copyPos(x.name)(Type.Name(x.name.value)), param.tparamClause)
}
private def convertNameTypeParamClauseToType(name: Type.Name, tpc: Type.ParamClause) = tpc match {
case q: Quasi => atPos(name, q)(Type.Apply(name, q.become[Type.ArgClause]))
case x if x.nonEmpty =>
val args = x.values.map(tp => convertTypeParamToType(tp))
atPos(name, x)(Type.Apply(name, copyPos(x)(Type.ArgClause(args))))
case _ => name
}
private def convertTermParamToType(param: Term.Param): Type = param.becomeOr[Type] { x =>
def name = copyPos(x.name)(Type.Name(x.name.value))
x.decltpe.fold[Type](name) { tpe =>
if (x.mods.isEmpty && x.name.isAnonymous) tpe
else copyPos(x)(Type.TypedParam(name, tpe, x.mods))
}
}
private def convertTermParamClauseToType(pc: Term.ParamClause) = pc.becomeOr[Type] { x =>
x.values match {
case Nil => copyPos(x)(Lit.Unit())
case v :: Nil => convertTermParamToType(v)
case vs => copyPos(x)(Type.Tuple(vs.map(convertTermParamToType)))
}
}
private def convertTypeToTermParamClause(tpe: Type) = tpe.becomeOr[Term.ParamClause] { x =>
copyPos(x)(Term.ParamClause(copyPos(x)(Term.Param(Nil, anonNameAt(x), Some(x), None)) :: Nil))
}
def funDefOrDclOrExtensionOrSecondaryCtor(mods: List[Mod]): Stat =
if (peek[KwThis]) secondaryCtor(mods) else funDefRest(mods)
// TmplDef ::= ‘extension’ [DefTypeParamClause] ‘(’ DefParam ‘)’ {UsingParamClause} ExtMethods
// ExtMethods ::= ExtMethod | [nl] ‘{’ ExtMethod {semi ExtMethod ‘}’
// ExtMethod ::= {Annotation [nl]} {Modifier} ‘def’ DefDef
def extensionGroupDecl(mods: List[Mod]): Defn.ExtensionGroup = autoEndPos(mods) {
next() // 'extension'
val pcg = ExtensionSigContext.within(memberParamClauseGroup(
isFirst = true,
ownerIsType = false,
allowUnderscore = dialect.allowTypeParamUnderscore
))
newLinesOpt()
def getStats() = statSeq(templateStat())
val body: Stat = currToken match {
case _: LeftBrace => blockOnBrace(getStats())
case _: Indentation.Indent => autoPosOpt(indentedOnOpen(getStats() match {
case t :: Nil if !isPrecededByDetachedComment(currIndex, t.endIndex) => t
case stats => toBlockRaw(stats)
}))
case _ if isDefIntro(currIndex) => nonLocalDefOrDcl()
case _ => syntaxError("Extension without extension method", currToken)
}
Defn.ExtensionGroup(pcg, body)
}
def funDefRest(mods: List[Mod]): Stat = autoEndPos(mods) {
accept[KwDef]
val name = termName()
def procedureSyntaxDeclType: Type = {
val hint = s"Convert procedure `$name` to method by adding `: Unit =`."
if (dialect.allowProcedureSyntax)
deprecationWarning(s"Procedure syntax is deprecated. $hint", at = name)
else syntaxError(s"Procedure syntax is not supported. $hint", at = name)
atCurPosEmpty(Type.Name("Unit"))
}
val paramClauses: List[Member.ParamClauseGroup] =
if (dialect.allowParamClauseInterleaving) memberParamClauseGroups(ownerIsType = false)
else memberParamClauseGroup(isFirst = true, ownerIsType = false).toList
def defn(declType: Option[Type]) = Defn.Def(mods, name, paramClauses, declType, expr())
val restype = ReturnTypeContext.within(typedOpt())
if (acceptOpt[Equals])
if (!acceptOpt[KwMacro]) defn(restype)
else Defn.Macro(mods, name, paramClauses, restype, expr())
else if (StatSeqEnd(currToken) || StatSep(currToken)) Decl
.Def(mods, name, paramClauses, restype.getOrElse(procedureSyntaxDeclType))
else if (restype.isEmpty && isAfterOptNewLine[LeftBrace]) defn(Some(procedureSyntaxDeclType))
else syntaxErrorExpected[Equals]
}
def typeDefOrDcl(mods: List[Mod]): Stat.TypeDef = autoEndPos(mods) {
accept[KwType]
newLinesOpt()
val name = typeName()
val tparams = typeParamClauseOpt(ownerIsType = true)
def aliasType() = {
// empty bounds also need to have origin
val emptyBounds = atCurPosEmpty(Type.Bounds(None, None))
Defn.Type(mods, name, tparams, typeIndentedOpt(), emptyBounds)
}
def abstractType() = {
val bounds = typeBounds()
if (acceptOpt[Equals]) {
val tpe = typeIndentedOpt()
if (tpe.is[Type.Match]) Defn.Type(mods, name, tparams, tpe, bounds)
else syntaxError("cannot combine bound and alias", at = tpe.pos)
} else Decl.Type(mods, name, tparams, bounds)
}
if (mods.exists(_.is[Mod.Opaque])) {
val bounds = typeBounds()
accept[Equals]
Defn.Type(mods, name, tparams, typeIndentedOpt(), bounds)
} else currToken match {
case _: Equals => next(); aliasType()
case _: Supertype | _: Subtype | _: Comma | StatSep() | StatSeqEnd() => abstractType()
case _ => syntaxError("`=', `>:', or `<:' expected", at = currToken)
}
}
/** Hook for IDE, for top-level classes/objects. */
def topLevelTmplDef: Stat = tmplDef(listBy[Mod] { buf =>
annotsBuf(buf, skipNewLines = true)
modifiersBuf(buf)
})
private def tmplDef(mods: List[Mod], okTopLevel: Boolean = true): Stat = autoEndPosOpt(mods) {
currToken match {
case _: KwTrait => traitDef(mods)
case _: KwEnum => enumDef(mods)
case _: KwClass => classDef(mods)
case _: KwObject => objectDef(mods)
case _: At => syntaxError("Annotations must precede keyword modifiers", at = currToken)
case _ if okTopLevel && dialect.allowToplevelStatements && isDefIntro(currIndex) =>
defOrDclOrSecondaryCtor(mods)
case _ => syntaxError(s"expected start of definition", at = currToken)
}
}
private def traitDef(mods: List[Mod]): Defn.Trait = {
next()
val traitName = typeName()
Defn.Trait(
mods,
traitName,
typeParamClauseOpt(ownerIsType = true, allowUnderscore = dialect.allowTypeParamUnderscore),
primaryCtor(OwnedByTrait),
templateOpt(OwnedByTrait)
)
}
def classDef(mods: List[Mod]): Defn.Class = {
next()
val className = typeName()
val typeParams =
typeParamClauseOpt(ownerIsType = true, allowUnderscore = dialect.allowTypeParamUnderscore)
val ctor = primaryCtor(if (mods.has[Mod.Case]) OwnedByCaseClass else OwnedByClass)
if (!dialect.allowCaseClassWithoutParameterList && mods.has[Mod.Case] &&
ctor.paramClauses.isEmpty) syntaxError(
s"case classes must have a parameter list; try 'case class $className()' or 'case object $className'",
at = currToken
)
val tmpl = templateOpt(OwnedByClass)
Defn.Class(mods, className, typeParams, ctor, tmpl)
}
// EnumDef ::= id ClassConstr InheritClauses EnumBody
private def enumDef(mods: List[Mod]): Defn.Enum = {
next()
val enumName = typeName()
val typeParams =
typeParamClauseOpt(ownerIsType = true, allowUnderscore = dialect.allowTypeParamUnderscore)
val ctor = primaryCtor(OwnedByEnum)
val tmpl = templateOpt(OwnedByEnum)
Defn.Enum(mods, enumName, typeParams, ctor, tmpl)
}
// EnumCase ::= ‘case’ (id ClassConstr [‘extends’ ConstrApps]] | ids)
// ids ::= id {‘,’ id}
// ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses
def enumCaseDef(mods: List[Mod]): Stat = autoEndPos(mods) {
accept[KwCase]
if (at[Ident] && peek[Comma]) enumRepeatedCaseDef(mods) else enumSingleCaseDef(mods)
}
def enumRepeatedCaseDef(mods: List[Mod]): Defn.RepeatedEnumCase = {
val values = commaSeparated(termName())
Defn.RepeatedEnumCase(mods, values)
}
def enumSingleCaseDef(mods: List[Mod]): Defn.EnumCase = {
val name = termName()
val tparams =
typeParamClauseOpt(ownerIsType = true, allowUnderscore = dialect.allowTypeParamUnderscore)
val ctor = primaryCtor(OwnedByEnum)
val inits = if (acceptOpt[KwExtends]) templateParents(afterExtend = true) else List()
Defn.EnumCase(mods, name, tparams, ctor, inits)
}
def objectDef(mods: List[Mod]): Defn.Object = {
next()
val objectName = termName()
Defn.Object(mods, objectName, templateOpt(OwnedByObject))
}
/* -------- CONSTRUCTORS ------------------------------------------- */
def primaryCtor(owner: TemplateOwner): Ctor.Primary = autoPos {
if (owner.isPrimaryCtorAllowed) {
val mods = listBy[Mod] { buf =>
annotsBuf(buf, skipNewLines = false, allowArgss = false, insidePrimaryCtorAnnot = true)
ctorModifiers(buf)
}
val name = anonName()
val paramss = termParamClauses(ownerIsType = true, ownerIsCase = owner == OwnedByCaseClass)
Ctor.Primary(mods, name, paramss)
} else Ctor.Primary(Nil, anonName(), Seq.empty[Term.ParamClause])
}
def secondaryCtor(mods: List[Mod]): Ctor.Secondary = autoEndPos(mods) {
accept[KwDef]
expect[KwThis]
val name = nameThis()
currToken match {
case _: LeftParen => secondaryCtorRest(mods, name, termParamClausesOnParen(ownerIsType = true))
case t => syntaxError("auxiliary constructor needs non-implicit parameter list", at = t)
}
}
def quasiquoteCtor(): Ctor = autoPos {
val mods = listBy[Mod] { buf =>
annotsBuf(buf, skipNewLines = true)
modifiersBuf(buf)
}
accept[KwDef]
val thisPos = currIndex
accept[KwThis]
val paramss = termParamClauses(ownerIsType = true)
newLineOptWhenFollowedBy[LeftBrace]
if (at[EOF]) Ctor.Primary(mods, anonNameAt(thisPos), paramss)
else secondaryCtorRest(mods, atPos(thisPos)(Name.This()), paramss)
}
def entrypointCtor(): Ctor = ???
def secondaryCtorRest(
mods: List[Mod],
name: Name.This,
paramss: Seq[Term.ParamClause]
): Ctor.Secondary = {
val hasLeftBrace = isAfterOptNewLine[LeftBrace] || { accept[Equals]; at[LeftBrace] }
val body = autoPos {
if (hasLeftBrace) inBracesOnOpen(constrInternal())
else indentedOr(constrInternal())(Ctor.Block(initInsideConstructor(), Nil))
}
Ctor.Secondary(mods, name, paramss, body)
}
def constrInternal(): Ctor.Block = {
val init = initInsideConstructor()
val stats = if (acceptIfStatSep()) blockStatSeq() else Nil
Ctor.Block(init, stats)
}
def initInsideConstructor(): Init = {
def tpe = {
expect[KwThis]
val t = anonThis()
copyPos(t)(Type.Singleton(t))
}
initRest(tpe, allowArgss = true, allowBraces = true)
}
def initInsideTemplate(): Init =
initRest(startModType(), allowArgss = true, allowTypeSingleton = false)
def quasiquoteInit(): Init = entrypointInit()
def entrypointInit(): Init = currToken match {
case _: KwThis => initInsideConstructor()
case _ => initInsideTemplate()
}
private def initImpl(startPos: StartPos, tpe: Type, allowSingleton: Boolean)(
argss: => List[Term.ArgClause]
): Init = {
if (!allowSingleton && tpe.is[Type.Singleton])
syntaxError(s"class type required but $tpe found", at = tpe)
autoEndPos(startPos)(Init(tpe, anonName(), argss))
}
def initRestAt(startPos: StartPos, tpe: Type)(
allowArgss: Boolean,
insidePrimaryCtorAnnot: Boolean = false,
allowBraces: Boolean = false,
allowSingleton: Boolean = true
): Init = {
def getArgss = listBy[Term.ArgClause] { argss =>
def allowParens: Boolean = peekToken match {
case _ if !insidePrimaryCtorAnnot => true
// explained here:
// https://github.com/lampepfl/dotty/blob/675ae0c6440d5527150d650ad45d20fda5e03e69/compiler/src/dotty/tools/dotc/parsing/Parsers.scala#L2581
case _: RightParen => argss.isEmpty
case _: Ident => !ahead(peek[Colon])
case _: At => false
case _ => !isModifier(peekIndex)
}
def maybeBody() = {
if (at[AtEOL]) nextIf(peekToken match {
case _: LeftParen => isIndentingOrEOL(false)
case _: LeftBrace => isIndentingOrEOL(allowBraces)
case _ => false
})
currToken match {
case _: LeftParen if allowParens => argss += getArgClauseOnParen(); true
case _: LeftBrace if allowBraces => argss += getArgClauseOnBrace(); true
case _ => false
}
}
if (maybeBody() && allowArgss) while (maybeBody()) {}
}
initImpl(startPos, tpe, allowSingleton = allowSingleton)(getArgss)
}
def initRest(
typeParser: => Type,
allowArgss: Boolean,
insidePrimaryCtorAnnot: Boolean = false,
allowBraces: Boolean = false,
allowTypeSingleton: Boolean = true
): Init = unquoteOpt[Init](!(peek[LeftParen] || (allowBraces && peek[LeftBrace]))).getOrElse {
initRestAt(currIndex, typeParser)(
allowArgss = allowArgss,
insidePrimaryCtorAnnot = insidePrimaryCtorAnnot,
allowBraces = allowBraces,
allowSingleton = allowTypeSingleton
)
}
/* ---------- SELFS --------------------------------------------- */
def quasiquoteSelf(): Self = self(quasiquote = true)
def entrypointSelf(): Self = self(quasiquote = false)
private def self(quasiquote: Boolean): Self = selfEither(quasiquote)
.fold(syntaxError(_, currToken), identity)
private def selfEither(quasiquote: Boolean = false): Either[String, Self] = {
val startPos = currIndex
def withSelf(self: => Self) = Either
.cond(acceptOpt[RightArrow] || quasiquote && at[EOF], self, "expected `=>`")
def withDeclTpe(name: Name, declTpe: Option[Type]) =
withSelf(autoEndPos(startPos)(Self(name, declTpe)))
def withName(name: Name) = // possible fewer braces after colon
if (!peek[Indentation, EOL]) withDeclTpe(name, getDeclTpeOpt(fullTypeOK = false))
else if (!at[Colon]) withDeclTpe(name, None)
else Left("missing type after self")
currToken match {
case t: Ident => withName(termName(t))
case _: KwThis => withName(nameThis())
case _: Underscore => withName(namePlaceholder())
case t: Unquote =>
val self = unquote[Self](t)
if (at[Colon]) withName(self.become[Name]) else withSelf(self)
case _ => Left("expected identifier, `this' or unquote")
}
}
/* -------- TEMPLATES ------------------------------------------- */
sealed trait TemplateOwner {
def isEnumCaseAllowed: Boolean
def isPrimaryCtorAllowed(implicit dialect: Dialect): Boolean
def isSecondaryCtorAllowed: Boolean
}
object OwnedByTrait extends TemplateOwner {
override final def isEnumCaseAllowed: Boolean = false
override final def isPrimaryCtorAllowed(implicit dialect: Dialect): Boolean =
dialect.allowTraitParameters
override final def isSecondaryCtorAllowed: Boolean = false
}
object OwnedByCaseClass extends TemplateOwner {
override final def isEnumCaseAllowed: Boolean = false
override final def isPrimaryCtorAllowed(implicit dialect: Dialect): Boolean = true
override final def isSecondaryCtorAllowed: Boolean = true
}
object OwnedByClass extends TemplateOwner {
override final def isEnumCaseAllowed: Boolean = false
override final def isPrimaryCtorAllowed(implicit dialect: Dialect): Boolean = true
override final def isSecondaryCtorAllowed: Boolean = true
}
object OwnedByEnum extends TemplateOwner {
override final def isEnumCaseAllowed: Boolean = true
override final def isPrimaryCtorAllowed(implicit dialect: Dialect): Boolean = true
override final def isSecondaryCtorAllowed: Boolean = true
}
object OwnedByObject extends TemplateOwner {
override final def isEnumCaseAllowed: Boolean = false
override final def isPrimaryCtorAllowed(implicit dialect: Dialect): Boolean = false
override final def isSecondaryCtorAllowed: Boolean = false
}
object OwnedByGiven extends TemplateOwner {
override final def isEnumCaseAllowed: Boolean = false
override final def isPrimaryCtorAllowed(implicit dialect: Dialect): Boolean = false
override final def isSecondaryCtorAllowed: Boolean = false
}
def init() = currToken match {
case t: Ellipsis => ellipsis[Init](t, 1)
case _ => initInsideTemplate()
}
def templateParentsWithFirst(allowComma: Boolean, allowWithBody: Boolean = false)(
first: Init
): List[Init] = {
def impl(isSeparator: => Boolean) =
if (!isSeparator) None
else Some(listBy[Init] { x => x += first; doWhile { next(); x += init() }(isSeparator) })
// whitespace includes Indent and AtEOL
(if (allowWithBody) impl(at[KwWith] && !peek[Whitespace, LeftBrace]) else impl(at[KwWith]))
.orElse(if (allowComma && dialect.allowCommaSeparatedExtend) impl(at[Comma]) else None)
.getOrElse(first :: Nil)
}
def templateParents(afterExtend: Boolean = false): List[Init] =
templateParentsWithFirst(allowComma = afterExtend)(init())
def derivesClasses(): List[Type] =
if (atOrPeekAfterEOL(soft.KwDerives(_))) {
next()
newLineOpt()
val deriving = ListBuffer[Type]()
doWhile {
currToken match {
case t: Ellipsis => deriving += ellipsis[Type](t, 1)
case _ => deriving += startModType()
}
}(acceptOpt[Comma])
deriving.toList
} else Nil
private def templateAfterExtends(
owner: TemplateOwner,
parents: List[Init] = Nil,
edefs: Option[Stat.Block] = None
): Template = {
val derived = derivesClasses()
val body = templateBodyOpt(owner)
Template(edefs, parents, body, derived)
}
def template(owner: TemplateOwner, afterExtend: Boolean = false): Template = autoPos {
if (isAfterOptNewLine[LeftBrace]) {
// @S: pre template body cannot stub like post body can!
val body = templateBodyOnLeftBrace(owner)
if (at[KwWith] && body.selfOpt.isEmpty) {
val edefs = body.stats
edefs.foreach(_ match {
case _: Quasi | _: Defn.Val | _: Defn.Var | _: Defn.Type =>
case other => syntaxError("not a valid early definition", at = other)
})
next()
val parents = templateParents(afterExtend)
val early = copyPos(body)(toStatsBlockRaw(edefs))
templateAfterExtends(owner, parents, Some(early))
} else Template(None, Nil, body, Nil)
} else {
val parents = if (at[Colon]) Nil else templateParents(afterExtend)
templateAfterExtends(owner, parents)
}
}
def quasiquoteTemplate(): Template = entrypointTemplate()
def entrypointTemplate(): Template = autoPos(template(OwnedByClass))
def templateOpt(owner: TemplateOwner): Template = autoPos {
unquoteOpt[Template](peekToken match {
case _: Dot | _: Hash | _: At | _: Ellipsis | _: LeftParen | _: LeftBracket | _: LeftBrace |
_: KwWith => false
case _ => true
}).getOrElse {
val onExtends = acceptOpt[KwExtends] || (owner eq OwnedByTrait) && acceptOpt[Subtype]
if (onExtends) template(owner, afterExtend = true) else templateAfterExtends(owner)
}
}
@inline
private def emptyTemplateBody(): Template.Body = atCurPosEmpty(Template.Body(None, Nil))
@inline
private def templateBodyOnLeftBrace(owner: TemplateOwner): Template.Body =
autoPos(inBracesOnOpen(templateStatSeq(owner)))
@inline
private def templateBodyOnIndentRaw(owner: TemplateOwner): Template.Body =
indentedOnOpen(templateStatSeq(owner))
def templateBodyOpt(owner: TemplateOwner): Template.Body =
if (isAfterOptNewLine[LeftBrace]) templateBodyOnLeftBrace(owner)
else if (at[Colon] && peek[Indentation, EOL]) autoPos {
next()
expect[Indentation.Indent]("template body")
templateBodyOnIndentRaw(owner)
}
else if (at[LeftParen])
if (owner.isPrimaryCtorAllowed) syntaxError("unexpected opening parenthesis", at = currToken)
else {
val what = owner.getClass.getSimpleName.stripPrefix("OwnedBy")
syntaxError(s"$what may not have parameters", at = currToken)
}
else emptyTemplateBody()
private def toStatsBlockRaw(stats: List[Stat]): Stat.Block = stats.reduceWith(Stat.Block.apply)
private def toStatsBlock(startPos: Int)(stats: List[Stat]): Stat.Block =
autoEndPos(startPos)(toStatsBlockRaw(stats))
private def refineWith(innerType: Option[Type], statsPos: Int, stats: => List[Stat]) =
autoEndPos(innerType)(Type.Refine(innerType, toStatsBlock(statsPos)(stats)))
private def refinement(innerType: Option[Type]): Option[Type] = {
def maybeRefineIndented(startPos: => Int) =
if (!tryAhead[Indentation.Indent]) innerType
else Some(refineWith(innerType, startPos, indented(refineStatSeq())))
currToken match {
case _ if !dialect.allowSignificantIndentation => refinementInBraces(innerType, -1)
case _: Colon => maybeRefineIndented(prevIndex)
case _: KwWith => maybeRefineIndented(currIndex)
case _ => refinementInBraces(innerType, in.previousIndentation + 1)
}
}
@tailrec
private def refinementInBraces(innerType: Option[Type], minIndent: Int): Option[Type] = {
val notRefined = currToken match {
case t: LeftBrace => minIndent > 0 && t.pos.startColumn < minIndent &&
prevToken.pos.endLine < t.pos.endLine
case _: EOL => minIndent > 0 && peekToken.pos.startColumn < minIndent || !tryAhead[LeftBrace]
case _ => true
}
if (notRefined) innerType
else refinementInBraces(
Some(refineWith(innerType, currIndex, inBraces(refineStatSeq()))),
minIndent
)
}
private def existentialTypeOnForSome(t: Type): Type.Existential = {
next()
val statsClause = toStatsBlock(currIndex) {
val stats = inBraces(refineStatSeq())
stats.foreach { x =>
if (!x.isExistentialStat) syntaxError("not a legal existential clause", at = x)
}
stats
}
atPos(t, statsClause)(Type.Existential(t, statsClause))
}
/* -------- STATSEQS ------------------------------------------- */
private val consumeStat: PartialFunction[Token, Stat] = {
case _: KwImport => importStmt()
case _: KwExport => exportStmt()
case _: KwPackage =>
packageOrPackageObjectDef(if (dialect.allowToplevelTerms) consumeStat else topStat)
case _ if isDefIntro(currIndex) => nonLocalDefOrDcl(secondaryConstructorAllowed = true)
case _ if isAtEndMarker() => endMarker()
case _ if isIdentOrExprIntro(currToken) => stat(expr(location = NoStat, allowRepeated = true))
case t: Ellipsis => ellipsis[Stat](t, 1)
}
def quasiquoteStat(): Stat = {
def failEmpty() = syntaxError("unexpected end of input", at = currToken)
def failMix(advice: Option[String]) = {
val message = "these statements can't be mixed together"
val addendum = advice.fold("")(", " + _)
syntaxError(message + addendum, at = tokens.head)
}
statSeq(consumeStat) match {
case Nil => failEmpty()
case (stat @ Stat.Quasi(1, _)) :: Nil => toBlockRaw(List(stat))
case stat :: Nil => stat
case stats if stats.forall(_.isBlockStat) => toBlockRaw(stats)
case stats if stats.forall(_.isTopLevelStat) => failMix(Some("try source\"...\" instead"))
case _ => failMix(None)
}
}
def entrypointStat(): Stat = {
val stat = consumeStat
.applyOrElse(currToken, (t: Token) => syntaxError("unexpected start of statement", at = t))
skipAllStatSep()
expect[EOF]
stat
}
def stat(body: => Stat): Stat = body.become[Stat]
def statSeq[T <: Tree](
statpf: PartialFunction[Token, T],
errorMsg: String = "illegal start of definition"
): List[T] = listBy[T](statSeqBuf(_, statpf, errorMsg))
def statSeqBuf[T <: Tree](
stats: ListBuffer[T],
statpf: PartialFunction[Token, T],
errorMsg: String = "illegal start of definition"
): Boolean = {
val isIndented = acceptOpt[Indentation.Indent]
val statpfAdd = statpf.runWith(stats += _)
while (!StatSeqEnd(currToken))
if (statpfAdd(currToken)) acceptStatSepOpt()
else if (!acceptIfStatSep()) syntaxError(errorMsg + s" `${currToken.name}`", at = currToken)
if (isIndented) accept[Indentation.Outdent]
isIndented
}
val topStat: PartialFunction[Token, Stat] = {
case t: Ellipsis => ellipsis[Stat](t, 1)
case t: Unquote => unquote[Stat](t)
case _: KwPackage => packageOrPackageObjectDef(topStat)
case _: KwImport => importStmt()
case _: KwExport => exportStmt()
case _ if isTemplateIntro(currIndex) => topLevelTmplDef
case _ if isAtEndMarker() => endMarker()
case _ if dialect.allowToplevelStatements && isDefIntro(currIndex) => nonLocalDefOrDcl()
}
private def templateStatSeq(owner: TemplateOwner): Template.Body = {
val enumCaseAllowed = owner.isEnumCaseAllowed
val secondaryConstructorAllowed = owner.isSecondaryCtorAllowed
val selfTreeOpt = tryParse(selfEither().right.toOption)
if (owner eq OwnedByGiven) selfTreeOpt.foreach(_.decltpe.foreach { x =>
syntaxError("given cannot have a self type", at = x)
})
val stats = listBy[Stat] { buf =>
def getStats() = statSeqBuf(buf, templateStat(enumCaseAllowed, secondaryConstructorAllowed))
val wasIndented = getStats() // some stats could be indented relative to self-type
if (wasIndented && selfTreeOpt.isDefined) getStats() // and the rest might not be
}
Template.Body(selfTreeOpt, stats)
}
def templateStat(
enumCaseAllowed: Boolean = false,
secondaryConstructorAllowed: Boolean = false
): PartialFunction[Token, Stat] = {
case _: KwImport => importStmt()
case _: KwExport => exportStmt()
case _ if isDefIntro(currIndex) => nonLocalDefOrDcl(enumCaseAllowed, secondaryConstructorAllowed)
case t: Unquote => unquote[Stat](t)
case t: Ellipsis => ellipsis[Stat](t, 1)
case _ if isAtEndMarker() => endMarker()
case _ if isIdentOrExprIntro(currToken) => expr(location = TemplateStat, allowRepeated = false)
}
private def refineStatSeq(): List[Stat] = listBy[Stat] { stats =>
def cond(tok: Token): Boolean = !StatSeqEnd(tok) && {
def fail(err: String) = syntaxError(err, at = tok)
if (tok.is[Ellipsis]) stats += ellipsis[Stat](tok.asInstanceOf[Ellipsis], 1)
else if (isDclIntro(currIndex)) {
val stat = defOrDclOrSecondaryCtor(Nil)
if (stat.isRefineStat) stats += stat else fail("is not a valid refinement declaration")
} else if (ReturnTypeContext.isInside()) fail(
"illegal start of declaration (possible cause: missing `=' in front of current method body)"
)
else fail("illegal start of declaration")
acceptStatSepOpt()
}
doWhile(skipAllStatSep())(cond(currToken))
}
def localDef(implicitMod: Option[Mod.Implicit]): Stat = {
val mods = listBy[Mod] { buf =>
annotsBuf(buf, skipNewLines = true)
implicitMod.foreach(buf += _)
modifiersBuf(buf, isLocal = true)
}
if (mods.forall {
case _: Mod.Implicit | _: Mod.Lazy | _: Mod.Inline | _: Mod.Infix | _: Mod.Annot => true
case _ => false
}) defOrDclOrSecondaryCtor(mods)
else tmplDef(mods) match {
case stat: Decl.Type if dialect.allowTypeInBlock => stat
case stat: Decl.Type => syntaxError("is not a valid block statement", at = stat)
case stat if stat.isBlockStat => stat
case other => syntaxError("is not a valid block statement", at = other)
}
}
def blockStatSeq(allowRepeated: Boolean = false): List[Stat] = listBy[Stat] { stats =>
def notCaseDefEnd(): Boolean = currToken match {
case _: RightParen | StatSeqEnd() => false
case _: KwCase => !isCaseIntroOnKwCase()
case _: Ellipsis => !peek[KwCase]
case _ => true
}
def cond(): Boolean = {
skipAllStatSep()
notCaseDefEnd()
}
def getStat(): Stat = currToken match {
case _: KwExport => exportStmt()
case _: KwImport => importStmt()
case _: KwImplicit =>
val implicitPos = currIndex
next()
if (at[Ident] && !isSoftModifier(currIndex)) implicitClosure(BlockStat)
else localDef(Some(atPos(implicitPos)(Mod.Implicit())))
case t if !isNonlocalModifier(t) && isDefIntro(currIndex) => localDef(None)
case _ if isAtEndMarker() => endMarker()
case _ if isIdentOrExprIntro(currToken) =>
stat(expr(location = BlockStat, allowRepeated = allowRepeated))
case t: Ellipsis => ellipsis[Stat](t, 1)
case _ => syntaxError("illegal start of statement", at = currToken)
}
if (cond()) doWhile(stats += getStat())(notCaseDefEnd() && { acceptStatSep(); cond() })
if (allowRepeated && stats.length > 1) stats.foreach {
case t: Term.Repeated => syntaxError("repeated argument not allowed here", at = t)
case _ =>
}
}
private def toPkgBody(startPos: Int)(stats: List[Stat]): Pkg.Body =
autoEndPos(startPos)(stats.reduceWith(Pkg.Body.apply))
private def packageOrPackageObjectDef(statpf: PartialFunction[Token, Stat]): Stat = autoPos {
next()
if (acceptOpt[KwObject]) Pkg.Object(Nil, termName(), templateOpt(OwnedByObject))
else {
def packageBody = toPkgBody(currIndex)(
if (nextIfColonIndent()) indentedOnOpen(statSeq(statpf)) else inBracesOrNil(statSeq(statpf))
)
Pkg(qualId(), packageBody)
}
}
def source(): Source = autoPos {
acceptOpt[Shebang]
val statpf = if (dialect.allowToplevelTerms) consumeStat else topStat
@tailrec
def bracelessPackageStats(f: List[Stat] => Source): Source = {
skipAllStatSep()
if (at[KwPackage] && tryAheadNot[KwObject]) {
val startPos = prevIndex
val qid = qualId()
def getPackage(pkgDelimPos: Int)(stats: => List[Stat]) =
autoEndPos(startPos)(Pkg(qid, toPkgBody(pkgDelimPos)(stats)))
def inPackageOnOpen[T <: Token: ClassTag](pkgDelimPos: Int) = f(listBy[Stat] { buf =>
val pkg = getPackage(pkgDelimPos) {
next()
statSeqBuf(buf, statpf)
acceptAfterOptNL[T]
buf.toList
}
buf.clear()
buf += pkg
if (!at[EOF]) {
acceptStatSep()
statSeqBuf(buf, statpf)
}
})
if (nextIfColonIndent()) inPackageOnOpen[Indentation.Outdent](prevIndex)
else if (isAfterOptNewLine[LeftBrace]) inPackageOnOpen[RightBrace](currIndex)
else {
val inPkgPos = currIndex
bracelessPackageStats(stats => f(getPackage(inPkgPos)(stats) :: Nil))
}
} else f(statSeq(statpf))
}
bracelessPackageStats(Source.apply)
}
def entrypointSource(): Source = source()
def quasiquoteSource(): Source = entrypointSource()
}
object ScalametaParser {
def doWhile(body: => Unit)(cond: => Boolean): Unit = {
body
while (cond) body
}
@inline
private def toBlockRaw(stats: List[Stat]): Term.Block = Term.Block(stats)
private def maybeAnonymousFunction(t: Term): Term = {
val ok = PlaceholderChecks.hasPlaceholder(t, includeArg = false)
if (ok) copyPos(t)(Term.AnonymousFunction(t)) else t
}
private def maybeAnonymousLambda(t: Type)(implicit dialect: Dialect): Type = {
val ok = dialect.allowTypeLambdas && PlaceholderChecks.hasAnonymousParam(t, includeArg = false)
if (ok) copyPos(t)(Type.AnonymousLambda(t)) else t
}
private def toParamClause(mod: Option[Mod.ParamsType]): List[Term.Param] => Term.ParamClause =
if (mod.isDefined) Term.ParamClause(_, mod)
else { v => Term.ParamClause(v, Term.ParamClause.getMod(v)) }
private implicit class ImplicitTree[T <: Tree](private val tree: T) extends AnyVal {
def become[A >: T <: Tree: AstInfo]: A = tree match {
case q: Quasi => q.become[A]
case _ => tree
}
def becomeOr[A <: Tree: AstInfo](f: T => A): A = tree match {
case q: Quasi => q.become[A]
case _ => f(tree)
}
}
private def copyPos[T <: Tree](tree: Tree)(body: => T): T = body.withOrigin(tree.origin)
@inline
private def quasi[T <: Tree](rank: Int, tree: Tree)(implicit astInfo: AstInfo[T]): T with Quasi =
astInfo.quasi(rank, tree)
private def reellipsis[T <: Tree: AstInfo](q: Quasi, rank: Int): T = {
val became = q.become[T]
if (became.rank != rank) copyPos(became)(quasi[T](rank, became.tree)) else became
}
private implicit class XtensionList[A <: Tree](private val list: List[A]) extends AnyVal {
def reduceWith[B <: Tree: AstInfo](f: List[A] => B, reduceRank: Int = 1): B = ScalametaParser
.reduceAs[A, B, B](list, f, reduceRank)
}
private def reduceAs[A <: Tree, B <: Tree, C <: B: AstInfo](
list: List[A],
f: List[A] => B,
reduceRank: Int = 1
): B = list match {
case (t: Quasi) :: Nil if t.rank >= reduceRank => reellipsis[C](t, t.rank - reduceRank)
case v => f(v)
}
private val bigIntMaxInt = BigInt(Int.MaxValue) + 1
private val bigIntMaxUInt = bigIntMaxInt << 1
private val bigIntMaxLong = BigInt(Long.MaxValue) + 1
private val bigIntMaxULong = bigIntMaxLong << 1
private def getTokenName[T <: Token: ClassTag]: String = {
val name = classTag[T].runtimeClass.getName
val simplerName = name.substring(name.lastIndexOf('.') + 1)
simplerName.stripPrefix("Token$").replace('$', '.') match {
case "Semicolon" => ";"
case "Hash" => "#"
case "Colon" => ":"
case "Viewbound" => "<%"
case "LeftArrow" => "<-"
case "Subtype" => "<:"
case "Equals" => "="
case "RightArrow" => "=>"
case "Supertype" => ">:"
case "At" => "@"
case "Underscore" => "_"
case "TypeLambdaArrow" => "=>>"
case "ContextArrow" => "?=>"
case "MacroQuote" => "'"
case "MacroSplice" => "$"
case "LeftParen" => "("
case "RightParen" => ")"
case "Comma" => ","
case "Dot" => "."
case "LeftBracket" => "["
case "RightBracket" => "]"
case "LeftBrace" => "{"
case "RightBrace" => "}"
case "Ident" => "identifier"
case "EOF" => "end of file"
case "BOF" => "beginning of file"
case other =>
val kw = other.stripPrefix("Kw").stripPrefix("Indentation.")
if (kw eq other) kw else kw.toLowerCase
}
}
private def syntaxExpectedMessage[T <: Token: ClassTag](tok: Token): String =
s"`${getTokenName[T]}` expected but `${tok.name}` found"
private def syntaxNotExpectedMessage[T <: Token: ClassTag]: String =
s"not expected `${getTokenName[T]}`"
private object TParamVariantStr {
def unapply(ident: String): Option[Mod.Variant] = ident match {
case "+" => Some(Mod.Covariant())
case "-" => Some(Mod.Contravariant())
case _ => None
}
}
private object TParamVariant {
def unapply(ident: Token.Ident): Option[Mod.Variant] = TParamVariantStr.unapply(ident.text)
}
private case class GivenSig(
name: Name,
pcg: List[Member.ParamClauseGroup] = Nil,
declType: Option[Type] = None,
declPos: Option[Int] = None
)
}
class Location private (
val value: Int,
val anonFuncOK: Boolean = true,
val funcParamOK: Boolean = false,
val fullTypeOK: Boolean = false
)
object Location {
val NoStat = new Location(0, funcParamOK = true, fullTypeOK = true)
val BlockStat = new Location(1, funcParamOK = true)
val TemplateStat = new Location(2)
val PostfixStat = new Location(3, anonFuncOK = false, fullTypeOK = true)
val UnquoteStat = new Location(4, anonFuncOK = false)
}
object InfixMode extends Enumeration {
val FirstOp, LeftOp, RightOp = Value
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy