<commit_before># Copyright (C) <NAME>. All rights reserved.
# BSD License. Look at license.txt for more info.

## Implements the new configuration system for Nimble. Uses Nim as a
## scripting language.

import
  compiler/ast, compiler/modules, compiler/passes, compiler/passaux,
  compiler/condsyms, compiler/options, compiler/sem, compiler/semdata,
  compiler/llstream, compiler/vm, compiler/vmdef, compiler/commands,
  compiler/msgs, compiler/magicsys, compiler/lists

from compiler/scriptconfig import setupVM
from compiler/idents import getIdent
from compiler/astalgo import strTableGet, `$`

import nimbletypes, version
import os, strutils

const
  internalCmd = "NimbleInternal"

proc raiseVariableError(ident, typ: string) {.noinline.} =
  raise newException(NimbleError,
    "NimScript's variable '" & ident & "' needs a value of type '" & typ & "'.")

proc isStrLit(n: PNode): bool = n.kind in {nkStrLit..nkTripleStrLit}

proc getGlobal(ident: string): string =
  let n = vm.globalCtx.getGlobalValue(getSysSym ident)
  if n.isStrLit:
    result = if n.strVal.isNil: "" else: n.strVal
  else:
    raiseVariableError(ident, "string")

proc getGlobalAsSeq(ident: string): seq[string] =
  let n = vm.globalCtx.getGlobalValue(getSysSym ident)
  result = @[]
  if n.kind == nkBracket:
    for x in n:
      if x.isStrLit:
        result.add x.strVal
      else:
        raiseVariableError(ident, "seq[string]")
  else:
    raiseVariableError(ident, "seq[string]")

proc extractRequires(result: var seq[PkgTuple]) =
  let n = vm.globalCtx.getGlobalValue(getSysSym "requiresData")
  if n.kind == nkBracket:
    for x in n:
      if x.kind == nkPar and x.len == 2 and x[0].isStrLit and x[1].isStrLit:
        result.add(parseRequires(x[0].strVal & x[1].strVal))
      elif x.isStrLit:
        result.add(parseRequires(x.strVal))
      else:
        raiseVariableError("requiresData", "seq[(string, VersionReq)]")
  else:
    raiseVariableError("requiresData", "seq[(string, VersionReq)]")

proc execScript(scriptName: string) =
  ## Executes the specified script.
  ##
  ## No clean up is performed and must be done manually!
  setDefaultLibpath()
  passes.gIncludeFile = includeModule
  passes.gImportModule = importModule
  initDefines()

  defineSymbol("nimscript")
  defineSymbol("nimconfig")
  defineSymbol("nimble")
  registerPass(semPass)
  registerPass(evalPass)

  appendStr(searchPaths, options.libpath)

  var m = makeModule(scriptName)
  incl(m.flags, sfMainModule)
  vm.globalCtx = setupVM(m, scriptName)

  compileSystemModule()
  processModule(m, llStreamOpen(scriptName, fmRead), nil)

proc cleanup() =
  # ensure everything can be called again:
  resetAllModulesHard()
  clearPasses()
  msgs.gErrorMax = 1
  msgs.writeLnHook = nil
  vm.globalCtx = nil
  initDefines()

proc readPackageInfoFromNims*(scriptName: string; result: var PackageInfo) =
  ## Executes the `scriptName` nimscript file. Reads the package information
  ## that it populates.

  # Setup custom error handling.
  msgs.gErrorMax = high(int)
  var previousMsg = ""
  msgs.writeLnHook =
    proc (output: string) =
      # The error counter is incremented after the writeLnHook is invoked.
      if msgs.gErrorCounter > 0:
        raise newException(NimbleError, previousMsg)
      previousMsg = output

  # Execute the nimscript file.
  execScript(scriptName)

  # Extract all the necessary fields populated by the nimscript file.
  template trivialField(field) =
    result.field = getGlobal(astToStr field)

  template trivialFieldSeq(field) =
    result.field.add getGlobalAsSeq(astToStr field)

  # keep reasonable default:
  let name = getGlobal"packageName"
  if name.len > 0: result.name = name

  trivialField version
  trivialField author
  trivialField description
  trivialField license
  trivialField srcdir
  trivialField bindir
  trivialFieldSeq skipDirs
  trivialFieldSeq skipFiles
  trivialFieldSeq skipExt
  trivialFieldSeq installDirs
  trivialFieldSeq installFiles
  trivialFieldSeq installExt

  extractRequires result.requires

  let binSeq = getGlobalAsSeq("bin")
  for i in binSeq:
    result.bin.add(i.addFileExt(ExeExt))

  let backend = getGlobal("backend")
  if backend.len == 0:
    result.backend = "c"
  elif cmpIgnoreStyle(backend, "javascript") == 0:
    result.backend = "js"
  else:
    result.backend = backend.toLower()

  cleanup()

