Updating Tomáš Matoušek’s repl.rb & adding repl.py
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
- load_assembly 'Microsoft.Scripting'
- Hosting = Microsoft::Scripting::Hosting
- Scripting = Microsoft::Scripting
- class REPL
- def initialize
- @engine = IronRuby.create_engine
- @scope = @engine.create_scope
- @exception_service = @engine.method(:get_service).of(Hosting::ExceptionOperations).call
- @language = "rb"
- end
- def run
- while true
- print "#@language> "
- line = gets
- break if line.nil?
- if line[0] == ?#
- execute_command line[1..-1].rstrip
- else
- execute_code read_code(line)
- end
- end
- end
- # Reads lines from standard input until a complete or invalid code snippet is entered.
- # Returns ScriptSource that represents an interactive code.
- def read_code first_line
- code = first_line
- while true
- interactive_code = @engine.create_script_source_from_string(code, Scripting::SourceCodeKind.InteractiveCode)
- case interactive_code.get_code_properties
- when Scripting::ScriptCodeParseResult.Complete, Scripting::ScriptCodeParseResult.Invalid then
- return interactive_code
- else
- print "#@language| "
- next_line = gets
- return interactive_code if next_line.nil? or next_line.strip.size == 0
- code += next_line
- end
- end
- end
- # Executes given ScriptSource and prints any exceptions that it might raise.
- def execute_code source
- source.execute(@scope)
- rescue Exception => e
- message, name = @exception_service.get_exception_message(e)
- puts "#{name}: #{message}"
- end
- def execute_command command
- case command
- when 'exit' then exit
- when 'ls?' then display_languages
- else puts "Unknown command '#{command}'" unless switch_language command
- end
- end
- def display_languages
- @engine.runtime.setup.language_setups.each { |ls| puts "#{ls.display_name}: #{ls.names.inspect}" }
- end
- def switch_language name
- has_engine, engine = @engine.runtime.try_get_engine(name)
- @language, @engine = name, engine if has_engine
- has_engine
- end
- end
- 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
- import clr, sys
- clr.AddReference("Microsoft.Scripting")
- from Microsoft.Scripting import *
- class REPL :
- def __init__ (self) :
- self.currentlanguage = "py"
- self.runtime = Hosting.ScriptRuntime.CreateFromConfiguration()
- self.engine = self.runtime.GetEngineByFileExtension(self.currentlanguage)
- self.scope = self.engine.CreateScope()
- def run (self) :
- while (True) :
- scriptinput = raw_input("{0}> ".format(self.currentlanguage))
- if scriptinput == "" :
- sys.exit()
- if scriptinput[0] == "#" :
- self.execute_command(scriptinput[1:])
- else :
- self.execute_code(self.read_code(scriptinput))
- def read_code(self, firstline):
- code = firstline
- while (True) :
- interactivecode = self.engine.CreateScriptSourceFromString(code, SourceCodeKind.InteractiveCode)
- parseresult = interactivecode.GetCodeProperties()
- if parseresult == ScriptCodeParseResult.Complete or parseresult == ScriptCodeParseResult.Invalid :
- return interactivecode
- else :
- scriptinput = raw_input("{0}| ".format(self.currentlanguage))
- if scriptinput == "" :
- return interactivecode
- else :
- code = code + "\n" + scriptinput
- def execute_code(self, source):
- try:
- source.Execute(self.scope)
- except Exception, e:
- print "{0}".format(e)
- def execute_command(self, command):
- if command == 'exit' :
- sys.exit()
- elif command == 'ls' :
- self.display_languages()
- else :
- self.engine = self.switch_language(command)
- def display_languages (self) :
- for i in self.engine.Runtime.Setup.LanguageSetups:
- print '{0}'.format(i.DisplayName)
- def switch_language(self, name) :
- old_engine = self.engine
- try :
- new_engine = self.runtime.GetEngineByFileExtension(name)
- self.currentlanguage = name
- except :
- print "{0} is an unknown script engine file association".format(name)
- new_engine = old_engine
- return new_engine
- r = REPL()
- r.run()