changeset 22104:cf19259edf87

TruffleVM.eval and Source.withMimeType
author Jaroslav Tulach <jaroslav.tulach@oracle.com>
date Mon, 24 Aug 2015 08:46:21 +0200
parents 7646278cca8a
children 7ee578004be7
files truffle/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/source/SourceTest.java truffle/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/vm/AccessorTest.java truffle/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/vm/ExceptionDuringParsingTest.java truffle/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/vm/GlobalSymbolTest.java truffle/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/vm/ImplicitExplicitExportTest.java truffle/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/vm/TruffleVMSingleThreadedTest.java truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleLanguage.java truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/package-info.java truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/source/Source.java truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/vm/TruffleVM.java truffle/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/SLTckTest.java truffle/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/SLTestRunner.java truffle/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/instrument/SLInstrumentTestRunner.java truffle/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLHandler.java truffle/com.oracle.truffle.sl/src/META-INF/services/java.nio.file.spi.FileTypeDetector truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLFileDetector.java truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java truffle/com.oracle.truffle.tck/src/com/oracle/truffle/tck/TruffleTCK.java
diffstat 18 files changed, 405 insertions(+), 77 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/truffle/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/source/SourceTest.java	Mon Aug 24 08:46:21 2015 +0200
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.api.test.source;
+
+import com.oracle.truffle.api.source.Source;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import org.junit.Test;
+
+public class SourceTest {
+    @Test
+    public void assignMimeTypeAndIdentity() {
+        Source s1 = Source.fromText("// a comment\n", "Empty comment");
+        assertNull("No mime type assigned", s1.getMimeType());
+        Source s2 = s1.withMimeType("text/x-c");
+        assertEquals("They have the same content", s1.getCode(), s2.getCode());
+        assertNotEquals("But different type", s1.getMimeType(), s2.getMimeType());
+        assertNotEquals("So they are different", s1, s2);
+    }
+
+    @Test
+    public void assignMimeTypeAndIdentityForApppendable() {
+        Source s1 = Source.fromAppendableText("<stdio>");
+        assertNull("No mime type assigned", s1.getMimeType());
+        s1.appendCode("// Hello");
+        Source s2 = s1.withMimeType("text/x-c");
+        assertEquals("They have the same content", s1.getCode(), s2.getCode());
+        assertEquals("// Hello", s1.getCode());
+        assertNotEquals("But different type", s1.getMimeType(), s2.getMimeType());
+        assertNotEquals("So they are different", s1, s2);
+    }
+
+    @Test
+    public void assignMimeTypeAndIdentityForBytes() {
+        String text = "// Hello";
+        Source s1 = Source.fromBytes(text.getBytes(StandardCharsets.UTF_8), "Hello", StandardCharsets.UTF_8);
+        assertNull("No mime type assigned", s1.getMimeType());
+        Source s2 = s1.withMimeType("text/x-c");
+        assertEquals("They have the same content", s1.getCode(), s2.getCode());
+        assertEquals("// Hello", s1.getCode());
+        assertNotEquals("But different type", s1.getMimeType(), s2.getMimeType());
+        assertNotEquals("So they are different", s1, s2);
+    }
+
+    @Test
+    public void assignMimeTypeAndIdentityForReader() throws IOException {
+        String text = "// Hello";
+        Source s1 = Source.fromReader(new StringReader(text), "Hello");
+        assertNull("No mime type assigned", s1.getMimeType());
+        Source s2 = s1.withMimeType("text/x-c");
+        assertEquals("They have the same content", s1.getCode(), s2.getCode());
+        assertEquals("// Hello", s1.getCode());
+        assertNotEquals("But different type", s1.getMimeType(), s2.getMimeType());
+        assertNotEquals("So they are different", s1, s2);
+    }
+
+    @Test
+    public void assignMimeTypeAndIdentityForFile() throws IOException {
+        File file = File.createTempFile("Hello", ".java");
+        file.deleteOnExit();
+
+        String text;
+        try (FileWriter w = new FileWriter(file)) {
+            text = "// Hello";
+            w.write(text);
+        }
+
+        Source s1 = Source.fromFileName(file.getPath());
+        assertEquals("Recognized as Java", "text/x-java", s1.getMimeType());
+        Source s2 = s1.withMimeType("text/x-c");
+        assertEquals("They have the same content", s1.getCode(), s2.getCode());
+        assertEquals("// Hello", s1.getCode());
+        assertNotEquals("But different type", s1.getMimeType(), s2.getMimeType());
+        assertNotEquals("So they are different", s1, s2);
+    }
+
+    @Test
+    public void assignMimeTypeAndIdentityForVirtualFile() throws IOException {
+        File file = File.createTempFile("Hello", ".java");
+        file.deleteOnExit();
+
+        String text = "// Hello";
+
+        Source s1 = Source.fromFileName(text, file.getPath());
+        assertEquals("Recognized as Java", "text/x-java", s1.getMimeType());
+        Source s2 = s1.withMimeType("text/x-c");
+        assertEquals("They have the same content", s1.getCode(), s2.getCode());
+        assertEquals("// Hello", s1.getCode());
+        assertNotEquals("But different type", s1.getMimeType(), s2.getMimeType());
+        assertNotEquals("So they are different", s1, s2);
+    }
+
+    @Test
+    public void assignMimeTypeAndIdentityForURL() throws IOException {
+        File file = File.createTempFile("Hello", ".java");
+        file.deleteOnExit();
+
+        String text;
+        try (FileWriter w = new FileWriter(file)) {
+            text = "// Hello";
+            w.write(text);
+        }
+
+        Source s1 = Source.fromURL(file.toURI().toURL(), "Hello.java");
+        assertEquals("Threated as plain", "text/plain", s1.getMimeType());
+        Source s2 = s1.withMimeType("text/x-c");
+        assertEquals("They have the same content", s1.getCode(), s2.getCode());
+        assertEquals("// Hello", s1.getCode());
+        assertNotEquals("But different type", s1.getMimeType(), s2.getMimeType());
+        assertNotEquals("So they are different", s1, s2);
+    }
+}
--- a/truffle/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/vm/AccessorTest.java	Mon Aug 24 08:25:31 2015 +0200
+++ b/truffle/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/vm/AccessorTest.java	Mon Aug 24 08:46:21 2015 +0200
@@ -24,6 +24,7 @@
 
 import com.oracle.truffle.api.TruffleLanguage;
 import com.oracle.truffle.api.impl.Accessor;
