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

raw.compiler.common.Compiler.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2023 RAW Labs S.A.
 *
 * Use of this software is governed by the Business Source License
 * included in the file licenses/BSL.txt.
 *
 * As of the Change Date specified in that file, in accordance with
 * the Business Source License, use of this software will be governed
 * by the Apache License, Version 2.0, included in the file
 * licenses/APL.txt.
 */

package raw.compiler.common

import org.bitbucket.inkytonik.kiama.rewriting.Rewriter._
import raw.compiler.CompilerException
import raw.compiler.base.source._
import raw.compiler.base._
import raw.compiler.common.source._
import raw.compiler._
import raw.runtime.{Entrypoint, ParamValue, RuntimeContext}

import scala.annotation.nowarn
import scala.collection.mutable

abstract class Compiler(implicit compilerContext: CompilerContext)
    extends raw.compiler.Compiler[SourceNode, SourceProgram, Exp] {

  override protected def phases: Seq[PhaseDescriptor] = Seq.empty

  override def clone(program: SourceProgram): SourceProgram = {
    import org.bitbucket.inkytonik.kiama.relation.TreeRelation.isLeaf

    val deepcloner = everywherebu(rule[SourceNode] {
      case Eval(RawBridgeImpl(lang, program)) =>
        // Clone across language barriers
        Eval(RawBridgeImpl(lang, CompilerProvider.clone(lang, program)))
      case n if isLeaf(n) =>
        // Clone all leafs
        copy(n)
    })

    rewrite(deepcloner)(program)
  }

  override def prune(program: SourceProgram, tipe: Type)(
      implicit programContext: ProgramContext
  ): Option[SourceProgram] = {
    None
  }

  override def project(program: SourceProgram, field: String)(
      implicit programContext: ProgramContext
  ): Option[SourceProgram] = {
    None
  }

  def getProgramContext(
      runtimeContext: RuntimeContext
  ): ProgramContext = {
    new ProgramContext(runtimeContext, compilerContext)
  }

  final def getProgramDescription(
      source: String
  )(implicit programContext: ProgramContext): Either[List[ErrorMessage], ProgramDescription] = {
    buildInputTree(source).right.map { tree =>
      val TreeDescription(decls, maybeType, comment) = tree.description
      val formattedDecls = decls.map {
        case (idn, programDecls) =>
          val formattedDecls = programDecls.map {
            case TreeDeclDescription(None, outType, comment) => DeclDescription(None, prettyPrint(outType), comment)
            case TreeDeclDescription(Some(params), outType, comment) =>
              val formattedParams = params.map {
                case TreeParamDescription(idn, tipe, required) => ParamDescription(idn, prettyPrint(tipe), required)
              }
              DeclDescription(Some(formattedParams), prettyPrint(outType), comment)
          }
          (idn, formattedDecls)
      }
      ProgramDescription(formattedDecls, maybeType.map(prettyPrint), comment)
    }
  }

  override def template(program: SourceProgram)(implicit programContext: ProgramContext): (String, SourceProgram) = {

    // (The following comment is copy/pasted from L0.Compiler. The case for 'common' language is simpler but the same basisc
    //  principle holds.)
    // Template a program - i.e. remove all constants and replace them by references that can be looked up dynamically.
    // Note that we also template calls to nested languages.
    // Templating works as follows:
    // - First, we normalize the program. Therefore, all variable names will be "uniform", which means that program
    //   signatures are likely to match more.
    // - Then, we template the program. There are two parts here: one is to replace all constant nodes (e.g. IntConst) by
    //   references that can be looked up in the ProgramContext at runtime (e.g. GetIntParam). We also collect those actual
    //   values to add them to the program context later.
    //   But we must also handle nested languages. Whenever we encounter a nested language, we call its compiler (so that
    //   this same process is done recursively upfront). Then, similarly to parameter/value constants, we also replace the
    //   call to a nested language with a reference.
    //   In both cases we use simple "autoincrement" numbers as identifiers for the constants/programs being replaced.
    // - Now that we have templated the program, we can compute its signature.
    // - But we are not done yet. We must still fix the program to ensure that every identifier is unique across nested
    //   programs. That's because these identifiers are kept in the ProgramContext, and we could have two separate "L0" trees
    //   for instance. But now that we computed a clean program signature, we go back and rewrite all nodes by prefixing the
    //   identifier with the program signature as well. We also add this same key/value pairs to the ProgramContext.

    val uniquePrefix = "$"

    var programCounter = 0

    def freshProgramId(): String = {
      programCounter += 1
      s"$uniquePrefix$programCounter"
    }

    // First off, normalize the program.
    val normalizedProgram = normalize(program)

    // Now, template the program with "temporary identifiers" (which will be made unique across programs later on.)
    val programs = mutable.HashMap[String, Entrypoint]()
    val params = mutable.HashMap[String, ParamValue]()

    lazy val s = sometd(rule[SourceNode] {
      case Eval(RawBridgeImpl(lang, program)) =>
        val t = CompilerProvider.validate(lang, program).right.get.get
        val entrypoint = CompilerProvider.compile(lang, program).right.get
        val id = freshProgramId()
        programs.put(id, entrypoint)
        Eval(RawBridgeRef(t, id))
    })

    // We now have a templated program.
    val templatedProgram = rewrite(attempt(s))(normalizedProgram)

    // Get signature of templated program.
    val sig = signature(templatedProgram)

    // Now replace temporary identifiers with final ones, by prefixing them with our unique program signature.
    lazy val s1 = sometd(rule[SourceNode] {
      case Eval(RawBridgeRef(t, id)) if id.startsWith(uniquePrefix) => Eval(RawBridgeRef(t, s"$sig$id"))
    })

    // We now have our program ready for emission.
    val finalTemplatedProgram = rewrite(attempt(s1))(templatedProgram)

    // Finally, add actual values from this tree to the program context.
    programs.foreach { case (id, p) => programContext.runtimeContext.addProgram(s"$sig$id", p) }
    params.foreach { case (id, v) => programContext.runtimeContext.addParam(s"$sig$id", v) }

    (sig, finalTemplatedProgram)
  }

  def lsp(request: LSPRequest)(
      implicit @nowarn programContext: ProgramContext
  ): LSPResponse = {
    throw new CompilerException("LSP not supported")
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy