Skip to content

Commit 0a6fdca

Browse files
committed
Update PChatBot: improved design doc, RAG examples, checker fixers, and generation pipeline
1 parent c255bf1 commit 0a6fdca

File tree

24 files changed

+2445
-269
lines changed

24 files changed

+2445
-269
lines changed

Src/PChatBot/resources/context_files/modular/p_machines_guide.txt

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,35 @@ Do NOT access functions contained inside of other machines.
2020

2121
<machine_file_example>
2222
```
23-
// Standalone functions (if needed)
24-
fun UtilityFunction(param: int): bool {
25-
// Implementation
26-
return param > 0;
27-
}
28-
29-
// Machine definitions - no events/types/enums
23+
// Machine definitions - no events/types/enums (they come from Enums_Types_Events.p)
3024
machine TaskManager {
31-
var tasks: seq[Task]; // Task type comes from context
32-
33-
start state Idle {
34-
on eStart goto Processing; // eStart event comes from context
25+
var workers: seq[machine]; // receive from constructor or config event
26+
var tasks: seq[Task]; // Task type comes from context
27+
28+
// Start state receives configuration via constructor payload
29+
start state Init {
30+
entry InitEntry;
31+
on eNewTask goto Processing;
3532
}
36-
33+
3734
state Processing {
38-
on eTask do HandleTask; // eTask event comes from context
35+
on eTask do HandleTask;
36+
defer eNewTask; // queue new tasks while busy
37+
ignore eStatusRequest; // drop status pings while processing
3938
}
40-
39+
40+
// Entry function receives the constructor payload
41+
fun InitEntry(config: (workers: seq[machine],)) {
42+
workers = config.workers;
43+
}
44+
4145
fun HandleTask(task: Task) {
4246
// Implementation using contextual types and events
4347
}
4448
}
4549
```
50+
51+
Created by test driver as: `new TaskManager((workers = workerList,));`
4652
</machine_file_example>
4753

4854
<state_machine_grammar>

Src/PChatBot/resources/instructions/generate_machine.txt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,16 @@ For each function:
1515
5. EVERY event that can be received by this machine in a given state MUST be handled in that state. If an event is not relevant in a state, add `ignore eEventName;` to that state. If it should be processed later, add `defer eEventName;`. PChecker will flag unhandled events as bugs.
1616
6. Think about which events could arrive late or out-of-order. For example, if a machine transitions from PhaseA to PhaseB, events from PhaseA may still be in the queue — add `ignore` for those stale events in PhaseB.
1717

18-
Before finalizing the implementation, identify all attributes and events that would be needed to properly setup and initialize this machine in test cases later. Verify that the generated code strictly follows all the P syntax rules before considering it final. Return only the generated P code without any explanation attached. Return the P code enclosed in XML tags where the tag name is the filename.
18+
## Initialization:
19+
If this machine has a start-state entry function with a parameter, that parameter carries the machine's constructor config (e.g., references to other machines, IDs, sizes). The implementation MUST:
20+
- Store every field from the config payload into the corresponding local variable.
21+
- Compute derived values (e.g., majoritySize = sizeof(acceptors) / 2 + 1).
22+
- If using the init-event pattern instead, the configuration handler function must similarly store all fields and then `goto` the first operational state.
23+
24+
## Checklist before returning code:
25+
- Every state has handlers, defer, or ignore for EVERY event this machine could receive.
26+
- The start state entry correctly unpacks the constructor payload (if parameterized).
27+
- All `send` targets are machine-typed variables (never null/default).
28+
- No events, types, or enums are declared in this file.
29+
30+
Verify that the generated code strictly follows all the P syntax rules before considering it final. Return only the generated P code without any explanation attached. Return the P code enclosed in XML tags where the tag name is the filename.

Src/PChatBot/resources/instructions/generate_machine_structure.txt

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,75 @@ Write the structure of P code for the state machine {machineName}. Include all v
1818
5. Defer and ignore statements
1919
6. Function declarations with proper parameters and return types, but EMPTY bodies, do add COMMENTS for future stages of code generation.
2020

21+
## CRITICAL: Machine Configuration / Initialization
2122