proc execTask*(scriptName, taskName: string): bool =
  ## Executes the specified task in the specified script.
  ##
  ## `scriptName` should be a filename pointing to the nimscript file.
  result = true
  options.command = internalCmd
  echo("Executing task ", taskName, " in ", scriptName)

  execScript(scriptName)
  # Explicitly execute the task procedure, instead of relying on hack.
  assert vm.globalCtx.module.kind == skModule
  let prc = vm.globalCtx.module.tab.strTableGet(getIdent(taskName & "Task"))
  if prc.isNil:
    # Procedure not defined in the NimScript module.
    return false
  discard vm.globalCtx.execProc(prc, [])
  cleanup()

proc getNimScriptCommand*(): string =
  options.command

proc setNimScriptCommand*(command: string) =
  options.command = command

proc hasTaskRequestedCommand*(): bool =
  ## Determines whether the last executed task used ``setCommand``
  return getNimScriptCommand() != internalCmd

proc listTasks*(scriptName: string) =
  setNimScriptCommand("help")

  execScript(scriptName)
  # TODO: Make the 'task' template generate explicit data structure containing
  # all the task names + descriptions.
  cleanup()<commit_msg>Fixed compilation<commit_after># Copyright (C) <NAME>. All rights reserved.
# BSD License. Look at license.txt for more info.

## Implements the new configuration system for Nimble. Uses Nim as a
## scripting language.

import
  compiler/ast, compiler/modules, compiler/passes, compiler/passaux,
  compiler/condsyms, compiler/options, compiler/sem, compiler/semdata,
  compiler/llstream, compiler/vm, compiler/vmdef, compiler/commands,
  compiler/msgs, compiler/magicsys, compiler/lists

from compiler/scriptconfig import setupVM
from compiler/idents import getIdent
from compiler/astalgo import strTableGet

import nimbletypes, version
import os, strutils

const
  internalCmd = "NimbleInternal"

proc raiseVariableError(ident, typ: string) {.noinline.} =
  raise newException(NimbleError,
    "NimScript's variable '" & ident & "' needs a value of type '" & typ & "'.")

proc isStrLit(n: PNode): bool = n.kind in {nkStrLit..nkTripleStrLit}

proc getGlobal(ident: string): string =
  let n = vm.globalCtx.getGlobalValue(getSysSym ident)
  if n.isStrLit:
    result = if n.strVal.isNil: "" else: n.strVal
  else:
    raiseVariableError(ident, "string")

proc getGlobalAsSeq(ident: string): seq[string] =
  let n = vm.globalCtx.getGlobalValue(getSysSym ident)
  result = @[]
  if n.kind == nkBracket:
    for x in n:
      if x.isStrLit:
        result.add x.strVal
      else:
        raiseVariableError(ident, "seq[string]")
  else:
    raiseVariableError(ident, "seq[string]")

proc extractRequires(result: var seq[PkgTuple]) =
  let n = vm.globalCtx.getGlobalValue(getSysSym "requiresData")
  if n.kind == nkBracket:
    for x in n:
      if x.kind == nkPar and x.len == 2 and x[0].isStrLit and x[1].isStrLit:
        result.add(parseRequires(x[0].strVal & x[1].strVal))
      elif x.isStrLit:
        result.add(parseRequires(x.strVal))
      else:
        raiseVariableError("requiresData", "seq[(string, VersionReq)]")
  else:
    raiseVariableError("requiresData", "seq[(string, VersionReq)]")

