(Semi) Practical IronRuby

So you followed the quick start I posted earlier, and you're thinking "So what?  What good is IronRuby and DLR to me?"  The DLR provides an extensible, powerful way to add scripting capabilities to your application.  Let's take a look at how IronRuby and C# can really interact. We'll create an "IronLogo" application, consisting of a windows form we can draw on utilizing a simple DSL implemented in Ruby.  Commands can be loaded via a file or through a console.

Our bulk of our application is actually going to be in C#, the details aren't important, but let's take a quick look at the class definition.

image

I've hidden a lot of the cruft, but the important things here are the TurtlePoint property that holds a Point object of the turtle's current location, and the MoveTurtle method.  What we're going to want to do is take the current turtle's location and move some offset.  Of course we don't have to write some complicated command parser, so we're going to use IronRuby and the DLR to handle all the heavy lifting.  Let's take a quick look at the application's Main method:

 

    1        static void Main()

    2         {

    3             Application.EnableVisualStyles();

    4             Application.SetCompatibleTextRenderingDefault(false);

    5 

    6             LogoWindow logowin = new LogoWindow();

    7 

    8             IScriptEngine ruby = IronRuby.GetEngine(IronRuby.CreateRuntime());

    9 

   10             Dictionary<SymbolId, object> globalvars = IronRuby.GetExecutionContext(ruby.Runtime).GlobalVariables;

   11             globalvars[SymbolTable.StringToId("logowin")] = logowin;

   12 

   13             IScriptScope IronLogoScope = ruby.Runtime.CreateScope();

   14 

   15             ruby.Execute(IronLogoScope, ruby.CreateScriptSourceFromString(Properties.Resources.IronLogoRuby, SourceCodeKind.File));

   16 

   17             logowin.PassScopeAndEngine(IronLogoScope, ruby);

   18 

   19             logowin.ShowDialog();

   20 

   21         }

 

You can see a lot of the same stuff happening here as you did in the quick start, though there are some subtle differences.  First, if you look at line 11 you can see that we're actually assigning a global variable to the reference to our windows form.  That's right, our Ruby code will be able to access our Window form object directly, all though the magic of the DLR!  Also, on line 13, I create a scope using the Runtime property of the engine, this is important as this will create a scope that has references to the right engine, context, and so forth.  We learned from the quick start about the IScriptEngine.Execute command, but what are we running on line 15?  Why, it's an embedded text file that contains our ruby code that implements the DSL!  Would you believe it's only 15 lines?  See for yourself:

    1 def up(steps=1)

    2     $logowin.MoveTurtle($logowin.get_TurtlePoint.X, $logowin.get_TurtlePoint.Y - steps)

    3 end

    4 

    5 def dn(steps=1)

    6     $logowin.MoveTurtle($logowin.get_TurtlePoint.X, $logowin.get_TurtlePoint.Y + steps)

    7 end

    8 

    9 def rt(steps=1)

   10     $logowin.MoveTurtle($logowin.get_TurtlePoint.X + steps, $logowin.get_TurtlePoint.Y)

   11 end

   12 

   13 def lt(steps=1)

   14     $logowin.MoveTurtle($logowin.get_TurtlePoint.X - steps, $logowin.get_TurtlePoint.Y)

   15 end


Here you can see we're defining four methods, they take a parameter called "steps" (which defaults to one) and then accesses the main form object through the global variable and calls the MoveTurtle method, as well as accessing the TurtlePoint method (remember that properties are really just syntactic sugar/metadata for method pairs, so we're actually calling the get method directly).

The other bit of trickery in the Main method is on line 17, this is where I pass both my engine and the scope that the above Ruby script was executed in, to the LogoWindow form.

Let's take a look at what we can do in the LogoWindow form since we have a reference to our scripting engine and scope.  Let's say we want to be able to load a script of our DSL and have the application run it.  Let's create a text file that looks like so:

up 10
lt 10
dn 10
rt 10
dn 10

As you can see, what it really consists of is just calls of the methods we defined above, let's see how we load such a file in the application:

  147        private void applyIronLogoScriptToolStripMenuItem_Click(object sender, EventArgs e)

  148         {

  149             OpenFileDialog ofd = new OpenFileDialog();

  150             ofd.Filter = "IronLogo Files (*.ilogo)|*.ilogo|All Files (*.*)|*.*";

  151             ofd.Title = "Apply An IronLogo File...";

  152             if (ofd.ShowDialog() == DialogResult.OK)

  153             {

  154                 this.scriptengine.Execute(this.scriptscope, this.scriptengine.CreateScriptSourceFromFile(ofd.FileName));

  155 

  156             }

  157         }