23+
If this machine depends on references to other machines (e.g., a list of acceptors, a server reference, learner machines) or any configuration, it MUST receive them through one of these two patterns:
24+
25+
### Pattern A — Constructor payload (preferred when all deps are known at creation time)
26+
27+
The start state entry function receives configuration directly from the `new` call:
28+
29+
```
30+
machine ExampleServer {{
31+
var workers: seq[machine];
32+
var serverId: int;
33+
34+
start state Init {{
35+
entry InitEntry; // receives config from new ExampleServer(config)
36+
on eRequest goto Serving;
37+
}}
38+
39+
fun InitEntry(config: (workers: seq[machine], id: int)) {{
40+
workers = config.workers;
41+
serverId = config.id;
42+
}}
43+
...
44+
}}
45+
```
46+
Test driver creates it as: `new ExampleServer((workers = workerList, id = 1));`
47+
48+
### Pattern B — Initialization event (when config arrives after creation)
49+
50+
The machine blocks in its start state waiting for a configuration event, then transitions to its core logic:
51+
52+
```
53+
machine ExampleWorker {{
54+
var coordinator: machine;
55+
56+
start state WaitForConfig {{
57+
on eWorkerConfig goto Ready with Configure;
58+
defer eTask; // defer work events until configured
59+
}}
60+
61+
state Ready {{
62+
on eTask do HandleTask;
63+
}}
64+
65+
fun Configure(config: tWorkerConfig) {{
66+
coordinator = config.coordinator;
67+
}}
68+
...
69+
}}
70+
```
71+
Test driver creates and configures it as:
72+
```
73+
worker1 = new ExampleWorker();
74+
send worker1, eWorkerConfig, (coordinator = coord,);
75+
```
76+
77+
### Rules
78+
- Pick ONE pattern per machine and be consistent.
79+
- Do NOT use ad-hoc event names like eStart or eInit that carry no typed payload. If using Pattern B, define a proper typed config event with a named-tuple payload in Enums_Types_Events.p.
80+
- If a machine has no external dependencies, its entry function can take no parameters.
81+
82+
## Event Handling Completeness
83+
84+
For EVERY state, think about which events could arrive in that state (including events from prior states still in the queue) and add:
85+
- `on eX do Handler;` — if the event should be processed
86+
- `defer eX;` — if the event should be queued for a later state
87+
- `ignore eX;` — if the event should be silently dropped
88+
89+
PChecker will report an unhandled-event bug for any event that arrives in a state without a handler, defer, or ignore.
2290

2391
The machine file should NOT contain:
2492

Lines changed: 87 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,108 @@
11
Write P code for the {filename} file.
22

33
CRITICAL REQUIREMENTS:
4+
45
1. For EACH scenario in <possible_scenarios>, create a dedicated scenario machine (e.g., Scenario1_XXX) with a start state that instantiates the system.
56
2. For EACH scenario machine, you MUST write a `test` declaration. Without test declarations, PChecker cannot discover or run any tests. The format is:
67
test tcScenarioName [main=ScenarioMachine]:
78
assert SpecName1, SpecName2 in
89
{{ MachineA, MachineB, ..., ScenarioMachine }};
910
Use the spec/monitor names from the PSpec files in the assert clause. List ALL machines that participate in the scenario inside the braces.
10-
3. Always create all dependent machines before initializing machines that will send events/messages to them; never use default/null machine references in initialization.
11-
4. The test driver file MUST contain BOTH the scenario machines AND the test declarations. Do NOT put them in separate files.
12-
5. Do NOT re-declare or re-define machines that already exist in PSrc (Broker, Producer, etc.). Only define NEW test-specific machines here (scenario drivers and test_component machines like Consumer if it was listed under <test_components>).
13-
6. You MUST only use events that are already defined in Enums_Types_Events.p. Do NOT invent new events (e.g., eStartProducing, eStartConsuming). If a machine needs to start doing work, trigger it via its constructor payload or entry action — not a custom start event.
14-
7. Scenario machines must be SIMPLE launchers. They create all system components in the correct order and then stop. They should NOT handle any protocol events (no `on eXxx` handlers). If a component like Timer needs a `client: machine` reference, pass the actual component that uses the timer (e.g., the Coordinator), NOT the scenario machine itself (`this`).
15-
8. Ensure every machine is fully initialized before other machines send events to it. Create machines in dependency order: infrastructure first (Timer), then core (Coordinator, Broker), then clients (Client, Producer, Consumer). Pass all required references via constructor payloads — do not leave machine references uninitialized.
11+
3. The test driver file MUST contain BOTH the scenario machines AND the test declarations. Do NOT put them in separate files.
12+
4. Do NOT re-declare or re-define machines that already exist in PSrc. Only define NEW scenario-driver machines here.
13+
5. You MUST only use events that are already defined in Enums_Types_Events.p. Do NOT invent new events.
14+
6. Scenario machines must be SIMPLE launchers. They create all system components in the correct order and then stop. They should NOT handle any protocol events (no `on eXxx` handlers).
1615

