Skip to content

Commit 552f71b

Browse files
committed
Add source plugin frontend API
1 parent b8a7796 commit 552f71b

43 files changed

Lines changed: 495 additions & 10596 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: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# RustScript
22

3-
RustScript is the language, VM, Lua/JavaScript frontends, standard library, examples, bytecode/AOT tooling, wasm runtime support, and debugger-facing runtime contract for the RustScript family.
3+
RustScript is the language, VM/compiler core, standard library, examples, bytecode/AOT tooling, wasm runtime support, and debugger-facing runtime contract for the RustScript family. Compatibility language frontends now live in source-plugin crates.
44

55
## Related projects
66

@@ -9,6 +9,7 @@ RustScript is the language, VM, Lua/JavaScript frontends, standard library, exam
99
- CLR VM: https://github.com/rustscript-lang/rustscript-clr-vm
1010
- Edge runtime and ABI: https://github.com/rustscript-lang/pd-edge
1111
- Controller: https://github.com/rustscript-lang/pd-controller
12+
- Compatibility frontends: https://github.com/rustscript-lang/rustscript-compat-frontends
1213

1314
## Crate usage
1415

@@ -26,8 +27,7 @@ cargo test --workspace --jobs 4
2627
cargo build --workspace --release --jobs 4
2728
```
2829

29-
`pd-vm` is a stack-based virtual machine plus compiler toolchain used as a backend for multiple
30-
source syntaxes (`.rss`, `.js`, `.lua`).
30+
`pd-vm` is a stack-based virtual machine plus compiler toolchain. It includes the RustScript (`.rss`) frontend and exposes a source-plugin API for compatibility languages such as JavaScript and Lua.
3131

3232
## Contents
3333

@@ -80,15 +80,10 @@ than optional hints.
8080
Run with the VM runner binary:
8181

8282
```powershell
83-
cargo run -p pd-vm --bin pd-vm-run -- --fuel 100000 examples/example.lua
83+
cargo run -p pd-vm --bin pd-vm-run -- --fuel 100000 examples/example.rss
8484
```
8585

86-
Other supported flavors:
87-
88-
```powershell
89-
cargo run -p pd-vm --bin pd-vm-run -- examples/example.js
90-
cargo run -p pd-vm --bin pd-vm-run -- examples/example.rss
91-
```
86+
Compatibility frontends such as JavaScript and Lua are provided by source-plugin crates. Use `CompileSourceFileOptions::with_source_plugin(...)` when compiling those files from an embedding crate.
9287

9388
### REPL
9489

@@ -104,13 +99,13 @@ cargo run -p pd-vm --bin pd-vm-run -- --repl
10499
Run with interactive `pdb` debugger on stdio:
105100

106101
```powershell
107-
cargo run -p pd-vm --bin pd-vm-run -- --debug examples/example.lua
102+
cargo run -p pd-vm --bin pd-vm-run -- --debug examples/example.rss
108103
```
109104

110105
Run debugger over TCP:
111106

112107
```powershell
113-
cargo run -p pd-vm --bin pd-vm-run -- --debug --tcp 127.0.0.1:9002 examples/example.lua
108+
cargo run -p pd-vm --bin pd-vm-run -- --debug --tcp 127.0.0.1:9002 examples/example.rss
114109
```
115110

116111
Useful commands: `break`, `break line`, `step`, `next`, `out`, `stack`, `locals`, `where`, `continue`, `fuel`, `epoch`.
@@ -120,7 +115,7 @@ Useful commands: `break`, `break line`, `step`, `next`, `out`, `stack`, `locals`
120115
Record execution:
121116

122117
```powershell
123-
cargo run -p pd-vm --bin pd-vm-run -- --record out/example.pdr examples/example.lua
118+
cargo run -p pd-vm --bin pd-vm-run -- --record out/example.pdr examples/example.rss
124119
```
125120

126121
Replay execution:
@@ -473,7 +468,7 @@ The end-to-end stack is split into layers. Not every entrypoint uses every layer
473468

474469
1. Module/source loading (`compile_source_file()` path)
475470
1. Unit linking (`linker::merge_units`)
476-
1. Frontend lowering (`rustscript`, `javascript`, `lua`)
471+
1. Frontend lowering (built-in `rustscript`, plus any registered source plugins)
477472
1. Frontend-independent IR
478473
1. Type-consistency validation on legalized IR (for example rejecting known `if`/`else` branch mismatches)
479474
1. Lifetime/liveness lowering plus type metadata collection
@@ -504,8 +499,7 @@ monomorphic runtime fast paths, not to provide a full source-language static typ
504499

505500
#### Compiler APIs
506501

507-
Use `compile_source()` for RustScript, or `compile_source_file()` for extension-based flavor
508-
selection (`.rss`, `.js`, `.lua`).
502+
Use `compile_source()` for RustScript, or `compile_source_file()` for built-in `.rss` path loading. For compatibility languages, build options with `CompileSourceFileOptions::with_source_plugin(...)` and call `compile_source_file_with_options(...)`.
509503

510504
```text
511505
fn print(x);
@@ -526,23 +520,17 @@ let add = |value| value + base;
526520
add(5);
527521
```
528522

529-
Lua closure equivalent lowered by frontend:
530-
531-
```lua
532-
local add = function(value) return value + base end
533-
```
523+
Compatibility frontends can lower equivalent closure forms through the source-plugin API.
534524

535525
Built-in print aliases (no declaration needed):
536526

537527
- RustScript: `print(value);`, `print("... {}", a);`, `println(value);`, `println("... {}", a);`
538-
- JavaScript subset: `console.log(value);` and `print(value);`
539-
- Lua subset: `print(value)`
528+
- Compatibility frontends may provide their own print aliases through plugin lowering.
540529

541530
Host calls must be explicitly imported:
542531

543532
- RustScript: `use runtime;`, `use http;`, `use rate_limit;`
544-
- JavaScript: `import * as runtime from "runtime";`, `import * as http from "http";`
545-
- Lua: `require("runtime")`, `require("http")`
533+
- Compatibility frontends own their import syntax through `SourcePlugin::parse_imports(...)` and `SourcePlugin::strip_imports(...)`.
546534

547535
#### Assembler API
548536

@@ -633,18 +621,11 @@ Module/source loading:
633621

634622
- `crate::...` module paths are not supported in RustScript source loading; use relative module paths
635623

636-
JavaScript frontend:
637-
638-
- arrow closures with block bodies are not supported (expression-body arrows only)
639-
- `return <expr>;` is lowered to `<expr>;` (function results still follow final-expression semantics)
640-
641-
Lua frontend:
624+
Source plugins:
642625

643-
- Lua pattern API string methods (`:match`, `:gsub`, etc.) are not supported
644-
- function literal bodies are limited to `function(...) end`, `function(...) return end`, or `function(...) return <expr[, expr...]> end`
645-
- direct `function`/`local function` bodies are still minimal: empty/fallthrough, `return`, `return <expr[, expr...]>`, or a single return-only `if`/`elseif`/`else` chain
646-
- `pcall(...)` / `xpcall(...)` lower with success-only semantics and always prefix `true` before the callee return values
647-
- multi-return unpacking is currently limited to compiler-known Lua function/closure return shapes; extra return values are dropped, missing locals are filled with `null`, but plain assignment destructuring and arbitrary host-call unpacking are not supported
626+
- Compatibility-language parsing, lowering, and source import scanning belong in plugin crates.
627+
- `pd-vm` exposes `SourcePlugin`, `FrontendIr`, parser dialect helpers, and IR builder types for plugin authors.
628+
- `compile_source_file()` without options only handles built-in `.rss`; use `compile_source_file_with_options()` for plugin-backed extensions.
648629

649630
### JIT Internals
650631

build.rs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ fn render_builtin_catalog(
378378
)
379379
.unwrap();
380380
writeln!(&mut out, "#[repr(u16)]").unwrap();
381-
writeln!(&mut out, "pub(crate) enum BuiltinFunction {{").unwrap();
381+
writeln!(&mut out, "pub enum BuiltinFunction {{").unwrap();
382382
for (index, variant) in builtin_variant_order.iter().enumerate() {
383383
if index == 0 {
384384
writeln!(&mut out, " {variant} = 0,").unwrap();
@@ -514,7 +514,7 @@ fn render_builtin_catalog(
514514

515515
writeln!(
516516
&mut out,
517-
"pub(crate) fn is_builtin_namespace(namespace: &str) -> bool {{"
517+
"pub fn is_builtin_namespace(namespace: &str) -> bool {{"
518518
)
519519
.unwrap();
520520
writeln!(
@@ -527,7 +527,7 @@ fn render_builtin_catalog(
527527

528528
writeln!(
529529
&mut out,
530-
"pub(crate) fn resolve_builtin_namespace_call(namespace: &str, member: &str) -> Option<BuiltinFunction> {{"
530+
"pub fn resolve_builtin_namespace_call(namespace: &str, member: &str) -> Option<BuiltinFunction> {{"
531531
)
532532
.unwrap();
533533
writeln!(
@@ -589,13 +589,13 @@ fn render_builtin_catalog(
589589
render_builtin_signature_method(&mut out, &builtin_variant_order);
590590
writeln!(
591591
&mut out,
592-
" pub(crate) fn from_namespaced_name(name: &str) -> Option<Self> {{"
592+
" pub fn from_namespaced_name(name: &str) -> Option<Self> {{"
593593
)
594594
.unwrap();
595595
writeln!(&mut out, " resolve_namespaced_builtin(name)").unwrap();
596596
writeln!(&mut out, " }}").unwrap();
597597
writeln!(&mut out).unwrap();
598-
writeln!(&mut out, " pub(crate) fn call_index(self) -> u16 {{").unwrap();
598+
writeln!(&mut out, " pub fn call_index(self) -> u16 {{").unwrap();
599599
writeln!(&mut out, " match self {{").unwrap();
600600
writeln!(
601601
&mut out,
@@ -1078,7 +1078,7 @@ fn render_builtin_arity_method(
10781078
builtin_variant_order: &[String],
10791079
actual_builtin_by_variant: &HashMap<String, Vec<&CallableDecl>>,
10801080
) {
1081-
writeln!(out, " pub(crate) fn arity(self) -> u8 {{").unwrap();
1081+
writeln!(out, " pub fn arity(self) -> u8 {{").unwrap();
10821082
writeln!(out, " match self {{").unwrap();
10831083
for variant in builtin_variant_order {
10841084
let arity = actual_builtin_by_variant
@@ -1103,11 +1103,7 @@ fn render_builtin_accepts_arity_method(
11031103
builtin_variant_order: &[String],
11041104
actual_builtin_by_variant: &HashMap<String, Vec<&CallableDecl>>,
11051105
) {
1106-
writeln!(
1107-
out,
1108-
" pub(crate) fn accepts_arity(self, arity: u8) -> bool {{"
1109-
)
1110-
.unwrap();
1106+
writeln!(out, " pub fn accepts_arity(self, arity: u8) -> bool {{").unwrap();
11111107
writeln!(out, " match self {{").unwrap();
11121108
for variant in builtin_variant_order {
11131109
let mut conditions = actual_builtin_by_variant

docs/blog/v07-why-rustscript.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
## The Starting Point
88

9-
pd-vm already runs four source-language frontends on the same stack-based VM with the same bytecode. All four lower into a shared `FrontendIr` before compilation, and all four run on a runtime that uses no garbage collector and relies on deterministic ownership for memory management.
9+
pd-vm runs RustScript on a stack-based VM with bytecode that plugin frontends can also target. Built-in RustScript and plugin sources lower into a shared `FrontendIr` before compilation, then run on a runtime that uses no garbage collector and relies on deterministic ownership for memory management.
1010

11-
Given those other frontends, a fair question is: why add another one? What does a Rust-shaped scripting syntax buy a VM that already works?
11+
Given the plugin path for compatibility languages, a fair question is: what does a Rust-shaped scripting syntax buy this VM?
1212

1313
The short answer is that RustScript exists because its syntax carries **intent that the compiler can act on**. Move-vs-copy distinctions, borrow annotations, and mutability declarations are not runtime features. They are compiler inputs. And they happen to be in a syntax that AI models already know well.
1414

@@ -34,14 +34,14 @@ let b = a; // move: `a` is consumed, slot is cleared
3434

3535
The compiler sees the local-to-local rebind of a non-copyable value, lowers it as a consuming load (`MoveVar` in the IR), and emits the null-clear sequence. Using `a` after this point is a compile-time error, not a runtime surprise.
3636

37-
Compare the same logic in the JavaScript frontend:
37+
Compare the same logic in a copy-by-default compatibility frontend:
3838

3939
```javascript
4040
let a = [1, 2, 3];
4141
let b = a; // copy: both `a` and `b` share the array
4242
```
4343

44-
Both programs compile to valid bytecode on the same VM. The difference is what the compiler knows at the point of the assignment. RustScript's syntax tells it "this is a transfer of ownership." The JS frontend's syntax tells it nothing, so it defaults to shared copy.
44+
Both programs compile to valid bytecode on the same VM. The difference is what the compiler knows at the point of the assignment. RustScript's syntax tells it "this is a transfer of ownership." A copy-by-default plugin syntax tells it nothing, so it defaults to shared copy.
4545

4646
### `.copy()` and Borrows
4747

@@ -70,7 +70,7 @@ The core of RustScript's value is the availability analysis in `availability.rs`
7070

7171
When `enable_local_move_semantics` is active (RustScript only), the pass enforces stricter rules:
7272

73-
| Situation | RustScript behavior | JS / Lua behavior |
73+
| Situation | RustScript behavior | Compatibility plugin behavior |
7474
|---|---|---|
7575
| Local-to-local rebind of a collection | Move source, reject reuse | Copy (shared ownership) |
7676
| Local read after move | Compile error | N/A (no moves) |
@@ -113,7 +113,7 @@ The capture binding modes are:
113113
| **BorrowMut** | `&mut x` in capture position | Non-consuming; mutation tracked |
114114
| **Move** | `x` (default for non-copyable) | Outer local consumed |
115115

116-
In the JS and Lua frontends, all captures degrade to `Copy` mode. The capture slot still gets a value, but the outer scope keeps its own copy unconditionally.
116+
Compatibility frontends can choose to degrade captures to `Copy` mode. The capture slot still gets a value, but the outer scope keeps its own copy unconditionally.
117117

118118
## Type Inference and the Ownership Surface
119119

@@ -128,12 +128,12 @@ These are all DX-layer decisions. They catch mistakes before bytecode is generat
128128

129129
## The AI Angle: Syntax Proximity to Rust
130130

131-
There is a practical benefit that was not part of the original design but became increasingly clear as the project grew: **AI code-generation models produce better RustScript than they produce JavaScript or Lua for this VM**.
131+
There is a practical benefit that was not part of the original design but became increasingly clear as the project grew: **AI code-generation models produce better RustScript than they produce compatibility languages for this VM**.
132132

133133
The reason is corpus proximity. Large language models trained on significant amounts of Rust code have internalized Rust's ownership patterns, borrow semantics, and idiomatic structures. When asked to write RustScript for pd-vm — which lives in the same repository as the Rust runtime — the model:
134134

135135
1. **Naturally uses ownership-aware patterns.** It writes `let b = a;` as a move and `let b = a.copy();` when it needs the source to survive. It does not try to share mutable state through aliasing.
136-
2. **Avoids GC-dependent patterns.** Rust-trained models do not reach for garbage-collected idioms like long-lived closures over mutable state or circular references. Those patterns would be valid in the JS frontend but counterproductive for a GC-free runtime.
136+
2. **Avoids GC-dependent patterns.** Rust-trained models do not reach for garbage-collected idioms like long-lived closures over mutable state or circular references. Those patterns can be natural in compatibility-language frontends but counterproductive for a GC-free runtime.
137137
3. **Matches the surrounding code style.** The pd-vm runtime is Rust. The compiler is Rust. The test harness is Rust. When the scripting language is also Rust-shaped, the model does not need to context-switch between two different programming idioms within the same codebase.
138138
4. **Produces more accurate type annotations.** Because the model knows Rust's type annotation syntax, it generates valid RustScript type hints that feed into the compiler's inference pipeline rather than leaving locals at `Unknown`.
139139

examples/example.js

Lines changed: 0 additions & 16 deletions
This file was deleted.

examples/example.lua

Lines changed: 0 additions & 14 deletions
This file was deleted.

examples/example.scm

Lines changed: 0 additions & 8 deletions
This file was deleted.

examples/example_complex.js

Lines changed: 0 additions & 52 deletions
This file was deleted.

0 commit comments

Comments
 (0)