Skip to content

Commit dc101bc

Browse files
feat: add event system (ConstructableEvent + TRIGGER_EVENT), RET_FAR instruction, TryNode escape sentinel, WorkflowInterpreter middleware, inline workflow pattern, runtime API updates (jump_offset_far, call_offset_far), 15 demos, Chinese doc fixes, sidebar reorganization, and Mermaid diagrams for manual stack management.
1 parent 23dbe91 commit dc101bc

60 files changed

Lines changed: 1994 additions & 52 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@
2121
</a>
2222
</p>
2323

24-
> ### _"Any is thing all you need."_
25-
>
26-
> ### _"Let developers return with Python again."_
24+
> ### _"Sense is all you need."_
2725
2826
</center>
2927

demos/01_minimal.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""01_minimal.py — Minimal example: single node + NOP + interpreter run
2+
3+
Usage:
4+
python demos/01_minimal.py
5+
"""
6+
7+
import asyncio
8+
9+
from amrita_sense import NOP, Node, WorkflowInterpreter
10+
11+
12+
@Node()
13+
async def hello() -> None:
14+
print("Hello, AmritaSense!")
15+
16+
17+
async def main() -> None:
18+
# Compose: hello followed by NOP sentinel node
19+
composition = hello >> NOP
20+
rendered = composition.render()
21+
22+
interpreter = WorkflowInterpreter(rendered)
23+
await interpreter.run()
24+
25+
26+
if __name__ == "__main__":
27+
asyncio.run(main())

demos/02_composition.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""02_composition.py — Multi-node chain + DI return-value passing
2+
3+
Usage:
4+
python demos/02_composition.py
5+
"""
6+
7+
import asyncio
8+
9+
from amrita_sense import NOP, Node, WorkflowInterpreter
10+
11+
12+
@Node()
13+
async def double() -> int:
14+
return 42
15+
16+
17+
@Node()
18+
async def add_one(value: int) -> int:
19+
# DI injects the return value (42) from double into `value`
20+
return value + 1
21+
22+
23+
@Node()
24+
async def print_result(value: int) -> None:
25+
print(f"Final result: {value}")
26+
27+
28+
async def main() -> None:
29+
composition = double >> add_one >> print_result >> NOP
30+
rendered = composition.render()
31+
interpreter = WorkflowInterpreter(rendered)
32+
await interpreter.run()
33+
34+
35+
if __name__ == "__main__":
36+
asyncio.run(main())

demos/03_dependency_injection.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""03_dependency_injection.py — extra_args / extra_kwargs explicit injection
2+
3+
Usage:
4+
python demos/03_dependency_injection.py
5+
"""
6+
7+
import asyncio
8+
9+
from amrita_sense import NOP, Node, WorkflowInterpreter
10+
11+
12+
@Node()
13+
async def greet(greeting: str, name: str) -> str:
14+
# greeting → injected by name via extra_kwargs
15+
# name → injected by type (str) via extra_args
16+
return f"{greeting}, {name}!"
17+
18+
19+
@Node()
20+
async def display(message: str) -> None:
21+
print(message)
22+
23+
24+
async def main() -> None:
25+
composition = greet >> display >> NOP
26+
rendered = composition.render()
27+
interpreter = WorkflowInterpreter(
28+
rendered,
29+
extra_args=("World",), # str type → injected into `name`
30+
extra_kwargs={"greeting": "Hello"}, # name match → injected into `greeting`
31+
)
32+
await interpreter.run()
33+
34+
35+
if __name__ == "__main__":
36+
asyncio.run(main())

demos/04_if_branch.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""04_if_branch.py — IF / ELIF / ELSE conditional branching
2+
3+
Usage:
4+
python demos/04_if_branch.py
5+
"""
6+
7+
import asyncio
8+
9+
from amrita_sense import IF, NOP, Node, WorkflowInterpreter
10+
11+
12+
@Node()
13+
async def check_score() -> int:
14+
"""Simulate returning a score"""
15+
return 85
16+
17+
18+
@Node()
19+
async def grade_a(score: int) -> str:
20+
print(f"Score {score}: Excellent")
21+
return "A"
22+
23+
24+
@Node()
25+
async def grade_b(score: int) -> str:
26+
print(f"Score {score}: Good")
27+
return "B"
28+
29+
30+
@Node()
31+
async def grade_c(score: int) -> str:
32+
print(f"Score {score}: Pass")
33+
return "C"
34+
35+
36+
@Node()
37+
async def finished(grade: str) -> None:
38+
print(f"Final grade: {grade}")
39+
40+
41+
async def main() -> None:
42+
comp = (
43+
IF(
44+
Node(lambda score: score >= 90, wrap_to_async=False), # type: ignore[arg-type]
45+
grade_a,
46+
)
47+
.ELIF(Node(lambda score: score >= 75, wrap_to_async=False), grade_b) # type: ignore[arg-type]
48+
.ELSE(grade_c)
49+
>> check_score
50+
>> finished
51+
>> NOP
52+
)
53+
rendered = comp.render()
54+
interpreter = WorkflowInterpreter(rendered)
55+
await interpreter.run()
56+
57+
58+
if __name__ == "__main__":
59+
asyncio.run(main())

