<commit_before>package lore.compiler.assembly.functions

import lore.compiler.assembly.types.TypeAssembler
import lore.compiler.assembly.values.ValueAssembler
import lore.compiler.assembly.{Chunk, PropertyOrder, RegisterProvider}
import lore.compiler.core.{CompilationException, Position}
import lore.compiler.poem.PoemInstruction.InstanceKind
import lore.compiler.poem._
import lore.compiler.semantics.Registry
import lore.compiler.semantics.expressions.Expression
import lore.compiler.semantics.functions.ParameterDefinition.NamedParameterView
import lore.compiler.semantics.functions.{CallTarget, FunctionSignature}
import lore.compiler.semantics.scopes.LocalVariable
import lore.compiler.types._

import scala.collection.immutable.HashMap

// TODO (assembly): There is a difference between an expression returning Unit and an expression's result not being
//                  used. In the case of a loop, Unit incidentally also expresses that the result is not being used,
//                  but only due to a "hack" inside Expression.Loop. In other cases, e.g. for Cond, without an "unused
//                  expression" analysis, we have to assume that the Cond should always result in a target, so we at
//                  least have to assign Unit to a target register if its result type is Unit. So, introducing a
//                  separate "unused expression" analysis would improve our ability to generate more optimal code
//                  without resorting to result type hacks.

/**
  * The expression assembler is not an ExpressionVisitor because select expressions either don't need their child
  * chunks in certain situations (e.g. when a list can be turned into a [[PoemListValue]]), or generating a child chunk
  * is even nonsensical (in the case of anonymous functions).
  *
  * To support anonymous function expressions, the assembler keeps a list of additionally generated functions, which
  * must be included in the [[FunctionAssembler]]'s result.
  *
  * The assembler generates Jump instructions with <i>label</i> locations. They will later be converted to absolute
  * locations when instructions are flattened.
  */