Thats.... it really.  See how simple that is?  Loading the file results in our methods being executed and our Turtle being moved:

image

Yay for the power of the DLR!  What if we want to be more interactive though?  We actually want to open a console and let people run the commands interactively.  No fear, with the DLR that's easy as well!  Since this is a Windows application first, we do need to import a couple of external Windows API funcitons, namely AllocConsole and FreeConsole.  Once we do that we can then create a console window and tell the DLR to open a Ruby console in it.  There's some Threading trickery going on that I'll spare you from, but the method for actually opening a console looks like this:

    1         private void RunConsole()

    2         {

    3             if (AllocConsole())

    4             {

    5                 this.consoleopen = true;

    6                 Ruby.Hosting.RubyCommandLine rubycommandline = new RubyCommandLine(new Ruby.Runtime.RubyContext(ScriptDomainManager.CurrentManager));

    7                 Microsoft.Scripting.Shell.SuperConsole superconsole = new Microsoft.Scripting.Shell.SuperConsole(rubycommandline, this.scriptengine, true);

    8                 rubycommandline.Run(this.scriptengine, superconsole, new Ruby.Hosting.RubyConsoleOptions());

    9 

   10             }

   11         }

 

11 lines of code...and the real heavy lifting is only done in three of them.  Wow.  All that work, done for us.  What you end up with after that is something that looks like this:

image

 

Summary

There you have it, a simple implementation of LOGO as a DSL in Ruby, running on the DLR, interoperating with our C# code.  I've uploaded the above application to CodePlex for your enjoyment.

IronLogo CodePlex Project

IronRuby Quick Start

IronRuby is Microsoft's, with collaboration by the public, implementation of Ruby on their imageDynamic Language Runtime. There's another version of Ruby for .NET called, ironically, Ruby.NET that runs directly on the CLR. This post won't be about that though, if you want to see a comparison, look here for a fairly good write up.

Downloading IronRuby from the SVN server and compiling in VS2005 was actually pretty painless. But after that I couldn't find any, working, examples of getting an Ruby script running in the DLR. A big part of this is due to IronRuby still being officially "pre-Alpha" with the Scripting Host API in flux. Regardless, I hope this will be enough to get some people trying to use the latest SVN (rev. 75) up and running.

Building The Source

Download the latest revision from RubyForge using SVN, if you need a client I HIGHLY recommend TortiseSVN. Once you're done downloading the source, you should be able to open the IronRuby.sln file in Visual Studio. We only need to make one change, and that's to the Microsoft Scripting Project. Bring up the project properties and go to the Build tab:

image

We have to remove the Conditional compilation symbol of "SIGNED", otherwise the Scripting host will be looking for Microsoft signed copies of the IronRuby library, which we don't have. After that go ahead and build the solution (cross your fingers if it makes you feel better).

When all is done you should end up with a bin\Debug folder in your SVN root that looks something like this:

image

You can go ahead and start rbx from right there and begin playing with Ruby if you'd like. But if that's all we wanted to do we would have just downloaded Ruby, right? This is IronRuby, let's do it the .NET Way!

Hosting IronRuby in C#

Create a new Console Application solution in Visual Studio, say RubyExample. Add References to the Microsoft.Scripting.dll, IronRuby.dll, and IronRuby.Libraries.dll files.

Let's begin with the most basic, a simple Hello World:

using System;
using Ruby;
using Ruby.Runtime;
using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;
namespace RubyExample
{
static class Program
{
/// <summary>
///
The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
IScriptEnvironment scriptenvironment = ScriptEnvironment.GetEnvironment();
IScriptEngine rubyengine = scriptenvironment.GetEngine("ruby");
scriptenvironment.ExecuteSourceUnit(rubyengine.CreateScriptSourceFromString("puts'Hello World!
\nPress Any Key To Continue..'"
) );
Console.ReadKey();
}
}
}


Let's take a quick look at what we're doing here. We're setting up a ScriptEnvironment, this is where our Dynamic languages are going to live and play. Then out of that environment we're asking for someone who understands Ruby. After that we're just saying, hey ScriptEnvironment, run what the Ruby guy says.

So that's pretty nifty, we could also tell the RubyEngine to CreateScriptSourceFromFile and move whatever code we want out of a string constant. Which is probably a good idea for anything beyond a line a two. But what if we actually want to talk back and forth? Let's move on to the next example.