proc execScript(scriptName: string) =
  ## Executes the specified script.
  ##
  ## No clean up is performed and must be done manually!
  setDefaultLibpath()
  passes.gIncludeFile = includeModule
  passes.gImportModule = importModule
  initDefines()

  defineSymbol("nimscript")
  defineSymbol("nimconfig")
  defineSymbol("nimble")
  registerPass(semPass)
  registerPass(evalPass)

  appendStr(searchPaths, options.libpath)

  var m = makeModule(scriptName)
  incl(m.flags, sfMainModule)
  vm.globalCtx = setupVM(m, scriptName)

  compileSystemModule()
  processModule(m, llStreamOpen(scriptName, fmRead), nil)

proc cleanup() =
  # ensure everything can be called again:
  resetAllModulesHard()
  clearPasses()
  msgs.gErrorMax = 1
  msgs.writeLnHook = nil
  vm.globalCtx = nil
  initDefines()

proc readPackageInfoFromNims*(scriptName: string; result: var PackageInfo) =
  ## Executes the `scriptName` nimscript file. Reads the package information
  ## that it populates.

  # Setup custom error handling.
  msgs.gErrorMax = high(int)
  var previousMsg = ""
  msgs.writeLnHook =
    proc (output: string) =
      # The error counter is incremented after the writeLnHook is invoked.
      if msgs.gErrorCounter > 0:
        raise newException(NimbleError, previousMsg)
      previousMsg = output

  # Execute the nimscript file.
  execScript(scriptName)

  # Extract all the necessary fields populated by the nimscript file.
  template trivialField(field) =
    result.field = getGlobal(astToStr field)

  template trivialFieldSeq(field) =
    result.field.add getGlobalAsSeq(astToStr field)

  # keep reasonable default:
  let name = getGlobal"packageName"
  if name.len > 0: result.name = name

  trivialField version
  trivialField author
  trivialField description
  trivialField license
  trivialField srcdir
  trivialField bindir
  trivialFieldSeq skipDirs
  trivialFieldSeq skipFiles
  trivialFieldSeq skipExt
  trivialFieldSeq installDirs
  trivialFieldSeq installFiles
  trivialFieldSeq installExt

  extractRequires result.requires

  let binSeq = getGlobalAsSeq("bin")
  for i in binSeq:
    result.bin.add(i.addFileExt(ExeExt))

  let backend = getGlobal("backend")
  if backend.len == 0:
    result.backend = "c"
  elif cmpIgnoreStyle(backend, "javascript") == 0:
    result.backend = "js"
  else:
    result.backend = backend.toLower()

  cleanup()

proc execTask*(scriptName, taskName: string): bool =
  ## Executes the specified task in the specified script.
  ##
  ## `scriptName` should be a filename pointing to the nimscript file.
  result = true
  options.command = internalCmd
  echo("Executing task ", taskName, " in ", scriptName)

  execScript(scriptName)
  # Explicitly execute the task procedure, instead of relying on hack.
  assert vm.globalCtx.module.kind == skModule
  let prc = vm.globalCtx.module.tab.strTableGet(getIdent(taskName & "Task"))
  if prc.isNil:
    # Procedure not defined in the NimScript module.
    return false
  discard vm.globalCtx.execProc(prc, [])
  cleanup()

proc getNimScriptCommand*(): string =
  options.command

proc setNimScriptCommand*(command: string) =
  options.command = command

proc hasTaskRequestedCommand*(): bool =
  ## Determines whether the last executed task used ``setCommand``
  return getNimScriptCommand() != internalCmd

proc listTasks*(scriptName: string) =
  setNimScriptCommand("help")

  execScript(scriptName)
  # TODO: Make the 'task' template generate explicit data structure containing
  # all the task names + descriptions.
  cleanup()

{'fraction_non_alphanumeric': 0.0989686991134431, 'fraction_numerical': 0.0018092998009770218, 'mean_word_length': 4.195018796992481, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 9, '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': '6521029', 'n_tokens_mistral': 3483, 'n_tokens_neox': 3412, 'n_words': 948}