class ExpressionAssembler(
  signature: FunctionSignature,
  capturedVariables: CapturedVariableMap,
)(implicit registry: Registry) {
  import Expression._

  var generatedPoemFunctions: Vector[PoemFunction] = Vector.empty

  private implicit val registerProvider: RegisterProvider = new RegisterProvider
  private implicit var variableRegisterMap: VariableRegisterMap = HashMap.empty
  private implicit val capturedVariableMap: CapturedVariableMap = capturedVariables

  private def declare(variable: LocalVariable, position: Position): Poem.Register = {
    if (variableRegisterMap.contains(variable.uniqueKey)) {
      throw CompilationException(s"The variable ${variable.name} at $position is already declared somewhere else.")
    }

    val register = registerProvider.fresh()
    variableRegisterMap += (variable.uniqueKey -> register)
    register
  }

  // The first N registers of the function are reserved for the parameters.
  signature.parameters.foreach { parameter =>
    parameter.name match {
      case Some(_) => declare(NamedParameterView(parameter).asVariable, parameter.position)
      case None =>
        // This register won't be used, but calling `fresh` is still important so that the register IDs are counted up,
        // which have to match for subsequent parameters.
        registerProvider.fresh()
    }
  }

  def generate(expression: Expression): Chunk = {
    expression match {
      case _: Hole => throw CompilationException("Expression.Hole cannot be assembled.")
      case node: Return => handle(node)
      case node: VariableDeclaration => handle(node)
      case node: Assignment => handle(node)
      case node: Block => handle(node)
      case node: BindingAccess => handle(node)
      case node: MemberAccess => handle(node)
      case _: UnresolvedMemberAccess => throw CompilationException("Expression.UnresolvedMemberAccess cannot be assembled.")
      case node: Literal => handle(node)
      case node: Tuple => handle(node)
      case node: AnonymousFunction => handle(node)
      case node: MultiFunctionValue => handle(node)
      case node: FixedFunctionValue => handle(node)
      case node: ConstructorValue => handle(node)
      case _: UntypedConstructorValue => throw CompilationException("Expression.UntypedConstructorValue cannot be assembled.")
      case node: ListConstruction => handle(node)
      case node: MapConstruction => handle(node)
      case node: ShapeValue => handle(node)
      case node: Symbol => handle(node)
      case node: PropertyDefaultValue => handle(node)
      case node: UnaryOperation => handle(node)
      case node: BinaryOperation => handle(node)
      case node: XaryOperation => handle(node)
      case node: Call => handle(node)
      case node: Cond => handle(node)
      case node: WhileLoop => handle(node)
      case node: ForLoop => handle(node)
      case node: Ascription => handle(node)<commit_msg>Compiler: The variables should be called `expression` instead of `node`.<commit_after>package lore.compiler.assembly.functions

import lore.compiler.assembly.types.TypeAssembler
import lore.compiler.assembly.values.ValueAssembler
import lore.compiler.assembly.{Chunk, PropertyOrder, RegisterProvider}
import lore.compiler.core.{CompilationException, Position}
import lore.compiler.poem.PoemInstruction.InstanceKind
import lore.compiler.poem._
import lore.compiler.semantics.Registry
import lore.compiler.semantics.expressions.Expression
import lore.compiler.semantics.functions.ParameterDefinition.NamedParameterView
import lore.compiler.semantics.functions.{CallTarget, FunctionSignature}
import lore.compiler.semantics.scopes.LocalVariable
import lore.compiler.types._

import scala.collection.immutable.HashMap

// TODO (assembly): There is a difference between an expression returning Unit and an expression's result not being
//                  used. In the case of a loop, Unit incidentally also expresses that the result is not being used,
//                  but only due to a "hack" inside Expression.Loop. In other cases, e.g. for Cond, without an "unused
//                  expression" analysis, we have to assume that the Cond should always result in a target, so we at
//                  least have to assign Unit to a target register if its result type is Unit. So, introducing a
//                  separate "unused expression" analysis would improve our ability to generate more optimal code
//                  without resorting to result type hacks.

/**
  * The expression assembler is not an ExpressionVisitor because select expressions either don't need their child
  * chunks in certain situations (e.g. when a list can be turned into a [[PoemListValue]]), or generating a child chunk
  * is even nonsensical (in the case of anonymous functions).
  *
  * To support anonymous function expressions, the assembler keeps a list of additionally generated functions, which
  * must be included in the [[FunctionAssembler]]'s result.
  *
  * The assembler generates Jump instructions with <i>label</i> locations. They will later be converted to absolute
  * locations when instructions are flattened.
  */
class ExpressionAssembler(
  signature: FunctionSignature,
  capturedVariables: CapturedVariableMap,
)(implicit registry: Registry) {
  import Expression._

  var generatedPoemFunctions: Vector[PoemFunction] = Vector.empty

  private implicit val registerProvider: RegisterProvider = new RegisterProvider
  private implicit var variableRegisterMap: VariableRegisterMap = HashMap.empty
  private implicit val capturedVariableMap: CapturedVariableMap = capturedVariables

  private def declare(variable: LocalVariable, position: Position): Poem.Register = {
    if (variableRegisterMap.contains(variable.uniqueKey)) {
      throw CompilationException(s"The variable ${variable.name} at $position is already declared somewhere else.")
    }

    val register = registerProvider.fresh()
    variableRegisterMap += (variable.uniqueKey -> register)
    register
  }

  // The first N registers of the function are reserved for the parameters.
  signature.parameters.foreach { parameter =>
    parameter.name match {
      case Some(_) => declare(NamedParameterView(parameter).asVariable, parameter.position)
      case None =>
        // This register won't be used, but calling `fresh` is still important so that the register IDs are counted up,
        // which have to match for subsequent parameters.
        registerProvider.fresh()
    }
  }

  def generate(expression: Expression): Chunk = {
    expression match {
      case _: Hole => throw CompilationException("Expression.Hole cannot be assembled.")
      case expression: Return => handle(expression)
      case expression: VariableDeclaration => handle(expression)
      case expression: Assignment => handle(expression)
      case expression: Block => handle(expression)
      case expression: BindingAccess => handle(expression)
      case expression: MemberAccess => handle(expression)
      case _: UnresolvedMemberAccess => throw CompilationException("Expression.UnresolvedMemberAccess cannot be assembled.")
      case expression: Literal => handle(expression)
      case expression: Tuple => handle(expression)
      case expression: AnonymousFunction => handle(expression)
      case expression: MultiFunctionValue => handle(expression)
      case expression: FixedFunctionValue => handle(expression)
      case expression: ConstructorValue => handle(expression)
      case _: UntypedConstructorValue => throw CompilationException("Expression.UntypedConstructorValue cannot be assembled.")
      case expression: ListConstruction => handle(expression)
      case expression: MapConstruction => handle(expression)
      case expression: ShapeValue => handle(expression)
      case expression: Symbol => handle(expression)
      case expression: PropertyDefaultValue => handle(expression)
      case expression: UnaryOperation => handle(expression)
      case expression: BinaryOperation => handle(expression)
      case expression: XaryOperation => handle(expression)
      case expression: Call => handle(expression)
      case expression: Cond => handle(expression)
      case expression: WhileLoop => handle(expression)
      case expression: ForLoop => handle(expression)
      case expression: Ascription => handle(expression)
{'fraction_non_alphanumeric': 0.07728270203224884, 'fraction_numerical': 0.0, 'mean_word_length': 3.984308131241084, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 71, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5030389', 'n_tokens_mistral': 2537, 'n_tokens_neox': 2461, 'n_words': 936}