AScript is a C#-based dynamic scripting library with a built-in
evalthat can execute string-based scripts. It addresses common pain points in dynamic expression execution, including whether variables are shared, whether assignments take effect, and why compiled mode can produce unexpected results. Keywords: AScript, eval, dynamic scripting.
Technical Specifications Snapshot
| Parameter | Description |
|---|---|
| Project Name | AScript |
| Primary Language | C# / .NET |
| Core Capabilities | Dynamic script parsing, expression execution, eval string evaluation |
| Execution Modes | Interpreted execution, compiled execution |
| Key Context Objects | BuildContext, ScriptContext, BuildOptions |
| Open Source Repository | https://gitee.com/rockey627/AScript |
| Star Count | Not provided in the source content |
| Core Dependency | .NET runtime, AScript.Functions.EvalFunction |
AScript eval Is More Than Just “Executing a String”
AScript’s eval is similar to Python’s exec, but its semantics are more precise.
It does not simply hand a string to an interpreter. Instead, it reuses the current script’s compilation context, data context, and build options.
That means the result of eval depends on two things: whether the current execution mode is interpreted or compiled, and whether the argument is a string literal or a variable.
Once you understand these two dimensions, every difference in the examples becomes predictable.
Start With the Most Important Reproduction Case
// eval behaves differently in interpreted and compiled modes when sharing variables
Console.WriteLine(new Script().Eval("int n=10;eval(\"n+20\")")); // 30
Console.WriteLine(new Script().Eval("int n=10;eval(\"n+20\")", ECompileMode.All)); // 30
Console.WriteLine(new Script().Eval("int n=10;var s=\"n+20\";eval(s)")); // 30
Console.WriteLine(new Script().Eval("int n=10;var s=\"n+20\";eval(s)", ECompileMode.All)); // 20
Console.WriteLine(new Script().Eval("int n=10;eval(\"n+=20\");n")); // 30
Console.WriteLine(new Script().Eval("int n=10;eval(\"n+=20\");n", ECompileMode.All)); // 30
Console.WriteLine(new Script().Eval("int n=10;var s=\"n+=20\";eval(s);n")); // 30
Console.WriteLine(new Script().Eval("int n=10;var s=\"n+=20\";eval(s);n", ECompileMode.All)); // 10
This code compares how constant strings and variable-based strings read and modify the main script variable n across different execution modes.
eval Relies on Context Reuse at Runtime
In AScript, eval ultimately calls the internal Eval method on Script.
It reuses three key objects from the current script: BuildContext, ScriptContext, and BuildOptions.
You can think of the current script as trunk A, and the script inside eval as branch A1.
When A1 can be merged directly into A, variables and assignments are fully shared. When A1 must be compiled independently, that sharing becomes limited.
AI Visual Insight: This diagram shows the context relationship between the main AScript script and the eval branch script. Trunk A represents the current execution unit, while branch A1 represents the script fragment injected at runtime. The key idea is that eval does not always execute independently. It first tries to share the main script’s compilation context, runtime data context, and build options. Only when the script content cannot be determined at compile time does it fall back to an independently compiled branch.
Interpreted Execution Tries to Share the Main Script State
In interpreted execution mode, the script inside eval can directly access variables from trunk A.
It can not only read n=10, but also synchronize changes such as n+=20 back to the trunk.
More importantly, variables defined inside the branch may also remain available to later steps in the trunk. This shows that interpreted execution behaves more like a dynamic expansion within the same scope.
var result = new Script().Eval("int n=10;eval(\"n+=20\");n");
// eval directly modifies n in the main script
Console.WriteLine(result); // 30
This example shows that in interpreted mode, changes made by eval to main-script variables are visible.
Compiled Execution Splits Behavior Based on the Argument Form
Compiled execution does not always share state.
It first checks whether the eval argument can be determined at compile time.
If the argument is a string literal, such as eval("n+20"), the parsed result can be merged directly into the main script for unified compilation.
In that case, no independent branch is created, and the behavior is very close to inline code in the main script.
String Literals Are Merged Into the Main Compilation Unit
var result = new Script().Eval(
"int n=10;eval(\"n+20\")",
ECompileMode.All
);
// A string literal can be expanded at compile time and merged into the main script
Console.WriteLine(result); // 30
This example shows that when the eval argument is a constant string, compiled mode can still read the real variable value from the main script.
Variable Arguments Trigger Independent Branch Compilation
If the argument is a variable, such as var s="n+20"; eval(s), the compiler cannot know the final content of s during compilation.
Because of that, it cannot merge this script into the main trunk and must create branch A1 for separate compilation and execution.
At that point, A1 can share only the variable declaration from the main script, but it cannot inherit assignment statements that have already executed in the trunk.
So although n exists, its value is not the trunk’s 10, but the default value .
var result = new Script().Eval(
"int n=10;var s=\"n+20\";eval(s)",
ECompileMode.All
);
// The independent branch sees only the declaration of n, not the assigned value from n=10
Console.WriteLine(result); // 20
This example shows that in compiled mode, variable-based eval loses the main script’s assignment context, so the result becomes 0+20.
The Eighth Line Outputs 10 Instead of 30 for the Same Reason
The same rule also explains the difference between eval("n+=20") and eval(s).
When s is passed as a variable, the modification to n happens inside independently compiled branch A1 and cannot be written back to the trunk.
So even if the branch executes n+=20, the n in the main script still remains 10.
That is the fundamental reason the eighth line outputs 10.
Use This Short Rule Set to Remember the Behavior
// Recommended memory rule
// 1. Interpreted execution: reads and writes to main-script variables are usually visible
// 2. Compiled execution + constant string: behaves almost like inline main-script code
// 3. Compiled execution + variable string: independent branch, cannot share assignment results
You can use this rule set directly as a troubleshooting checklist when AScript eval returns unexpected results.
You Should Choose the eval Pattern Based on the Real Project Scenario
If you need eval to read real-time variable values from the main script, prefer interpreted execution mode.
If you must use compiled execution mode, pass string literals whenever possible instead of indirectly passing script content through variables.
If your business logic requires dynamically assembled script variables, assume that AScript will enter an independently compiled branch. In that case, either pass the required data explicitly or redesign the script to avoid depending on runtime assignment state from the main script.
FAQ
Q1: Why does eval("n+20") return 30 in compiled mode?
Because the argument is a string literal, AScript can parse and merge it directly into the main script at compile time. No independent branch is created, so it can read the real value of n=10.
Q2: Why can’t eval(s) read n=10 in compiled mode?
Because s is a variable, the compiler cannot determine the script content during compilation. It must generate a separate branch for compilation. That branch shares only the variable declaration, not assignment statements that have already executed in the main script, so n is no longer 10.
Q3: When should I avoid variable-based eval?
Avoid forms like eval(s) when your script logic depends on real-time variable values in the main script, assignment side effects, or cross-statement state synchronization—especially under ECompileMode.All.
AI Readability Summary
This article systematically breaks down how eval works in AScript. It focuses on the context-sharing differences between interpreted and compiled execution, and explains why variable-based arguments can produce different results in compiled mode. It is especially useful for C# developers building dynamic scripting systems who need a correct mental model of AScript eval behavior.