In April 2009, Tomáš Matoušek created a nifty IronRuby script that demonstrated the sharing of ScriptScopes in the Dynamic Language Runtime (DLR). The examples on Tomáš’ blog post still work.

The script is like the movie Inception, with languages within languages; but at its core shows how the DLR permits object sharing between languages within the same scope. The code itself is IronRuby; the script spins up a new ScriptRuntime and Engine to perform the acrobatics.

NOTE: These two small scripts are now published to my github account

As IronRuby has moved to 1.1.x.x, and supporting the Ruby 1.9.x language, I’ve updated the code a little (essentially, replacing ‘:’ with ‘then’)

Code Snippet
  1. load_assembly 'Microsoft.Scripting'
  2. Hosting = Microsoft::Scripting::Hosting
  3. Scripting = Microsoft::Scripting
  4.  
  5. class REPL
  6.   def initialize
  7.     @engine = IronRuby.create_engine
  8.     @scope = @engine.create_scope
  9.     @exception_service = @engine.method(:get_service).of(Hosting::ExceptionOperations).call
  10.     @language = "rb"
  11.   end
  12.  
  13.   def run
  14.     while true
  15.       print "#@language> "
  16.       line = gets
  17.       break if line.nil?
  18.  
  19.       if line[0] == ?#
  20.         execute_command line[1..-1].rstrip
  21.       else
  22.         execute_code read_code(line)
  23.       end
  24.     end
  25.   end
  26.  
  27.    # Reads lines from standard input until a complete or invalid code snippet is entered.
  28.   # Returns ScriptSource that represents an interactive code.
  29.   def read_code first_line
  30.     code = first_line
  31.     while true
  32.       interactive_code = @engine.create_script_source_from_string(code, Scripting::SourceCodeKind.InteractiveCode)
  33.       case interactive_code.get_code_properties
  34.         when Scripting::ScriptCodeParseResult.Complete, Scripting::ScriptCodeParseResult.Invalid then
  35.           return interactive_code
  36.  
  37.         else
  38.           print "#@language| "
  39.           next_line = gets
  40.           return interactive_code if next_line.nil? or next_line.strip.size == 0
  41.           code += next_line
  42.       end
  43.     end
  44.   end
  45.  
  46.   # Executes given ScriptSource and prints any exceptions that it might raise.
  47.   def execute_code source
  48.     source.execute(@scope)
  49.   rescue Exception => e
  50.     message, name = @exception_service.get_exception_message(e)
  51.     puts "#{name}: #{message}"
  52.   end
  53.  
  54.   def execute_command command
  55.     case command
  56.       when 'exit' then exit
  57.       when 'ls?' then display_languages
  58.       else puts "Unknown command '#{command}'" unless switch_language command
  59.     end
  60.   end
  61.  
  62.   def display_languages
  63.     @engine.runtime.setup.language_setups.each { |ls| puts "#{ls.display_name}: #{ls.names.inspect}" }
  64.   end
  65.  
  66.   def switch_language name
  67.     has_engine, engine = @engine.runtime.try_get_engine(name)
  68.     @language, @engine = name, engine if has_engine
  69.     has_engine
  70.   end
  71. end
  72.  
  73. REPL.new.run

 

In my Pycon-AU keynote, I demonstrated the above piece of fun. Showing Ruby in a Python keynote did not result in my immediate death – just in case, for the forthcoming Kiwi Pycon I decided to rewrite this in IronPython:

Code Snippet
  1. import clr, sys
  2. clr.AddReference("Microsoft.Scripting")
  3. from Microsoft.Scripting import *
  4.  
  5. class REPL :
  6.     def __init__ (self) :
  7.         self.currentlanguage = "py"
  8.         self.runtime = Hosting.ScriptRuntime.CreateFromConfiguration()
  9.         self.engine = self.runtime.GetEngineByFileExtension(self.currentlanguage)
  10.         self.scope = self.engine.CreateScope()
  11.     def run (self) :
  12.         while (True) :
  13.             scriptinput = raw_input("{0}> ".format(self.currentlanguage))
  14.             if scriptinput == "" :
  15.                 sys.exit()
  16.             if scriptinput[0] == "#" :
  17.                 self.execute_command(scriptinput[1:])
  18.             else :
  19.                 self.execute_code(self.read_code(scriptinput))
  20.     def read_code(self, firstline):
  21.         code = firstline
  22.         while (True) :
  23.             interactivecode = self.engine.CreateScriptSourceFromString(code, SourceCodeKind.InteractiveCode)
  24.             parseresult = interactivecode.GetCodeProperties()
  25.             if parseresult ==  ScriptCodeParseResult.Complete or parseresult == ScriptCodeParseResult.Invalid :
  26.                 return interactivecode
  27.             else :
  28.                 scriptinput = raw_input("{0}| ".format(self.currentlanguage))
  29.                 if scriptinput == "" :
  30.                     return interactivecode
  31.                 else :
  32.                     code = code + "\n" + scriptinput
  33.     def execute_code(self, source):
  34.         try:
  35.             source.Execute(self.scope)
  36.         except Exception, e:
  37.             print "{0}".format(e)
  38.     def execute_command(self, command):
  39.         if command == 'exit' :
  40.             sys.exit()
  41.         elif command == 'ls' :
  42.             self.display_languages()
  43.         else :
  44.             self.engine = self.switch_language(command)
  45.     def display_languages (self) :
  46.         for i in self.engine.Runtime.Setup.LanguageSetups:
  47.             print '{0}'.format(i.DisplayName)
  48.     def switch_language(self, name) :
  49.         old_engine = self.engine
  50.         try :
  51.             new_engine = self.runtime.GetEngineByFileExtension(name)
  52.             self.currentlanguage = name
  53.         except :
  54.             print "{0} is an unknown script engine file association".format(name)
  55.             new_engine = old_engine
  56.         return new_engine
  57.  
  58. r = REPL()
  59. r.run()