Skip to content

Commit 908a207

Browse files
committed
Add runnable interpreter cycle hooks
1 parent 3f4391e commit 908a207

8 files changed

Lines changed: 227 additions & 12 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ If a lowered package contains `fn main() -> Unit` or `fn main() -> Result<Unit,
418418

419419
`rss verify-rust <file.rss> --out-dir <directory>` keeps the generated package used for backend checking, including `rsscript-source-map.json`, so unmappable rustc diagnostics can be inspected against the generated Rust.
420420

421-
The first observable runtime boundaries are `Log.write(message: read String)`, `Assert.equal(left: read String, right: read String)`, and the core `File`, `Json`, `Csv`, `Image`, `ImageCache`, HTTP handler, DB resource-pool, config reload, and `Counter` APIs, which lower to explicit runtime hooks. `ImageCache` is the first small retained managed-container hook: it stores managed image handles and enforces a capacity limit. Simple core operations such as `String.concat(left: read String, right: read String)` keep RSScript `.rssi` signatures for checking and review, but lower directly to Rust standard-library expressions. Built-in boolean literals, numeric operators, comparison operators, `Option<T>` constructors, and core surface types such as `Bytes`, `Buffer`, `Path`, `List<T>`, `Map<K,V>`, and `Set<T>` lower to the corresponding Rust literals, operators, enum constructors, and standard-library types; user-defined operator overloading remains forbidden.
421+
The first observable runtime boundaries are `Log.write(message: read String)`, `Assert.equal(left: read String, right: read String)`, and the core `File`, `Json`, `Csv`, `Image`, `ImageCache`, HTTP handler, DB resource-pool, config reload, interpreter object-cycle, and `Counter` APIs, which lower to explicit runtime hooks. `ImageCache` is the first small retained managed-container hook: it stores managed image handles and enforces a capacity limit. The interpreter hooks model `Environment` and `FunctionObject` as managed handles, including an environment/function closure cycle. Simple core operations such as `String.concat(left: read String, right: read String)` keep RSScript `.rssi` signatures for checking and review, but lower directly to Rust standard-library expressions. Built-in boolean literals, numeric operators, comparison operators, `Option<T>` constructors, and core surface types such as `Bytes`, `Buffer`, `Path`, `List<T>`, `Map<K,V>`, and `Set<T>` lower to the corresponding Rust literals, operators, enum constructors, and standard-library types; user-defined operator overloading remains forbidden.
422422

423423
Smallest runnable example:
424424

core/interpreter/interpreter.rssi

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
struct Environment
2+
3+
struct FunctionObject
4+
5+
pub fn Environment.root() -> fresh Environment
6+
7+
pub fn Environment.child(parent: read Environment) -> fresh Environment
8+
effects(retains(parent))
9+
10+
pub fn Environment.bind_function(
11+
env: mut Environment,
12+
function: read FunctionObject,
13+
) -> Unit
14+
effects(retains(function))
15+
16+
pub fn Environment.has_parent(env: read Environment) -> Bool
17+
18+
pub fn Environment.has_function(env: read Environment) -> Bool
19+
20+
pub fn FunctionObject.new(closure: read Environment) -> fresh FunctionObject
21+
effects(retains(closure))
22+
23+
pub fn FunctionObject.has_closure(function: read FunctionObject) -> Bool

core/prototype/builtins.rssi

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,3 @@ pub fn GlobalConfig.replace(
1515
pub fn Cache.lookup(cache: read Cache, key: read String)
1616

1717
pub fn Cache.get(cache: read Cache)
18-
19-
pub fn FunctionObject.new(closure: read Environment) -> fresh FunctionObject

examples/interpreter_cycle.rss

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
features: local
2+
3+
fn main() -> Unit {
4+
local root_value = Environment.root()
5+
let root = manage root_value
6+
7+
local child_value = Environment.child(parent: read root)
8+
let child = manage child_value
9+
10+
local function_value = FunctionObject.new(closure: read child)
11+
let function = manage function_value
12+
13+
Environment.bind_function(env: mut child, function: read function)
14+
15+
if Environment.has_parent(env: read child)
16+
&& Environment.has_function(env: read child)
17+
&& FunctionObject.has_closure(function: read function)
18+
{
19+
Log.write(message: read "interpreter cycle linked")
20+
}
21+
22+
return Unit
23+
}

runtime/src/lib.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,36 @@ pub struct Counter {
4545
value: i64,
4646
}
4747

48+
#[derive(Clone)]
49+
pub struct Environment {
50+
parent: Option<Gc<Environment>>,
51+
function: Option<Gc<FunctionObject>>,
52+
}
53+
54+
#[derive(Clone)]
55+
pub struct FunctionObject {
56+
closure: Gc<Environment>,
57+
}
58+
59+
impl fmt::Debug for Environment {
60+
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
61+
formatter
62+
.debug_struct("Environment")
63+
.field("has_parent", &self.parent.is_some())
64+
.field("has_function", &self.function.is_some())
65+
.finish()
66+
}
67+
}
68+
69+
impl fmt::Debug for FunctionObject {
70+
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
71+
formatter
72+
.debug_struct("FunctionObject")
73+
.field("has_closure", &true)
74+
.finish()
75+
}
76+
}
77+
4878
#[derive(Debug, Clone, PartialEq, Eq)]
4979
pub struct ConfigError {
5080
message: String,
@@ -391,6 +421,43 @@ pub fn counter_value(counter: &Counter) -> i64 {
391421
counter.value
392422
}
393423

424+
pub fn environment_root() -> Environment {
425+
Environment {
426+
parent: None,
427+
function: None,
428+
}
429+
}
430+
431+
pub fn environment_child(parent: &Gc<Environment>) -> Environment {
432+
Environment {
433+
parent: Some(parent.clone()),
434+
function: None,
435+
}
436+
}
437+
438+
pub fn environment_bind_function(env: &mut Gc<Environment>, function: &Gc<FunctionObject>) {
439+
env.write().function = Some(function.clone());
440+
}
441+
442+
pub fn environment_has_parent(env: &Gc<Environment>) -> bool {
443+
env.read().parent.is_some()
444+
}
445+
446+
pub fn environment_has_function(env: &Gc<Environment>) -> bool {
447+
env.read().function.is_some()
448+
}
449+
450+
pub fn function_object_new(closure: &Gc<Environment>) -> FunctionObject {
451+
FunctionObject {
452+
closure: closure.clone(),
453+
}
454+
}
455+
456+
pub fn function_object_has_closure(function: &Gc<FunctionObject>) -> bool {
457+
let _closure = function.read().closure.clone();
458+
true
459+
}
460+
394461
pub fn db_connection_open(url: &str) -> DbConnection {
395462
DbConnection {
396463
url: url.to_string(),
@@ -982,6 +1049,20 @@ mod tests {
9821049
assert_eq!(super::counter_value(&counter), 3);
9831050
}
9841051

1052+
#[test]
1053+
fn interpreter_runtime_hooks_link_environment_function_cycle() {
1054+
let root = super::manage(super::environment_root());
1055+
let child = super::manage(super::environment_child(&root));
1056+
let function = super::manage(super::function_object_new(&child));
1057+
let mut child_handle = child.clone();
1058+
1059+
super::environment_bind_function(&mut child_handle, &function);
1060+
1061+
assert!(super::environment_has_parent(&child));
1062+
assert!(super::environment_has_function(&child));
1063+
assert!(super::function_object_has_closure(&function));
1064+
}
1065+
9851066
#[test]
9861067
fn image_runtime_hooks_load_transform_and_save() {
9871068
let input = std::env::temp_dir().join(format!(

src/interfaces.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ pub(crate) const CORE_INTERFACES: &[(&str, &str)] = &[
2222
"core/http/http.rssi",
2323
include_str!("../core/http/http.rssi"),
2424
),
25+
(
26+
"core/interpreter/interpreter.rssi",
27+
include_str!("../core/interpreter/interpreter.rssi"),
28+
),
2529
(
2630
"core/image/image.rssi",
2731
include_str!("../core/image/image.rssi"),

src/rust_lower.rs

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use crate::analyzer::analyze_source_with_core;
77
use crate::diagnostic::{Diagnostic, Severity, Span, code};
88
use crate::syntax::ast::{
99
BinaryOp, Block, CallArg, Callee, DataEffect, EffectDecl, Expr, FieldDecl, FileFeature,
10-
FunctionDecl, GenericBound, GenericParam, Item, LetKind, Param, Program, Stmt, TypeDecl,
11-
TypeKind, TypeRef,
10+
FunctionDecl, GenericBound, GenericParam, Item, Param, Program, Stmt, TypeDecl, TypeKind,
11+
TypeRef,
1212
};
1313
use crate::syntax::parse_source;
1414

@@ -484,12 +484,11 @@ impl<'a> RustLowerer<'a> {
484484
self.record_statement_source_map(statement, &marker.generated);
485485
match statement {
486486
Stmt::Let(stmt) => {
487-
let mutable =
488-
if stmt.kind == LetKind::Local || self.mutated_bindings.contains(&stmt.name) {
489-
"mut "
490-
} else {
491-
""
492-
};
487+
let mutable = if self.mutated_bindings.contains(&stmt.name) {
488+
"mut "
489+
} else {
490+
""
491+
};
493492
if let Some(value) = &stmt.value {
494493
out.push_str(&format!(
495494
"{pad}let {mutable}{} = {};\n",
@@ -812,6 +811,8 @@ impl<'a> RustLowerer<'a> {
812811
"Fd" => "i64".to_string(),
813812
"Bytes" | "Buffer" => "Vec<u8>".to_string(),
814813
"Path" => "std::path::PathBuf".to_string(),
814+
"Environment" => "rsscript_runtime::Environment".to_string(),
815+
"FunctionObject" => "rsscript_runtime::FunctionObject".to_string(),
815816
"Counter" => "rsscript_runtime::Counter".to_string(),
816817
"File" => "rsscript_runtime::File".to_string(),
817818
"FileError" | "IOError" => "std::io::Error".to_string(),
@@ -1214,6 +1215,27 @@ fn lower_callee(callee: &Callee) -> String {
12141215
callee if is_counter_new_callee(callee) => "rsscript_runtime::counter_new".to_string(),
12151216
callee if is_counter_add_callee(callee) => "rsscript_runtime::counter_add".to_string(),
12161217
callee if is_counter_value_callee(callee) => "rsscript_runtime::counter_value".to_string(),
1218+
callee if is_environment_root_callee(callee) => {
1219+
"rsscript_runtime::environment_root".to_string()
1220+
}
1221+
callee if is_environment_child_callee(callee) => {
1222+
"rsscript_runtime::environment_child".to_string()
1223+
}
1224+
callee if is_environment_bind_function_callee(callee) => {
1225+
"rsscript_runtime::environment_bind_function".to_string()
1226+
}
1227+
callee if is_environment_has_parent_callee(callee) => {
1228+
"rsscript_runtime::environment_has_parent".to_string()
1229+
}
1230+
callee if is_environment_has_function_callee(callee) => {
1231+
"rsscript_runtime::environment_has_function".to_string()
1232+
}
1233+
callee if is_function_object_new_callee(callee) => {
1234+
"rsscript_runtime::function_object_new".to_string()
1235+
}
1236+
callee if is_function_object_has_closure_callee(callee) => {
1237+
"rsscript_runtime::function_object_has_closure".to_string()
1238+
}
12171239
callee if is_db_connection_open_callee(callee) => {
12181240
"rsscript_runtime::db_connection_open".to_string()
12191241
}
@@ -1345,6 +1367,34 @@ fn is_counter_value_callee(callee: &Callee) -> bool {
13451367
matches!(callee, Callee::Qualified { namespace, name } if type_root_name(namespace) == "Counter" && name == "value")
13461368
}
13471369

1370+
fn is_environment_root_callee(callee: &Callee) -> bool {
1371+
matches!(callee, Callee::Qualified { namespace, name } if type_root_name(namespace) == "Environment" && name == "root")
1372+
}
1373+
1374+
fn is_environment_child_callee(callee: &Callee) -> bool {
1375+
matches!(callee, Callee::Qualified { namespace, name } if type_root_name(namespace) == "Environment" && name == "child")
1376+
}
1377+
1378+
fn is_environment_bind_function_callee(callee: &Callee) -> bool {
1379+
matches!(callee, Callee::Qualified { namespace, name } if type_root_name(namespace) == "Environment" && name == "bind_function")
1380+
}
1381+
1382+
fn is_environment_has_parent_callee(callee: &Callee) -> bool {
1383+
matches!(callee, Callee::Qualified { namespace, name } if type_root_name(namespace) == "Environment" && name == "has_parent")
1384+
}
1385+
1386+
fn is_environment_has_function_callee(callee: &Callee) -> bool {
1387+
matches!(callee, Callee::Qualified { namespace, name } if type_root_name(namespace) == "Environment" && name == "has_function")
1388+
}
1389+
1390+
fn is_function_object_new_callee(callee: &Callee) -> bool {
1391+
matches!(callee, Callee::Qualified { namespace, name } if type_root_name(namespace) == "FunctionObject" && name == "new")
1392+
}
1393+
1394+
fn is_function_object_has_closure_callee(callee: &Callee) -> bool {
1395+
matches!(callee, Callee::Qualified { namespace, name } if type_root_name(namespace) == "FunctionObject" && name == "has_closure")
1396+
}
1397+
13481398
fn is_db_connection_open_callee(callee: &Callee) -> bool {
13491399
matches!(callee, Callee::Qualified { namespace, name } if type_root_name(namespace) == "DbConnection" && name == "open")
13501400
}

tests/checker.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ fn bundled_core_interfaces_are_available_to_checker() {
9797
.iter()
9898
.any(|(path, _)| *path == "core/counter/counter.rssi")
9999
);
100+
assert!(
101+
core_interfaces()
102+
.iter()
103+
.any(|(path, _)| *path == "core/interpreter/interpreter.rssi")
104+
);
100105

101106
let source = r#"
102107
fn check_label(actual: read String, expected: read String) -> Unit {
@@ -540,6 +545,37 @@ fn main() -> Unit {
540545
assert!(rust.contains("if value == 3 {"));
541546
}
542547

548+
#[test]
549+
fn rust_lowering_maps_interpreter_cycle_core_calls_to_runtime_hooks() {
550+
let source = r#"
551+
features: local
552+
553+
fn main() -> Unit {
554+
local root_value = Environment.root()
555+
let root = manage root_value
556+
local child_value = Environment.child(parent: read root)
557+
let child = manage child_value
558+
local function_value = FunctionObject.new(closure: read child)
559+
let function = manage function_value
560+
Environment.bind_function(env: mut child, function: read function)
561+
if Environment.has_function(env: read child) && FunctionObject.has_closure(function: read function) {
562+
Log.write(message: read "linked")
563+
}
564+
return Unit
565+
}
566+
"#;
567+
let rust = lower_source_to_rust("interpreter-cycle.rss", source).expect("source should lower");
568+
569+
assert!(rust.contains("let root_value = rsscript_runtime::environment_root();"));
570+
assert!(rust.contains("let root = rsscript_runtime::manage_at(root_value,"));
571+
assert!(rust.contains("let child_value = rsscript_runtime::environment_child(&root);"));
572+
assert!(rust.contains("let mut child = rsscript_runtime::manage_at(child_value,"));
573+
assert!(rust.contains("let function_value = rsscript_runtime::function_object_new(&child);"));
574+
assert!(rust.contains("let function = rsscript_runtime::manage_at(function_value,"));
575+
assert!(rust.contains("rsscript_runtime::environment_bind_function(&mut child, &function);"));
576+
assert!(rust.contains("rsscript_runtime::function_object_has_closure(&function)"));
577+
}
578+
543579
#[test]
544580
fn rust_lowering_decodes_string_escape_sequences() {
545581
let source = r#"
@@ -881,7 +917,7 @@ pub fn make_session(id: Int) -> Session {
881917

882918
assert!(rust.contains("pub struct Session"));
883919
assert!(rust.contains("pub fn make_session(id: i64) -> rsscript_runtime::Gc<Session>"));
884-
assert!(rust.contains("let mut session = Session { id: id };"));
920+
assert!(rust.contains("let session = Session { id: id };"));
885921
assert!(rust.contains("return rsscript_runtime::manage_at(session, rsscript_runtime::SourceSpan::new(\"session.rss\", 10, 12, 6));"));
886922
}
887923

0 commit comments

Comments
 (0)