+import com.oracle.truffle.api.source.Source;
 import com.oracle.truffle.api.test.vm.ImplicitExplicitExportTest.ExportImportLanguage1;
 import static com.oracle.truffle.api.test.vm.ImplicitExplicitExportTest.L1;
 import com.oracle.truffle.api.vm.TruffleVM;
@@ -50,7 +51,8 @@
         TruffleVM.Language language = vm.getLanguages().get(L1);
         assertNotNull("L1 language is defined", language);
 
-        Object ret = vm.eval(L1, "return nothing");
+        Source s = Source.fromText("return nothing", "nothing").withMimeType(L1);
+        Object ret = vm.eval(s);
         assertNull("nothing is returned", ret);
 
         Object afterInitialization = findLanguageByClass(vm);
--- a/truffle/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/vm/ExceptionDuringParsingTest.java	Mon Aug 24 08:25:31 2015 +0200
+++ b/truffle/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/vm/ExceptionDuringParsingTest.java	Mon Aug 24 08:46:21 2015 +0200
@@ -23,6 +23,7 @@
 package com.oracle.truffle.api.test.vm;
 
 import com.oracle.truffle.api.impl.Accessor;
+import com.oracle.truffle.api.source.Source;
 import static com.oracle.truffle.api.test.vm.ImplicitExplicitExportTest.L1;
 import com.oracle.truffle.api.vm.TruffleVM;
 import java.io.IOException;