17-
Example structure for a file with 2 scenarios:
16+
## CRITICAL: Machine Wiring — Read the Machine Entry Signatures
1817

19-
machine Scenario1_SingleClient {{
20-
start state Init {{
21-
entry {{
22-
// create system components
23-
}}
24-
}}
18+
Look at every machine file provided as context. Each machine's start state has an entry function. That entry function's parameter defines EXACTLY what the machine expects when created. You MUST match it.
19+
20+
There are two initialization patterns used by the machines:
21+
22+
### Pattern A: Constructor payload — machine expects config via `new Machine(payload)`
23+
If a machine's start-state entry function has a parameter, you MUST pass a matching named-tuple payload when creating it with `new`.
24+
25+
Example — if Proposer.p has:
26+
```
27+
start state Init {{
28+
entry InitEntry;
29+
...
30+
}}
31+
fun InitEntry(config: (acceptors: seq[machine], learners: seq[machine], id: int)) {{
32+
...
2533
}}
34+
```
35+
Then the test driver MUST create it as:
36+
```
37+
proposer = new Proposer((acceptors = acceptorSeq, learners = learnerSeq, id = 1));
38+
```
2639

27-
machine Scenario2_MultipleClients {{
40+
### Pattern B: Initialization event — machine blocks on a config event
41+
If a machine's start state waits for an event (e.g., `on eAcceptorConfig goto Ready with Configure;`), you MUST send that event after creating the machine.
42+
43+
Example — if Acceptor.p has:
44+
```
45+
start state WaitForConfig {{
46+
on eAcceptorConfig goto Ready with Configure;
47+
defer ePropose, eAcceptRequest;
48+
}}
49+
fun Configure(config: tAcceptorConfig) {{
50+
...
51+
}}
52+
```
53+
Then the test driver MUST do:
54+
```
55+
acceptor1 = new Acceptor();
56+
send acceptor1, eAcceptorConfig, (learners = learnerSeq,);
57+
```
58+
59+
### Wiring Rules
60+
- Create machines in DEPENDENCY ORDER: machines with no dependencies first (e.g., Learner), then machines that reference them (e.g., Acceptor needs learners), then machines that reference those (e.g., Proposer needs acceptors).
61+
- Build `seq[machine]` or `set[machine]` collections BEFORE passing them to dependent machines.
62+
- NEVER leave machine-typed variables uninitialized (default/null). Every machine reference must point to a created machine.
63+
- NEVER pass `this` (the scenario machine) as a protocol participant reference.
64+
- For each `new` call, verify the payload tuple field names and types match the machine's entry function parameter EXACTLY.
65+
66+
## Complete Example
67+
68+
Given machines with these signatures:
69+
- Learner: `fun InitEntry()` — no config needed
70+
- Acceptor: `fun InitEntry(config: (learners: seq[machine],))` — needs learners
71+
- Proposer: `fun InitEntry(config: (acceptors: seq[machine], learners: seq[machine], id: int))` — needs acceptors + learners
72+
- Client: `fun InitEntry(config: (proposer: machine, value: int))` — needs proposer
73+
74+
The scenario machine must be:
75+
```
76+
machine Scenario1_BasicPaxos {{
2877
start state Init {{
2978
entry {{
30-
// create system components
79+
var learner1: machine;
80+
var acceptors: seq[machine];
81+
var learners: seq[machine];
82+
var proposer: machine;
83+
var client: machine;
84+
85+
// 1. Create machines with no dependencies
86+
learner1 = new Learner();
87+
learners += (0, learner1);
88+
89+
// 2. Create machines that depend on step 1
90+
acceptors += (0, new Acceptor((learners = learners,)));
91+
acceptors += (1, new Acceptor((learners = learners,)));
92+
acceptors += (2, new Acceptor((learners = learners,)));
93+
94+
// 3. Create machines that depend on steps 1-2
95+
proposer = new Proposer((acceptors = acceptors, learners = learners, id = 1));
96+
97+
// 4. Create clients that depend on proposer
98+
client = new Client((proposer = proposer, value = 42));
3199
}}
32100
}}
33101
}}
34102

35-
test tcSingleClient [main=Scenario1_SingleClient]:
36-
assert SafetySpec in
37-
{{ ComponentA, ComponentB, Scenario1_SingleClient }};
38-
39-
test tcMultipleClients [main=Scenario2_MultipleClients]:
40-
assert SafetySpec in
41-
{{ ComponentA, ComponentB, Scenario2_MultipleClients }};
103+
test tcBasicPaxos [main=Scenario1_BasicPaxos]:
104+
assert OnlyOneValueChosen in
105+
{{ Proposer, Acceptor, Learner, Client, Scenario1_BasicPaxos }};
106+
```
42107

43108
Verify that the generated code strictly follows all the P syntax rules before considering it final. Return only the generated P code without any explanation attached. Return the P code enclosed in XML tags where the tag name is the filename.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// ============================================================================
2+
// BEST PRACTICES: Coordinator with Broadcast Pattern
3+
// ============================================================================
4+
//
5+
// Demonstrates:
6+
// 1. How to properly receive a component list via setup event
7+
// 2. How to broadcast to all components
8+
// 3. How to handle/ignore broadcast responses in all states
9+
// ============================================================================
10+
11+
machine Coordinator {
12+
var workers: seq[machine];
13+
var allComponents: seq[machine];
14+
var numWorkers: int;
15+
16+
start state Init {
17+
entry InitEntry;
18+
// BEST PRACTICE: Accept setup events in the start state.
19+
// These arrive from the test driver after all machines are created.
20+
on eSetupComponentList do HandleSetupComponents;
21+
// BEST PRACTICE: Ignore protocol events that may arrive before setup.
22+
ignore eResponse, eNotifyAll;
23+
}
24+
25+
state Ready {
26+
on eRequest do HandleRequest;
27+
on eResponse do HandleResponse;
28+
// BEST PRACTICE: When you broadcast eNotifyAll to all components,
29+
// you might also be in the component list — handle or ignore your own broadcast.
30+
ignore eNotifyAll;
31+
}
32+
33+
state Done {
34+
// BEST PRACTICE: Terminal states should ignore all events still in flight.
35+
ignore eRequest, eResponse, eNotifyAll, eSetupComponentList;
36+
}
37+
38+
fun InitEntry(config: tCoordinatorConfig) {
39+
numWorkers = config.numWorkers;
40+
// Don't goto Ready yet — wait for setup event with component list.
41+
}
42+
43+
fun HandleSetupComponents(components: seq[machine]) {
44+
allComponents = components;
45+
goto Ready;
46+
}
47+
48+
fun HandleRequest(req: (sender: machine, data: int)) {
49+
// Process request and notify all components
50+
BroadcastNotification(req.data);
51+
}
52+
53+
fun HandleResponse(resp: (receiver: machine, result: int)) {
54+
// Process response from worker
55+
}
56+
57+
// BEST PRACTICE: Factor broadcast logic into a helper function.
58+
fun BroadcastNotification(value: int) {
59+
var i: int;
60+
i = 0;
61+
while (i < sizeof(allComponents)) {
62+
send allComponents[i], eNotifyAll, (value = value);
63+
i = i + 1;
64+
}
65+
}
66+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// ============================================================================
2+
// BEST PRACTICES: Types, Events, and Setup Events
3+
// ============================================================================
4+
//
5+
// This file demonstrates best practices for defining types, events, and
6+
// setup/configuration events in P programs.
7+
//
8+
// KEY PRINCIPLES:
9+
// 1. Separate protocol events from setup events
10+
// 2. Use setup events for post-creation machine wiring
11+
// 3. Define clear payload types for each event
12+
// ============================================================================
13+
14+
// --- Protocol events (used during normal operation) ---
15+
event eRequest: (sender: machine, data: int);
16+
event eResponse: (receiver: machine, result: int);
17+
event eNotifyAll: (value: int);
18+
19+
// --- Setup/configuration events (used ONLY during initialization) ---
20+
// BEST PRACTICE: Define dedicated setup events for post-creation wiring.
21+
// NEVER reuse protocol events for setup purposes.
22+
//
23+
// Common patterns:
24+
// 1. Broadcast list setup: event eSetupComponents: seq[machine]
25+
// 2. Back-reference setup: event eSetupOwner: machine
26+
// 3. Peer list setup: event eSetupPeers: seq[machine]
27+
// 4. Full config setup: event eSetupConfig: tConfigType
28+
29+
event eSetupComponentList: seq[machine];
30+
event eSetupCoordinatorRef: machine;
31+
32+
// --- Configuration types ---
33+
type tWorkerConfig = (coordinator: machine, workerId: int);
34+
type tCoordinatorConfig = (numWorkers: int);

0 commit comments

Comments
 (0)