demos/05_while_loop.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""05_while_loop.py — WHILE + DO-WHILE loops
2+
3+
Usage:
4+
python demos/05_while_loop.py
5+
"""
6+
7+
import asyncio
8+
9+
from amrita_sense import DO, NOP, WHILE, Node, WorkflowInterpreter
10+
from amrita_sense.exceptions import BreakLoop
11+
12+
13+
@Node()
14+
def counter() -> int:
15+
"""Increment and return the counter on each call"""
16+
if not hasattr(counter, "n"):
17+
counter.n = 1 # type: ignore[attr-defined]
18+
else:
19+
counter.n += 1 # type: ignore[attr-defined]
20+
return counter.n # type: ignore[attr-defined]
21+
22+
23+
@Node()
24+
async def under_three(n: int) -> bool:
25+
"""WHILE loop condition"""
26+
return n < 3
27+
28+
29+
@Node()
30+
def body(n: int) -> None:
31+
"""Loop body"""
32+
print(f" WHILE iteration {n}")
33+
34+
35+
@Node()
36+
def early_break(n: int) -> None:
37+
"""DO loop body: break out on iteration 3"""
38+
print(f" DO-WHILE iteration {n}")
39+
if n >= 3:
40+
raise BreakLoop
41+
42+
43+
async def main() -> None:
44+
print("=== WHILE example ===")
45+
wf = (counter >> WHILE(under_three).ACTION(body) >> NOP).render()
46+
await WorkflowInterpreter(wf).run()
47+
48+
# Reset counter
49+
counter.n = 0 # type: ignore[attr-defined]
50+
51+
print("\n=== DO-WHILE example ===")
52+
wf2 = (
53+
counter >> DO(early_break).WHILE(Node(lambda n: n < 5, wrap_to_async=False)) >> NOP # type: ignore[arg-type]
54+
).render()
55+
await WorkflowInterpreter(wf2).run()
56+
57+
58+
if __name__ == "__main__":
59+
asyncio.run(main())

demos/06_try_catch.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""06_try_catch.py — TRY / CATCH / FINALLY / THEN exception handling
2+
3+
Usage:
4+
python demos/06_try_catch.py
5+
"""
6+
7+
import asyncio
8+
9+
from amrita_sense import NOP, Node, NodeType, Try, WorkflowInterpreter
10+
11+
12+
@Node()
13+
async def may_fail() -> str:
14+
"""Always raises ValueError"""
15+
raise ValueError("something went wrong")
16+
17+
18+
@Node()
19+
async def handle_error(exc_val: ValueError):
20+
"""Catch ValueError and return fallback"""
21+
print(f"Caught: {exc_val}")
22+
23+
24+
@Node()
25+
async def on_success() -> None:
26+
"""Executes when TRY succeeds (THEN)"""
27+
print("Success: all good")
28+
29+
30+
@Node()
31+
async def cleanup() -> None:
32+
"""Always runs — success or failure (FINALLY)"""
33+
print("Cleanup complete")
34+
35+
36+
async def example_1() -> None:
37+
"""Exception caught by CATCH"""
38+
print("=== Example 1: ValueError → caught by CATCH ===")
39+
comp = Try(may_fail).CATCH(ValueError, handle_error) >> NOP
40+
rendered = comp.render()
41+
await WorkflowInterpreter(rendered).run()
42+
43+
44+
async def example_2() -> None:
45+
"""Normal execution + THEN + FINALLY"""
46+
print("\n=== Example 2: normal execution + THEN + FINALLY ===")
47+
comp = (
48+
Try(NodeType(lambda: "all good", wrap_to_async=False, address_able=False, tag=None)) # type: ignore[arg-type]
49+
.THEN(on_success)
50+
.CATCH(ValueError, handle_error)
51+
.FINALLY(cleanup)
52+
>> NOP
53+
)
54+
rendered = comp.render()
55+
await WorkflowInterpreter(rendered).run()
56+
57+
58+
async def main() -> None:
59+
await example_1()
60+
await example_2()
61+
62+
63+
if __name__ == "__main__":
64+
asyncio.run(main())

