hamzaremmal/amy

View on GitHub
compiler/src/main/scala/amyc/analyzer/NameAnalyzer.scala

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
package amyc.analyzer

import amyc.*
import amyc.core.*
import amyc.core.Types.*
import amyc.core.StdDefinitions.*
import amyc.core.Symbols.*
import amyc.core.StdTypes.*
import amyc.utils.*
import amyc.ast.{NominalTreeModule as N, SymbolicTreeModule as S}
import amyc.analyzer.Transformer.*

// Name analyzer for Amy
// Takes a nominal program (names are plain string, qualified names are string pairs)
// and returns a symbolic program, where all names have been resolved to unique Identifiers.
// Rejects programs that violate the Amy naming rules.
// Also populates symbol table.
object NameAnalyzer extends Pipeline[N.Program, S.Program] {

  override val name = "NameAnalyzer"
  override def run(p: N.Program)(using Context): S.Program = {

    // Step 0: Initialize symbol table
    ctx.withSymTable(new SymbolTable)

    // Step 1: Add modules
    registerModules(p)

    // Step 2: Check name uniqueness in modules
    for mod <- p.modules do checkModuleConsistency(mod)

    // Step 3: Discover types
    for mod <- p.modules do registerTypes(mod)
    // Uses String type, we need it to be registered first
    registerUnnamed

    // Step 4: Discover type constructors
    for m <- p.modules do registerConstructors(m)

    // Step 5: Discover functions signatures.
    for m <- p.modules do registerFunctions(m)

    // Step 6: We now know all definitions in the program.
    //         Reconstruct modules and analyse function bodies/ expressions
    transformProgram(p)

  }

  // ==============================================================================================
  // ===================================== REGISTER FUCTIONS ======================================
  // ==============================================================================================

  /**
    * @param mod
    * @param Context
    */
  def registerFunctions(mod: N.ModuleDef)(using Context) =
    for fd @ N.FunDef(name, _, retType1, _) <- mod.defs do
      symbols.addFunction(
        symbols.module(mod.name),
        name,
        fd.mods,
        fd.params,
        transformType(retType1, mod.name)
      )

  /**
    * @param mod
    * @param Context
    */
  def registerConstructors(mod: N.ModuleDef)(using Context) =
    for N.CaseClassDef(name, fields, parent) <- mod.defs do
      symbols.addConstructor(
        mod.name,
        name,
        fields,
        symbols.`type`(mod.name, parent)
      )

  /**
    * @param prog
    * @param Context
    */
  def registerModules(prog: N.Program)(using Context) =
    val modNames = prog.modules.groupBy(_.name)
    for(name, modules) <- modNames do
      if modules.size > 1 then
        reporter.fatal(
          s"Two modules named $name in program",
          modules.head.position
        )
    for mod <- modNames.keys.toList do
      val id = symbols.addModule(mod)
      ctx.withScope(id)

  /**
    * @param mod
    * @param Context
    */
  def registerTypes(mod: N.ModuleDef)(using Context) =
    for N.AbstractClassDef(name) <- mod.defs do symbols.addType(mod.name, name)

  /**
    * @param mod
    * @param Context
    */
  def checkModuleConsistency(mod: N.ModuleDef)(using Context) =
    val names = mod.defs.groupBy(_.name)
    for(name, defs) <- names do
      if(defs.size > 1) {
        reporter.fatal(
          s"Two definitions named $name in module ${mod.name}",
          defs.head
        )
      }

  private def registerUnnamed(using Context) =
    // A lot of patches everywhere to make it work, this will remain like this until the implementation of polymorphic functions
    symbols.addFunction(
      stdDef.UnnamedModule,
      "==",
      List("infix"),
      List(
        N.ParamDef("lhs", N.TTypeTree(NoType)),
        N.ParamDef("rhs", N.TTypeTree(NoType))
      ),
      S.TTypeTree(stdType.BooleanType)
    )
}