com.nawforce.common.cst.stmts.Switch.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of apexlink Show documentation
Show all versions of apexlink Show documentation
Offline validation and transformation toolkit for Salesforce packages
/*
[The "BSD licence"]
Copyright (c) 2019 Kevin Jones
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.nawforce.common.cst.stmts
import com.nawforce.common.api.TypeName
import com.nawforce.common.cst._
import com.nawforce.common.names.TypeNames
import com.nawforce.common.org.OrgImpl
import com.nawforce.common.types.core.{ENUM_NATURE, TypeDeclaration}
import com.nawforce.runtime.parsers.ApexParser.{
SwitchStatementContext,
WhenControlContext,
WhenLiteralContext,
WhenValueContext
}
import com.nawforce.runtime.parsers.CodeParser
sealed abstract class WhenLiteral extends CST
final class WhenNullLiteral extends WhenLiteral
final case class WhenIdLiteral(id: Id) extends WhenLiteral {
override def toString: String = id.name.value.toLowerCase
}
final case class WhenStringLiteral(value: String) extends WhenLiteral {
override def toString: String = value
}
final case class WhenIntegerLiteral(negate: Boolean, value: String) extends WhenLiteral {
override def toString: String = (if (negate) "-" else "") + value
}
object WhenLiteral {
def construct(literal: WhenLiteralContext): WhenLiteral = {
val whenLiteral = CodeParser
.toScala(literal.NULL())
.map(_ => new WhenNullLiteral())
.orElse(
CodeParser
.toScala(literal.IntegerLiteral())
.map(l =>
WhenIntegerLiteral(CodeParser.toScala(literal.SUB()).nonEmpty, CodeParser.getText(l))))
.orElse(CodeParser
.toScala(literal.StringLiteral())
.map(l => WhenStringLiteral(CodeParser.getText(l))))
.orElse(CodeParser
.toScala(literal.id())
.map(l => WhenIdLiteral(Id.construct(l))))
if (whenLiteral.isEmpty)
throw new CSTException()
else
whenLiteral.get.withContext(literal)
}
}
sealed abstract class WhenValue extends CST {
def checkMatchableTo(typeName: TypeName): Seq[String]
def checkIsSObject(context: BlockVerifyContext): Seq[String]
def checkEnumValue(typeDeclaration: TypeDeclaration): Seq[String]
def verify(context: BlockVerifyContext): Unit = {}
}
final class WhenElseValue extends WhenValue {
def checkMatchableTo(typeName: TypeName): Seq[String] = Seq()
def checkIsSObject(context: BlockVerifyContext): Seq[String] = Seq()
def checkEnumValue(typeDeclaration: TypeDeclaration): Seq[String] = Seq()
}
final case class WhenLiteralsValue(literals: Seq[WhenLiteral]) extends WhenValue {
override def checkMatchableTo(typeName: TypeName): Seq[String] = {
def processErrors(invalid: Seq[WhenLiteral],
all: Seq[WhenLiteral],
typeName: TypeName): Seq[String] = {
if (invalid.nonEmpty) {
OrgImpl.logError(invalid.head.location, s"A $typeName literal is required for this value")
Seq()
} else {
all.map(_.toString())
}
}
val nonNull = literals.filterNot(_.isInstanceOf[WhenNullLiteral])
typeName match {
case TypeNames.Integer | TypeNames.Long =>
processErrors(nonNull.filter(!_.isInstanceOf[WhenIntegerLiteral]), nonNull, typeName)
case TypeNames.String =>
processErrors(nonNull.filter(!_.isInstanceOf[WhenStringLiteral]), nonNull, typeName)
case _ => assert(false); Seq()
}
}
override def checkIsSObject(context: BlockVerifyContext): Seq[String] = {
val nonNull = literals.filterNot(_.isInstanceOf[WhenNullLiteral])
if (nonNull.nonEmpty)
OrgImpl.logError(nonNull.head.location,
"An SObject name and variable name are required for this value")
Seq()
}
override def checkEnumValue(typeDeclaration: TypeDeclaration): Seq[String] = {
val nonNull = literals.filterNot(_.isInstanceOf[WhenNullLiteral])
val notEnum = nonNull.filter(!_.isInstanceOf[WhenIdLiteral])
if (notEnum.nonEmpty) {
OrgImpl.logError(notEnum.head.location, "An Enum value is required for this value")
return Seq()
}
nonNull.foreach {
case iv: WhenIdLiteral =>
if (typeDeclaration.findField(iv.id.name, Some(true)).isEmpty) {
OrgImpl.logError(iv.id.location, "Value must be a enum constant")
return Seq()
}
case _ =>
}
nonNull.map(_.toString)
}
}
final case class WhenIdsValue(ids: Seq[Id]) extends WhenValue {
override def checkMatchableTo(typeName: TypeName): Seq[String] = {
OrgImpl.logError(ids.head.location, s"A $typeName literal is required for this value")
Seq()
}
override def checkIsSObject(context: BlockVerifyContext): Seq[String] = {
// Check can be deferred to verify()
Seq(ids.head.name.value.toLowerCase())
}
override def checkEnumValue(typeDeclaration: TypeDeclaration): Seq[String] = {
OrgImpl.logError(ids.head.location, "Expecting an enum constant value")
Seq()
}
override def verify(context: BlockVerifyContext): Unit = {
context.addVar(ids(1).name, ids.head.location, TypeName(ids.head.name))
}
}
object WhenValue {
def construct(value: WhenValueContext): WhenValue = {
if (CodeParser.toScala(value.ELSE()).nonEmpty)
new WhenElseValue()
else {
val literals = CodeParser.toScala(value.whenLiteral()).map(l => WhenLiteral.construct(l))
if (literals.nonEmpty)
WhenLiteralsValue(literals)
else
WhenIdsValue(
CodeParser
.toScala(value.id())
.map(id => Id.construct(id).withContext(id)))
}
}
}
final case class WhenControl(whenValue: WhenValue, block: Block) extends CST {
def verify(context: BlockVerifyContext): Unit = {
val bc = new InnerBlockVerifyContext(context)
whenValue.verify(bc)
block.verify(bc)
}
}
object WhenControl {
def construct(parser: CodeParser, whenControl: WhenControlContext): WhenControl = {
WhenControl(CodeParser.toScala(whenControl.whenValue()).map(v => WhenValue.construct(v)).get,
Block.construct(parser, whenControl.block(), isTrigger = false))
}
}
final case class SwitchStatement(expression: Expression, whenControls: List[WhenControl])
extends Statement {
override def verify(context: BlockVerifyContext): Unit = {
val result = expression.verify(context)
if (result.isDefined) {
val values = result.typeName match {
case TypeNames.Integer | TypeNames.Long | TypeNames.String =>
checkMatchableTo(result.typeName)
case TypeNames.SObject =>
checkIsSObject(context)
case _ if result.typeDeclaration.nature == ENUM_NATURE =>
checkEnumValue(result.typeDeclaration)
case _ =>
OrgImpl.logError(
expression.location,
s"Switch expression must be a Integer, Long, String, SObject record or enum value, not '${result.typeName}'")
return;
}
checkWhenElseIsLast()
checkForDoubleNull()
val duplicates = values.groupBy(identity).collect { case (_, Seq(_, y, _*)) => y }
duplicates.headOption.foreach(dup =>
OrgImpl.logError(expression.location, s"Duplicate when case for $dup"))
whenControls.foreach(_.verify(context))
}
}
private def checkMatchableTo(typeName: TypeName): Seq[String] = {
whenControls.flatMap(_.whenValue.checkMatchableTo(typeName))
}
private def checkIsSObject(context: BlockVerifyContext): Seq[String] = {
whenControls.flatMap(_.whenValue.checkIsSObject(context))
}
private def checkEnumValue(typeDeclaration: TypeDeclaration): Seq[String] = {
whenControls.flatMap(_.whenValue.checkEnumValue(typeDeclaration))
}
private def checkWhenElseIsLast(): Unit = {
val notLastElse = whenControls.zipWithIndex
.filter(_._1.whenValue.isInstanceOf[WhenElseValue])
.find(_._2 != whenControls.length - 1)
if (notLastElse.nonEmpty)
OrgImpl.logError(notLastElse.get._1.location, "'when else' must be the last when block")
}
private def checkForDoubleNull(): Unit = {
val literals = whenControls.flatMap(wc => {
wc.whenValue match {
case l: WhenLiteralsValue => l.literals
case _ => Seq()
}
})
if (literals.count(_.isInstanceOf[WhenNullLiteral]) > 1)
OrgImpl.logError(literals.last.location,
"There should only be one 'when null' block in a switch")
}
}
object SwitchStatement {
def construct(parser: CodeParser, switchStatement: SwitchStatementContext): SwitchStatement = {
SwitchStatement(Expression.construct(switchStatement.expression()),
CodeParser
.toScala(switchStatement.whenControl())
.map(wc => WhenControl.construct(parser, wc).withContext(wc))
.toList,
)
}
}