summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/CompilerCommand.scala
blob: ea0aa5f969c469f1227fba00bbb4d388f4b10f46 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/* NSC -- new Scala compiler
 * Copyright 2005-2009 LAMP/EPFL
 * @author  Martin Odersky
 */
// $Id$

package scala.tools.nsc

import Settings.Setting
import java.io.IOException

/** A class representing command line info for scalac */
class CompilerCommand(
  arguments: List[String],
  val settings: Settings,
  error: String => Unit,
  interactive: Boolean,
  shouldProcessArguments: Boolean)
{
  def this(arguments: List[String], settings: Settings, error: String => Unit, interactive: Boolean) =
    this(arguments, settings, error, interactive, true)

  /** Private buffer for accumulating files to compile */
  private var fs: List[String] = List()

  /** Public list of files to compile */
  def files: List[String] = fs.reverse

  /** The name of the command */
  val cmdName = "scalac"

  /** The file extensions of files that the compiler can process */
  lazy val fileEndings = Properties.fileEndingString.split("""\|""").toList

  private val helpSyntaxColumnWidth: Int =
    Iterable.max(settings.allSettings map (_.helpSyntax.length))

  private def format(s: String): String = {
    val buf = new StringBuilder(s)
    var i = s.length
    while (i < helpSyntaxColumnWidth) { buf.append(' '); i += 1 }
    buf.toString()
  }

  /** Creates a help message for a subset of options based on cond */
  def createUsageMsg(label: String, cond: (Setting) => Boolean): String =
    settings.allSettings .
      filter(cond) .
      map(s => format(s.helpSyntax) + "  " + s.helpDescription) .
      mkString("Usage: %s <options> <source files>\n%s options include:\n  " .
        format(cmdName, label), "\n  ", "\n")

  /** Messages explaining usage and options */
  def usageMsg    = createUsageMsg("where possible standard", _.isStandard)
  def xusageMsg   = createUsageMsg("Possible advanced", _.isAdvanced)
  def yusageMsg   = createUsageMsg("Possible private", _.isPrivate)

  // If any of these settings is set, the compiler shouldn't start;
  // an informative message of some sort should be printed instead.
  // (note: do not add "files.isEmpty" do this list)
  val stopSettings = List[(() => Boolean, (Global) => String)](
    (settings.help.value _,         usageMsg + _.pluginOptionsHelp),
    (settings.Xhelp.value _,        _ => xusageMsg),
    (settings.Yhelp.value _,        _ => yusageMsg),
    (settings.showPlugins.value _,  _.pluginDescriptions),
    (settings.showPlugins.value _,  _.phaseDescriptions)
  )
  def shouldStopWithInfo: Boolean = stopSettings exists { _._1() }

  def getInfoMessage(compiler: Global): String =
    stopSettings.find(pair => (pair._1)()) match {
      case Some((test, getMessage)) => getMessage(compiler)
      case None => ""
    }

  /** Whether the command was processed okay */
  var ok = true

  /** Process the arguments and update the settings accordingly.
      This method is called only once, during initialization.  */
  protected def processArguments() {
    // initialization
    var args = arguments
    def errorAndNotOk(msg: String) = { error(msg) ; ok = false }

    // given a @ argument expands it out
    def doExpand(x: String) =
      try   { args = util.ArgumentsExpander.expandArg(x) ::: args.tail }
      catch { case ex: IOException  => errorAndNotOk(ex.getMessage) }

    // true if it's a legit looking source file
    def isSourceFile(x: String) =
      (settings.script.value != "") ||
      (fileEndings exists (ext => x endsWith ext))

    // given an option for scalac finds out what it is
    def doOption(x: String): Unit = {
      if (interactive)
        return errorAndNotOk("no options can be given in interactive mode")

      val argsLeft = settings.parseParams(args)
      if (args != argsLeft) args = argsLeft
      else errorAndNotOk("bad option: '" + x + "'")
    }

    // cycle through args until empty or error
    while (!args.isEmpty && ok) args.head match {
      case x if x startsWith "@"  => doExpand(x)
      case x if x startsWith "-"  => doOption(x)
      case x if isSourceFile(x)   => fs = x :: fs ; args = args.tail
      case ""                     => args = args.tail // quick fix [martin: for what?]
      case x                      => errorAndNotOk("don't know what to do with " + x)
    }

    ok &&= settings.checkDependencies
  }

  // CompilerCommand needs processArguments called at the end of its constructor,
  // as does its subclass GenericRunnerCommand, but it cannot be called twice as it
  // accumulates arguments.  The fact that it's called from within the constructors
  // makes initialization order an obstacle to simplicity.
  if (shouldProcessArguments)
    processArguments()
}