Introduction

Aphid is an embeddable, cross-platform, multi-paradigm, and highly interoperable .NET scripting language. The Aphid interpreter is implemented entirely in C#. This article is intended to be an introduction to Aphid, and as such, only covers some of the features available. Further, Aphid is currently in an alpha state, so as it evolves, expect this article to change and grow with it. For the most recent version of Aphid, visit the CodePlex page.

What Languages is Aphid Inspired By?

Aphid is C-style language heavily inspired by JavaScript. However, it does draw from C#, and to a lesser extent, F#.

Why Another Scripting Language?

Currently, few easily embeddable scripting languages exist for the .NET platform. Among those available, many have several dependencies, necessitating the inclusion of various assemblies. Still others are lacking in interoperability, requiring inordinate amounts of wire-up code. Aphid seeks to solve these problems by providing an easily embeddable, highly interoperable scripting language contained within a single DLL.

Aphid Types 

Aphid has seven types: stringnumberbooleanlistobjectfunction, and null. The table below shows how Aphid's types are mapped to .NET's types.

Aphid Type .NET Type
string System.String
number System.Decimal
boolean System.Boolean
list System.Collections.Generic.List<T>
object Components.Aphid.Interpreter.AphidObject
function Components.Aphid.Interpreter.AphidFunction
null null

Hello, World

Getting started with Aphid is rather painless. First, add a reference to Components.Aphid.dll. Next, instantiateAphidInterpreter. Finally, invoke the instance method AphidInterpreter.Interpret to execute an Aphid script. Painless, huh? A complete C#/Aphid "Hello world" program is shown below, in listing 1.

Listing 1. A simple C#/Aphid hello world program
 Collapse | Copy Code
using Components.Aphid.Interpreter;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            var interpreter = new AphidInterpreter();
            
            interpreter.Interpret(@"
                #'Std';
                print('Hello, world');
            ");
        }
    }
}

The C# portion of the application should be self explanatory. The Aphid program, however, warrants a bit of an explanation. The program consists of two statements.

The first is a load script statement, consisting of the load script operator (#) and the string operand, 'Std'. By default, the Aphid loader first searches the Library subdirectory of the directory in which Components.Aphid.dll resides. The loader automatically appends the ALX extension to script name passed, so in this instance it looks for<dll>\Library\Std.alx. Assuming everything is in order, it should find and load the file, which is the standard Aphid library, and contains helpful functions for manipulating strings, printing console output, etc.

The second statement is a call expression which invokes print, a function that is part of the Aphid standard library. This line of code should be rather self explanatory.

When the program is run, the output is as expected (listing 2).

Listing 2. The output a simple hello world program
 Collapse | Copy Code
Hello, world
Press any key to continue . . .

Functions

Aphid functions are defined by using the function operator (@). Since functions are first-class citizens in Aphid, they can be stored in variables (listing 3).

Listing 3. A C#/Aphid program that defines and invokes an Aphid function
 Collapse | Copy Code
using Components.Aphid.Interpreter;

namespace FunctionSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var interpreter = new AphidInterpreter();

            interpreter.Interpret(@"
                #'Std';

                add = @(x, y) {
                    ret x + y;
                };

                print(add(3, 7));
            ");
        }
    }
}

And the output of the program (listing 4).

Listing 4. The output of the function sample
 Collapse | Copy Code
10
Press any key to continue . . .

Our add function is nice, but it could be made more concise with a language feature that might be familiar to some: lambda expressions.

Lambda Expressions

Aphid lambda expressions are special functions that are formed from a single expression. When a lambda expression is invoked, the expression is evaluated and the value is returned.

Since the body of the add function from the previous example consists of a single return statement, it can be refactored into a lambda expression (listing 5).

Listing 5. A C#/Aphid program that defines and invokes an Aphid lambda expression
 Collapse | Copy Code
using Components.Aphid.Interpreter;

namespace LambdaSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var interpreter = new AphidInterpreter();

            interpreter.Interpret(@"
                #'Std';
                add = @(x, y) x + y;
                print(add(3, 7));
            ");
        }
    }
}

And the output of the program (listing 6).

Listing 6. The output of the lambda sample
 Collapse | Copy Code
10
Press any key to continue . . .

Higher-order functions

Aphid functions are values. This makes higher-order functions (i.e. functions that accept and/or return other functions) possible. Listing 7 shows a higher-order Aphid function.

Listing 7. A C#/Aphid program that defines and invokes a higher-order Aphid function
 Collapse | Copy Code
using Components.Aphid.Interpreter;

namespace HigherOrderFunctionSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var interpreter = new AphidInterpreter();

            interpreter.Interpret(@"
                #'Std';
                call = @(func) func();
                foo = @() print('foo() called');                
                call(foo);
            ");
        }
    }
}

