« Building On The DLR | Main | DLR Notes 2 »
One of the first things I tried to get working on my simple programming language was being able to define functions. Initially I started just with global functions, and then with local (i.e. nested) functions.
For the most part, this is easy in the DLR. You create a new function by creating a CodeBlock, which has a bunch of properties and methods such as the function name, the local variables it has, any parameters it takes and what the type of the return value is. It also has a Body, which is an Expression (normally a Block expression which contains a list of expressions). You create an empty CodeBlock using Ast.CodeBlock() and then you can fill in the rest using its properties.
Currently, a CodeBlock isn't directly part of the AST itself, that is, it isn't directly a node in the tree. Instead, you build a CodeBlockExpressions out of it (using Ast.CodeBlockExpression(), naturally) and use that as a reference to your function. In fact, at runtime, a CodeBlockExpression eventually becomes just that: a delegate instance.
My initial function implementation was extremely simplistic, meaning that functions weren't really handled using any language-specific object (like [Iron]Ruby's Proc or ToyScript's ToyFunction classes). Instead, I was directly storing CodeBlockExpressions in variables and using them instead. also didn't implementing function calls initially.
To test that my function definitions were working, I instead opted for simply returning the CodeBlockExpression directly. My tests (built using NUnit) make use of the ScriptEngine.Execute() method to parse and evaluate a script and return the result (my language has implicit returns, so all expressions evaluate to something). So what comes out when I evaluated a variable containing a CodeBlockExpression? Yep, a delegate, which I could call directly from the outside to validate it was working!
It's worth saying that the delegate you get directly from such a simple construct might have a bit of an unexpected syntax: The first parameter will always be of type CodeContext, after which the actual parameters you defined in the CodeBlock will appear (many times it's safe to simply pass null for this).
One of the things that I ran into while implementing this was getting variable definitions right. Creating and defining variables on the DLR isn't really hard:
Obviously, keeping track of variable definitions and their scope is your responsibility. For local and parameter variables, you need to keep track of the Variable objects returned by CreateLocalVariable()/CreateParameter(), since that's how you reference the variable when reading/writing to it using Ast.Read()/ReadDefined()/Write()/Assign() and friends.
If you somehow set up things incorrectly, then some debug asserts in the DLR might get triggered at runtime when you try to use the variables (surprisingly enough things still seemed to work after ignoring the asserts, but that was probably a fluke).
Syndicate
About
Tomas Restrepo is a software developer located in Colombia, South America. His interests include .NET, Connected Systems, PowerShell and lately dynamic programming languages. More...
email: tomas@winterdom.com msn: tomasr@passport.com
Ads
Links
Categories
Statistics
Blogroll
Post Archive
Other
Copyright © 2002-2008, Tomas Restrepo.
Powered by: newtelligence dasBlog 2.1.8139.823