demos/07_goto_call.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""07_goto_call.py — GOTO + CALL + ALIAS + ARCHIVED_NODES
2+
3+
Usage:
4+
python demos/07_goto_call.py
5+
"""
6+
7+
import asyncio
8+
9+
from amrita_sense import ALIAS, ARCHIVED_NODES, CALL, NOP, Node, WorkflowInterpreter
10+
from amrita_sense.instructions import GOTO
11+
12+
13+
@Node()
14+
async def start() -> None:
15+
print("Start")
16+
17+
18+
@Node()
19+
async def skip_me() -> None:
20+
print("This line should never appear")
21+
22+
23+
@Node()
24+
async def after_jump() -> None:
25+
print("Arrived after GOTO jump")
26+
27+
28+
@Node()
29+
async def reusable_greet(name: str = "World") -> str:
30+
print(f" Hello, {name}!")
31+
return name
32+
33+
34+
@Node()
35+
async def done(result: str) -> None:
36+
print(f"CALL returned: {result}")
37+
38+
39+
async def main() -> None:
40+
print("=== GOTO example ===")
41+
42+
# GOTO("target") skips skip_me, goes directly to after_jump
43+
comp = start >> GOTO("target") >> skip_me >> ALIAS(after_jump, "target") >> NOP
44+
await WorkflowInterpreter(comp.render()).run()
45+
46+
print("\n=== CALL example ===")
47+
48+
# CALL("greeter") invokes the subroutine, then returns to continue
49+
sub = ARCHIVED_NODES(ALIAS(reusable_greet, "greeter"))
50+
comp2 = start >> CALL("greeter") >> done >> NOP >> sub
51+
await WorkflowInterpreter(comp2.render()).run()
52+
53+
54+
if __name__ == "__main__":
55+
asyncio.run(main())

demos/08_interrupt_suspend.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""08_interrupt_suspend.py — run_step_by() step-by-step debugging
2+
3+
Usage:
4+
python demos/08_interrupt_suspend.py
5+
"""
6+
7+
import asyncio
8+
9+
from amrita_sense import NOP, Node, WorkflowInterpreter
10+
11+
12+
@Node()
13+
async def step_one() -> int:
14+
print("[1] First step")
15+
return 1
16+
17+
18+
@Node()
19+
async def step_two(prev: int) -> int:
20+
print(f"[2] Received previous result: {prev}")
21+
return prev + 1
22+
23+
24+
@Node()
25+
async def step_three(prev: int) -> None:
26+
print(f"[3] Final result: {prev}")
27+
28+
29+
async def main() -> None:
30+
print("=== Using run_step_by() to inspect between steps ===")
31+
comp = (step_one >> step_two >> step_three >> NOP).render()
32+
interpreter = WorkflowInterpreter(comp)
33+
34+
async for result in interpreter.run_step_by():
35+
print(f" → Node output: {result}")
36+
# You can pause, inspect the pointer, or decide whether to continue here
37+
38+
39+
if __name__ == "__main__":
40+
asyncio.run(main())

0 commit comments

Comments
 (0)