DLR Hosting API : How to create ScriptScopes using backing symbol dictionaries to implement dynamic variable lookup

The factory methods used to create a ScriptScope object have overloads that take a 'IAttributeCollection' object to create the scope using the collection as a backing store. This feature is very useful and any app that needs non trivial Hosting would mostly use this feature.

The easiest way to implement the IAttributeCollection interface is to implement the CustomSymbolDictionary defined in the the Hosting API. Once this is done, all variable access by a script executing in the context of this scope will result in a call to the set or get methods that were overriden.

You can do some interesting things like chaining of scopes, inspecting variable values upon script access, value validation etc using this symbol dictionary implementation.

The following example uses a custom symbol dictionary to chain scopes to create a pseudo hierarchy.

using System;

using Microsoft.Scripting;

using Microsoft.Scripting.Hosting;

using Microsoft.Scripting.Runtime;

namespace ScopeOps {

public class ScopeHiererchy {

public static void Main() {

ScriptEngine eng = new ScriptRuntime().GetEngine("py");

ScriptScope baseScope = eng.CreateScope();

var dict = new DynamicLookupDictionary(baseScope);

//set variables in the base scope.

baseScope.SetVariable("base_int", 100);

baseScope.SetVariable("base_string", "from base");

ScriptScope useScope = eng.CreateScope(dict);

var src = eng.CreateScriptSourceFromString(

"print base_int\nprint base_string",

SourceCodeKind.Statements);

//'useScope' doesn't have the variables accessed by the script yet.

//The overriden 'get' method dynamically looks up the variable in the base scope.

src.Execute(useScope);

}

}

internal class DynamicLookupDictionary : CustomSymbolDictionary {

private ScriptScope _baseScope;

public DynamicLookupDictionary(ScriptScope baseScope):base() {

_baseScope = baseScope;

}

//This method would be invoked whenever a script executing in the context of this scope

//accesses a variable

protected override bool TryGetExtraValue(SymbolId key, out object value) {

//If the variable is part of the base scope, return that value - this effectively

//creates a 'scope chain'

if (_baseScope.TryGetVariable(SymbolTable.IdToString(key), out value)) {

return true;

}

value = null;

return false;

}

public override SymbolId[] GetExtraKeys() {

return null;

}

protected override bool TrySetExtraValue(SymbolId key, object value) {

return false;

}

}

}