And the output of the program (listing 8).

Listing 8. The output of the higher-order function sample
 Collapse | Copy Code
foo() called
Press any key to continue . . .

Of course, the programs shown so far don't really need to be scriptable. Let's take a look at some interoperability to see what we can do.

Accessing Aphid Variables from .NET

Getting and setting Aphid variables from .NET is done by accessing the CurrentScope property of anAphidInterpreter instance. CurrentScope is nothing more than an AphidObject, which is itself derived fromDictionary<string, AphidObject>. An example of getting an Aphid variable is shown in listing 9, and setting a variable is shown in listing 10.

Listing 9. An interop program that demonstrates getting an Aphid variable with C#
 Collapse | Copy Code
using Components.Aphid.Interpreter;
using System;

namespace VariableGetSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var interpreter = new AphidInterpreter();
            interpreter.Interpret("x = 'foo';");
            Console.WriteLine(interpreter.CurrentScope["x"].Value);
        }
    }
}
Listing 10. An interop program that demonstrates setting an Aphid variable with C#
 Collapse | Copy Code
using Components.Aphid.Interpreter;
using System;

namespace VariableSetSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var interpreter = new AphidInterpreter();
            interpreter.CurrentScope.Add("x", new AphidObject("foo"));

            interpreter.Interpret(@"
                #'Std';
                print(x);
            ");
        }
    }
}

Calling .NET functions from Aphid scripts

Chances are, if you're reading this, you may want to expose some of your .NET functions to Aphid scripts. Doing so is quite simple. First, the .NET function of choice must be decorated with AphidInteropFunctionAttribute (listing 11). The constructor of AphidInteropFunctionAttribute accepts a string that specifies the name of the function as seen by Aphid. This can be a simple identifier (e.g. foo) or a member access expression (e.g. foo.bar.x.y). If it is the latter, Aphid will either construct an object or add members to an existing object as necessary when the function is imported.

Listing 11. An Aphid interop function
 Collapse | Copy Code
using Components.Aphid.Interpreter;

namespace InteropFunctionSample
{
    public static class AphidMath
    {
        [AphidInteropFunction("math.add")]
        public static decimal Add(decimal x, decimal y)
        {
            return x + y;
        }
    }
}

Note that the decorated function is both public and static; this is a requirement for all Aphid interop function. Now that we've created our interop function, we can proceed to write a script that imports and invokes it (listing 12).

Listing 12. An Aphid program that demonstrates loading a library and invoking an interop function
 Collapse | Copy Code
#'Std';
##'InteropFunctionSample.AphidMath';
print(math.add(3, 7));

The first line, the load script statement, was described in the Hello, world section. The second, however, is slightly different. The load library operator (##) searches Aphid modules (more on this in a bit) for the class specified by the string operand. You may recognize that the operand is the fully qualified name of the container class for the interop function we wrote previously.

So how does the Aphid interpreter know where to find InteropFunctionSample.AphidMath, you ask? Simple: we add the appropriate .NET assembly to the Aphid loader's list of modules. The relevant code is shown in listing 13.

Listing 13. An C#/Aphid program that demonstrates loading a library and invoking an interop function
 Collapse | Copy Code
using Components.Aphid.Interpreter;
using System.Reflection;

namespace InteropFunctionSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var interprer = new AphidInterpreter();
            interprer.Loader.LoadModule(Assembly.GetExecutingAssembly());
            
            interprer.Interpret(@"
                #'Std';
                ##'InteropFunctionSample.AphidMath';
                print(math.add(3, 7));
            ");
        }
    }
}

When run, the application yields the expected output (listing 14).

Listing 14. The output of the interop sample
 Collapse | Copy Code
10
Press any key to continue . . .

Now, let's flip things around.

Calling Aphid Functions from .NET

In some scenarios, you may find yourself needing to invoke Aphid functions from .NET code. This can be achieved by calling the AphidInterpreter.CallFunction instance method, which accepts a function name and the arguments to be passed (listing 15).

Listing 15. A C#/Aphid program that demonstrates calling an Aphid function from .NET
 Collapse | Copy Code
using Components.Aphid.Interpreter;
using System;

namespace CallAphidFunctionSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var interpreter = new AphidInterpreter();
            interpreter.Interpret("add = @(x, y) x + y;");
            var x = interpreter.CallFunction("add", 3, 7).Value;
            Console.WriteLine(x);
        }
    }
}

When run, the Aphid function add is called (listing 16).

Listing 16. The output of the interop sample
 Collapse | Copy Code
10
Press any key to continue . . .

Last edited Nov 3, 2013 at 6:32 AM by JohnLeitch, version 1