@@ -39,7 +40,7 @@
         assertNotNull("L1 language is defined", language);
 
         try {
-            vm.eval(L1, "parse=No, no, no!");
+            vm.eval(Source.fromText("parse=No, no, no!", "Fail on parsing").withMimeType(L1));
             fail("Exception thrown");
         } catch (IOException ex) {
             assertEquals(ex.getMessage(), "No, no, no!");
--- a/truffle/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/vm/GlobalSymbolTest.java	Mon Aug 24 08:25:31 2015 +0200
+++ b/truffle/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/vm/GlobalSymbolTest.java	Mon Aug 24 08:46:21 2015 +0200
@@ -22,6 +22,7 @@
  */
 package com.oracle.truffle.api.test.vm;
 
+import com.oracle.truffle.api.source.Source;
 import static org.junit.Assert.*;
 
 import java.io.*;
@@ -36,8 +37,8 @@
     public void globalSymbolFoundByLanguage() throws IOException {
         TruffleVM vm = TruffleVM.newVM().globalSymbol("ahoj", "42").build();
         // @formatter:off
-        Object ret = vm.eval(L3,
-            "return=ahoj"
+        Object ret = vm.eval(
+            Source.fromText("return=ahoj", "Return").withMimeType(L3)
         );
         // @formatter:on
         assertEquals("42", ret);
--- a/truffle/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/vm/ImplicitExplicitExportTest.java	Mon Aug 24 08:25:31 2015 +0200
+++ b/truffle/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/vm/ImplicitExplicitExportTest.java	Mon Aug 24 08:46:21 2015 +0200
@@ -52,11 +52,9 @@
     @Test
     public void explicitExportFound() throws IOException {
         // @formatter:off
-        vm.eval(L1,
-            "explicit.ahoj=42"
-        );
-        Object ret = vm.eval(L3,
-            "return=ahoj"
+        vm.eval(Source.fromText("explicit.ahoj=42", "Fourty two").withMimeType(L1));
+        Object ret = vm.eval(
+            Source.fromText("return=ahoj", "Return").withMimeType(L3)
         );
         // @formatter:on
         assertEquals("42", ret);
@@ -65,11 +63,11 @@
     @Test
     public void implicitExportFound() throws IOException {
         // @formatter:off
-        vm.eval(L1,
-            "implicit.ahoj=42"
+        vm.eval(
+            Source.fromText("implicit.ahoj=42", "Fourty two").withMimeType(L1)
         );
-        Object ret = vm.eval(L3,
-            "return=ahoj"
+        Object ret = vm.eval(
+            Source.fromText("return=ahoj", "Return").withMimeType(L3)
         );
         // @formatter:on
         assertEquals("42", ret);
@@ -78,14 +76,14 @@
     @Test
     public void explicitExportPreferred2() throws IOException {
         // @formatter:off
-        vm.eval(L1,
-            "implicit.ahoj=42"
+        vm.eval(
+            Source.fromText("implicit.ahoj=42", "Fourty two").withMimeType(L1)
         );
-        vm.eval(L2,
-            "explicit.ahoj=43"
+        vm.eval(
+            Source.fromText("explicit.ahoj=43", "Fourty three").withMimeType(L2)
         );
-        Object ret = vm.eval(L3,
-            "return=ahoj"
+        Object ret = vm.eval(
+            Source.fromText("return=ahoj", "Return").withMimeType(L3)
         );
         // @formatter:on
         assertEquals("Explicit import from L2 is used", "43", ret);
@@ -95,14 +93,14 @@
     @Test
     public void explicitExportPreferred1() throws IOException {
         // @formatter:off
-        vm.eval(L1,
-            "explicit.ahoj=43"
+        vm.eval(
+            Source.fromText("explicit.ahoj=43", "Fourty three").withMimeType(L1)
         );
-        vm.eval(L2,
-            "implicit.ahoj=42"
+        vm.eval(
+            Source.fromText("implicit.ahoj=42", "Fourty two").withMimeType(L2)
         );
-        Object ret = vm.eval(L3,
-            "return=ahoj"
+        Object ret = vm.eval(
+            Source.fromText("return=ahoj", "Return").withMimeType(L3)
         );
         // @formatter:on
         assertEquals("Explicit import from L2 is used", "43", ret);
--- a/truffle/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/vm/TruffleVMSingleThreadedTest.java	Mon Aug 24 08:25:31 2015 +0200
+++ b/truffle/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/vm/TruffleVMSingleThreadedTest.java	Mon Aug 24 08:46:21 2015 +0200
@@ -22,6 +22,7 @@
  */
 package com.oracle.truffle.api.test.vm;
 
+import com.oracle.truffle.api.source.Source;
 import java.io.*;
 import java.net.*;
 
@@ -45,16 +46,19 @@
         t.join();
     }
 
+    @SuppressWarnings("deprecation")
     @Test(expected = IllegalStateException.class)
     public void evalURI() throws IOException, URISyntaxException {
         tvm.eval(new URI("http://unknown.js"));
     }
 
+    @SuppressWarnings("deprecation")
     @Test(expected = IllegalStateException.class)
     public void evalString() throws IOException {
         tvm.eval("text/javascript", "1 + 1");
     }
 
+    @SuppressWarnings("deprecation")
     @Test(expected = IllegalStateException.class)
     public void evalReader() throws IOException {
         try (StringReader sr = new StringReader("1 + 1")) {
@@ -63,6 +67,11 @@
     }
 
     @Test(expected = IllegalStateException.class)
+    public void evalSource() throws IOException {
+        tvm.eval(Source.fromText("", "Empty"));
+    }
+
+    @Test(expected = IllegalStateException.class)
     public void findGlobalSymbol() {
         tvm.findGlobalSymbol("doesNotExists");
     }
--- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleLanguage.java	Mon Aug 24 08:25:31 2015 +0200
+++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleLanguage.java	Mon Aug 24 08:46:21 2015 +0200
@@ -84,8 +84,8 @@
 
         /**
          * List of MIME types associated with your language. Users will use them (directly or
-         * indirectly) when {@link TruffleVM#eval(java.lang.String, java.lang.String) executing}
-         * their code snippets or their {@link TruffleVM#eval(java.net.URI) files}.
+         * indirectly) when {@link TruffleVM#eval(com.oracle.truffle.api.source.Source) executing}
+         * their code snippets or their {@link Source files}.
          *
          * @return array of MIME types assigned to your language files
          */
--- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/package-info.java	Mon Aug 24 08:25:31 2015 +0200
+++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/package-info.java	Mon Aug 24 08:46:21 2015 +0200
@@ -39,7 +39,7 @@
  *     {@link com.oracle.truffle.api.vm.TruffleVM.Builder#onEvent(com.oracle.truffle.api.vm.EventConsumer) onEvent}(<b>new</b> {@link com.oracle.truffle.api.vm.EventConsumer EventConsumer}
  *     {@code <}{@link com.oracle.truffle.api.debug.ExecutionEvent}{@code >}() {
  *         <b>public void</b> handle({@link com.oracle.truffle.api.debug.ExecutionEvent} ev) {
- *             <em>// configure the virtual machine as {@link com.oracle.truffle.api.vm.TruffleVM#eval(java.net.URI) new execution} is starting</em>
+ *             <em>// configure the virtual machine as {@link com.oracle.truffle.api.vm.TruffleVM#eval(com.oracle.truffle.api.source.Source) new execution} is starting</em>
  *         }
  *     }).
  *     {@link com.oracle.truffle.api.vm.TruffleVM.Builder#onEvent(com.oracle.truffle.api.vm.EventConsumer) onEvent}(<b>new</b> {@link com.oracle.truffle.api.vm.EventConsumer EventConsumer}{@code <}
@@ -51,7 +51,7 @@
  * </pre>
  * The debugging is controlled by events emitted by the Truffle virtual machine
  * at important moments. The {@link com.oracle.truffle.api.debug.ExecutionEvent}
- * is sent when a call to {@link com.oracle.truffle.api.vm.TruffleVM#eval(java.net.URI) eval(...)}
+ * is sent when a call to {@link com.oracle.truffle.api.vm.TruffleVM#eval(com.oracle.truffle.api.source.Source)}
  * is made and allows one to configure {@link com.oracle.truffle.api.debug.Breakpoint breakpoints} and/or decide whether the
  * program should {@link com.oracle.truffle.api.debug.ExecutionEvent#prepareStepInto() step-into} or
  * {@link com.oracle.truffle.api.debug.ExecutionEvent#prepareContinue() just run}. Once the execution is suspended a
--- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/source/Source.java	Mon Aug 24 08:25:31 2015 +0200
+++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/source/Source.java	Mon Aug 24 08:46:21 2015 +0200
@@ -32,6 +32,11 @@
 import java.util.*;
 
 import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.TruffleLanguage.Registration;
+import java.nio.file.Files;
+import java.nio.file.spi.FileTypeDetector;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 /**
  * Representation of a guest language source code unit and its contents. Sources originate in
@@ -87,6 +92,7 @@
  * <p>
  */
 public abstract class Source {
+    private static final Logger LOG = Logger.getLogger(Source.class.getName());
 
     // TODO (mlvdv) consider canonicalizing and reusing SourceSection instances
     // TOOD (mlvdv) connect SourceSections into a spatial tree for fast geometric lookup
@@ -374,7 +380,8 @@
     private Source() {
     }
 
-    private TextMap textMap = null;
+    private String mimeType;
+    private TextMap textMap;
 
     abstract void reset();
 
@@ -402,6 +409,8 @@
 
     /**
      * The URL if the source is retrieved via URL.
+     * 
+     * @return URL or <code>null</code>
      */
     public abstract URL getURL();
 
@@ -632,7 +641,50 @@
         return TextMap.fromString(code);
     }
 
-    private static final class LiteralSource extends Source {
+    /**
+     * Associates the source with specified MIME type. The mime type may be used to select the right
+     * {@link Registration Truffle language} to use to execute the returned source. The value of MIME
+     * type can be obtained via {@link #getMimeType()} method.
+     *
+     * @param mime mime type to use
+     * @return new (identical) source, just associated {@link #getMimeType()}
+     */
+    public final Source withMimeType(String mime) {
+        try {
+            Source another = (Source) clone();
+            another.mimeType = mime;
+            return another;
+        } catch (CloneNotSupportedException ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    /**
+     * MIME type that is associated with this source. By default file extensions known to the system
+     * are used to determine the MIME type (via registered {@link FileTypeDetector} classes), yet
+     * one can directly {@link #withMimeType(java.lang.String) provide a MIME type} to each source.
+     *
+     * @return MIME type of this source or <code>null</code>, if unknown
+     */
+    public String getMimeType() {
+        if (mimeType == null) {
+            mimeType = findMimeType();
+        }
+        return mimeType;
+    }
+
+    String findMimeType() {
+        return null;
+    }
+
+    final boolean equalMime(Source other) {
+        if (mimeType == null) {
+            return other.mimeType == null;
+        }
+        return mimeType.equals(other.mimeType);
+    }
+
+    private static final class LiteralSource extends Source implements Cloneable {
 
         private final String description;
         private final String code;
@@ -691,14 +743,14 @@
             }
             if (obj instanceof LiteralSource) {
                 LiteralSource other = (LiteralSource) obj;
-                return description.equals(other.description) && code.equals(other.code);
+                return description.equals(other.description) && code.equals(other.code) && equalMime(other);
             }
             return false;
         }
     }
 
-    private static final class AppendableLiteralSource extends Source {
-        private String description;
+    private static final class AppendableLiteralSource extends Source implements Cloneable {
+        private final String description;
         final List<CharSequence> codeList = new ArrayList<>();
 
         public AppendableLiteralSource(String description) {
@@ -756,7 +808,7 @@
 
     }
 
-    private static final class FileSource extends Source {
+    private static final class FileSource extends Source implements Cloneable {
 
         private final File file;
         private final String name; // Name used originally to describe the source
@@ -836,13 +888,29 @@
         }
 
         @Override
+        String findMimeType() {
+            if (file.getName().endsWith(".c")) {
+                return "text/x-c";
+            } else if (file.getName().endsWith(".R") || file.getName().endsWith(".r")) {
+                return "application/x-r";
+            } else {
+                try {
+                    return Files.probeContentType(file.toPath());
+                } catch (IOException ex) {
+                    LOG.log(Level.SEVERE, null, ex);
+                }
+            }
+            return null;
+        }
+
+        @Override
         public boolean equals(Object obj) {
             if (this == obj) {
                 return true;
             }
             if (obj instanceof FileSource) {
                 FileSource other = (FileSource) obj;
-                return path.equals(other.path);
+                return path.equals(other.path) && equalMime(other);
             }
             return false;
         }
@@ -854,7 +922,7 @@
     }
 
     // TODO (mlvdv) if we keep this, hoist a superclass in common with FileSource.
-    private static final class ClientManagedFileSource extends Source {
+    private static final class ClientManagedFileSource extends Source implements Cloneable {
 
         private final File file;
         private final String name; // Name used originally to describe the source
@@ -908,6 +976,22 @@
         }
 
         @Override
+        String findMimeType() {
+            if (file.getName().endsWith(".c")) {
+                return "text/x-c";
+            } else if (file.getName().endsWith(".R") || file.getName().endsWith(".r")) {
+                return "application/x-r";
+            } else {
+                try {
+                    return Files.probeContentType(file.toPath());
+                } catch (IOException ex) {
+                    LOG.log(Level.SEVERE, null, ex);
+                }
+            }
+            return null;
+        }
+
+        @Override
         public int hashCode() {
             return path.hashCode();
         }
@@ -919,7 +1003,7 @@
             }
             if (obj instanceof ClientManagedFileSource) {
                 ClientManagedFileSource other = (ClientManagedFileSource) obj;
-                return path.equals(other.path);
+                return path.equals(other.path) && equalMime(other);
             }
             return false;
         }
@@ -930,7 +1014,7 @@
         }
     }
 
-    private static final class URLSource extends Source {
+    private static final class URLSource extends Source implements Cloneable {
 
         private static final Map<URL, WeakReference<URLSource>> urlToSource = new HashMap<>();
 
@@ -946,12 +1030,16 @@
 
         private final URL url;
         private final String name;
-        private String code = null;  // A cache of the source contents
+        private String code;  // A cache of the source contents
 
         public URLSource(URL url, String name) throws IOException {
             this.url = url;
             this.name = name;
-            code = read(new InputStreamReader(url.openStream()));
+            URLConnection c = url.openConnection();
+            if (super.mimeType == null) {
+                super.mimeType = c.getContentType();
+            }
+            code = read(new InputStreamReader(c.getInputStream()));
         }
 
         @Override
@@ -989,7 +1077,7 @@
         }
     }
 
-    private static final class SubSource extends Source {
+    private static final class SubSource extends Source implements Cloneable {
         private final Source base;
         private final int baseIndex;
         private final int subLength;
@@ -1044,7 +1132,7 @@
         }
     }
 
-    private static final class BytesSource extends Source {
+    private static final class BytesSource extends Source implements Cloneable {
 
         private final String name;
         private final byte[] bytes;
--- a/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/vm/TruffleVM.java	Mon Aug 24 08:25:31 2015 +0200
+++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/vm/TruffleVM.java	Mon Aug 24 08:46:21 2015 +0200
@@ -344,7 +344,9 @@
      * @return result of a processing the file, possibly <code>null</code>
      * @throws IOException exception to signal I/O problems or problems with processing the file's
      *             content
+     * @deprecated use {@link #eval(com.oracle.truffle.api.source.Source)}
      */
+    @Deprecated
     public Object eval(URI location) throws IOException {
         checkThread();
         Source s;
@@ -382,7 +384,9 @@
      * @param reader the source of code snippet to execute
      * @return result of an execution, possibly <code>null</code>
      * @throws IOException thrown to signal errors while processing the code
+     * @deprecated use {@link #eval(com.oracle.truffle.api.source.Source)}
      */
+    @Deprecated
     public Object eval(String mimeType, Reader reader) throws IOException {
         checkThread();
         TruffleLanguage<?> l = getTruffleLang(mimeType);
@@ -400,7 +404,9 @@
      * @param code the code snippet to execute
      * @return result of an execution, possibly <code>null</code>
      * @throws IOException thrown to signal errors while processing the code
+     * @deprecated use {@link #eval(com.oracle.truffle.api.source.Source)}
      */
+    @Deprecated
     public Object eval(String mimeType, String code) throws IOException {
         checkThread();
         TruffleLanguage<?> l = getTruffleLang(mimeType);
@@ -410,6 +416,25 @@
         return eval(l, Source.fromText(code, mimeType));
     }
 
+    /**
+     * Evaluates provided source. Chooses language registered for a particular
+     * {@link Source#getMimeType() MIME type} (throws {@link IOException} if there is none). The
+     * language is then allowed to parse and execute the source.
+     *
+     * @param source code snippet to execute
+     * @return result of an execution, possibly <code>null</code>
+     * @throws IOException thrown to signal errors while processing the code
+     */
+    public Object eval(Source source) throws IOException {
+        String mimeType = source.getMimeType();
+        checkThread();
+        TruffleLanguage<?> l = getTruffleLang(mimeType);
+        if (l == null) {
+            throw new IOException("No language for MIME type " + mimeType + " found. Supported types: " + langs.keySet());
+        }
+        return eval(l, source);
+    }
+
     private Object eval(TruffleLanguage<?> l, Source s) throws IOException {
         TruffleVM.findDebuggerSupport(l);
         Debugger[] fillIn = {debugger};
--- a/truffle/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/SLTckTest.java	Mon Aug 24 08:25:31 2015 +0200
+++ b/truffle/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/SLTckTest.java	Mon Aug 24 08:46:21 2015 +0200
@@ -40,6 +40,7 @@
  */
 package com.oracle.truffle.sl.test;
 
+import com.oracle.truffle.api.source.Source;
 import com.oracle.truffle.tck.TruffleTCK;
 import com.oracle.truffle.api.vm.TruffleVM;
 import static org.junit.Assert.*;
@@ -60,26 +61,28 @@
     protected TruffleVM prepareVM() throws Exception {
         TruffleVM vm = TruffleVM.newVM().build();
         // @formatter:off
-        vm.eval("application/x-sl",
-            "function fourtyTwo() {\n" +
-            "  return 42;\n" + //
-            "}\n" +
-            "function plus(a, b) {\n" +
-            "  return a + b;\n" +
-            "}\n" +
-            "function apply(f) {\n" +
-            "  return f(18, 32) + 10;\n" +
-            "}\n" +
-            "function cnt() {\n" +
-            "  return 0;\n" +
-            "}\n" +
-            "function count() {\n" +
-            "  n = cnt() + 1;\n" +
-            "  defineFunction(\"function cnt() { return \" + n + \"; }\");\n" +
-            "  return n;\n" +
-            "}\n" +
-            "function null() {\n" +
-            "}\n"
+        vm.eval(
+            Source.fromText(
+                "function fourtyTwo() {\n" +
+                "  return 42;\n" + //
+                "}\n" +
+                "function plus(a, b) {\n" +
+                "  return a + b;\n" +
+                "}\n" +
+                "function apply(f) {\n" +
+                "  return f(18, 32) + 10;\n" +
+                "}\n" +
+                "function cnt() {\n" +
+                "  return 0;\n" +
+                "}\n" +
+                "function count() {\n" +
+                "  n = cnt() + 1;\n" +
+                "  defineFunction(\"function cnt() { return \" + n + \"; }\");\n" +
+                "  return n;\n" +
+                "}\n" +
+                "function null() {\n" +
+                "}\n", "SL TCK"
+            ).withMimeType("application/x-sl")
         );
         // @formatter:on
         return vm;
--- a/truffle/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/SLTestRunner.java	Mon Aug 24 08:25:31 2015 +0200
+++ b/truffle/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/SLTestRunner.java	Mon Aug 24 08:46:21 2015 +0200
@@ -221,7 +221,7 @@
             TruffleVM vm = TruffleVM.newVM().stdIn(new BufferedReader(new StringReader(repeat(testCase.testInput, repeats)))).stdOut(printer).build();
 
             String script = readAllLines(testCase.path);
-            SLLanguage.run(vm, testCase.path.toUri(), null, printer, repeats, builtins);
+            SLLanguage.run(vm, testCase.path, null, printer, repeats, builtins);
 
             printer.flush();
             String actualOutput = new String(out.toByteArray());
--- a/truffle/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/instrument/SLInstrumentTestRunner.java	Mon Aug 24 08:25:31 2015 +0200
+++ b/truffle/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/instrument/SLInstrumentTestRunner.java	Mon Aug 24 08:46:21 2015 +0200
@@ -56,6 +56,7 @@
 
 import com.oracle.truffle.api.instrument.*;
 import com.oracle.truffle.api.instrument.impl.*;
+import com.oracle.truffle.api.source.Source;
 import com.oracle.truffle.api.vm.*;
 import com.oracle.truffle.sl.nodes.instrument.*;
 import com.oracle.truffle.sl.nodes.local.*;
@@ -229,7 +230,7 @@
                 TruffleVM vm = TruffleVM.newVM().stdIn(new BufferedReader(new StringReader(testCase.testInput))).stdOut(printer).build();
 
                 final String src = readAllLines(testCase.path);
-                vm.eval("application/x-sl", src);
+                vm.eval(Source.fromText(src, testCase.path.toString()).withMimeType("application/x-sl"));
 
                 // Attach an instrument to every probe tagged as an assignment
                 for (Probe probe : Probe.findProbesTaggedAs(StandardSyntaxTag.ASSIGNMENT)) {
--- a/truffle/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLHandler.java	Mon Aug 24 08:25:31 2015 +0200
+++ b/truffle/com.oracle.truffle.sl.tools/src/com/oracle/truffle/sl/tools/debug/SLREPLHandler.java	Mon Aug 24 08:46:21 2015 +0200
@@ -44,6 +44,7 @@
 import java.util.*;
 
 import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.source.Source;
 import com.oracle.truffle.api.vm.*;
 import com.oracle.truffle.tools.debug.shell.*;
 import com.oracle.truffle.tools.debug.shell.client.*;
@@ -127,7 +128,7 @@
                 return finishReplyFailed(reply, "can't find file \"" + fileName + "\"");
             }
             final TruffleVM vm = serverContext.vm();
-            vm.eval(file.toURI());
+            vm.eval(Source.fromFileName(file.getPath()));
             TruffleVM.Symbol main = vm.findGlobalSymbol("main");
             if (main != null) {
                 main.invoke(null);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/truffle/com.oracle.truffle.sl/src/META-INF/services/java.nio.file.spi.FileTypeDetector	Mon Aug 24 08:46:21 2015 +0200
@@ -0,0 +1,1 @@
+com.oracle.truffle.sl.SLFileDetector
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLFileDetector.java	Mon Aug 24 08:46:21 2015 +0200
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * The Universal Permissive License (UPL), Version 1.0
+ *
+ * Subject to the condition set forth below, permission is hereby granted to any
+ * person obtaining a copy of this software, associated documentation and/or
+ * data (collectively the "Software"), free of charge and under any and all
+ * copyright rights in the Software, and any and all patent rights owned or
+ * freely licensable by each licensor hereunder covering either (i) the
+ * unmodified Software as contributed to or provided by such licensor, or (ii)
+ * the Larger Works (as defined below), to deal in both
+ *
+ * (a) the Software, and
+ *
+ * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
+ * one is included with the Software each a "Larger Work" to which the Software
+ * is contributed by such licensors),
+ *
+ * without restriction, including without limitation the rights to copy, create
+ * derivative works of, display, perform, and distribute the Software and make,
+ * use, sell, offer for sale, import, export, have made, and have sold the
+ * Software and the Larger Work(s), and to sublicense the foregoing rights on
+ * either these or other terms.
+ *
+ * This license is subject to the following condition:
+ *
+ * The above copyright notice and either this complete permission notice or at a
+ * minimum a reference to the UPL must be included in all copies or substantial
+ * portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package com.oracle.truffle.sl;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.spi.FileTypeDetector;
+
+public final class SLFileDetector extends FileTypeDetector {
+    @Override
+    public String probeContentType(Path path) throws IOException {
+        if (path.getFileName().toString().endsWith(".sl")) {
+            return "application/x-sl";
+        }
+        return null;
+    }
+}
--- a/truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java	Mon Aug 24 08:25:31 2015 +0200
+++ b/truffle/com.oracle.truffle.sl/src/com/oracle/truffle/sl/SLLanguage.java	Mon Aug 24 08:46:21 2015 +0200
@@ -65,6 +65,7 @@
 import com.oracle.truffle.sl.parser.*;
 import com.oracle.truffle.sl.runtime.*;
 import com.oracle.truffle.tools.*;
+import java.nio.file.Path;
 
 /**
  * SL is a simple language to demonstrate and showcase features of Truffle. The implementation is as
@@ -200,11 +201,13 @@
             repeats = Integer.parseInt(args[1]);
         }
 
+        Source source;
         if (args.length == 0) {
-            vm.eval("application/x-sl", new InputStreamReader(System.in));
+            source = Source.fromReader(new InputStreamReader(System.in), "<stdin>").withMimeType("application/x-sl");
         } else {
-            vm.eval(new File(args[0]).toURI());
+            source = Source.fromFileName(args[0]);
         }
+        vm.eval(source);
         Symbol main = vm.findGlobalSymbol("main");
         if (main == null) {
             throw new SLException("No function main() defined in SL source file.");
@@ -221,7 +224,7 @@
     public static void run(Source source) throws IOException {
         TruffleVM vm = TruffleVM.newVM().build();
         assert vm.getLanguages().containsKey("application/x-sl");
-        vm.eval(new File(source.getPath()).toURI());
+        vm.eval(source);
         Symbol main = vm.findGlobalSymbol("main");
         if (main == null) {
             throw new SLException("No function main() defined in SL source file.");
@@ -233,7 +236,7 @@
      * Parse and run the specified SL source. Factored out in a separate method so that it can also
      * be used by the unit test harness.
      */
-    public static long run(TruffleVM context, URI source, PrintWriter logOutput, PrintWriter out, int repeats, List<NodeFactory<? extends SLBuiltinNode>> currentBuiltins) throws IOException {
+    public static long run(TruffleVM context, Path path, PrintWriter logOutput, PrintWriter out, int repeats, List<NodeFactory<? extends SLBuiltinNode>> currentBuiltins) throws IOException {
         builtins = currentBuiltins;
 
         if (logOutput != null) {
@@ -241,8 +244,9 @@
             // logOutput.println("Source = " + source.getCode());
         }
 
+        Source src = Source.fromFileName(path.toString());
         /* Parse the SL source file. */
-        Object result = context.eval(source);
+        Object result = context.eval(src.withMimeType("application/x-sl"));
         if (result != null) {
             out.println(result);
         }
--- a/truffle/com.oracle.truffle.tck/src/com/oracle/truffle/tck/TruffleTCK.java	Mon Aug 24 08:25:31 2015 +0200
+++ b/truffle/com.oracle.truffle.tck/src/com/oracle/truffle/tck/TruffleTCK.java	Mon Aug 24 08:46:21 2015 +0200
@@ -24,6 +24,7 @@
  */
 package com.oracle.truffle.tck;
 
+import com.oracle.truffle.api.source.Source;
 import com.oracle.truffle.api.vm.TruffleVM;
 import java.io.IOException;
 import java.util.Random;
@@ -44,8 +45,8 @@
     /**
      * This methods is called before first test is executed. It's purpose is to set a TruffleVM with
      * your language up, so it is ready for testing.
-     * {@link TruffleVM#eval(java.lang.String, java.lang.String) Execute} any scripts you need, and
-     * prepare global symbols with proper names. The symbols will then be looked up by the
+     * {@link TruffleVM#eval(com.oracle.truffle.api.source.Source) Execute} any scripts you need,
+     * and prepare global symbols with proper names. The symbols will then be looked up by the
      * infrastructure (using the names provided by you from methods like {@link #plusInt()}) and
      * used for internal testing.
      *
@@ -56,8 +57,8 @@
 
     /**
      * Mimetype associated with your language. The mimetype will be passed to
-     * {@link TruffleVM#eval(java.lang.String, java.lang.String)} method of the {@link #prepareVM()
-     * created TruffleVM}.
+     * {@link TruffleVM#eval(com.oracle.truffle.api.source.Source)} method of the
+     * {@link #prepareVM() created TruffleVM}.
      *
      * @return mime type of the tested language
      */
@@ -116,7 +117,7 @@
 
     /**
      * Return a code snippet that is invalid in your language. Its
-     * {@link TruffleVM#eval(java.lang.String, java.lang.String) evaluation} should fail and yield
+     * {@link TruffleVM#eval(com.oracle.truffle.api.source.Source) evaluation} should fail and yield
      * an exception.
      *
      * @return code snippet invalid in the tested language
@@ -180,7 +181,7 @@
     public void testInvalidTestMethod() throws Exception {
         String mime = mimeType();
         String code = invalidCode();
-        Object ret = vm().eval(mime, code);
+        Object ret = vm().eval(Source.fromText(code, "Invalid code").withMimeType(mime));
         fail("Should yield IOException, but returned " + ret);
     }