summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/MainScript.scala
blob: 308fa8509b77b3d25c5b3b7e4de61f96c1a96726 (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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/* NSC -- new Scala compiler
 * Copyright 2005-2006 LAMP/EPFL
 * @author  Martin Odersky
 */
// $Id$

package scala.tools.nsc

import java.io.{BufferedReader, FileReader, File}
import scala.tools.nsc.util._
import scala.tools.nsc.io._

/** A main routine to support putting Scala code into scripts.
 *
 *  An shell script example on Unix would look like this:
 *
 *    #!/bin/sh
 *    exec scalascript "$0" "$@"
 *    !#
 *    Console.println("Hello, world!")
 *    argv.toList foreach Console.println
 *
 * A batch file example on Windows XP would look like this:
 *
 *    ::#!
 *    @echo off
 *    call scalascript %0 %*
 *    goto :eof
 *    ::!#
 *    Console.println("Hello, world!")
 *    argv.toList foreach Console.println
 *
 * TODO: It would be better if error output went to stderr instead
 * of stdout....
 */
object MainScript {
  /** Read the entire contents of a file as a String. */
  private def contentsOfFile(filename: String): String = {
    val strbuf = new StringBuffer
    val reader = new FileReader(filename)
    val cbuf = new Array[Char](1024)
    while(true) {
      val n = reader.read(cbuf)
      if(n <= 0)
        return strbuf.toString
      strbuf.append(cbuf, 0, n)
    }
    throw new Error("impossible")
  }

  /** Find the length of the header in the specified file, if
    * there is one.  The header part starts with "#!" or "::#!"
    * and ends with a line that begins with "!#" ar "::!#".
    */
  def headerLength(filename: String): Int = {
    import java.util.regex._

    val fileContents = contentsOfFile(filename)

    if(!(fileContents.startsWith("#!") || fileContents.startsWith("::#!")))
      return 0

    val matcher =
      (Pattern.compile("^(::)?!#.*(\\r|\\n|\\r\\n)", Pattern.MULTILINE)
              .matcher(fileContents))
    if(! matcher.find)
      throw new Error("script file does not close its header with !# or ::!#")

    return matcher.end
  }

  /** Print a usage message and then exit. */
  def usageExit: Nothing = {
    Console.println(
        "scalascript [ compiler arguments... - ] scriptfile " +
        "[ script arguments... ]")
    Console.println
    Console.println(
        "Note that if you do specify compiler arguments, you \n" +
        "must put an explicit hyphen (\"-\") at the end of them.")
    System.exit(1).asInstanceOf[Nothing]
  }

  /** Parse an argument list into three portions:
    *
    *   Arguments to the compiler
    *   The filename of the script to run
    *   Arguments to pass to the script
    */
  def parseArgs(args: List[String])
      :Tuple3[List[String], String, List[String]] =
  {
    if (args.length == 0)
      usageExit

    if (args(0).startsWith("-")) {
      // the line includes compiler arguments
      val hyphenIndex = args.indexOf("-")
      if (hyphenIndex < 0)
        usageExit
      if (hyphenIndex == (args.length - 1))
        usageExit

      Tuple3(
        args.subseq(0, hyphenIndex).toList,
        args(hyphenIndex + 1),
        args.subseq(hyphenIndex + 2, args.length - hyphenIndex - 2).toList)
    } else {
      Tuple3(
        Nil,
        args(0),
        args.subseq(1, args.length-1).toList)
    }
  }


  def wrappedScript(filename: String): SourceFile = {
    val preamble =
      new SourceFile("<script preamble>",
          ("package scala.scripting\n" +
          "object Main {\n" +
          "  def main(argv: Array[String]): Unit = {\n" +
          "  val args = argv;\n").toCharArray)

    val middle =
      new SourceFileFragment(
          new SourceFile(new PlainFile(new File(filename))),
          headerLength(filename),
          new File(filename).length.asInstanceOf[Int])

    val end = new SourceFile("<script trailer>", "\n} }\n".toCharArray)

    new CompoundSourceFile(preamble, middle, end)
  }


  def runScript(
      settings: Settings,
      scriptFile: String,
      scriptArgs: List[String]): Unit =
  {
    val interpreter = new Interpreter(settings)
    try {
      interpreter.beQuiet

      if(!interpreter.compileSources(List(wrappedScript(scriptFile))))
        return () // compilation error

      interpreter.bind("argv", "Array[String]", scriptArgs.toArray)
      interpreter.interpret("scala.scripting.Main.main(argv)")
    } finally {
      interpreter.close
    }
  }

  def main(args: Array[String]): Unit = {
    val parsedArgs = parseArgs(args.toList)
    val compilerArgs = parsedArgs._1
    val scriptFile = parsedArgs._2
    val scriptArgs = parsedArgs._3

    val command =
      new CompilerCommand(
        compilerArgs,
        Console.println,
        false)

    if (!command.ok || command.settings.help.value) {
      // either the command line is wrong, or the user
      // explicitly requested a help listing
      if (!command.ok)
        Console.println
      usageExit
    }


    runScript(command.settings, scriptFile, scriptArgs)
  }

}