use compact_str::ToCompactString;
use lashlang::{
AbilityOp, AbilityResult, ExecutionHost, ExecutionHostError, HostValue, ImageValue,
LASH_PROCESS_NAME_KEY, LashlangAbilities, LashlangSurface, LinkedModule, ListValue,
ProjectedBindings, ProjectedFuture, ProjectedHostValue, ProjectedReadRequest,
ProjectedReadResponse, ProjectedValue, Record, ResourceCatalog, State, TypeExpr, TypeField,
Value, from_json,
};
use std::fmt;
use std::sync::{Arc, OnceLock};
#[derive(Clone, Copy, Debug)]
pub enum Scenario {
Baseline,
LanguageSurface,
AsyncAwait,
DirectUnwrap,
GeneralFanout,
LoopControl,
IndexedAssignment,
ProjectedValues,
LargeData,
CachePressure,
ProjectedOperations,
TypeSystemStress,
WrappedErrorPaths,
ToolControlSurface,
SnapshotProjectedState,
ContinueAsSeedSurface,
TriggerRegistrySurface,
SyntaxTextSurface,
IntegerRangeSurface,
FanoutExpressionSurface,
ImageSurface,
}
impl Scenario {
pub const ALL: &'static [Self] = &[
Self::Baseline,
Self::LanguageSurface,
Self::AsyncAwait,
Self::DirectUnwrap,
Self::GeneralFanout,
Self::LoopControl,
Self::IndexedAssignment,
Self::ProjectedValues,
Self::LargeData,
Self::CachePressure,
Self::ProjectedOperations,
Self::TypeSystemStress,
Self::WrappedErrorPaths,
Self::ToolControlSurface,
Self::SnapshotProjectedState,
Self::ContinueAsSeedSurface,
Self::TriggerRegistrySurface,
Self::SyntaxTextSurface,
Self::IntegerRangeSurface,
Self::FanoutExpressionSurface,
Self::ImageSurface,
];
#[allow(dead_code)]
pub fn parse(value: &str) -> Option<Self> {
Some(match value {
"baseline" => Self::Baseline,
"language_surface" => Self::LanguageSurface,
"async_await" => Self::AsyncAwait,
"direct_unwrap" => Self::DirectUnwrap,
"general_fanout" => Self::GeneralFanout,
"loop_control" => Self::LoopControl,
"indexed_assignment" => Self::IndexedAssignment,
"projected_values" => Self::ProjectedValues,
"large_data" => Self::LargeData,
"cache_pressure" => Self::CachePressure,
"projected_operations" => Self::ProjectedOperations,
"type_system_stress" => Self::TypeSystemStress,
"wrapped_error_paths" => Self::WrappedErrorPaths,
"tool_control_surface" => Self::ToolControlSurface,
"snapshot_projected_state" => Self::SnapshotProjectedState,
"continue_as_seed_surface" => Self::ContinueAsSeedSurface,
"trigger_registry_surface" => Self::TriggerRegistrySurface,
"syntax_text_surface" => Self::SyntaxTextSurface,
"integer_range_surface" => Self::IntegerRangeSurface,
"fanout_expression_surface" => Self::FanoutExpressionSurface,
"image_surface" => Self::ImageSurface,
_ => return None,
})
}
#[allow(dead_code)]
pub fn expected_values() -> &'static str {
"baseline, language_surface, async_await, direct_unwrap, general_fanout, loop_control, indexed_assignment, projected_values, large_data, cache_pressure, projected_operations, type_system_stress, wrapped_error_paths, tool_control_surface, snapshot_projected_state, continue_as_seed_surface, trigger_registry_surface, syntax_text_surface, integer_range_surface, fanout_expression_surface, image_surface, or all"
}
}
impl fmt::Display for Scenario {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Baseline => "baseline",
Self::LanguageSurface => "language_surface",
Self::AsyncAwait => "async_await",
Self::DirectUnwrap => "direct_unwrap",
Self::GeneralFanout => "general_fanout",
Self::LoopControl => "loop_control",
Self::IndexedAssignment => "indexed_assignment",
Self::ProjectedValues => "projected_values",
Self::LargeData => "large_data",
Self::CachePressure => "cache_pressure",
Self::ProjectedOperations => "projected_operations",
Self::TypeSystemStress => "type_system_stress",
Self::WrappedErrorPaths => "wrapped_error_paths",
Self::ToolControlSurface => "tool_control_surface",
Self::SnapshotProjectedState => "snapshot_projected_state",
Self::ContinueAsSeedSurface => "continue_as_seed_surface",
Self::TriggerRegistrySurface => "trigger_registry_surface",
Self::SyntaxTextSurface => "syntax_text_surface",
Self::IntegerRangeSurface => "integer_range_surface",
Self::FanoutExpressionSurface => "fanout_expression_surface",
Self::ImageSurface => "image_surface",
})
}
}
#[allow(dead_code)]
pub fn seeded_state() -> State {
seeded_state_for(Scenario::Baseline)
}
pub fn seeded_state_for(scenario: Scenario) -> State {
let mut globals = Record::default();
globals.insert(
"history".to_string(),
Value::List(
vec![
Value::String("alpha".to_string().into()),
Value::String("beta".to_string().into()),
Value::String("gamma".to_string().into()),
]
.into(),
),
);
globals.insert(
"ctx".to_string(),
Value::Record({
let mut record = Record::default();
record.insert("user".to_string(), Value::String("sam".into()));
record.insert("attempt".to_string(), Value::Number(3.0));
record.into()
}),
);
if matches!(scenario, Scenario::SnapshotProjectedState) {
globals.insert("snap".to_string(), snapshot_projected_record());
}
if matches!(scenario, Scenario::ImageSurface) {
globals.insert(
"img".to_string(),
Value::Image(ImageValue::new(
"img-1",
"chart.png",
1234,
Some(640),
Some(480),
)),
);
}
State::from_snapshot(lashlang::Snapshot { globals })
}
const BENCH_PROCESS_DECLS: &str = r#"
process echo(value: any) {
finish value
}
process spawn_child(task: str, capability: str) {
finish { claim: format("done:{0}", task) }
}
process query_llm(prompt: str, model: str) {
finish { text: "benchmark summary", tokens: 42 }
}
"#;
pub fn benchmark_program(scenario: Scenario) -> String {
let main = match scenario {
Scenario::Baseline => {
r#"
items = [
{ label: "alpha", weight: 1, active: true },
{ label: "beta", weight: 2, active: false },
{ label: "gamma", weight: 3, active: true }
]
indexes = range(0, len(items))
all_indexes = push(indexes, len(items))
total = 0
labels = []
for item in items {
total = total + item.weight
if item.active {
labels = labels + [format("{0}:{1}", item.label, item.weight)]
}
}
lookup_handle = start echo(value: join(labels, ","))
stats_handle = start echo(value: { total: total, count: len(items), seen: len(history), index_count: len(all_indexes) })
fanout = await { lookup: lookup_handle, stats: stats_handle }
lookup_value = fanout.lookup?
stats_value = validate(fanout.stats?, Type { total: int, count: int, seen: int, index_count: int })
summary = format(
"user={0};attempt={1};active={2};total={3};count={4};seen={5};indexes={6}",
ctx.user,
ctx.attempt,
lookup_value,
stats_value.total,
stats_value.count,
stats_value.seen,
stats_value.index_count
)
submit summary
"#
}
Scenario::LanguageSurface => {
r#"
source = join(history, ",")
tokens = split(source, ",")
trimmed_user = trim(format(" {0} ", ctx.user))
beta_index = find(source, "beta")
line_matches = grep_text(join(tokens, "\n"), "a")
count = len(tokens)
empty_tail = empty(slice(tokens, count, count))
predicates = [
contains(source, tokens[1]),
starts_with(source, tokens[0]),
ends_with(source, tokens[2])
]
numeric = {
neg: -ctx.attempt,
sum: ctx.attempt + count,
diff: count - 1,
product: count * 2,
quotient: count / 2,
modulo: count % 2,
parsed_int: to_int(ctx.attempt),
parsed_float: to_float(ctx.attempt)
}
logic = not false and (count > 2 or empty_tail)
comparisons = [
count == 3,
count != 4,
count < 4,
count <= 3,
count > 2,
count >= 3
]
choice = logic ? "yes" : "no"
json_text = "{\"attempt\":" + to_string(ctx.attempt) + ",\"ok\":true}"
parsed = json_parse(json_text)
positional_results = await [
start echo(value: format("left:{0}", tokens[0])),
start echo(value: tokens[1]),
start echo(value: len(tokens))
]
positional = [positional_results[0]?, positional_results[1], positional_results[2]?]
named_results = await {
lookup: start echo(value: source),
summary: start echo(value: format("{0}:{1}", trimmed_user, count))
}
named = { lookup: named_results.lookup, summary: named_results.summary? }
cancelled = start echo(value: "cancelled")
cancel cancelled
handle = start echo(value: "awaited")
awaited = (await handle)?
direct = await tools.echo({ value: trimmed_user })?
print direct
state = {
tags: push(tokens, "delta"),
counts: {},
kept: [],
predicates: predicates,
comparisons: comparisons,
numeric: numeric,
parsed: parsed,
line_matches: line_matches
}
state.tags[1] = "beta"
for token in state.tags {
state.counts[token] = state.counts[token] + 1
}
for token in keys(state.counts) {
if token == "beta" {
continue
}
if token == "delta" {
break
}
state.kept = push(state.kept, token)
}
Payload = Type {
user: str,
choice: enum["yes", "no"],
tags: list[str],
counts: dict,
kept: list[str],
maybe: str | null,
optional_note: str?
}
validated = validate(
{
user: direct,
choice: choice,
tags: state.tags,
counts: state.counts,
kept: state.kept,
maybe: null
},
Payload
)
submit {
direct: direct,
awaited: awaited,
positional: positional,
lookup: named.lookup?,
summary: named.summary,
values: values(state.counts),
beta_index: beta_index,
line_matches: line_matches,
first_two: slice(state.tags, null, 2),
validated: validated,
stringified: to_string(validated)
}
"#
}
Scenario::AsyncAwait => {
r#"
handles = {
alpha: start echo(value: "alpha"),
beta: start echo(value: "beta"),
gamma: start echo(value: "gamma")
}
results = await handles
formatted = [results.alpha?, results.beta?, results.gamma?]
submit join(formatted, ",")
"#
}
Scenario::DirectUnwrap => {
r#"
first = await tools.echo({ value: "alpha" })?
second = await tools.echo({ value: format("{0}:{1}", first, "beta") })?
third = await tools.echo({ value: join([first, second], ",") })?
submit third
"#
}
Scenario::GeneralFanout => {
r#"
seed = ["alpha", "beta", "gamma"]
results = await {
left: start echo(value: format("{0}:{1}", seed[0], len(seed))),
right: start echo(value: format("{0}:{1}", seed[1], len(seed)))
}
submit format("{0}|{1}", results.left?, results.right?)
"#
}
Scenario::LoopControl => {
r#"
items = range(0, 128)
outer = "restored"
kept = 0
skipped = 0
for outer in items {
if outer < 32 {
skipped = skipped + 1
continue
}
if outer >= 96 {
break
}
if outer % 3 == 0 {
continue
}
kept = kept + 1
}
submit { kept: kept, skipped: skipped, outer: outer }
"#
}
Scenario::IndexedAssignment => {
r#"
groups = ["alpha", "beta", "alpha", "gamma", "beta", "alpha", "delta", "gamma"]
counts = {}
for group in groups {
counts[group] = counts[group] + 1
}
state = { groups: { alpha: { count: 0 }, beta: { count: 0 }, gamma: { count: 0 }, delta: { count: 0 } } }
for group in keys(counts) {
state.groups[group].count = counts[group]
}
summary = []
for group in keys(counts) {
summary = summary + [format("{0}:{1}", group, state.groups[group].count)]
}
submit { counts: counts, state: state, summary: join(summary, ",") }
"#
}
Scenario::ProjectedValues => {
r#"
first = history[0]
second = history[1]
body_head = docs.body[0]
body_match_index = find(docs.body, "markdown")
body_matches = grep_text(docs.body, "markdown")
second_matches = grep_text(second.content, "response")
if docs.body {
body_truthy = true
} else {
body_truthy = false
}
print docs.body
summary = {
history_len: len(history),
first_role: first.role,
first_content: first.content,
second_content: second.content,
doc_title: docs.title,
doc_summary: docs.summary,
body_head: body_head,
body_match_index: body_match_index,
body_matches: body_matches,
second_matches: second_matches,
body_truthy: body_truthy,
body_text: docs.body
}
submit summary
"#
}
Scenario::LargeData => {
r#"
items = range(0, 512)
groups = {}
total = 0
evens = []
odds = []
for item in items {
key = format("bucket_{0}", item % 16)
groups[key] = groups[key] + 1
total = total + item
if item % 2 == 0 {
evens = push(evens, item)
} else {
odds = push(odds, item)
}
}
lines = []
for key in keys(groups) {
lines = push(lines, format("{0}:{1}", key, groups[key]))
}
payload = {
count: len(items),
total: total,
groups: groups,
evens: len(evens),
odds: len(odds),
summary: join(lines, "|")
}
submit validate(payload, Type {
count: int,
total: int,
groups: dict,
evens: int,
odds: int,
summary: str
})
"#
}
Scenario::CachePressure => {
r#"
seed = {
user: ctx.user,
attempt: ctx.attempt,
history_len: len(history),
labels: ["alpha", "beta", "gamma", "delta", "epsilon", "zeta"]
}
a0 = format("{0}:{1}", seed.labels[0], seed.attempt)
a1 = format("{0}:{1}", seed.labels[1], seed.history_len)
a2 = format("{0}:{1}", seed.labels[2], len(a0))
a3 = format("{0}:{1}", seed.labels[3], len(a1))
a4 = format("{0}:{1}", seed.labels[4], len(a2))
a5 = format("{0}:{1}", seed.labels[5], len(a3))
r0 = { name: "r0", value: a0, next: a1 }
r1 = { name: "r1", value: a1, next: a2 }
r2 = { name: "r2", value: a2, next: a3 }
r3 = { name: "r3", value: a3, next: a4 }
r4 = { name: "r4", value: a4, next: a5 }
r5 = { name: "r5", value: a5, next: a0 }
TypeA = Type { name: str, value: str, next: str }
validated = [
validate(r0, TypeA),
validate(r1, TypeA),
validate(r2, TypeA),
validate(r3, TypeA),
validate(r4, TypeA),
validate(r5, TypeA)
]
submit join([
validated[0].value,
validated[1].value,
validated[2].value,
validated[3].value,
validated[4].value,
validated[5].value
], "|")
"#
}
Scenario::ProjectedOperations => {
r#"
summary = {
len: len(proj.items),
empty: empty(proj.items),
keys: keys(proj.record),
values: values(proj.record),
contains: contains(proj.items, "beta"),
starts: starts_with(proj.text, "alpha"),
ends: ends_with(proj.text, "delta"),
split_count: len(split(proj.text, " ")),
join: join(proj.items, ","),
trim: trim(proj.padded),
slice_text: slice(proj.text, 6, 10),
slice_list: slice(proj.items, 1, 3),
pushed: push(proj.items, "epsilon"),
as_int: to_int(proj.number),
as_float: to_float(proj.number),
parsed: json_parse(proj.json),
first: proj.items[0],
field: proj.record.topic
}
submit summary
"#
}
Scenario::TypeSystemStress => {
r#"
Meta = Type {
source: str,
attempt: int,
tags: list[enum["alpha", "beta", "gamma", "delta"]],
optional_note: str?
}
Item = Type {
id: int,
title: str,
score: float,
active: bool,
meta: Meta,
maybe: str | int | null
}
items = []
for i in range(0, 64) {
raw = {
id: i,
title: format("item-{0}", i),
score: i / 2,
active: i % 2 == 0,
meta: {
source: ctx.user,
attempt: ctx.attempt,
tags: ["alpha", "beta", "gamma"]
},
maybe: i % 3 == 0 ? null : format("v{0}", i)
}
items = push(items, validate(raw, Item))
}
submit {
count: len(items),
first: items[0].title,
last: items[63].title,
tags: join(items[1].meta.tags, ",")
}
"#
}
Scenario::WrappedErrorPaths => {
r#"
missing = await tools.missing_tool({ value: "x" })
boom = await tools.boom({ reason: "explicit" })
ok = await tools.echo({ value: "still-running" })
probe = await shell.exec({ cmd: "test -f Cargo.lock", allow_nonzero_exit: true })
submit {
missing_ok: missing.ok,
missing_error: contains(missing.error, "unknown tool"),
boom_ok: boom.ok,
boom_error: contains(boom.error, "explicit failure"),
ok_value: ok.value,
probe_exit: probe.value.exit_code,
probe_done: probe.value.done
}
"#
}
Scenario::ToolControlSurface => {
r#"
first = start spawn_child(task: "inspect auth", capability: "explore")
second = start spawn_child(task: "inspect api", capability: "explore")
llm = start query_llm(prompt: "summarize benchmark", model: "gpt-5.4-mini")
probe = start echo(value: "app log")
handles = await processes.list({})?
results = await {
first: first,
second: second,
llm: llm,
probe: probe
}
cancel second
submit {
first: results.first.value.claim,
second: results.second.value.claim,
llm: results.llm.value.text,
probe: results.probe.value,
tools: len(handles)
}
"#
}
Scenario::SnapshotProjectedState => {
r#"
head = slice(snap.projected.body, 0, 16)
materialized = to_string(snap.projected.body)
nested_head = snap.mixed.nested.projected_title
snap.mixed.count = snap.mixed.count + 1
submit {
id: snap.id,
normal_title: snap.normal.title,
head: head,
materialized_len: len(materialized),
nested_head: nested_head,
count: snap.mixed.count,
tags: join(snap.normal.tags, ",")
}
"#
}
Scenario::ContinueAsSeedSurface => {
r#"
agent = start spawn_child(task: "inspect carry-forward", capability: "explore")
handles = await processes.list({})?
frame = await control.continue_as({
task: "continue from compact state",
seed: {
projected_problem: proj.text,
nested_projected: { body: proj.json },
computed_summary: format("{0}:{1}", ctx.user, len(history)),
live_agent: handles[0],
started_agent: agent
}
})?
submit {
frame_id: frame.frame_id,
task: frame.task,
seed_keys: frame.seed_keys,
projected_count: frame.projected_count,
global_count: frame.global_count
}
"#
}
Scenario::TriggerRegistrySurface => {
r#"
process daily_digest(tick: cron.Tick) {
finish { kind: "daily_digest", fired_at: tick.fired_at }
}
process on_button(event: ui.button.Pressed) {
finish { kind: "button", button: event.button }
}
daily_handle = await triggers.register({
source: cron.Schedule({ expr: "0 8 * * *", tz: "UTC" }),
target: daily_digest,
inputs: { tick: trigger.event },
name: "daily_digest"
})?
button_handle = await triggers.register({
source: ui.button.pressed({}),
target: on_button,
inputs: { event: trigger.event },
name: "button watcher"
})?
registrations = await triggers.list({ target: daily_digest })?
cancelled = await triggers.cancel({ handle: daily_handle })?
submit {
daily_handle: daily_handle.id,
button_handle: button_handle.id,
registration_count: len(registrations),
listed_target: registrations[0].target.process_name,
listed_source: registrations[0].source_type,
cancelled: cancelled
}
"#
}
Scenario::SyntaxTextSurface => {
r####"
// Exercise parser-heavy string forms, comments, semicolon recovery, and text builtins.
patch = r"""*** Begin Patch
*** Update File: crates/lashlang/src/lib.rs
@@
-old
+new
\n { braces stay raw }
*** End Patch""";
script = r'''python3 - <<'PY'
print("""double quotes are preserved""")
\n { braces stay raw }
PY''';
plain = """first
"quoted"
second""";
"bare expression branch"
pieces = [
len(patch),
contains(patch, "*** Begin Patch"),
starts_with(script, "python3"),
ends_with(trim(script), "PY"),
len(split(plain, "\n")),
slice(plain, 0, 5)
]
submit {
patch_head: slice(patch, 0, 15),
script_head: slice(script, 0, 7),
plain_lines: len(split(plain, "\n")),
pieces: pieces
}
"####
}
Scenario::IntegerRangeSurface => {
r#"
items = range(-8, 9)
forward = range(0, 10, 3)
backward = range(7, -3, -2)
stride = ceil_div(len(items), 4)
starts = []
windows = []
for i in range(0, len(items), stride) {
starts = push(starts, i)
windows = push(windows, slice(items, i, i + stride))
}
text = "alpha beta gamma beta delta"
first_beta = find(text, "beta")
second_beta = find(text, "beta", first_beta + 1)
submit {
count: len(items),
first: items[0],
last: items[-1],
forward: forward,
backward: backward,
stride: stride,
starts: starts,
windows: windows,
mid: slice(items, 2, -2),
head: slice(text, null, 5),
tail: slice(text, -5, null),
first_beta: first_beta,
second_beta: second_beta,
ceil_neg: ceil_div(-10, 3),
floor_neg: floor_div(-10, 3)
}
"#
}
Scenario::FanoutExpressionSurface => {
r#"
left = await tools.echo({ value: "left" })
right = await tools.echo({ value: "right" })
computed = len(history) + 39
discarded = ["branch_a", 40 + 2, len(history)]
batched_results = await {
first: start echo(value: left.value),
second: start echo(value: right.value)
}
batched = { first: batched_results.first, second: batched_results.second, computed: computed }
submit {
left: left.value,
right: right.value,
computed: computed,
discarded: discarded,
first: batched.first?,
second: batched.second?,
batched_computed: batched.computed
}
"#
}
Scenario::ImageSurface => {
r#"
descriptor = to_string(img)
metadata = {
id: img.id,
label: img.label,
size: img.size,
width: img.width,
height: img.height,
missing: img.missing
}
print img
submit {
metadata: metadata,
descriptor_has_type: contains(descriptor, "\"type\":\"image\""),
descriptor_has_id: contains(descriptor, "\"id\":\"img-1\""),
dims: format("{0}x{1}", img.width, img.height),
size_bucket: floor_div(img.size, 100)
}
"#
}
};
format!("{BENCH_PROCESS_DECLS}\n{main}")
}
pub fn benchmark_surface() -> &'static LashlangSurface {
static SURFACE: OnceLock<LashlangSurface> = OnceLock::new();
SURFACE.get_or_init(build_benchmark_surface)
}
fn build_benchmark_surface() -> LashlangSurface {
let mut resources = ResourceCatalog::tool_default(["echo", "boom", "missing_tool"]);
lashlang::add_trigger_resource_operations(&mut resources);
resources
.add_trigger_source_constructor(
["cron", "Schedule"],
TypeExpr::Object(vec![
TypeField {
name: "expr".into(),
ty: TypeExpr::Str,
optional: false,
},
TypeField {
name: "tz".into(),
ty: TypeExpr::Str,
optional: true,
},
]),
lashlang::NamedDataType::object(
"cron.Tick",
vec![TypeField {
name: "fired_at".into(),
ty: TypeExpr::Str,
optional: false,
}],
)
.expect("valid cron tick type"),
)
.expect("valid cron trigger source");
resources
.add_trigger_source_constructor(
["ui", "button", "pressed"],
TypeExpr::Object(Vec::new()),
lashlang::NamedDataType::object(
"ui.button.Pressed",
vec![
TypeField {
name: "button".into(),
ty: TypeExpr::Union(vec![
TypeExpr::Enum(vec!["Red".into()]),
TypeExpr::Enum(vec!["Blue".into()]),
]),
optional: false,
},
TypeField {
name: "message".into(),
ty: TypeExpr::Str,
optional: false,
},
TypeField {
name: "pressed_at".into(),
ty: TypeExpr::Str,
optional: false,
},
],
)
.expect("valid button event type"),
)
.expect("valid button trigger source");
resources.add_module_operation(
["shell"],
"Shell",
"exec",
"exec_command",
TypeExpr::Any,
TypeExpr::Any,
);
resources.add_module_operation(
["llm"],
"Llm",
"query",
"llm_query",
TypeExpr::Any,
TypeExpr::Any,
);
resources.add_module_operation(
["agents"],
"Agents",
"spawn",
"spawn_agent",
TypeExpr::Any,
TypeExpr::Any,
);
resources.add_module_operation(
["processes"],
"Processes",
"list",
"list_process_handles",
TypeExpr::Any,
TypeExpr::Any,
);
resources.add_module_operation(
["control"],
"Control",
"continue_as",
"continue_as",
TypeExpr::Any,
TypeExpr::Any,
);
LashlangSurface::new(resources, LashlangAbilities::all())
}
pub fn linked_benchmark_program(source: &str) -> LinkedModule {
LinkedModule::link(
lashlang::parse(source).expect("benchmark program should parse"),
benchmark_surface(),
)
.expect("benchmark program should link")
}
pub fn projected_bindings(scenario: Scenario) -> ProjectedBindings {
let mut bindings = ProjectedBindings::new();
if !matches!(
scenario,
Scenario::ProjectedValues | Scenario::ProjectedOperations | Scenario::ContinueAsSeedSurface
) {
return bindings;
}
match scenario {
Scenario::ProjectedValues => {
bindings.insert(
"history",
ProjectedValue::custom("history", Arc::new(ProjectedList::history())),
);
bindings.insert(
"docs",
ProjectedValue::scalar("docs", projected_docs_record()),
);
}
Scenario::ProjectedOperations | Scenario::ContinueAsSeedSurface => {
bindings.insert(
"proj",
ProjectedValue::scalar("proj", projected_operations_record()),
);
}
_ => {}
}
bindings
}
fn projected_docs_record() -> Value {
let mut record = Record::default();
record.insert("title".to_string(), Value::String("Authoring Rules".into()));
record.insert(
"summary".to_string(),
Value::String("Rules for editing workflow JSON drafts.".into()),
);
record.insert(
"body".to_string(),
Value::Projected(ProjectedValue::custom(
"docs.body",
Arc::new(ProjectedText::new("body", "lazy markdown body")),
)),
);
Value::Record(Arc::new(record))
}
fn snapshot_projected_record() -> Value {
let mut projected = Record::default();
projected.insert(
"body".to_string(),
Value::Projected(ProjectedValue::custom(
"snap.projected.body",
Arc::new(ProjectedText::new(
"snapshot_body",
"projected body stays lazy across snapshot markers",
)),
)),
);
let mut normal = Record::default();
normal.insert("title".to_string(), Value::String("Snapshot Rules".into()));
normal.insert(
"tags".to_string(),
Value::List(
vec![
Value::String("snapshot".into()),
Value::String("projected".into()),
Value::String("mixed".into()),
]
.into(),
),
);
let mut nested = Record::default();
nested.insert(
"projected_title".to_string(),
Value::Projected(ProjectedValue::custom(
"snap.mixed.nested.projected_title",
Arc::new(ProjectedText::new("nested_title", "Nested Projection")),
)),
);
let mut mixed = Record::default();
mixed.insert("count".to_string(), Value::Number(7.0));
mixed.insert("nested".to_string(), Value::Record(Arc::new(nested)));
let mut root = Record::default();
root.insert("id".to_string(), Value::String("snapshot-mixed".into()));
root.insert("normal".to_string(), Value::Record(Arc::new(normal)));
root.insert("projected".to_string(), Value::Record(Arc::new(projected)));
root.insert("mixed".to_string(), Value::Record(Arc::new(mixed)));
Value::Record(Arc::new(root))
}
fn projected_operations_record() -> Value {
let mut record = Record::default();
record.insert(
"items".to_string(),
Value::Projected(ProjectedValue::custom(
"proj.items",
Arc::new(ProjectedList {
name: "items",
values: vec![
Value::String("alpha".into()),
Value::String("beta".into()),
Value::String("gamma".into()),
Value::String("delta".into()),
]
.into(),
}),
)),
);
record.insert(
"text".to_string(),
Value::Projected(ProjectedValue::custom(
"proj.text",
Arc::new(ProjectedText::new("text", "alpha beta gamma delta")),
)),
);
record.insert(
"padded".to_string(),
Value::Projected(ProjectedValue::custom(
"proj.padded",
Arc::new(ProjectedText::new("padded", " alpha ")),
)),
);
record.insert(
"json".to_string(),
Value::Projected(ProjectedValue::custom(
"proj.json",
Arc::new(ProjectedText::new("json", "{\"ok\":true,\"count\":4}")),
)),
);
record.insert(
"number".to_string(),
Value::Projected(ProjectedValue::scalar("proj.number", Value::Number(4.0))),
);
record.insert(
"record".to_string(),
Value::Projected(ProjectedValue::scalar("proj.record", {
let mut inner = Record::default();
inner.insert("topic".to_string(), Value::String("bench".into()));
inner.insert("count".to_string(), Value::Number(4.0));
Value::Record(Arc::new(inner))
})),
);
Value::Record(Arc::new(record))
}
struct ProjectedList {
name: &'static str,
values: ListValue,
}
impl ProjectedList {
fn history() -> Self {
Self {
name: "history",
values: vec![
history_item("user", "alpha request"),
history_item("assistant", "beta response"),
history_item("tool", "gamma observation"),
]
.into(),
}
}
}
fn string_value(value: &Value) -> Result<compact_str::CompactString, ()> {
match value {
Value::String(value) => Ok(value.clone()),
Value::Number(value) => Ok(value.to_string().into()),
Value::Bool(value) => Ok(if *value { "true" } else { "false" }.into()),
Value::Null => Ok("null".into()),
_ => Err(()),
}
}
fn clamp_slice_bounds(
start: Option<isize>,
end: Option<isize>,
len: usize,
) -> Option<(usize, usize)> {
let len = len as isize;
let normalize = |bound: Option<isize>, default| {
let bound = bound.unwrap_or(default);
if bound < 0 { len + bound } else { bound }.clamp(0, len)
};
let start = normalize(start, 0);
let end = normalize(end, len);
(start < end).then_some((start as usize, end as usize))
}
fn slice_string(text: &str, start: Option<isize>, end: Option<isize>) -> String {
let chars = text.chars().collect::<Vec<_>>();
let Some((start, end)) = clamp_slice_bounds(start, end, chars.len()) else {
return String::new();
};
chars[start..end].iter().collect()
}
impl ProjectedHostValue for ProjectedList {
fn type_name(&self) -> &str {
"list"
}
fn read_one(
&self,
request: ProjectedReadRequest,
) -> ProjectedFuture<'_, ProjectedReadResponse> {
Box::pin(async move {
let ProjectedReadRequest::Index(index) = request else {
return match request {
ProjectedReadRequest::Len => ProjectedReadResponse::Len(self.values.len()),
ProjectedReadRequest::Empty => {
ProjectedReadResponse::Bool(self.values.is_empty())
}
ProjectedReadRequest::Truthy => ProjectedReadResponse::Bool(true),
ProjectedReadRequest::Contains(needle) => ProjectedReadResponse::Bool(
self.values.iter().any(|value| value == &needle),
),
ProjectedReadRequest::Join(sep) => {
let Ok(sep) = string_value(&sep) else {
return ProjectedReadResponse::Missing;
};
let mut joined = String::new();
for (index, value) in self.values.iter().enumerate() {
let Ok(value) = string_value(value) else {
return ProjectedReadResponse::Missing;
};
if index > 0 {
joined.push_str(sep.as_ref());
}
joined.push_str(value.as_ref());
}
ProjectedReadResponse::Value(Value::String(joined.into()))
}
ProjectedReadRequest::Slice { start, end } => {
let Some((start, end)) = clamp_slice_bounds(start, end, self.values.len())
else {
return ProjectedReadResponse::Value(Value::List(Vec::new().into()));
};
ProjectedReadResponse::Value(Value::List(
self.values[start..end].to_vec().into(),
))
}
ProjectedReadRequest::Push(item) => {
let mut values = self.values.to_vec();
values.push(item);
ProjectedReadResponse::Value(Value::List(values.into()))
}
ProjectedReadRequest::Render => ProjectedReadResponse::Text(format!(
"<{}:{}>",
self.name,
self.values.len()
)),
ProjectedReadRequest::Materialize => {
ProjectedReadResponse::Value(Value::List(self.values.clone()))
}
_ => ProjectedReadResponse::Missing,
};
};
let Some(index) = resolve_index(&index, self.values.len()) else {
return ProjectedReadResponse::Missing;
};
self.values
.get(index)
.cloned()
.map(ProjectedReadResponse::Value)
.unwrap_or(ProjectedReadResponse::Missing)
})
}
}
fn history_item(role: &str, content: &str) -> Value {
let mut record = Record::default();
record.insert("role".to_string(), Value::String(role.to_string().into()));
record.insert(
"content".to_string(),
Value::Projected(ProjectedValue::custom(
format!("history.{role}.content"),
Arc::new(ProjectedText::new("content", content)),
)),
);
Value::Record(Arc::new(record))
}
struct ProjectedText {
name: &'static str,
text: Arc<str>,
}
impl ProjectedText {
fn new(name: &'static str, text: impl Into<Arc<str>>) -> Self {
Self {
name,
text: text.into(),
}
}
}
impl ProjectedHostValue for ProjectedText {
fn type_name(&self) -> &str {
"string"
}
fn read_one(
&self,
request: ProjectedReadRequest,
) -> ProjectedFuture<'_, ProjectedReadResponse> {
Box::pin(async move {
match request {
ProjectedReadRequest::Len => ProjectedReadResponse::Len(self.text.chars().count()),
ProjectedReadRequest::Empty => ProjectedReadResponse::Bool(self.text.is_empty()),
ProjectedReadRequest::Truthy => ProjectedReadResponse::Bool(!self.text.is_empty()),
ProjectedReadRequest::Index(index) => {
let Some(index) = resolve_index(&index, self.text.chars().count()) else {
return ProjectedReadResponse::Missing;
};
self.text
.chars()
.nth(index)
.map(|ch| {
ProjectedReadResponse::Value(Value::String(ch.to_compact_string()))
})
.unwrap_or(ProjectedReadResponse::Missing)
}
ProjectedReadRequest::Find { needle, start } => {
let Value::String(needle) = needle else {
return ProjectedReadResponse::Missing;
};
ProjectedReadResponse::Value(match find_text(&self.text, &needle, start) {
Some(index) => Value::Number(index as f64),
None => Value::Null,
})
}
ProjectedReadRequest::GrepText(needle) => {
let Value::String(needle) = needle else {
return ProjectedReadResponse::Missing;
};
grep_text_records(&self.text, &needle)
.map(ProjectedReadResponse::Value)
.unwrap_or(ProjectedReadResponse::Missing)
}
ProjectedReadRequest::StartsWith(prefix) => {
let Value::String(prefix) = prefix else {
return ProjectedReadResponse::Missing;
};
ProjectedReadResponse::Bool(self.text.starts_with(&*prefix))
}
ProjectedReadRequest::EndsWith(suffix) => {
let Value::String(suffix) = suffix else {
return ProjectedReadResponse::Missing;
};
ProjectedReadResponse::Bool(self.text.ends_with(&*suffix))
}
ProjectedReadRequest::Split(needle) => {
let Value::String(needle) = needle else {
return ProjectedReadResponse::Missing;
};
ProjectedReadResponse::Value(Value::List(
self.text
.split(&*needle)
.map(|part| Value::String(part.into()))
.collect::<Vec<_>>()
.into(),
))
}
ProjectedReadRequest::Trim => {
ProjectedReadResponse::Value(Value::String(self.text.trim().into()))
}
ProjectedReadRequest::Slice { start, end } => ProjectedReadResponse::Value(
Value::String(slice_string(&self.text, start, end).into()),
),
ProjectedReadRequest::ToNumber => self
.text
.parse::<f64>()
.ok()
.map(Value::Number)
.map(ProjectedReadResponse::Value)
.unwrap_or(ProjectedReadResponse::Missing),
ProjectedReadRequest::JsonParse => {
serde_json::from_str::<serde_json::Value>(&self.text)
.map(from_json)
.map(ProjectedReadResponse::Value)
.unwrap_or(ProjectedReadResponse::Missing)
}
ProjectedReadRequest::Render => ProjectedReadResponse::Text(format!(
"<{}:{} chars>",
self.name,
self.text.chars().count()
)),
ProjectedReadRequest::Materialize => {
ProjectedReadResponse::Value(Value::String(self.text.as_ref().into()))
}
_ => ProjectedReadResponse::Missing,
}
})
}
}
fn grep_text_records(text: &str, needle: &str) -> Option<Value> {
if needle.is_empty() {
return None;
}
let needle_len = needle.chars().count();
let needle_value = Value::String(needle.into());
let mut matches = Vec::new();
for (line_index, line) in text.lines().enumerate() {
let Some(start) = find_text(line, needle, 0) else {
continue;
};
let mut record = Record::with_capacity(5);
record.insert_str("line", Value::Number((line_index + 1) as f64));
record.insert_str("text", Value::String(line.into()));
record.insert_str("match", needle_value.clone());
record.insert_str("start", Value::Number(start as f64));
record.insert_str("end", Value::Number((start + needle_len) as f64));
matches.push(Value::Record(Arc::new(record)));
}
Some(Value::List(matches.into()))
}
fn find_text(text: &str, needle: &str, start: usize) -> Option<usize> {
let start_byte = if start == 0 {
0
} else {
byte_index_for_char(text, start)?
};
if needle.is_empty() {
return Some(start);
}
let tail = &text[start_byte..];
let match_byte = tail.find(needle)?;
Some(start + tail[..match_byte].chars().count())
}
fn byte_index_for_char(text: &str, target: usize) -> Option<usize> {
let mut char_count = 0;
for (byte_index, _) in text.char_indices() {
if char_count == target {
return Some(byte_index);
}
char_count += 1;
}
(char_count == target).then_some(text.len())
}
fn resolve_index(index: &Value, len: usize) -> Option<usize> {
let Value::Number(index) = index else {
return None;
};
if !index.is_finite() || index.fract() != 0.0 {
return None;
}
let len = len as isize;
let index = *index as isize;
let normalized = if index < 0 { len + index } else { index };
(0..len)
.contains(&normalized)
.then_some(normalized as usize)
}
pub struct BenchHost;
impl ExecutionHost for BenchHost {
async fn perform(&self, op: AbilityOp) -> Result<AbilityResult, ExecutionHostError> {
match op {
AbilityOp::ResourceOperation(operation) => {
let empty = Record::new();
let args = operation
.args
.first()
.and_then(Value::as_record)
.unwrap_or(&empty);
bench_resource_call(&operation, args).map(AbilityResult::Value)
}
AbilityOp::StartProcess(start) => {
Self::task_handle(&start.process_name, &start.args).map(AbilityResult::Value)
}
AbilityOp::Await(handle) => {
let record = handle
.as_record()
.ok_or_else(|| ExecutionHostError::new("expected handle record"))?;
Ok(AbilityResult::Value(
record.get("value").cloned().unwrap_or(Value::Null),
))
}
AbilityOp::Cancel(handle) => Ok(AbilityResult::Value(handle)),
AbilityOp::Print(_) => Ok(AbilityResult::Unit),
AbilityOp::Submit(value) | AbilityOp::Finish(value) | AbilityOp::Fail(value) => {
Ok(AbilityResult::Value(value))
}
_ => Err(ExecutionHostError::new("unsupported host ability")),
}
}
}
fn bench_resource_call(
operation: &lashlang::ResourceOperation,
args: &Record,
) -> Result<Value, ExecutionHostError> {
let host_operation = match &operation.receiver {
Value::Resource(receiver) => benchmark_surface()
.resources
.resolve_module_operation(
&receiver.resource_type,
&receiver.alias,
&operation.operation,
)
.map(|binding| binding.host_operation.as_str())
.ok_or_else(|| {
ExecutionHostError::new(format!(
"module `{}` of type `{}` does not expose operation `{}`",
receiver.alias, receiver.resource_type, operation.operation
))
})?,
_ => operation.operation.as_str(),
};
bench_call(host_operation, args)
}
fn bench_call(name: &str, args: &Record) -> Result<Value, ExecutionHostError> {
match name {
"echo" => Ok(args.get("value").cloned().unwrap_or(Value::Null)),
"boom" => Err(ExecutionHostError::new("explicit failure for benchmark")),
"exec_command" => {
let mut record = Record::default();
record.insert("status".to_string(), Value::String("completed".into()));
record.insert("done".to_string(), Value::Bool(true));
record.insert("running".to_string(), Value::Bool(false));
record.insert("exit_code".to_string(), Value::Number(1.0));
record.insert(
"output".to_string(),
Value::String(
format!(
"ran: {}",
args.get("cmd")
.and_then(|value| match value {
Value::String(text) => Some(text.as_str()),
_ => None,
})
.unwrap_or("")
)
.into(),
),
);
Ok(Value::Record(Arc::new(record)))
}
"llm_query" | "query_llm" => {
let mut record = Record::default();
record.insert(
"text".to_string(),
Value::String("benchmark summary".into()),
);
record.insert("tokens".to_string(), Value::Number(42.0));
Ok(Value::Record(Arc::new(record)))
}
"spawn_agent" | "spawn_child" => {
let task = args
.get("task")
.and_then(|value| match value {
Value::String(text) => Some(text.as_str()),
_ => None,
})
.unwrap_or("agent");
let mut record = Record::default();
record.insert(
"claim".to_string(),
Value::String(format!("done:{task}").into()),
);
Ok(Value::Record(Arc::new(record)))
}
"list_process_handles" => Ok(process_handles_record()),
"continue_as" => Ok(continue_as_record(args)),
"triggers.register" => Ok(trigger_register_record(args)),
"triggers.list" => Ok(trigger_list_value(args)),
"triggers.cancel" => Ok(Value::Bool(true)),
_ => Err(unknown_tool(name)),
}
}
fn trigger_register_record(args: &Record) -> Value {
let source_type = args
.get("source")
.cloned()
.and_then(|source| serde_json::to_value(source).ok())
.and_then(|source| HostValue::decode(&source).ok())
.map(|source| source.source_type)
.unwrap_or_else(|| "unknown.Source".to_string());
let process_name = args
.get("target")
.and_then(Value::as_record)
.and_then(|target| target.get(LASH_PROCESS_NAME_KEY))
.and_then(string_ref)
.unwrap_or("target");
let mut record = Record::default();
record.insert("type".to_string(), Value::String("trigger_handle".into()));
record.insert(
"id".to_string(),
Value::String(format!("trigger:{source_type}:{process_name}").into()),
);
record.insert(
"source_type".to_string(),
Value::String(source_type.clone().into()),
);
record.insert(
"process_name".to_string(),
Value::String(process_name.to_string().into()),
);
Value::Record(Arc::new(record))
}
fn trigger_list_value(args: &Record) -> Value {
let process_name = args
.get("target")
.and_then(Value::as_record)
.and_then(|target| target.get(LASH_PROCESS_NAME_KEY))
.and_then(string_ref)
.unwrap_or("target");
let source_type = match process_name {
"daily_digest" => "cron.Schedule",
"on_button" => "ui.button.pressed",
_ => "unknown.Source",
};
let mut target = Record::default();
target.insert(
"process_name".to_string(),
Value::String(process_name.to_string().into()),
);
target.insert("inputs".to_string(), trigger_inputs_value("event"));
let mut route = Record::default();
route.insert(
"handle".to_string(),
Value::String(format!("trigger:{source_type}:{process_name}").into()),
);
route.insert(
"source_type".to_string(),
Value::String(source_type.to_string().into()),
);
route.insert("source".to_string(), trigger_source_value(source_type));
route.insert("target".to_string(), Value::Record(Arc::new(target)));
route.insert("enabled".to_string(), Value::Bool(true));
Value::List(vec![Value::Record(Arc::new(route))].into())
}
fn trigger_inputs_value(input_name: &str) -> Value {
let mut event = Record::default();
event.insert("kind".to_string(), Value::String("event".into()));
let mut inputs = Record::default();
inputs.insert(input_name.to_string(), Value::Record(Arc::new(event)));
Value::Record(Arc::new(inputs))
}
fn trigger_source_value(source_type: &str) -> Value {
let encoded = HostValue::encode(source_type, serde_json::json!({}))
.expect("benchmark trigger source should encode");
from_json(encoded)
}
fn continue_as_record(args: &Record) -> Value {
let seed = args.get("seed").and_then(Value::as_record);
let mut seed_keys = Vec::new();
let mut projected_count = 0usize;
let mut global_count = 0usize;
if let Some(seed) = seed {
for (key, value) in seed.iter() {
seed_keys.push(Value::String(key.into()));
if matches!(value, Value::Projected(_)) {
projected_count += 1;
} else {
global_count += 1;
}
}
}
let mut record = Record::default();
record.insert("ok".to_string(), Value::Bool(true));
record.insert("frame_id".to_string(), Value::String("frame:bench".into()));
record.insert(
"task".to_string(),
args.get("task")
.cloned()
.unwrap_or_else(|| Value::String("continue".into())),
);
record.insert("seed_keys".to_string(), Value::List(seed_keys.into()));
record.insert(
"projected_count".to_string(),
Value::Number(projected_count as f64),
);
record.insert(
"global_count".to_string(),
Value::Number(global_count as f64),
);
Value::Record(Arc::new(record))
}
fn string_ref(value: &Value) -> Option<&str> {
match value {
Value::String(value) => Some(value.as_str()),
_ => None,
}
}
fn process_handles_record() -> Value {
let mut chunk_1 = Record::default();
chunk_1.insert("__handle__".to_string(), Value::String("process".into()));
chunk_1.insert("id".to_string(), Value::String("spawn-one".into()));
chunk_1.insert("process_id".to_string(), Value::String("spawn-one".into()));
chunk_1.insert("tool".to_string(), Value::String("spawn_child".into()));
chunk_1.insert("value".to_string(), spawn_child_value("inspect auth"));
let mut chunk_2 = Record::default();
chunk_2.insert("__handle__".to_string(), Value::String("process".into()));
chunk_2.insert("id".to_string(), Value::String("spawn-two".into()));
chunk_2.insert("process_id".to_string(), Value::String("spawn-two".into()));
chunk_2.insert("tool".to_string(), Value::String("spawn_child".into()));
chunk_2.insert("value".to_string(), spawn_child_value("inspect api"));
Value::List(
vec![
Value::Record(Arc::new(chunk_1)),
Value::Record(Arc::new(chunk_2)),
]
.into(),
)
}
fn spawn_child_value(name: &str) -> Value {
let mut record = Record::default();
record.insert(
"claim".to_string(),
Value::String(format!("done:{name}").into()),
);
Value::Record(Arc::new(record))
}
fn unknown_tool(name: &str) -> ExecutionHostError {
ExecutionHostError::new(format!("unknown tool: {name}"))
}
impl BenchHost {
fn task_handle(name: &str, args: &Record) -> Result<Value, ExecutionHostError> {
match name {
"echo" | "query_llm" | "spawn_child" | "continue_as" => {
let mut record = Record::default();
record.insert("__handle__".to_string(), Value::String("process".into()));
record.insert("tool".to_string(), Value::String(name.to_string().into()));
record.insert("value".to_string(), bench_call(name, args)?);
Ok(Value::Record(Arc::new(record)))
}
_ => Err(unknown_tool(name)),
}
}
}