Accessing Global Variables

The easiest way to pass data between IronRuby and C# is via Global Variables. See below for an example.



using System;
using System.Collections.Generic;
using Ruby;
using Ruby.Runtime;
using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;
namespace RubyExample
{
static class Program
{
/// <summary>
///
The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
IScriptEngine rubyengine = IronRuby.GetEngine(IronRuby.CreateRuntime());
Dictionary<SymbolId, object> globalvars = IronRuby.GetExecutionContext(rubyengine.Runtime).GlobalVariables;

globalvars[SymbolTable.StringToId("widget")] = ".NET";

IScriptScope scope = rubyengine.CreateScope();

rubyengine.Execute(scope, rubyengine.CreateScriptSourceFromString("puts 'Ruby and ' +
$widget.to_s + ' together at last'"
));

SymbolId rubynumber = SymbolTable.StringToId("mynumber");
int mynumber = 12;

globalvars[rubynumber] = mynumber;

rubyengine.Execute(scope, rubyengine.CreateScriptSourceFromString("$mynumber = $mynumber
+ 13"
));

mynumber = (int)globalvars[rubynumber];

Console.WriteLine("The variable mynumber = {0}", mynumber);
Console.ReadKey();
}
}
}


As you can see I switched it up a bit and inited the Environment and Engine a bit differently, one way is more general, the other specific to IronRuby. The power of the ScriptingHost API is you can on the fly decide what language you want to use. But if you know you're only going to be doing IronRuby, you can use the above method.



The real trick is in the second line, we get a reference to the GlobalVariables of the current IronRuby execution context. Once we have that we can start assigning global variables values and retrieving them back.

Summary

Hopefully this is enough to get you started. As I continue to delve into IronRuby, I will be sure to post what I find here, so keep an eye out!

IronRuby Homepage
IronRuby RubyForge Project
Ruby Language Homepage
DLR Hosting Spec
John Lam's homepage

Developer Events In North East Ohio

North East Ohio seems to suffer from a dearth of good Developer centric events.  And the events we do get seem to be poorly advertised.  And the developers who would be interested in planning and implementing events can often have a hard time finding like minded souls.  To help alleviate both problems, I am implementing the North East Ohio Developer Events blog and Google group.  The blog is intended as a place where North East Ohio based developers can hear about upcoming events that may interest them, while the Google group is a place where those passionate North East Ohio developers can collaborate and plan events.  I'm hoping that we can really get some activity going in this region, I truly think it's under appreciated, and I know others feel as I do.  The blog kicks off with details on the next Cleveland ArcReady event.  I hear word of an upcoming Coding Dojo, with other talk of a Code Camp and rumors of a possible DevCares event both in the near future.  As I get details they will be announced on the NEODevEvent blog.

North East Ohio Developer Events Blog

North East Ohio Developer Events Group

CodeMash 2008 Wrap Up

A little late, I know, but I finally got some time to put my thoughts into place.  First I want to send out my congratulations to Jim Holmes, Brian Prince, Jason Gilmore, Jason Follas, Dianne Marsh, Jeff Blakenburg, Josh Holmes, and John Hopkins for putting on ANOTHER wonderful event.  I went into CodeMash '08 with very high expectations based on last years spectacular event, and the group did not disappoint.  A quick thank you to the CodeMash sponsors for helping those folks do that job!  Your loyalty to the development community will not be forgotten by this developer.

It was kicked off by a very insightful panel on how to "sell" yourself and your ideals to clients and/or colleagues.  Then were the two days of sessions which were again some of the most insightful and educational I've ever had the pleasure of attending.

The keynotes were five star once again, with Scott Hanselman, Neal Ford, and Brian Geotz all doing an outstanding job.

I strongly urge everyone to go listen to Chris Woodruff's CodeMash Podcasts, as well as checkout the CodeMash site for slide decks and session audio.

Of course the other half the conference occurs after the sessions are long over and deep into the night.  I had great conversations with folks like Joe O'Brien (who is a way bigger twitter addict then me, Keith) , Jay Wren, and Steven Harman that I am still digesting over. 

Let the countdown to CodeMash 2009 begin!

Changes At CodeBetter.

Looks like Glenn Block, from Microsoft's patterns & practices group, is now blogging at CodeBetter. I'm sure it's only a coincidence that Scott Bellware (ooold link, but only one still valid) no longer is (and had all his content removed).


Update: Glenn replied in the comments that Scott was actually the one that asked him to join CodeBetter.