forked from dylan-sutton-chavez/edge-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcommon.rs
More file actions
288 lines (256 loc) · 11.5 KB
/
common.rs
File metadata and controls
288 lines (256 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
/*
Test infra for `packages`: TestResolver (modules + nested manifests, walk-up parity), `test_native(name)`, fixtures for pure/impure/alloc/error/primitive paths.
*/
#![allow(dead_code)]
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use compiler::util::fx::FxHashMap;
use compiler::modules::packages::{
NativeBinding, Resolved, Resolver, partition_bindings,
Manifest, walk_up_dirs, dir_of, join_relative,
};
use compiler::modules::vm::types::{HeapObj, HeapPool, Val, VmErr};
// TestResolver
/* Shared state across a TestResolver and its children; mirrors the WASM bridge's flat cache + in-flight set. */
#[derive(Default)]
struct TestResolverState {
modules: HashMap<String, Resolved>,
/* Manifests keyed by directory; walk-up checks each parent of the importer's location. */
manifests: HashMap<String, Manifest>,
in_flight: HashSet<String>,
/* Raw bytes per spec consumed by `fetch_bytes`; drives `#sha256-` integrity tests. */
bytes: HashMap<String, Vec<u8>>,
}
pub struct TestResolver {
state: Rc<RefCell<TestResolverState>>,
in_flight_marker: Option<String>,
dir: String, // Scoped dir for this resolver; bare-name imports walk up from here. Empty = entry script.
}
impl Drop for TestResolver {
fn drop(&mut self) {
if let Some(canon) = self.in_flight_marker.take() {
self.state.borrow_mut().in_flight.remove(&canon);
}
}
}
impl TestResolver {
pub fn new() -> Self {
Self {
state: Rc::new(RefCell::new(TestResolverState::default())),
in_flight_marker: None,
dir: String::new(),
}
}
pub fn with_native(self, spec: &str, bindings: Vec<NativeBinding>) -> Self {
// Partition by export-name convention, mirroring the real WASM host resolver.
let (bindings, classes, consts) = partition_bindings(bindings);
self.state.borrow_mut().modules.insert(
spec.to_string(),
Resolved::Native { bindings, classes, consts, canonical: spec.to_string() },
);
self
}
pub fn with_code(self, spec: &str, src: &str) -> Self {
self.state.borrow_mut().modules.insert(
spec.to_string(),
Resolved::Code { src: src.to_string(), canonical: spec.to_string() },
);
self
}
/* Bytes the parser will hash for `spec` when verifying `#sha256-...`. */
pub fn with_bytes(self, spec: &str, bytes: Vec<u8>) -> Self {
self.state.borrow_mut().bytes.insert(spec.to_string(), bytes);
self
}
/* Add an alias to the root manifest; additive, accumulates across calls. */
pub fn with_alias(self, name: &str, target: &str) -> Self {
{
let mut s = self.state.borrow_mut();
let m = s.manifests.entry(String::new()).or_insert_with(|| Manifest {imports: FxHashMap::default(), extends: None});
m.imports.insert(name.to_string(), target.to_string());
}
self
}
/* Register a manifest at `dir`; nearer manifests win for bare-name resolution. */
pub fn with_manifest(self, dir: &str, imports: &[(&str, &str)], extends: Option<&str>) -> Self {
let mut imp = FxHashMap::default();
for (k, v) in imports { imp.insert(k.to_string(), v.to_string()); }
let m = Manifest { imports: imp, extends: extends.map(|s| s.to_string()) };
self.state.borrow_mut().manifests.insert(dir.to_string(), m);
self
}
}
impl Resolver for TestResolver {
fn resolve(&mut self, spec: &str) -> Result<Resolved, String> {
if !spec.contains('/') {
let dir = self.dir.clone();
return self.resolve_bare(spec, &dir);
}
let canonical = if spec.contains("://") || spec.starts_with('/') {
spec.to_string()
} else {
join_relative(&self.dir, spec)
};
self.resolve_canonical(&canonical)
}
/* Transitive sub-resolver: shares state, rescopes `dir`; Drop clears the in-flight marker. */
fn child(&self, spec: &str) -> Box<dyn Resolver> {
let canon = spec.to_string();
self.state.borrow_mut().in_flight.insert(canon.clone());
Box::new(TestResolver {
state: Rc::clone(&self.state),
in_flight_marker: Some(canon),
dir: dir_of(spec).to_string(),
})
}
fn fetch_bytes(
&mut self,
spec: &str,
_expected_hash: Option<[u8; 32]>,
) -> Result<Vec<u8>, String> {
// In-memory map; the parser still runs its hash check downstream.
match self.state.borrow().bytes.get(spec) {
Some(b) => Ok(b.clone()),
None => Err(format!("module '{}' integrity verification not supported by this resolver", spec)),
}
}
}
impl TestResolver {
/* Walk up from `start_dir` for the nearest manifest declaring `name`; `extends` chains with cycle detection. */
fn resolve_bare(&mut self, name: &str, start_dir: &str) -> Result<Resolved, String> {
let mut visited: HashSet<String> = HashSet::new();
let mut search_dir = start_dir.to_string();
let mut hops = 0u32;
loop {
if hops > 32 { return Err(format!("packages.json walk-up exceeded 32 hops resolving '{}'", name)); }
hops += 1;
let mut hit: Option<(String, Option<String>, Option<String>)> = None;
for dir in walk_up_dirs(&search_dir) {
let s = self.state.borrow();
if let Some(m) = s.manifests.get(&dir) {
let target = m.imports.get(name).cloned();
let ext = m.extends.clone();
drop(s);
hit = Some((dir, target, ext));
break;
}
}
let Some((dir, target, ext)) = hit else {
return Err(format!(
"no packages.json above '{}' declares '{}'", start_dir, name));
};
if let Some(target) = target {
let canonical = join_relative(&dir, &target);
return self.resolve_canonical(&canonical);
}
if let Some(ext) = ext {
let m_spec = format!("{}packages.json", dir);
if !visited.insert(m_spec) {
return Err("circular extends chain in packages.json".to_string());
}
let mut next = join_relative(&dir, &ext);
if !next.ends_with('/') { next.push('/'); }
search_dir = next;
continue;
}
return Err(format!("alias '{}' not declared in '{}packages.json'\nhelp: declare it, add \"extends\": \"..\" to inherit, or use a quoted path", name, dir));
}
}
fn resolve_canonical(&self, spec: &str) -> Result<Resolved, String> {
let s = self.state.borrow();
if s.in_flight.contains(spec) {
return Err(format!("circular import: '{}'", spec));
}
match s.modules.get(spec) {
// Clone so the same module can be re-imported under multiple aliases.
Some(r) => Ok(r.clone()),
None => Err(format!("module '{}' not found in TestResolver", spec)),
}
}
}
// Fixture functions. Third arg is the kwargs slot (`None` for positional calls); these fixtures ignore it.
/* Pure: a + b, exercises CallExtern dispatch, arg marshalling, and template memo. */
fn add(_: &mut HeapPool, args: &[Val], _kw: Option<Val>) -> Result<Val, VmErr> {
if args.len() != 2 { return Err(VmErr::Type("add: expected 2 args")); }
let a = if args[0].is_int() { args[0].as_int() } else { return Err(VmErr::Type("add: arg 0 not int")); };
let b = if args[1].is_int() { args[1].as_int() } else { return Err(VmErr::Type("add: arg 1 not int")); };
Ok(Val::int(a + b))
}
/* Pure: x * x. Used to verify nested calls (square(add(1,2))). */
fn square(_: &mut HeapPool, args: &[Val], _kw: Option<Val>) -> Result<Val, VmErr> {
if args.len() != 1 { return Err(VmErr::Type("square: expected 1 arg")); }
let x = if args[0].is_int() { args[0].as_int() } else { return Err(VmErr::Type("square: arg not int")); };
Ok(Val::int(x * x))
}
/* Pure-but-allocs: heap string of length n; exercises HeapPool::alloc from extern context. */
fn make_str(heap: &mut HeapPool, args: &[Val], _kw: Option<Val>) -> Result<Val, VmErr> {
if args.len() != 1 { return Err(VmErr::Type("make_str: expected 1 arg")); }
let n = if args[0].is_int() { args[0].as_int() } else { return Err(VmErr::Type("make_str: arg not int")); };
let s: String = "x".repeat(n.max(0) as usize);
heap.alloc(HeapObj::Str(s))
}
/* Impure counter; verifies impurity taints the caller and skips memo. */
fn counter(_: &mut HeapPool, _args: &[Val], _kw: Option<Val>) -> Result<Val, VmErr> {
use std::sync::atomic::{AtomicI64, Ordering};
static N: AtomicI64 = AtomicI64::new(0);
Ok(Val::int(N.fetch_add(1, Ordering::SeqCst)))
}
/* Pure constant 42; for tests that only care extern was called. */
fn const_42(_: &mut HeapPool, _args: &[Val], _kw: Option<Val>) -> Result<Val, VmErr> {
Ok(Val::int(42))
}
/* Always errors; exercises extern-error propagation through dispatch. */
fn boom(_: &mut HeapPool, _args: &[Val], _kw: Option<Val>) -> Result<Val, VmErr> {
Err(VmErr::Runtime("boom from extern"))
}
/* f64 round-trip through an extern call (no int coercion). */
fn double_f(_: &mut HeapPool, args: &[Val], _kw: Option<Val>) -> Result<Val, VmErr> {
if args.len() != 1 || !args[0].is_float() {
return Err(VmErr::Type("double_f: expected one float arg"));
}
Ok(Val::float(args[0].as_float() * 2.0))
}
/* Pure: bool -> bool. Asserts that bool tags survive the extern dispatch. */
fn negate(_: &mut HeapPool, args: &[Val], _kw: Option<Val>) -> Result<Val, VmErr> {
if args.len() != 1 || !args[0].is_bool() {
return Err(VmErr::Type("negate: expected one bool arg"));
}
Ok(Val::bool(!args[0].as_bool()))
}
/* Returns HostCallDeferred; exercises the PendingHostCall yield path through call_extern. */
fn host_defer(_: &mut HeapPool, _args: &[Val], _kw: Option<Val>) -> Result<Val, VmErr> {
Err(VmErr::HostCallDeferred)
}
/* Const fixture: zero-arg export materialised at init, bound as a module value attr. */
fn const_pi(_: &mut HeapPool, _args: &[Val], _kw: Option<Val>) -> Result<Val, VmErr> {
Ok(Val::float(core::f64::consts::PI))
}
/* Pure: bool, int -> int. Mixes types to confirm per-arg decode is correct. */
fn pick(_: &mut HeapPool, args: &[Val], _kw: Option<Val>) -> Result<Val, VmErr> {
if args.len() != 3 || !args[0].is_bool() || !args[1].is_int() || !args[2].is_int() {
return Err(VmErr::Type("pick: expected (bool, int, int)"));
}
Ok(if args[0].as_bool() { args[2] } else { args[1] })
}
/* Fixture-name -> (fn ptr, purity); the runner turns each into a NativeBinding. */
type NativeFn = fn(&mut HeapPool, &[Val], Option<Val>) -> Result<Val, VmErr>;
pub fn test_native(name: &str) -> Option<NativeBinding> {
let (func, pure): (NativeFn, bool) = match name {
"add" => (add, true),
"square" => (square, true),
"make_str" => (make_str, true),
"counter" => (counter, false),
"const_42" => (const_42, true),
"boom" => (boom, true),
"double_f" => (double_f, true),
"negate" => (negate, true),
"pick" => (pick, true),
"host_defer" => (host_defer, false),
"__const_pi" => (const_pi, true),
// Registered under a builtin name to assert imported natives shadow builtins.
"abs" => (const_42, true),
_ => return None,
};
Some(NativeBinding::from_fn(name, func, pure))
}