summaryrefslogtreecommitdiff
path: root/sources/scala/tools/nsc/Interpreter.scala
blob: 847cdade6a951170aa0c4a0e4d1e3ad5ddf22052 (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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/* NSC -- new scala compiler
 * Copyright 2005 LAMP/EPFL
 * @author  Martin Odersky
 */
// $Id$
package scala.tools.nsc;

import scala.tools.nsc.reporters.Reporter;

abstract class Interpreter {
  import scala.collection.mutable.ListBuffer;
  import symtab.Names;

  // list of names defined, for each line number
  val prevDefines : ListBuffer[Pair[Int,ListBuffer[Names#Name]]] = new ListBuffer();

  val compiler: Global;

  import scala.tools.nsc.ast.parser.SyntaxAnalyzer;
  object syntaxAnalyzer extends SyntaxAnalyzer {
    val global: compiler.type = compiler
  }

  def interpret(line: String, reporter: Reporter): unit = {
    import scala.tools.nsc.util.SourceFile;

    // convert input to a compilation unit, using SourceFile;
    // and parse it, using syntaxAnalyzer, to get input ASTs
    val inASTs = syntaxAnalyzer.interpreterParse(
                   new compiler.CompilationUnit(
                     //if SourceFile is not modified, then fix a bug
                     //here by adding an EOF character to the end of
                     //the 'line'
                     new SourceFile("<console>",line.toCharArray())));

    //todo:  if (errors in parsing) after reporting them, exit method

    val dvt = new DefinedVarsTraverser;
    dvt.traverseTrees(inASTs);
    val definedVars = dvt.definedVars;

    val ivt = new ImportVarsTraverser(definedVars);
    ivt.traverseTrees(inASTs);
    val importVars = ivt.importVars;

    val lineno = prevDefines.length;
    //todo: it is probably nice to include date & time, as well as a process id, in the filename
    val filename = getTempPath().getPath()+java.io.File.separator+"InterpreterTempLine"+lineno+".scala";
    writeTempScalaFile(filename, line, lineno, definedVars, importVars);

    // first phase: compile auto-generated file
    compiler.settings.outdir.value = getTempPath().getPath();
    val cr = new compiler.Run;
    cr compile List(filename);

    //todo: if no errors in compilation then
    // second phase: execute JVM, and print outcome
    // else consider definition as if has not happened and exit method
    //todo: use Scala's reflection API, which I designed, instead, for the following code
    val cl = new java.net.URLClassLoader(Predef.Array(getTempPath().toURL()));
    val interpreterResultObject: Class = Class.forName("InterpreterLine"+lineno+"Result",true,cl);
    val resultValMethod: java.lang.reflect.Method = interpreterResultObject.getMethod("result",null);
    var interpreterResultString: String = resultValMethod.invoke(interpreterResultObject,null).toString();

    //var interpreterResultJavaTypeString: String = resultValMethod.getReturnType().getName();
    //Console.println(compiler.definitions.EmptyPackage.info.members);
    val interpreterResultSym: compiler.Symbol =
        compiler.definitions.getMember(compiler.definitions.EmptyPackage,
          compiler.newTermName("InterpreterLine"+lineno+"Result"));

    def findSymbolWithName(ls: List[compiler.Symbol], name: compiler.Name): compiler.Symbol =
      ls.find(s=>s.name == name) match {
        case None => throw new IllegalStateException("Cannot find field '"+name+"' in InterpreterResult");
        case Some(s) => s;
      }

    //var lastname: String = compiler.atPhase(cr.typerPhase.next){interpreterResultSym.info.decls.toList.last.name.toString()};
    //reporter.info(null,lastname,true);
    //todo: similar to what I should be doing for Scala's reflection??
    var interpreterResultScalaTypeString: String =
      compiler.atPhase(cr.typerPhase.next){
        findSymbolWithName(interpreterResultSym.info.decls.toList,
          compiler.nme.getterToLocal(compiler.newTermName("result")))
        .tpe.toString()
      };
    reporter.info(null,interpreterResultString+": "+interpreterResultScalaTypeString/*+" ("+interpreterResultJavaTypeString+")"*/,true);

/*
    val scalaInterpFile: File = ScalaInterpFile(filename);
    scalaInterpFile.deleteOnExit();
    if(scalaInterpFile.exists())
       scalaInterpFile.delete();

    getvalue of line#.last_var_defined_in_line (from defined_vars)
    (works for 'it' as it was added as last val to definedvars)
    and send it to reporter
*/

    // book-keeping
    //todo: if no errors in evaluation then
    prevDefines += Pair(lineno,definedVars);
    // else consider definition as if has not happened.

    // report some debug info
    //reporter.info(null,"inASTs="+inASTs,true);
    //reporter.info(null,"definedVars="+definedVars,true);
    //reporter.info(null,"importVars="+importVars,true);
    //reporter.info(null,"prevDefines="+prevDefines,true);
  }

  import java.io.File;
  def getTempPath(): File = {
    val tempdir = {
      val tempdir1 = System.getProperty("java.io.tmpdir");
      if (tempdir1 == null){
        val tempdir2 = System.getProperty("TEMP");
        if (tempdir2 == null){
          val tempdir3 = System.getProperty("TMP");
          if (tempdir3 == null)
            throw new IllegalStateException("No temporary folder defined")
          else tempdir3 }
        else tempdir2 }
      else tempdir1
    };
    val path = new File(tempdir);
    if (!path.exists() || !path.isDirectory())
      throw new IllegalStateException("Invalid temporary directory")
    else if (!path.canWrite())
      throw new IllegalStateException("Temporary directory not writable")
    else path
  };

  def writeTempScalaFile(filename: String, line: String, lineno: Int, definedVars: ListBuffer[Names#Name], importVars: ListBuffer[Pair[Names#Name,Int]]) = {
    import java.io.{File, PrintWriter, FileOutputStream};
    val scalaFile = new File(filename);
    scalaFile.deleteOnExit();
    if(scalaFile.exists()) // to prevent old lingering files from having results from them reported!
       scalaFile.delete();

    val module = new PrintWriter(new FileOutputStream(scalaFile));
    //todo:"import "+LoadedModules?.getName
    //module.println("\n");

    for(val Pair(ivname,ivlineno) <- importVars.toList) yield
       module.println("import line"+ivlineno+"."+ivname+";\n");

    module.println("object line"+lineno+" {");
    var fullLine = line;
    if(definedVars.length == 0) { // input is just an expression
      fullLine = "  var it = " + line;
      definedVars += compiler.encode("it"); }
    else fullLine = "  " + line;
    module.println(fullLine);
    module.println("}");
    module.println();
    module.println("object InterpreterLine"+lineno+"Result ");
    module.println("{ val result = (line"+lineno+"."+definedVars.toList.reverse.head+"); }");
    // reflection is used later (see above) to get the result value above

    module.flush();
    module.close();
  }

  import compiler.Traverser;
  import compiler.Tree;
  class DefinedVarsTraverser extends Traverser {
    val definedVars = new ListBuffer[Names#Name];
    override def traverse(ast: Tree): unit =
      if (!ast.isDef) ()
      else {
        import compiler._;
        ast match {
          // only the outer level needed, so do not recurse to go deeper
          // todo: combine similar cases in one case
          case ClassDef(_,name,_,_,_) => definedVars += name
          case ModuleDef(_, name,_) => definedVars += name
          case ValDef(_, name,_,_) => definedVars += name
          case DefDef(_,name,_,_,_,_) => definedVars += name
          //todo:case Bind(name,_) => ((name != nme.WILDCARD) && (definedVars.elements forall (name !=))) definedVars += name

          //case Ident(name) => if (name...is defined) definedVars += name;

          //todo:
          //case PackageDef(name, _) => throw new InterpIllegalDefException(name.toString()+": package definitions not allowed")
          //case AbsTypeDef(_,name,_,_) => throw new InterpIllegalDefException(name.toString()+": absract type definitions not allowed")
          //case AliasTypeDef(_,name,_,_) => throw new InterpIllegalDefException(name.toString()+": alias type definitions not allowed")
          //case LabelDef(name,_,_) => throw new InterpIllegalDefException(name.toString()+": label definitions not allowed")
          case _ => throw new InterpIllegalDefException("Unsupported interpreter definition. Contact Scala developers for adding interpreter support for it.")// ()
        }
      }
  }
  case class InterpIllegalDefException(msg: String) extends RuntimeException(msg);

//    class ListTraverser extends Traverser {
//      def traverse(trees: List[Tree]): Unit =
//        trees foreach traverse;
//    }
//
//    class ListDefinedVarsTraverser extends DefinedVarsTraverser with ListTraverser;

  class ImportVarsTraverser(definedVars: ListBuffer[Names#Name]) extends Traverser {
    val importVars = new ListBuffer[Pair[Names#Name,Int]];
    var curAST = 0;
    import compiler.Ident;
    override def traverse(ast: Tree): unit = ast match {
      case Ident(name) => {
        var lastPrevDefsIdx = -1;
        //reporter.info(null,"name="+name,true);
        for(val Pair(lineno,defs) <- prevDefines.toList) yield {
          //reporter.info(null,"line#="+lineno+", defs="+defs,true);
          if (defs.indexOf(name) != -1) lastPrevDefsIdx = lineno
        }
        val foundInPrevDefines = (lastPrevDefsIdx != -1);
        //reporter.info(null,"lastPrevDefsIdx="+lastPrevDefsIdx,true);
        if(foundInPrevDefines) {
           val firstCurDefIdx = definedVars.indexOf(name);
           val foundInDefinedVars = (firstCurDefIdx != -1);
           if((!foundInDefinedVars ||
               (foundInDefinedVars && (firstCurDefIdx > curAST)))
               && (importVars.indexOf(Pair(name,lastPrevDefsIdx)) == -1))
               // to prevent duplicate imports (todo: use find instead of indexOf?)
             importVars += Pair(name,lastPrevDefsIdx);
        }
      }
      case _ => {
          // using case x, instead of case _, we can have: reporter.info(null,"x="+x,true);
          super.traverse(ast)
      }
    }
    override def traverseTrees(asts: List[Tree]): unit =
      asts foreach { curAST = curAST+1; traverse; }
  }
  //todo: unit-test cases
}