Introduction: The Power of Custom Scripting

In the world of software development, there comes a time when general-purpose programming languages feel either too heavy or too rigid for specific use cases. This is where custom scripting engines shine—they provide a lightweight, domain-specific way to express logic that can be modified without recompiling the entire application.

AScript represents an elegant solution to this challenge. As an open-source C# dynamic script parsing and execution library, it offers the flexibility to define custom syntax while maintaining the performance and type safety of the .NET ecosystem. This article explores how to leverage AScript to build a Chinese language script engine, demonstrating principles that apply to any custom language design project.


Understanding AScript's Architecture

Before diving into implementation details, it's essential to grasp what makes AScript distinctive. Unlike simple expression evaluators or template engines, AScript provides a complete framework for defining and executing custom languages.

Core Components

The library's architecture centers around several key abstractions:

  • Token Handlers: Define how specific keywords or symbols are parsed and transformed into executable nodes
  • Syntax Analyzer: Orchestrates the parsing process, managing token streams and building abstract syntax trees
  • Script Language Definition: Encapsulates the complete language environment, including type definitions and registered handlers
  • Runtime Execution Engine: Interprets the parsed syntax tree and produces results

This modular design means you can incrementally build language features without rewriting the entire parser. Each new keyword or construct is an independent addition to the existing framework.


Step 1: Implementing Custom Token Handlers

The foundation of any custom language in AScript is the token handler. Think of token handlers as the grammar rules that tell the parser how to interpret specific sequences of tokens.

The Chinese Conditional Statement

Let's implement a Chinese conditional statement: "如果 ... 则 ... 否则 ..." (If ... Then ... Else ...). This example demonstrates the complete process of extending AScript with custom syntax.

public class 如果语法处理器 : ITokenHandler
{
    private static readonly HashSet<string> _StatementEndTokens = 
        new HashSet<string> { "则", "否则" };

    public void Build(DefaultSyntaxAnalyzer analyzer, TokenAnalyzingArgs e)
    {
        e.IsHandled = true;
        e.End = true;

        // Return if there's already a statement
        if (e.TreeBuilder.Root != null)
        {
            e.TokenReader.Push(e.CurrentToken);
            return;
        }

        // Parse the condition
        var condition = analyzer.BuildOneStatement(
            e.BuildContext, 
            e.ScriptContext, 
            e.Options, 
            e.TokenReader, 
            e.Control, 
            e.Ignore, 
            endTokens: _StatementEndTokens
        );
        
        // Validate the "then" keyword
        analyzer.ValidateNextToken(e.TokenReader, "则");
        
        // Parse the body
        var createAllOptions = new BuildOptions(e.Options) 
        { 
            CreateFullTreeNode = true 
        };
        var body = analyzer.BuildOneStatement(
            e.BuildContext, 
            e.ScriptContext, 
            createAllOptions, 
            e.TokenReader, 
            e.Control, 
            e.Ignore, 
            endTokens: _StatementEndTokens
        );
        
        // Create the IF node
        var node = new IfNode 
        { 
            Condition = condition, 
            Body = body 
        };
        
        // Check for else clause
        var nextToken = e.TokenReader.Read();
        if (nextToken.HasValue && nextToken.Value.Value == ";")
        {
            nextToken = e.TokenReader.Read();
        }
        if (nextToken.HasValue)
        {
            if (nextToken.Value.Value == "否则")
            {
                node.Else = analyzer.BuildOneStatement(
                    e.BuildContext, 
                    e.ScriptContext, 
                    createAllOptions, 
                    e.TokenReader, 
                    e.Control, 
                    e.Ignore
                );
            }
            else
            {
                e.TokenReader.Push(nextToken.Value);
            }
        }
        
        e.TreeBuilder.Add(e.BuildContext, e.ScriptContext, e.Options, e.Control, node);
    }
}

Key Implementation Details

Several aspects of this implementation deserve attention:

End Token Management: The _StatementEndTokens set defines where the condition and body end. This approach allows the parser to correctly identify boundaries without requiring explicit delimiters like parentheses or braces.

Token Stream Manipulation: Notice the use of Push() to return tokens to the stream. This is crucial for proper parsing—when we encounter a token that belongs to the next statement, we must return it so the next handler can process it.

Node Construction: The IfNode class represents the parsed conditional in the abstract syntax tree. AScript will later execute this node during script evaluation.


Step 2: Defining the Language Environment

With the token handler in place, the next step is to create a language definition that registers all available features.

public class 中文语言 : ScriptLang
{
    public static readonly 中文语言 实例 = new 中文语言();

    public 中文语言()
    {
        // Register type mappings
        AddType<int>("整型");
        AddType<string>("文本");

        // Register token handlers
        AddTokenHandler("如果", new 如果语法处理器());
        AddTokenHandler("返回", AScript.TokenHandlers.ReturnTokenHandler.Instance);
    }
}

Language Registration

The language definition serves as a central registry for all language features:

  • Type Mappings: Connect Chinese type names to their .NET equivalents. This allows scripts to use "整型" instead of "int" and "文本" instead of "string".
  • Token Handler Registration: Associates keywords with their corresponding handlers. When the parser encounters "如果", it knows to invoke the conditional handler we defined earlier.

Singleton Pattern

Notice the use of a static instance (实例). This follows the singleton pattern, ensuring that the language definition is created once and reused across all script executions. This is both efficient and ensures consistency.


Step 3: Registering the Language

Before scripts can use the new language, it must be registered with the AScript runtime:

Script.Langs["中文"] = 中文语言。实例;

This single line makes the Chinese language available for script execution. The key "中文" can now be referenced when creating script instances.


Step 4: Writing and Executing Chinese Scripts

With the language fully defined and registered, we can now write and execute scripts using Chinese syntax:

string s = @"
整型 n=10;
文本 s='';
如果 n<5 则 {
    s='小于 5';
} 否则 如果 n<20 则 {
    s='大于等于 5 且小于 20';
} 否则 {
    s='大于等于 20';
}
返回 $'{n},{s}';
";

var script = new Script();
Assert.AreEqual("10,大于等于 5 且小于 20", script.Eval(s));

Script Analysis

Let's break down what happens during execution:

  1. Variable Declaration: 整型 n=10; declares an integer variable. AScript translates this to int n = 10; internally.
  2. Conditional Logic: The "如果...则...否则..." structure is parsed by our custom handler and converted into an executable conditional node.
  3. String Interpolation: The $'{n},{s}' syntax uses AScript's string interpolation feature, similar to C#'s $"{n},{s}".
  4. Return Statement: The "返回" keyword triggers the return handler, which terminates execution and returns the specified value.

Expected Output

For the given input (n=10), the script evaluates the condition n<20 as true and returns "10,大于等于 5 且小于 20". This demonstrates that the Chinese syntax is functionally equivalent to its English counterpart.


Practical Applications

The ability to create custom scripting languages opens numerous possibilities:

Business Rule Engines

Organizations often need to encode business logic that changes frequently. A domain-specific language allows business analysts to modify rules without developer intervention. Imagine a pricing engine where marketing teams can directly update discount rules using familiar terminology.

Educational Tools

Programming education becomes more accessible when students can start with concepts in their native language. A Chinese scripting engine could serve as an introduction to programming concepts before transitioning to traditional languages.

Configuration and Automation

Complex systems often require configuration that goes beyond simple key-value pairs. A custom scripting language allows users to express sophisticated logic in configuration files—conditional settings, computed values, and dynamic behavior.

Workflow Automation

Business processes can be encoded as scripts that non-technical users can modify. Approval workflows, data transformation pipelines, and notification systems all benefit from this approach.


Best Practices for Language Design

When creating custom languages with AScript (or any similar framework), consider these guidelines:

Start Small

Begin with a minimal set of features and expand gradually. It's easier to add functionality than to remove or redesign existing features. Start with variables, basic operations, and one control structure.

Maintain Consistency

Once you establish syntax patterns, stick with them. If "如果...则..." is your conditional syntax, don't introduce an alternative "当...的时候..." unless there's a compelling reason. Consistency reduces cognitive load for script authors.

Provide Clear Error Messages

When scripts fail to parse or execute, error messages should guide users toward correction. AScript's framework allows you to customize error reporting—use this capability to provide context-aware guidance.

Document Extensively

Custom languages lack the extensive documentation and community knowledge of established languages. Invest in comprehensive documentation with examples for every feature.

Test Thoroughly

Create a test suite that covers all language features and their combinations. Edge cases in custom languages can produce unexpected behavior—automated tests catch these issues before users encounter them.


Advanced Considerations

Performance Optimization

For high-frequency script execution, consider these optimizations:

  • Caching Parsed Scripts: If the same script runs multiple times, cache the parsed syntax tree to avoid repeated parsing.
  • Compilation to IL: For performance-critical scenarios, AScript can compile scripts to .NET Intermediate Language for faster execution.
  • Minimal Runtime: Reduce the script runtime footprint by only including necessary features.

Security Boundaries

When executing user-provided scripts, implement appropriate security measures:

  • Resource Limits: Restrict execution time and memory usage to prevent denial-of-service scenarios.
  • Capability Restrictions: Limit which APIs and system resources scripts can access.
  • Input Validation: Sanitize all inputs to scripts to prevent injection attacks.

Debugging Support

Consider adding debugging capabilities:

  • Line Number Tracking: Map execution back to source lines for error reporting.
  • Variable Inspection: Allow users to examine variable values during execution.
  • Step-Through Execution: Enable stepping through script execution for troubleshooting.

Comparison with Alternative Approaches

Embedded Domain-Specific Languages (EDSL)

Some teams choose to create EDSLs using the host language's features (e.g., fluent interfaces in C#). While this approach offers type safety and IDE support, it requires recompilation for changes and may be less accessible to non-developers.

External DSLs with Custom Parsers

Building a parser from scratch using tools like ANTLR provides maximum flexibility but requires significant investment in parser design, testing, and maintenance. AScript offers a middle ground—custom syntax with a pre-built execution framework.

Scripting with Existing Languages

Using established scripting languages (Lua, Python, JavaScript) provides mature ecosystems but may introduce unnecessary complexity or licensing concerns. Custom languages offer precise control over capabilities and syntax.


Conclusion: The Art of Language Design

Creating a custom scripting language with AScript demonstrates a fundamental truth about software development: the best tools are those that match the problem domain. By designing languages that speak the user's terminology—whether that's Chinese keywords or business-specific concepts—we bridge the gap between human intention and machine execution.

The Chinese script engine example illustrates that language design is not about complexity—it's about clarity. Every keyword, every syntax rule, every type mapping should serve the goal of making the user's intent unmistakably clear.

As you embark on your own language design journey, remember that the most successful domain-specific languages are those that feel inevitable to their users. When a business analyst can express a complex rule in language that feels natural to them, when a student can grasp programming concepts without fighting unfamiliar syntax, when a system administrator can automate tasks using terminology from their domain—that's when custom languages fulfill their promise.

AScript provides the foundation; your domain knowledge provides the direction. Together, they enable a new class of applications that are both powerful and accessible.


Further Reading

  • AScript GitHub Repository: Explore the source code and additional examples
  • Language Implementation Patterns: Study common approaches to language design
  • Domain-Specific Languages by Martin Fowler: Comprehensive guide to DSL design principles

The journey of language design is ongoing. Each feature you add, each syntax refinement you make, brings your users one step closer to expressing their intentions with perfect clarity. Start small, iterate often, and always keep your users' needs at the center of your design decisions.