Skip to content

Commit 2dd22c8

Browse files
committed
Replace serial compiler helper with stateful REPL
1 parent 090a69d commit 2dd22c8

13 files changed

Lines changed: 1109 additions & 555 deletions

Cargo.lock

Lines changed: 131 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,23 @@ path = "src/bin/rustscript-compile-vmbc.rs"
1616
required-features = ["host"]
1717

1818
[[bin]]
19-
name = "rustscript-repl-compile"
20-
path = "src/bin/rustscript-repl-compile.rs"
19+
name = "rustscript-serial-repl"
20+
path = "src/bin/rustscript-serial-repl.rs"
2121
required-features = ["host"]
2222

2323
[features]
2424
default = ["host"]
25-
host = ["dep:vm", "vm/runtime"]
25+
host = ["dep:serialport", "dep:vm", "vm/runtime"]
2626
esp32c3 = ["dep:pd_vm_nostd"]
2727
esp32s31 = ["dep:pd_vm_nostd"]
2828
arduino = ["dep:pd_vm_nostd"]
2929
wifi = []
3030
bluetooth = []
3131

3232
[dependencies]
33-
pd_vm_nostd = { package = "pd-vm-nostd", git = "https://github.com/rustscript-lang/rustscript.git", rev = "ab04637a9b6192dd4f37b7e6abe4dde83f2fc710", version = "0.1.0", optional = true }
34-
vm = { package = "pd-vm", git = "https://github.com/rustscript-lang/rustscript.git", rev = "ab04637a9b6192dd4f37b7e6abe4dde83f2fc710", version = "0.1.0", default-features = false, optional = true }
33+
pd_vm_nostd = { package = "pd-vm-nostd", git = "https://github.com/rustscript-lang/rustscript.git", rev = "5380c19", version = "0.1.0", optional = true }
34+
serialport = { version = "4.8.1", default-features = false, optional = true }
35+
vm = { package = "pd-vm", git = "https://github.com/rustscript-lang/rustscript.git", rev = "5380c19", version = "0.1.0", default-features = false, optional = true }
3536

3637
[profile.release]
3738
opt-level = "z"

README.md

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,21 +120,41 @@ RSS input compilation uses `rustscript-compile-vmbc` from `PATH`, or the compile
120120
checkout through Cargo. A raw `.vmbc` file can be copied directly to the SD path; SD files do not
121121
use the flash-partition header.
122122

123-
## Serial VMBC REPL
123+
## Serial REPL
124124

125-
When neither startup source exists, the firmware presents a VMBC-oriented serial REPL. Commands are
126-
`load`, `install`, `run`, `info`, and `help`. `load` executes a transferred VMBC payload without
127-
writing flash; `install` writes the script partition first. The helper implements the binary framing:
125+
When neither startup source exists, the firmware accepts both the legacy VMBC commands and an
126+
interactive source REPL. Build and start the source REPL on the development machine:
127+
128+
```bash
129+
cargo build --release --bin rustscript-serial-repl
130+
./target/release/rustscript-serial-repl /dev/ttyACM0 115200
131+
```
132+
133+
The prompt follows `pd-vm-run`: enter one expression or statement at a time and expression results
134+
are printed immediately. Bindings, mutations, type metadata, and moved-value state carry across
135+
entries. Delimiter-based multiline input uses the `...>` prompt; `.cancel` clears pending input,
136+
`.clear` clears session locals, and `.quit` exits.
137+
138+
```text
139+
rss> let mut x = 40;
140+
rss> x = x + 2;
141+
rss> x
142+
=> 42
143+
```
144+
145+
Source compilation stays on the development machine. Each entry is sent as VMBC plus its current
146+
local values using fixed-length binary frames; the firmware contains only the decoder and
147+
interpreter.
148+
149+
The existing `load`, `install`, `run`, `info`, and `help` commands remain available. To send a
150+
precompiled payload directly:
128151

129152
```bash
130153
python -m pip install pyserial
131154
python scripts/repl_vmbc.py app.vmbc --port /dev/ttyACM0
132155
python scripts/repl_vmbc.py app.vmbc --port /dev/ttyACM0 --install
133156
```
134157

135-
Source compilation stays on the development machine; the firmware image contains the decoder and
136-
interpreter, without the desktop compiler.
137-
138158
## Build
139159

140160
The repository root is a complete PlatformIO project:

firmware/script_loader.cpp

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -278,19 +278,31 @@ void rustscript_repl_source() {
278278
delay(10);
279279
continue;
280280
}
281-
String header = Serial.readStringUntil('\n');
282-
header.trim();
283-
if (header.length() == 0) {
284-
continue;
285-
}
286-
// Binary protocol
287-
if (header.startsWith(REPL_MAGIC) && header.length() == strlen(REPL_MAGIC) + 8) {
288-
const uint32_t program_len = *reinterpret_cast<const uint32_t *>(header.c_str() + 4);
289-
const uint32_t state_len = *reinterpret_cast<const uint32_t *>(header.c_str() + 8);
281+
if (Serial.peek() == 'R') {
282+
uint8_t frame_header[REPL_FRAME_HEADER_SIZE] = {};
283+
if (!read_serial_payload(frame_header, sizeof(frame_header))) {
284+
continue;
285+
}
286+
if (std::memcmp(frame_header, REPL_REQUEST_MAGIC, 4) != 0) {
287+
Serial.println("rss:error=invalid-repl-magic");
288+
continue;
289+
}
290+
uint32_t program_len = 0;
291+
uint32_t state_len = 0;
292+
std::memcpy(&program_len, frame_header + 4, sizeof(program_len));
293+
std::memcpy(&state_len, frame_header + 8, sizeof(state_len));
294+
if (program_len == 0 || state_len == 0 ||
295+
program_len > REPL_MAX_FRAME_SIZE || state_len > REPL_MAX_FRAME_SIZE ||
296+
program_len > REPL_MAX_FRAME_SIZE - state_len) {
297+
Serial.println("rss:error=invalid-repl-length");
298+
continue;
299+
}
290300
uint8_t *program = read_serial_binary(program_len);
291-
if (program == nullptr) continue;
292-
uint8_t *state = state_len > 0 ? read_serial_binary(state_len) : nullptr;
293-
if (state_len > 0 && state == nullptr) {
301+
if (program == nullptr) {
302+
continue;
303+
}
304+
uint8_t *state = read_serial_binary(state_len);
305+
if (state == nullptr) {
294306
std::free(program);
295307
continue;
296308
}
@@ -303,14 +315,24 @@ void rustscript_repl_source() {
303315
);
304316
std::free(program);
305317
std::free(state);
318+
319+
uint8_t response_header[REPL_FRAME_HEADER_SIZE] = {};
320+
std::memcpy(response_header, REPL_RESPONSE_MAGIC, 4);
321+
const uint32_t output_len = static_cast<uint32_t>(output.len);
322+
std::memcpy(response_header + 4, &status, sizeof(status));
323+
std::memcpy(response_header + 8, &output_len, sizeof(output_len));
324+
Serial.write(response_header, sizeof(response_header));
306325
if (output.len > 0) {
307-
Serial.printf("rss:%c%04x", 'D', static_cast<unsigned>(output.len));
308326
Serial.write(output.data, output.len);
309327
rustscript_buffer_free(output);
310-
} else {
311-
Serial.printf("rss:repl-status=%d\n", static_cast<int>(status));
312328
}
313-
Serial.print("rss:pd-vm> ");
329+
Serial.flush();
330+
continue;
331+
}
332+
333+
String header = Serial.readStringUntil('\n');
334+
header.trim();
335+
if (header.length() == 0) {
314336
continue;
315337
}
316338
// Legacy commands

include/rustscript_embedded.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ enum rustscript_status {
1616
RUSTSCRIPT_STATUS_HOST_ERROR = -3,
1717
RUSTSCRIPT_STATUS_OUT_OF_FUEL = -4,
1818
RUSTSCRIPT_STATUS_VM_ERROR = -5,
19+
RUSTSCRIPT_STATUS_INVALID_REPL_STATE = -6,
1920
};
2021

2122
enum rustscript_value_tag {

include/rustscript_loader.h

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,10 @@ void rustscript_repl();
2222
}
2323
#endif
2424

25-
#define REPL_MAGIC "RSSR"
26-
#define REPL_LEN_SIZE 4
27-
#define REPL_CMD_HELLO 1
28-
#define REPL_CMD_HELLO_RESPONSE 2
29-
#define REPL_CMD_PAYLOAD 3
30-
#define REPL_CMD_PAYLOAD_RESPONSE 4
31-
#define REPL_CMD_ERROR 5
25+
#define REPL_REQUEST_MAGIC "RSSQ"
26+
#define REPL_RESPONSE_MAGIC "RSSP"
27+
#define REPL_FRAME_HEADER_SIZE 12
28+
#define REPL_MAX_FRAME_SIZE (16U * 1024U * 1024U)
3229

33-
// Power-on state: send HELLO -> receive HELLO_RESPONSE (or timeout -> legacy PROMPT).
34-
// Main loop: send PAYLOAD (program_len + state_len + program + state)
35-
// -> receive PAYLOAD_RESPONSE (status_code + response_len + response)
36-
// or ERROR (message_len + message).
30+
// Request: "RSSQ" + program_len(u32 LE) + state_len(u32 LE) + program + state.
31+
// Response: "RSSP" + status(i32 LE) + response_len(u32 LE) + response.

0 commit comments

Comments
 (0)