Loop over a list and stop at the first match with For each (#249)
Finding the first item that matches took Count, Repeat, Item at and a Branch, and the loop could not stop once it had found something. A graph has no lambdas, so the predicate is an input that is read every round. - For each runs body once per element, with item and index set, and goes on at done. - Its break input is read anew before every round, after item is set. Once it is true the loop ends there and that round's body does not run. - done sees the matching item and index with stopped true. Without a match it sees no item, index -1 and stopped false, the OrDefault. - RepeatResult has optional items, stopWhen, stopped and exhausted. GraphInterpreter.loop reads them, Repeat runs as before. Tests: - ./check.sh: 180 files, 2623 tests, CHECK EXIT 0 - e2e GM_STEPS=1-6: ALL PASSED, 269 checks - 5 interpreter tests in graph.test.ts, 2 catalog cases - e2e 6z-iv: the palette creates the node, and a search for Name == "Gold" in Axe, Gold, Bow, Gold logs "1true" - Counter-checks: without the break check 2 unit tests fail, and with break not read over its edge 6z-iv fails - This commit alone: tsc and the three touched unit files pass (349 tests) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a5ae21481b
commit
cb8a2d029a
8 files changed
+253
-3
No files matched your search
@@ -0,0 +1,36 @@
|
||||
import type { NodeDef } from '../../NodeRegistry';
|
||||
import { asList } from '../../values';
|
||||
import { any, bool, exec, list, num, withDefault } from '../shared';
|
||||
|
||||
/** What `item`, `index` and `stopped` say when no round stopped the loop – or there was none. */
|
||||
const NOTHING = { item: null, index: -1, stopped: false };
|
||||
|
||||
export const ForEach: NodeDef = {
|
||||
type: 'ForEach',
|
||||
label: 'For each',
|
||||
category: 'flow',
|
||||
description:
|
||||
'Runs "body" once per element of the list, "item" and "index" telling which one, and continues at ' +
|
||||
'"done" afterwards. "break" is read before every round: once it is true, the loop ends there – ' +
|
||||
'that round\'s body does not run, and "done" sees its item with "stopped" set. Without a break, ' +
|
||||
'"done" sees no item, index -1. "break" built on "item" finds the first element that matches.',
|
||||
inputs: [exec(), list('list'), withDefault(bool('break'), false)],
|
||||
outputs: [exec('body', 'body'), any('item'), num('index'), bool('stopped'), exec('done', 'done')],
|
||||
params: [],
|
||||
run: (_ctx, inputs) => {
|
||||
const values = asList(inputs.list);
|
||||
return {
|
||||
outputs: { ...NOTHING },
|
||||
repeat: {
|
||||
port: 'body',
|
||||
index: 'index',
|
||||
times: values.length,
|
||||
items: { port: 'item', values },
|
||||
stopWhen: 'break',
|
||||
stopped: 'stopped',
|
||||
exhausted: NOTHING,
|
||||
},
|
||||
next: 'done',
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -113,6 +113,7 @@ import { OnUpdate } from './events/OnUpdate';
|
||||
import { BehaviorEnd } from './flow/BehaviorEnd';
|
||||
import { BehaviorStart } from './flow/BehaviorStart';
|
||||
import { Branch } from './flow/Branch';
|
||||
import { ForEach } from './flow/ForEach';
|
||||
import { Repeat } from './flow/Repeat';
|
||||
import { RunBehavior } from './flow/RunBehavior';
|
||||
import { Sequence } from './flow/Sequence';
|
||||
@@ -150,6 +151,7 @@ export const BUILT_IN_NODES: readonly NodeDef[] = [
|
||||
Branch,
|
||||
Sequence,
|
||||
Repeat,
|
||||
ForEach,
|
||||
RunBehavior,
|
||||
StateBranch,
|
||||
SetState,
|
||||
|
||||
Reference in new issue
Block a user