All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nawforce.apexlink.cst.CST.scala Maven / Gradle / Ivy

/*
 Copyright (c) 2017 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.
 */
package com.nawforce.apexlink.cst

import com.nawforce.apexlink.diagnostics.IssueOps
import com.nawforce.apexlink.names.XNames.NameUtils
import com.nawforce.pkgforce.diagnostics.Issue
import com.nawforce.pkgforce.names.{Name, Names, TypeName}
import com.nawforce.pkgforce.path.Positionable
import com.nawforce.runtime.parsers.{CodeParser, Source}
import io.github.apexdevtools.apexparser.ApexParser._

import scala.util.DynamicVariable

/** Base for all CST nodes, provides some basic location handling.
  *
  * This is mutable for historic reasons, you must call withContext() on all CST nodes for them to
  * pick up the location information from the parser. It also supports lines & column adjustments
  * for when we re-parse blocks, see LazyBlock for details.
  */
class CST extends Positionable {

  def withContext(context: CodeParser.ParserRuleContext): this.type = {
    if (context != null)
      CST.sourceContext.value.get.stampLocation(this, context)
    this
  }
}

object CST {
  // Nasty hack to allow content information to be set globally and accessed as needed
  val sourceContext: DynamicVariable[Option[Source]] = new DynamicVariable(None)
}

final case class Id(name: Name) extends CST {
  def validate(context: VerifyContext): Unit = {
    validateReserved((n: Name) => n.isReservedIdentifier).foreach(context.log)
  }

  def validateForMethod(context: VerifyContext): Unit = {
    validateReserved((n: Name) => n.isReservedMethodIdentifier)
      .orElse {
        if (name.isReservedIdentifier) {
          Some(IssueOps.reservedMethodIdentifierWarning(location, name))
        } else {
          None
        }
      }
      .foreach(context.log)
  }

  private def validateReserved(reserved: Name => Boolean): Option[Issue] = {
    if (name.isEmpty)
      None
    else
      name.isLegalIdentifier
        .map(error => {
          IssueOps.illegalIdentifier(location, name, error)
        })
        .orElse {
          if (reserved(name))
            Some(IssueOps.reservedIdentifier(location, name))
          else
            None
        }
  }
}

object Id {
  def construct(idContext: IdContext): Id = {
    Id(Names(CodeParser.getText(idContext))).withContext(idContext)
  }

  def constructAny(idContext: AnyIdContext): Id = {
    Id(Names(CodeParser.getText(idContext))).withContext(idContext)
  }
}

final case class QualifiedName(names: Seq[Name]) extends CST {
  def asTypeName(): TypeName = TypeName(names.reverse)
}

object QualifiedName {
  def construct(qualifiedNames: Seq[QualifiedNameContext]): Seq[QualifiedName] = {
    qualifiedNames.flatMap(n => QualifiedName.construct(n))
  }

  def construct(qualifiedName: QualifiedNameContext): Option[QualifiedName] = {
    Option(qualifiedName).map(qualifiedName => {
      val ids = CodeParser.toScala(qualifiedName.id())
      QualifiedName(ids.map(id => Names(CodeParser.getText(id)))).withContext(qualifiedName)
    })
  }
}

final case class Annotation(
  name: Option[QualifiedName],
  elementValuePairs: List[ElementValuePair],
  elementValue: Option[ElementValue]
) extends CST

object Annotation {
  def construct(annotation: AnnotationContext): Annotation = {
    val elementValue =
      CodeParser
        .toScala(annotation.elementValue())
        .flatMap(ElementValue.construct)
    val elementValuePairs =
      CodeParser
        .toScala(annotation.elementValuePairs())
        .map(ElementValuePairs.construct)
        .getOrElse(Nil)

    Annotation(QualifiedName.construct(annotation.qualifiedName()), elementValuePairs, elementValue)
      .withContext(annotation)
  }
}

sealed abstract class ElementValue() extends CST

final case class ExpressionElementValue(expression: Expression) extends ElementValue

final case class AnnotationElementValue(annotation: Annotation) extends ElementValue

final case class ArrayInitializerElementValue(arrayInitializer: ElementValueArrayInitializer)
    extends ElementValue

object ElementValue {
  def construct(elementValue: ElementValueContext): Option[ElementValue] = {
    val expression       = CodeParser.toScala(elementValue.expression())
    val annotation       = CodeParser.toScala(elementValue.annotation())
    val arrayInitializer = CodeParser.toScala(elementValue.elementValueArrayInitializer())

    if (expression.nonEmpty) {
      Some(ExpressionElementValue(Expression.construct(expression.get)).withContext(elementValue))
    } else if (annotation.nonEmpty) {
      Some(AnnotationElementValue(Annotation.construct(annotation.get)).withContext(elementValue))
    } else if (arrayInitializer.nonEmpty) {
      Some(
        ArrayInitializerElementValue(ElementValueArrayInitializer.construct(arrayInitializer.get))
          .withContext(elementValue)
      )
    } else {
      None
    }
  }
}

final case class ElementValueArrayInitializer(elementValues: Seq[ElementValue]) extends CST

object ElementValueArrayInitializer {
  def construct(from: ElementValueArrayInitializerContext): ElementValueArrayInitializer = {
    val elements = CodeParser.toScala(from.elementValue())
    ElementValueArrayInitializer(elements.flatMap(x => ElementValue.construct(x)))
      .withContext(from)
  }
}

final case class ElementValuePair(id: String, elementValue: Option[ElementValue]) extends CST

object ElementValuePair {
  def construct(from: ElementValuePairContext): ElementValuePair = {
    ElementValuePair(CodeParser.getText(from.id()), ElementValue.construct(from.elementValue()))
      .withContext(from)
  }
}

object ElementValuePairs {
  def construct(from: ElementValuePairsContext): List[ElementValuePair] = {
    if (from != null) {
      val pairs = CodeParser.toScala(from.elementValuePair())
      pairs.toList.map(x => ElementValuePair.construct(x))
    } else {
      List()
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy