Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/bundles/binary_tree/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,26 @@ describe(funcs.is_tree, () => {
);
await expect(funcs.is_tree(handler, tree)).resolves.toEqual(true);
});

it('returns true for a tree round-tripped back in as DataType.ARRAY, not just DataType.PAIR', async () => {
// Per py-slang: pythonToModule now builds every Python list as a flat DataType.ARRAY, never a
// DataType.PAIR chain - so a tree round-tripped out to Python (via moduleToPython) and passed
// back into e.g. entry()/left_branch() arrives tagged ARRAY, not PAIR. is_tree (and
// assertNonEmptyTree) must accept either shape - this is the regression test for that.
const handler = new TestDataHandler();
const rightPair = await handler.array_make(DataType.ANY, 2, emptyListValue());
await handler.array_set(rightPair, 0, emptyListValue());
await handler.array_set(rightPair, 1, emptyListValue());
const leftPair = await handler.array_make(DataType.ANY, 2, emptyListValue());
await handler.array_set(leftPair, 0, emptyListValue());
await handler.array_set(leftPair, 1, rightPair);
const tree = await handler.array_make(DataType.ANY, 2, emptyListValue());
await handler.array_set(tree, 0, await opaqueNumber(handler, 0));
await handler.array_set(tree, 1, leftPair);

await expect(funcs.is_tree(handler, tree)).resolves.toEqual(true);
await expect(handler.opaque_get(await funcs.entry(handler, tree))).resolves.toEqual(0);
});
});

describe(funcs.make_tree, () => {
Expand Down
35 changes: 25 additions & 10 deletions src/bundles/binary_tree/src/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,24 +61,36 @@ export async function make_tree(
export async function is_tree(evaluator: IDataHandler, value: TypedValue<DataType>): Promise<boolean> {
if (!value) return false;
if (value.type === DataType.EMPTY_LIST) return true;
if (value.type !== DataType.PAIR) return false;
// A tree node is a pair - per conductor's "a pair is just an array of length 2" model, that may
// arrive tagged DataType.PAIR (built directly by make_tree) or DataType.ARRAY (round-tripped
// back in through a Python list, since py-slang's pythonToModule builds every list as an ARRAY
// now, not a PAIR chain). pair_head/pair_tail already read either shape the same way.
if (!isPairLike(value)) return false;

const rest = await evaluator.pair_tail(value);
if (rest.type !== DataType.PAIR) return false;
const rest = await evaluator.pair_tail(value as TypedValue<DataType.PAIR>);
if (!isPairLike(rest)) return false;

const left = await evaluator.pair_head(rest);
const left = await evaluator.pair_head(rest as TypedValue<DataType.PAIR>);
if (!await is_tree(evaluator, left)) return false;

const rightRest = await evaluator.pair_tail(rest);
if (rightRest.type !== DataType.PAIR) return false;
const rightRest = await evaluator.pair_tail(rest as TypedValue<DataType.PAIR>);
if (!isPairLike(rightRest)) return false;

const right = await evaluator.pair_head(rightRest);
const right = await evaluator.pair_head(rightRest as TypedValue<DataType.PAIR>);
if (!await is_tree(evaluator, right)) return false;

const tail = await evaluator.pair_tail(rightRest);
const tail = await evaluator.pair_tail(rightRest as TypedValue<DataType.PAIR>);
return tail.type === DataType.EMPTY_LIST;
}

/** A pair is just an array of length 2 (no distinct "pair" representation) - a tree node may be
* tagged either DataType.PAIR (built directly by make_tree's own pair_make calls) or
* DataType.ARRAY (round-tripped back in through Python, since pythonToModule builds every list as
* an ARRAY now). Both are equally valid; pair_head/pair_tail read either the same way. */
function isPairLike(value: TypedValue<DataType>): boolean {
return value.type === DataType.PAIR || value.type === DataType.ARRAY;
}

/**
* Returns a boolean value, indicating whether the given
* value is an empty binary tree.
Expand All @@ -103,11 +115,14 @@ async function assertNonEmptyTree(
throw new EvaluatorTypeError(`${funcName} expects binary tree`, 'binary tree', value ? DataType[value.type] : 'undefined');
}

if (value.type !== DataType.PAIR) {
if (!isPairLike(value)) {
throw new EvaluatorRuntimeError(`${funcName} received an empty binary tree!`);
}

return value;
// NonEmptyBinaryTree is declared DataType.PAIR, but the runtime value may genuinely be
// DataType.ARRAY (round-tripped back in through Python) - pair_head/pair_tail read either the
// same way (see isPairLike's doc comment), so this is a safe, documented cast, not a lie.
return value as NonEmptyBinaryTree;
}

/**
Expand Down