CLR Inside Out

Dynamic Languages and Silverlight

Jimmy Schementi

Contents

Getting DLRConsole
Using DLRConsole
Inside the Code
2 + 2. How Hard Can It Be?
But Wait, There's More
Start Playing Now!

Children learn through creative playing. From LEGO to GI Joe and Barbie (they were my sister's, I swear), children tend to pick up toys and intuitively do something with them; they don't pick up the manual and read it, at least not for the purpose of understanding the toy! It's human nature to experiment and play with things to understand them, especially as a child when the "right way to do things" isn't burned into your brain. Such a hands-on approach is arguably one of the best ways to learn.

Applying that same idea to software, dynamic languages are traditionally associated with an interactive environment, giving you a run-evaluate-print loop (REPL), and allowing for an explorative trial-and-error approach to programming. In a sense, the REPL approach is a way of turning programming into playing. This is why dynamic language advocates often say, "You just have to play with it."

When my team was integrating dynamic languages into SilverlightTM, we immediately wanted to be able to play with Silverlight. Through some clever programming by Jim Hugunin, DLRConsole was born. This column will explore how DLRConsole works, and in turn discuss IronPython, DLR Hosting, and the DLR's integration with Silverlight.

Getting DLRConsole

Running CLRConsole Locally

You can download and run DLRConsole on your local machine, but getting set up is slightly more complicated because it requires a Web server. After setting up a Web server, you can extract DLRConsole.zip, place the folder in your Web server folder, and navigate to /DLRConsole/index.htm on your Web server. (Setting up a Web server on your machine is out of the scope of this column, but you can find some pointers for Windows Vista at iis.net/articles/view.aspx/IIS7/Deploy-an-IIS7-Server/Installing-IIS7/Install-IIS7-on-Vista, and for Mac OS 10.4 at docs.info.apple.com/article.html?path=Mac/10.4/en/mh1921.html.)

The only caveat when running on your own Web server is that you need to set up the server's configuration for supported DLR languages. You'll know this is an issue if you get this error:

Silverlight error message ErrorCode: 2024 ErrorType: ParserError 
Message: Invalid attribute value text/python. 
XamlFile: DLRConsole.xaml Line: 6 Position: 53

You will need to add the following MIME type mappings to your server configuration:

.py == text/python .jsx == text/jscript

At this point, pat yourself on the back; you're running a DLR Silverlight app!

But before digging into DLRConsole, perhaps I should bring you up to speed on the Dynamic Language Runtime (DLR), a dynamic language support system that sits on top of the CLR and provides language services required by most dynamic language implementations. These services include a dynamic type system, dynamic method dispatching, dynamic code generation, and hosting APIs. For more information on the DLR, check out Jim Hugunin's blog post at blogs.msdn.com/hugunin/ archive/2007/04/30/a-dynamic-language-runtime-dlr.aspx.

DLRConsole is a Silverlight 1.1 sample that demonstrates a very lightweight code development and experimentation tool. (Please note that as of January 1, 2008 Silverlight 1.1 was renamed Silverlight 2.0.) It enables you to edit and run XAML and Python code and also to experiment with Python and JScript® in an interactive editor that supports code coloring and completion.

DLRConsole exercises the DLR's integration with Silverlight in two ways. First, it is a Silverlight application written in the DLR-compliant language, IronPython. Secondly, it utilizes the DLR hosting APIs to run the DLR code typed into the console and editor.

You can download the DLRConsole code to run locally from silverlight.net/Samples/1.1/DLR-Console/DLRConsole.zip. If you just want to use DLRConsole interactively, go to silverlight.net/Samples/1.1/DLR-Console/python for a hosted version. To run DLRConsole, you must have Silverlight installed on your machine. Running DLRConsole without Silverlight installed will prompt you with a link to download it, but if you'd like to install it ahead of time for Windows® or Apple platforms go to microsoft.com/silverlight/install.aspx. If you are running a Linux-based OS, check out Mono's Moonlight project, an implementation of Silverlight for Linux, at mono-project.com/Moonlight.

Using DLRConsole

The two ways to get DLRConsole running in your browser are either to just run it from silverlight.net, or run it locally on your own machine. For the purposes of this column, I'll demonstrate running from Silverlight.net, but if you want to download DLRConsole.zip, browse through the code, and run it on your development machine or a local server, see the sidebar "Running DLRConsole Locally" for some useful tips.

To get started, open silverlight.net/Samples/1.1/DLR-Console/python in your browser. Yes, that's it! You should see the DLRConsole open in your browser as shown in Figure 1.

Figure 1 Opening DLRConsole

Figure 1** Opening DLRConsole **(Click the image for a larger view)

When DLRConsole loads, you are presented with a Python command prompt, letting you type Python code line-by-line and see its effects either right in the console or in the Silverlight canvas as shown in Figure 2. You can click on the different languages to change the console to that language and see a chunk of sample code in the language you select. Try mixing and matching languages as you type statements, and see how everything you did in one language is available in the other.

Figure 2 Writing Code in the Console

Figure 2** Writing Code in the Console **(Click the image for a larger view)

The XAML tab shows you the XAML code behind the initial Silverlight Canvas pane (see Figure 3). You can edit it or add to it, and then update the canvas by pressing the Ctrl+Enter keys.

Figure 3 The XAML Pane

Figure 3** The XAML Pane **(Click the image for a larger view)

The Python Code tab shows you a chunk of Python code that will produce a blue background with text that changes color when you hover over it. Again, you'll need to press the Ctrl+Enter keys to execute this code, for the first time and after you make any changes.

The About tab just links to the Dynamic Silverlight Web page on CodePlex (codeplex.com/dynamicsilverlight).

Inside the Code

Now that you've seen what DLRConsole can do, let's take a look at what files make up DLRConsole, and how it all works. Figure 4 lists the source files included in the DLRConsole download.

Figure 4 DLRConsole Source Files

File Description
Index.htm Simple HTML page that includes Silverlight.js and CreateSilverlight.js and calls CreateSilverlight.
Silverlight.js Contains logic to create the Silverlight application in your browser.
CreateSilverlight.js Defines CreateSilverlight, which initializes Silverlight and loads DLRConsole.xaml.
DLRConsole.xaml Small XAML file which points to DLRConsole.py and binds the Loaded event to the OnLoaded python method.
DLRConsole.py Contains all the functionality of the app.
Wpf.py Defines helper methods for interacting with WPF APIs.
README.txt Documentation for DLRConsole.

The files Index.htm, Silverlight.js, and CreateSilverlight.js are used to initialize Silverlight. Though these files are important, I'm not going to discuss them here. You can find a deeper discussion about the basic Silverlight project setup at silverlight.net/QuickStarts/Start/CreateProject.aspx.

Wpf.py is a Python file that defines some helpers for interacting with WPF, such as a LoadXaml(url):

def LoadXaml(url): 
  uri = MakeUri(url) request = System.Windows.Browser.Net.Browser
  HttpWebRequest(uri) response = request.GetResponse() 
  reader = System.IO.StreamReader(response.GetResponseStream()) 
  xaml = reader.ReadToEnd () 
  reader.Close() 
  return XamlReader.Load(xaml)

This method actually downloads the provided URL and returns the result of XamlReader.Load.

DLRConsole.xaml loads DLRConsole.py and then calls the OnLoaded method defined in the Python code. It serves as a placeholder since Silverlight 1.1 Alpha apps are required to have one XAML file. (That is expected to change in future releases of Silverlight, which will allow for loading without requiring bootstrapping XAML files like DLRConsole.xaml.) The part to highlight is the x:Code tag—this is the hook into the DLR, causing Silverlight to load the file and execute it using the DLR. This allows any events defined in XAML to be bound to methods defined in a DLR language. The definition of Canvas is simply there to get the Python code running.

<Canvas x:Name="root" Width="1000" Height="500"
    xmlns="https://schemas.microsoft.com/client/2007"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" >
    <x:Code Source="DLRConsole.py" Type="text/python" />
    <Canvas x:Name="loadingHelper" Loaded="OnLoaded" /></Canvas

Coming in at a 2164 lines of Python, I'm not going to put the entire contents of DLRConsole.py here, but I will walk you through the most interesting parts.

From the time the XAML file is loaded and OnLoaded is called, Silverlight is being driven by Python and the DLR. OnLoaded initializes the Application class, and eventually you arrive at the Application.Initialize(self) method. This is where all the stuff on your screen comes from. It registers a KeyHandler, creates the ConsoleWindow, Editors, and the rest of the user interface, and starts the console up for your interactive enjoyment.

2 + 2. How Hard Can It Be?

So now you have an interactive console running in your browser. Wow! Let's take the canonical dynamic language test, 2 + 2, and see what happens.

First, type 2 + 2 into the console.

py> 2 + 2 
4 
py>

While typing you would expect to see what you're typing in. However, from a WPF point of view, this works by the first line in Application's Initialize method:

self.KeyHandler = TextInputHandler(self.root)

This constructs the TextInputHandler, which defines textual representations of keys, and binds the OnKeyDown method of TextInputHandler to the root Canvas's KeyDown event. On every KeyPress, it constructs a KeyPress object with a textual representation for the pressed key and tells its Target to handle the key. Target can be any class that has the HandleKey method. In this case, it is a ConsoleInput so it handles the key by inserting the textual representation through the TextBuffer object at the cursor and moves the cursor to the next column.

Though that takes a bit of work, there's nothing new here other than using WPF. However, when you press Enter, you expect to have the language compute the result of the line and display the answer. In this case, it's doing that computation in the chosen language on the user's browser! Now that's the interesting part; let's take a closer look at how it works.

To start, here's the method that handles the key in this case:

def HandleKey(self, key): 
  if key.Text == '\n': 
    self.DoInput(key.Ctrl) 
  elif key.Text == '.': 
    self.DoCompletions() 
  elif key.Name == 'Up' and self.Caret.line == 0: 
    self.DoHistory() 
  else: 
    Editor.HandleKey(self, key)

DoInput, in this case, calls DoSingleLine, which tries to parse the expression. If successful, it evaluates the expression, writes the output to the console, and sets the "_" convenience variable (see Figure 5). Just in case you're interested, you can look in the source code to see how TryExpression parses an expression and how GetEngine creates the LanguageEngine.

Figure 5 DoSingleLine Parsing

def DoSingleLine(self, text, forceExecute): 
  text1 = self.le.TryExpression(text) 
  if text1: 
    self.WriteInputToConsole(text) 
    try: 
      ret = self.Engine.Evaluate(text1, self.CurrentModule) 
      if ret is not None: 
        self.console.write(repr(ret) + '\n', 'output') 
        self.CurrentModule.SetVariable('_', ret) 
      except Exception, e: 
        self.HandleException(e) 
  else: self.DoMultiLine(text, forceExecute)

So, that's what it takes to calculate 2+2 in DLRConsole. In fact, this covers all the one-liners you can do. With a little variation you can handle multiple liners, too. The most interesting parts are where the DLRConsole calls into the DLR to execute the code.

But Wait, There's More

2 + 2 is a pretty simple example. What about something more complicated? OK, how about .NET Framework integration? Take a look at this:

py> import System 
py> from System.Collections import * 
py> h = Hashtable() 
py> h["a"] = "IronPython" 
py> h{"b"] = "in Silverlight" 
py> for e in h: print e.Key, ":", e.Value 
a: IronPython b: in Silverlight

As you can see, you can use System.Collections from IronPython, and it can act like Python hashes or .NET Framework hashes, whichever style you choose. I used this same example in the October 2006 issue of MSDN® Magazine (msdn.microsoft.com/msdnmag/issues/06/10/CLRInsideOut), but now it's running in a browser. Feel free to look through that article for a more detailed overview of IronPython and .NET integration, though keep in mind that the information is slightly outdated. You can find additional information at blogs.msdn.com/ironpython.

What about interacting with the canvas? Let's take a look at the Application class's StartConsole method:

def StartConsole(self, console, canvas, langInfo): 
  ... 
  console.CurrentModule.SetVariable("canvas", canvas) 
  console.CurrentModule.SetVariable("wpf", wpf) 
  ...

When DLRConsole initializes the console, it sets a couple of variables for you automatically, including a WPF helper module and a pointer to the canvas. Given these variables, you can write code like that shown in Figure 6, which implements the mouseover color-change effect. If you want to see this code in action, select the Python Code tab and press Ctrl+Enter.

Figure 6 Mouseover Events

import wpf canvas.Background = wpf.BlueBrush 
r = wpf.TextBlock() r.Text = "Touch Me" 
canvas.Children.Add(r) r.FontSize = 60 
wpf.SetPosition(r, 40, 60) 
def light(s, e): 
  s.Foreground = wpf.SolidColorBrush(wpf.Colors.White) 
def dark(s, e): 
  s.Foreground = wpf.SolidColorBrush(wpf.Colors.Black) 
r.MouseEnter += light 
r.MouseLeave += dark

When typing in any of these examples, did you notice a little box pop up when you typed a class or object name followed by a period? DLRConsole supports something the team likes to call "code-sense." It's not quite as advanced as the Visual Studio® IntelliSense® feature—this is a sample, after all—but it still helps a great deal at finding out what methods or properties you have available to you on a given object (see Figure 7).

Figure 7 Code-sense Autocompletion

Figure 7** Code-sense Autocompletion **

With this, you can use the up/down arrow keys to select which element you want, and then press Enter to select it and continue typing. To see how it works, let's look at the HandleKey method again:

def HandleKey(self, key): 
  if key.Text == '\n': 
    self.DoInput(key.Ctrl) 
  elif key.Text == '.': 
    self.DoCompletions() 
  elif key.Name == 'Up' and self.Caret.line == 0: 
    self.DoHistory() 
  else: 
    Editor.HandleKey(self, key)

That's a rather nice feature for just a little more than 100 lines of Python code.

From Python to JScript

As I mentioned before, DLRConsole supports multiple DLR languages, and the currently released bits show support for Python and JScript. Since all these languages are built on-top of the DLR, they are not only integrated with .NET but can they also share declarations across languages. This demonstrates sharing variables and methods between Python and JScript:

js> a = 2 + 2 
4.0 
py> b = a * 2 
py> b 
8.0 
js> function times10(n) { js| return n * 10 js| } 
py> times10(5) 
50.0

To accomplish this, just click the language you want to program in and it changes the console prompt, syntax coloring, and, of course, the language. This works by telling the console to set its language, which delegates to the Editor:

def SetLanguage(self, name): 
  self.le = GetEngine(name) 
  self.Engine = self.CurrentEngine = self.le.engine 
  self.TokenCategorizer = self.Engine.LanguageProvider.GetTokenCategorizer()

This may not seem very practical at first, but it enables code-reuse like never before by giving developers the freedom to code in their favorite language, but not limit the use of their code based on the implementation language. Arguably, developers are most creative when writing in their favorite language, so this may even help improve the quality of software.

Start Playing Now!

DLRConsole enables you to explore Silverlight, as well as dynamic languages, with minimal commitment—you don't even need to install any software beyond Silverlight. What better way to start experimenting than in someone else's hosted sandbox?

Keep your eyes open for a new release of dynamic language integration in Silverlight, as well as new and updated versions of our Silverlight dynamic language samples from codeplex.com/dynamicsilverlight. The next version should also include support for IronRuby, which you can download today from www.ironruby.net. And, as always, you can download IronPython from codeplex.com/ironpython. If you are interested in more Silverlight samples, you can find plenty of fun and interesting ones in the Silverlight Community Gallery: silverlight.net/community/communitygallery.aspx.

Send your questions and comments to clrinout@microsoft.com.

Jimmy Schementi is a Program Manager on the Dynamic Languages team at Microsoft. He's focused on making Silverlight application-authoring a fun and productive experience with dynamic languages, such as Ruby and Python, and building a community around those languages. You can contact Jimmy at https://jimmy.schementi.com.