<commit_before>const R = require('ramda')
const ModelLHSReader = require('./ModelLHSReader')
const EquationGen = require('./EquationGen')
const Model = require('./Model')
const { sub, allDimensions, allMappings, subscriptFamilies } = require('./Subscript')
const { asort, lines, strlist, abend } = require('./Helpers')

let codeGenerator = (parseTree, opts) => {
  const { spec, operation, extData, directData } = opts
  // Set true when in the init section, false in the eval section.
  let initMode = false
  // Set true to output all variables when there is no model run spec.
  let outputAllVars = spec.outputVars && spec.outputVars.length > 0 ? false : true
  // Function to generate a section of the code
  let generateSection = R.map(v => new EquationGen(v, extData, directData, initMode).generate())
  let section = R.pipe(generateSection, R.flatten, lines)
  function generate() {
    // Read variables and subscript ranges from the model parse tree.
    // This is the main entry point for code generation and is called just once.
    try {
      Model.read(parseTree, spec, extData, directData)
      // In list mode, print variables to the console instead of generating code.
      if (operation === 'printRefIdTest') {
        Model.printRefIdTest()
      } else if (operation === 'printRefGraph') {
        Model.printRefGraph(opts.varname)
      } else if (operation === 'convertNames') {
        // Do not generate output, but leave the results of model analysis.
      } else if (operation === 'generateC') {
        // Generate code for each variable in the proper order.
        let code = emitDeclCode()
        code += emitInitCode()
        code += emitEvalCode()
        code += emitIOCode()
        return code
      }
    } catch (e) {
      abend(e)
    }
  }

  // Each code section follows in an outline of the generated model code.

  //
  // Declaration section
  //
  function emitDeclCode() {
    initMode = false
    return `#include "sde.h"

// Model variables
${declSection()}

// Internal variables
${internalVarsSection()}

// Array dimensions
${arrayDimensionsSection()}

// Dimension mappings
${dimensionMappingsSection()}

// Lookup data arrays
${section(Model.lookupVars())}
`
  }
  //
  // Initialization section
  //
  function emitInitCode() {
    initMode = true
    return `// Internal state
bool lookups_initialized = false;

void initConstants() {
  // Initialize constants.
${section(Model.constVars())}

  // Initialize lookups.
if (!lookups_initialized) {
${section(Model.lookupVars())}
${section(Model.dataVars())}
  lookups_initialized = true;
}
}
void initLevels() {
  // Initialize variables with initialization values, such as levels, and the variables they depend on.
  _time = _initial_time;
${section(Model.initVars())}
}

`
  }
  //
  // Evaluation section
  //
  function emitEvalCode() {
    initMode = false
    return `void evalAux() {
  // Evaluate auxiliaries in order from the bottom up.
${section(Model.auxVars())}
}

void evalLevels() {
  // Evaluate levels.
${section(Model.levelVars())}
}

`
  }
  //
  // Input/output section
  //
  function emitIOCode() {
    let headerVars = outputAllVars ? expandedVarNames(true) : spec.outputVars
    let outputVars = outputAllVars ? expandedVarNames() : spec.outputVars
    initMode = false
    return `void setInputs(const char* inputData) {
${inputSection()}}
const char* getHeader() {
  return "${R.map(varName => headerTitle(varName), headerVars).join('\\t')}";
}

void storeOutputData() {
${outputSection(outputVars)}
}
`
  }

  //
  // Declaration section helpers
  //
  function declSection() {
    // Emit a declaration for each variable in the model.
    let decl = v => {
      // Build a C array declaration for the variable v.
      // This uses the subscript family for each dimension, which may overallocate
      // if the subscript is a subdimension.
      let varType = v.isLookup() || v.isData() ? 'Lookup* ' : 'double '
      let families = subscriptFamilies(v.subscripts)
      return varType + v.varName + R.map(family => `[${sub(family).size}]`, families).join('')
    }
    // Non-apply-to-all variables are declared multiple times, but coalesce using uniq.
    let decls = R.pipe(
      R.map(v => `${decl(v)};`),
      R.uniq,
      asort,
      lines
    )
    return decls(Model.allVars())<commit_msg>perf: break large functions into chunked sub-functions (#21)<commit_after>const R = require('ramda')
const ModelLHSReader = require('./ModelLHSReader')
const EquationGen = require('./EquationGen')
const Model = require('./Model')
const { sub, allDimensions, allMappings, subscriptFamilies } = require('./Subscript')
const { asort, lines, strlist, abend, mapIndexed } = require('./Helpers')

let codeGenerator = (parseTree, opts) => {
  const { spec, operation, extData, directData } = opts
  // Set true when in the init section, false in the eval section.
  let initMode = false
  // Set true to output all variables when there is no model run spec.
  let outputAllVars = spec.outputVars && spec.outputVars.length > 0 ? false : true
  // Function to generate a section of the code
  let generateSection = R.map(v => new EquationGen(v, extData, directData, initMode).generate())
  let section = R.pipe(generateSection, R.flatten, lines)
  function generate() {
    // Read variables and subscript ranges from the model parse tree.
    // This is the main entry point for code generation and is called just once.
    try {
      Model.read(parseTree, spec, extData, directData)
      // In list mode, print variables to the console instead of generating code.
      if (operation === 'printRefIdTest') {
        Model.printRefIdTest()
      } else if (operation === 'printRefGraph') {
        Model.printRefGraph(opts.varname)
      } else if (operation === 'convertNames') {
        // Do not generate output, but leave the results of model analysis.
      } else if (operation === 'generateC') {
        // Generate code for each variable in the proper order.
        let code = emitDeclCode()
        code += emitInitCode()
        code += emitEvalCode()
        code += emitIOCode()
        return code
      }
    } catch (e) {
      abend(e)
    }
  }

  // Each code section follows in an outline of the generated model code.

  //
  // Declaration section
  //
  function emitDeclCode() {
    initMode = false
    return `#include "sde.h"

// Model variables
${declSection()}

// Internal variables
${internalVarsSection()}

// Array dimensions
${arrayDimensionsSection()}

// Dimension mappings
${dimensionMappingsSection()}

// Lookup data arrays
${section(Model.lookupVars())}
`
  }

  //
  // Initialization section
  //
  function emitInitCode() {
    initMode = true
    return `// Internal state
bool lookups_initialized = false;

void initLookups() {
  // Initialize lookups.
  if (!lookups_initialized) {
    ${section(Model.lookupVars())}
    ${section(Model.dataVars())}
    lookups_initialized = true;
  }
}

${chunkedFunctions('initConstants', Model.constVars(),
'  // Initialize constants.',
'  initLookups();'
)}

${chunkedFunctions('initLevels', Model.initVars(), `
  // Initialize variables with initialization values, such as levels, and the variables they depend on.
  _time = _initial_time;`
)}
`
  }

  //
  // Evaluation section
  //
  function emitEvalCode() {
    initMode = false

    return `
${chunkedFunctions('evalAux', Model.auxVars(),
'  // Evaluate auxiliaries in order from the bottom up.'
)}

${chunkedFunctions('evalLevels', Model.levelVars(),
'  // Evaluate levels.'
)}
`
  }

  //
  // Input/output section
  //
  function emitIOCode() {
    let headerVars = outputAllVars ? expandedVarNames(true) : spec.outputVars
    let outputVars = outputAllVars ? expandedVarNames() : spec.outputVars
    initMode = false
    return `void setInputs(const char* inputData) {
${inputSection()}}
const char* getHeader() {
  return "${R.map(varName => headerTitle(varName), headerVars).join('\\t')}";
}

void storeOutputData() {
${outputSection(outputVars)}
}
`
  }

  //
  // Chunked function helper
  //
  function chunkedFunctions(name, vars, preStep, postStep) {
    // Emit one function for each chunk
    let func = (chunk, idx) => {
      return `
void ${name}${idx}() {
  ${section(chunk)}
}
`
    }
    let funcs = R.pipe(
      mapIndexed(func),
      lines
    )

    // Emit one roll-up function that calls the other chunk functions
    let funcCall = (chunk, idx) => {
      return `  ${name}${idx}();`
    }
    let funcCalls = R.pipe(
      mapIndexed(funcCall),
      lines
    )

    // Break the vars into chunks of 30; this number was empirically
    // determined by looking at runtime performance and memory usage
    // of the En-ROADS model on various devices
    let chunks = R.splitEvery(30, vars)

    if (!preStep) {
      preStep = ''
    }
    if (!postStep) {
      postStep = ''
    }

    return `
${funcs(chunks)}

void ${name}() {
${preStep}
${funcCalls(chunks)}
${postStep}
}
    `
  }

  //
  // Declaration section helpers
  //
  function declSection() {
    // Emit a declaration for each variable in the model.
    let decl = v => {
      // Build a C array declaration for the variable v.
      // This uses the subscript family for each dimension, which may overallocate
      // if the subscript is a subdimension.
      let varType = v.isLookup() || v.isData() ? 'Lookup* ' : 'double '
      let families = subscriptFamilies(v.subscripts)
      return varType + v.varName + R.map(family => `[${sub(family).size}]`, families).join('')
    }
    // Non-apply-to-all variables are declared multiple times, but coalesce using uniq.
    let decls = R.pipe(
      R.map(v => `${decl(v)};`),
      R.uniq,
      asort,
      lines
    )
    return decls(Model.allVars())
{'fraction_non_alphanumeric': 0.12850419480253733, 'fraction_numerical': 0.0008184980560671168, 'mean_word_length': 2.9963205233033525, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 19, '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': '1301683', 'n_tokens_mistral': 2921, 'n_tokens_neox': 2927, 'n_words': 894}