ScriptObjectA TemplateContext maintains a stack of ScriptObject that defines the state of the variables accessible from the current template.
When evaluating a template and resolving a variable, the TemplateContext will lookup to the stack of ScriptObject for the specified variable. From the top of the stack (the latest PushGlobal) to the bottom of the stack, when a variable is accessed from a template, the closest variable in the stack will be returned.
By default, the TemplateContext is initialized with a builtin ScriptObject which contains all the default builtin functions provided by scriban. You can pass your own builtin object if you want when creating a new TemplateContext.
Then, each time you do a TemplateContext.PushGlobal(scriptObject), you push a new ScriptObject accessible for resolving variable
Let's look at the following example:
// Creates scriptObject1
var scriptObject1 = new ScriptObject();
scriptObject1.Add("var1", "Variable 1");
scriptObject1.Add("var2", "Variable 2");
// Creates scriptObject2
var scriptObject2 = new ScriptObject();
// overrides the variable "var2"
scriptObject2.Add("var2", "Variable 2 - from ScriptObject 2");
// Creates a template with (builtins) + scriptObject1 + scriptObject2 variables
var context = new TemplateContext();
context.PushGlobal(scriptObject1);
context.PushGlobal(scriptObject2);
var template = Template.Parse("This is var1: `{{var1}}` and var2: `{{var2}}");
var result = template.Render(context);
// Prints: "This is var1: `Variable 1` and var2: `Variable 2 - from ScriptObject 2"
Console.WriteLine(result);
The TemplateContext stack is setup like this: scriptObject2 => scriptObject1 => builtins
As you can see the variable var1 will be resolved from scriptObject1 but the variable var2 will be resolved from scriptObject2 as there is an override here.
NOTE If a variable is not found, the runtime will not throw an error but will return
nullinstead. It allows to check for a variable existenceif !pagefor example. In case you want your script to throw an exception if a variable was not found, you can specifyTemplateContext.StrictVariables = trueto enforce checks. See the safe runtime section for more details.
When writing to a variable, only the ScriptObject at the top of the TemplateContext will be used. This top object is accessible through TemplateContext.CurrentGlobal property. It the previous example, if we had something like this in a template:
var template2 = Template.Parse("This is var1: `{{var1}}` and var2: `{{var2}}`{{var2 = 5}} and new var2: `{{var2}}");
var result = template2.Render(context);
// Prints: "This is var1: `Variable 1` and var2: `Variable 2 - from ScriptObject 2 and new var2: `5`"
Console.WriteLine(result);
The scriptObject2 object will now contain the var2 = 5
The stack provides a way to segregate variables between their usages or read-only/accessibility/mutability requirements. Typically, the builtins ScriptObject is a normal ScriptObject that contains all the builtins objects but you cannot modify directly the builtins object. But you could modify the sub-builtins objects.
For example, the following code adds a new property myprop to the builtin object string:
{{
string.myprop = "Yoyo"
}}
Because scriban allows you to define new functions directly into the language and also allow to store a function pointer by using the alias @ operator, you can basically extend an existing object with both properties and functions.
To access a variable at any point in the stack from a function, use context.GetValue().
public virtual object Invoke(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
{
// var1 defined in scriptObject pushed to global anywhere down the stack
ScriptVariableGlobal scriptVariableGlobal = new ScriptVariableGlobal("var1");
object contextObject = context.GetValue(scriptVariableGlobal);
string var1Value = "";
// cast to the correct type
if (contextObject != null) {
var1Value = (string)contextObject;
}
return $"var1 is {var1Value}";
}
To access a variable from only the top of the global stack from a function, use context.CurrentGlobal.TryGetValue().
public virtual object Invoke(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement)
{
// var1 defined in scriptObject pushed to only the top level of the global stack
context.CurrentGlobal.TryGetValue(context, span, key, out object contextObject);
string var1Value = "";
// cast to the correct type
if (contextObject != null) {
var1Value = (string)contextObject;
}
return $"var1 is {var1Value}";
}
with statement with the stackWhen using the with statement with a script object, it is relying on this concept of stack:
with <scriptobject> is equivalent of calling TemplateContext.PushGlobal(scriptObject)with statement will set variable on the target object of the with statement.context.PopGlobal()var scriptObject1 = new ScriptObject();
var context = new TemplateContext();
context.PushGlobal(scriptObject1);
var template = Template.Parse(@"
Create a variable
{{
myvar = {}
with myvar # Equivalent of calling context.PushGlobal(myvar)
x = 5 # Equivalent to set myvar.x = 5
y = 6
end # Equivalent of calling context.PopGlobal()
}}");
template.Render(context);
// Contains 5
Console.WriteLine(((ScriptObject)scriptObject1["myvar"])["x"]);