Skip to main content
Domain 是 Fluxon 的”控制流扩展点”。 它提供了比 :: 上下文调用更强大的机制,允许执行器完全控制代码块的执行时机、方式和环境。 Domain 的核心特性是闭包语义:执行器接收的是未求值的 AST,可以决定是否执行、何时执行、执行多少次。

适用场景

  • 异步执行:async { fetch("http://api.example.com") }
  • 事务管理:transaction { updateDatabase(); commit() }
  • 重试逻辑:retry { unstableOperation() }
  • 结构化并发:scope { async { task1() }; async { task2() } }
  • 条件执行:lazy { expensiveComputation() }

与其他机制的区别

特性:: 上下文调用Domain
语法value :: blockdomain block
块执行立即执行,无法延迟执行器控制执行时机
闭包语义不支持(块立即求值)支持(接收未求值的 AST)
编译支持❌(仅解释执行)
适用场景简单上下文传递异步、事务、重试、结构化并发
Domain 的字节码生成会抛 UnsupportedOperationException,因此含 domain 的脚本不要走 编译执行引擎

组成与执行流程

组件作用
DomainRegistry注册表:domainName -> DomainExecutor
DomainExecutor执行器:接收 EnvironmentSupplier<Object>(域体闭包)
DomainSyntaxMacro内置语法宏:命中已注册标识符
DomainExpressionAST 节点:在解析时捕获 executor 和域体 AST
执行大致流程:
  1. Parser 读到 IDENTIFIERDomainSyntaxMacro 发现已注册同名 domain。
  2. 先消费 domain 名称 token,再解析后续的块或表达式作为域体。
  3. 域体 AST 与 DomainExecutor 一起被封装进 DomainExpression
  4. Interpreter 执行到该节点时,将域体包装为 Supplier<Object> 并调用 executor。

注册与隔离

使用全局主注册表(最常见)

在应用启动时注册即可(建议注册阶段单线程):
DomainRegistry.primary().register("async", (env, body) -> {
    return CompletableFuture.supplyAsync(body::get);
});

为沙箱/多租户使用独立注册表

如果你需要”每份脚本一套 domain 集合”,可以创建独立实例并放到 CompilationContext
DomainRegistry registry = new DomainRegistry();
registry.register("async", (env, body) -> CompletableFuture.supplyAsync(body::get));

CompilationContext ctx = new CompilationContext(source);
ctx.setDomainRegistry(registry);

Environment env = FluxonRuntime.getInstance().newEnvironment();
List<ParseResult> ast = Fluxon.parse(env, ctx);
Object result = Fluxon.eval(ast, env);
如果你也自定义了 SyntaxMacroRegistry,记得把 DomainSyntaxMacro 放进去; 否则即使 registry 有 domain,解析器也不会命中。

最小示例:实现 retry

这个 domain 失败时最多重试 3 次:
import org.tabooproject.fluxon.Fluxon;
import org.tabooproject.fluxon.compiler.CompilationContext;
import org.tabooproject.fluxon.parser.DomainRegistry;
import org.tabooproject.fluxon.parser.ParseResult;
import org.tabooproject.fluxon.runtime.Environment;
import org.tabooproject.fluxon.runtime.FluxonRuntime;

import java.util.List;

public final class DomainDemo {

  public static void main(String[] args) {
    DomainRegistry registry = new DomainRegistry();
    
    registry.register("retry", (env, body) -> {
      int maxAttempts = 3;
      Exception lastException = null;
      
      for (int i = 0; i < maxAttempts; i++) {
        try {
          return body.get();  // 执行域体
        } catch (Exception e) {
          lastException = e;
          System.out.println("Attempt " + (i + 1) + " failed: " + e.getMessage());
        }
      }
      
      throw new RuntimeException("All " + maxAttempts + " attempts failed", lastException);
    });

    String source = "retry { unstableOperation() }";
    
    CompilationContext ctx = new CompilationContext(source);
    ctx.setDomainRegistry(registry);
    
    Environment env = FluxonRuntime.getInstance().newEnvironment();
    // 注册一个会失败 2 次的函数
    env.defineRootFunction("unstableOperation", new NativeFunction<>(null, funcCtx -> {
      // 实际实现...
      return "success";
    }));
    
    List<ParseResult> ast = Fluxon.parse(env, ctx);
    System.out.println(Fluxon.eval(ast, env));
  }
}

高级用法:通过 target 实现结构化并发

Domain 可以通过 Environment#setTarget(Object) 设置上下文对象,使内部代码能够访问域的上下文。 这是实现结构化并发、事务等协作模式的基础。

示例:Scope + Async

// 注册 scope 域:创建任务作用域
registry.register("scope", (env, body) -> {
    TaskScope scope = new TaskScope();
    Object previousTarget = env.getTarget();
    try {
        env.setTarget(scope);  // 设置 target,使内部代码能访问 scope
        Object result = body.get();
        scope.join();  // 等待所有子任务完成
        return result;
    } finally {
        env.setTarget(previousTarget);  // 恢复之前的 target
    }
});

// 注册 async 域:检查是否在 scope 内
registry.register("async", (env, body) -> {
    Object target = env.getTarget();
    
    // 如果在 scope 内,将任务注册到 scope
    if (target instanceof TaskScope) {
        TaskScope scope = (TaskScope) target;
        return scope.submit(() -> body.get());
    }
    
    // 否则独立执行
    return CompletableFuture.supplyAsync(body::get);
});
在脚本中使用:
scope {
    task1 = async { longOperation1() }  // 注册到 scope
    task2 = async { longOperation2() }  // 注册到 scope
    // scope.join() 自动等待所有任务完成
}

Target 类型检查约定

推荐使用 instanceof 进行类型检查:
Object target = env.getTarget();
if (target instanceof TaskScope) {
    TaskScope scope = (TaskScope) target;
    // 使用 scope
} else if (target instanceof TransactionContext) {
    TransactionContext tx = (TransactionContext) target;
    // 使用事务上下文
}

常见模式

1. 异步执行

registry.register("async", (env, body) -> {
    return CompletableFuture.supplyAsync(body::get);
});
脚本:
future = async { fetch("http://api.example.com") }

2. 结果转换

registry.register("double", (env, body) -> {
    Object result = body.get();
    if (result instanceof Number) {
        return ((Number) result).intValue() * 2;
    }
    return result;
});
脚本:
value = double { 21 }  // 返回 42

3. 条件执行(惰性求值)

registry.register("lazy", (env, body) -> {
    // 不执行 body,返回一个惰性求值的包装器
    return new LazyValue(body);
});
脚本:
lazyValue = lazy { expensiveComputation() }
// 只有在需要时才执行

4. 环境修改

registry.register("withContext", (env, body) -> {
    env.assign("contextValue", "special-context", -1);
    return body.get();
});
脚本:
withContext {
    print(&contextValue)  // 输出: special-context
}

注意事项

  • 命名规则:domain 名称是标识符(IDENTIFIER),允许包含 -(例如 retry-3-times)。
  • 命名冲突:domain 的匹配发生在”标识符兜底”之前, 如果你注册了与函数/变量同名的 domain,会改变解析结果。
  • 闭包捕获:域体是闭包,可以访问外部作用域的变量。
  • 异常处理
    • 执行器内抛出的异常会传播到调用方。
    • 建议抛出 FluxonRuntimeError 子类以获得 SourceTrace。
  • 线程安全
    • DomainExecutor 可能在多线程环境下被并发调用,应保证无状态或正确处理并发访问。
    • Environment 不是线程安全的,异步执行时需要创建新的环境或使用同步机制。

相关链接