schemurai 2.2.2 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +14 -3
- data/ext/schemurai_native/Cargo.lock +99 -0
- data/ext/schemurai_native/Cargo.toml +21 -0
- data/ext/schemurai_native/build.rs +35 -0
- data/ext/schemurai_native/extconf.rb +63 -0
- data/ext/schemurai_native/glue.c +639 -0
- data/ext/schemurai_native/protocol.def +63 -0
- data/ext/schemurai_native/protocol.h +10 -0
- data/ext/schemurai_native/src/content.rs +574 -0
- data/ext/schemurai_native/src/diagnostics.rs +965 -0
- data/ext/schemurai_native/src/format.rs +357 -0
- data/ext/schemurai_native/src/lib.rs +2376 -0
- data/ext/schemurai_native/src/number.rs +1055 -0
- data/ext/schemurai_native/src/pattern.rs +288 -0
- data/ext/schemurai_native/src/scope.rs +210 -0
- data/ext/schemurai_native/src/tracking.rs +532 -0
- data/lib/schemurai/backend.rb +2 -1
- data/lib/schemurai/native/diagnostics.rb +52 -0
- data/lib/schemurai/native/evaluator.rb +32 -0
- data/lib/schemurai/version.rb +1 -1
- data/lib/schemurai/vm/evaluator.rb +28 -32
- data/lib/schemurai.rb +8 -7
- data/sig/schemurai.rbs +1 -1
- metadata +22 -4
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
//! Crate-backed matching over borrowed Ruby string bytes.
|
|
2
|
+
//! The compatibility adapter translates VM shorthands and absolute anchors;
|
|
3
|
+
//! regex-syntax parses syntax, and regex-automata performs every search.
|
|
4
|
+
use super::*;
|
|
5
|
+
use regex_automata::{
|
|
6
|
+
Anchored, Input, MatchKind,
|
|
7
|
+
dfa::{Automaton, StartKind, dense},
|
|
8
|
+
meta::Regex,
|
|
9
|
+
nfa::thompson,
|
|
10
|
+
};
|
|
11
|
+
use regex_syntax::ast::{Ast, RepetitionKind, RepetitionRange};
|
|
12
|
+
|
|
13
|
+
#[derive(Debug)]
|
|
14
|
+
pub(super) enum Pattern {
|
|
15
|
+
Unsupported,
|
|
16
|
+
Dfa(Box<dense::DFA<Vec<u32>>>),
|
|
17
|
+
Compiled(Regex),
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
impl Pattern {
|
|
21
|
+
// Select the crate matcher once for a homogeneous array. The borrowed
|
|
22
|
+
// Ruby slices are used synchronously; classification and encoding are
|
|
23
|
+
// still checked on every element, including non-string instances.
|
|
24
|
+
pub(super) fn eval_items(&self, required: i32, values: &[Value]) -> Verdict {
|
|
25
|
+
self.visit_invalid_items(required, values, |_, _| Invalid)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
pub(super) fn visit_invalid_items(
|
|
29
|
+
&self,
|
|
30
|
+
required: i32,
|
|
31
|
+
values: &[Value],
|
|
32
|
+
invalid: impl FnMut(usize, Value) -> Verdict,
|
|
33
|
+
) -> Verdict {
|
|
34
|
+
match self {
|
|
35
|
+
Self::Dfa(dfa) => scan_items(required, values, invalid, |bytes| {
|
|
36
|
+
match dfa.try_search_fwd(&Input::new(bytes).anchored(Anchored::Yes).earliest(true))
|
|
37
|
+
{
|
|
38
|
+
Ok(matched) => verdict(matched.is_some()),
|
|
39
|
+
Err(_) => Unsupported,
|
|
40
|
+
}
|
|
41
|
+
}),
|
|
42
|
+
Self::Compiled(regex) => scan_items(required, values, invalid, |bytes| {
|
|
43
|
+
verdict(regex.is_match(bytes))
|
|
44
|
+
}),
|
|
45
|
+
Self::Unsupported => scan_items(required, values, invalid, |_| Unsupported),
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pub(super) fn memory_usage(&self) -> usize {
|
|
50
|
+
match self {
|
|
51
|
+
Self::Dfa(dfa) => std::mem::size_of_val(&**dfa) + dfa.memory_usage(),
|
|
52
|
+
Self::Compiled(regex) => regex.memory_usage(),
|
|
53
|
+
Self::Unsupported => 0,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pub(super) fn compile(value: Value) -> Self {
|
|
58
|
+
if unsafe { sn_kind(value) } != TYPE_STRING {
|
|
59
|
+
return Self::Unsupported;
|
|
60
|
+
}
|
|
61
|
+
let mut data = std::ptr::null();
|
|
62
|
+
let mut len = 0;
|
|
63
|
+
let encoding = unsafe { sn_string_view(value, &mut data, &mut len) };
|
|
64
|
+
if encoding == ENCODING_OTHER {
|
|
65
|
+
return Self::Unsupported;
|
|
66
|
+
}
|
|
67
|
+
let bytes = unsafe { std::slice::from_raw_parts(data, len) };
|
|
68
|
+
if encoding == ENCODING_ASCII && !bytes.is_ascii() {
|
|
69
|
+
return Self::Unsupported;
|
|
70
|
+
}
|
|
71
|
+
let Some(source) = std::str::from_utf8(bytes).ok().and_then(translate) else {
|
|
72
|
+
return Self::Unsupported;
|
|
73
|
+
};
|
|
74
|
+
let Ok(ast) = regex_syntax::ast::parse::Parser::new().parse(&source) else {
|
|
75
|
+
return Self::Unsupported;
|
|
76
|
+
};
|
|
77
|
+
if !compatible(&ast) {
|
|
78
|
+
return Self::Unsupported;
|
|
79
|
+
}
|
|
80
|
+
// Use a single immutable DFA for fully start-anchored validity. The
|
|
81
|
+
// meta engine retains its literal prefilters for unanchored searches.
|
|
82
|
+
// Bound both the DFA table and auxiliary determinization memory.
|
|
83
|
+
if let Ok(hir) = regex_syntax::hir::translate::Translator::new().translate(&source, &ast)
|
|
84
|
+
&& hir
|
|
85
|
+
.properties()
|
|
86
|
+
.look_set_prefix()
|
|
87
|
+
.contains(regex_syntax::hir::Look::Start)
|
|
88
|
+
&& let Ok(dfa) = dense::Builder::new()
|
|
89
|
+
.configure(
|
|
90
|
+
dense::Config::new()
|
|
91
|
+
.match_kind(MatchKind::LeftmostFirst)
|
|
92
|
+
.start_kind(StartKind::Anchored)
|
|
93
|
+
.dfa_size_limit(Some(1 << 16))
|
|
94
|
+
.determinize_size_limit(Some(1 << 18)),
|
|
95
|
+
)
|
|
96
|
+
.thompson(thompson::Config::new().nfa_size_limit(Some(1 << 20)))
|
|
97
|
+
.build(&source)
|
|
98
|
+
{
|
|
99
|
+
return Self::Dfa(Box::new(dfa));
|
|
100
|
+
}
|
|
101
|
+
Regex::builder()
|
|
102
|
+
.configure(
|
|
103
|
+
Regex::config()
|
|
104
|
+
.nfa_size_limit(Some(1 << 20))
|
|
105
|
+
.hybrid_cache_capacity(1 << 20),
|
|
106
|
+
)
|
|
107
|
+
.build(&source)
|
|
108
|
+
.map(Self::Compiled)
|
|
109
|
+
.unwrap_or(Self::Unsupported)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
pub(super) fn eval(&self, value: Value) -> Verdict {
|
|
113
|
+
if matches!(self, Self::Unsupported) {
|
|
114
|
+
return Unsupported;
|
|
115
|
+
}
|
|
116
|
+
let mut data = std::ptr::null();
|
|
117
|
+
let mut len = 0;
|
|
118
|
+
let encoding = unsafe { sn_pattern_view(value, &mut data, &mut len) };
|
|
119
|
+
if encoding == ENCODING_OTHER {
|
|
120
|
+
return Unsupported;
|
|
121
|
+
}
|
|
122
|
+
let bytes = unsafe { std::slice::from_raw_parts(data, len) };
|
|
123
|
+
self.eval_bytes(bytes, encoding)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
pub(super) fn eval_bytes(&self, bytes: &[u8], encoding: i32) -> Verdict {
|
|
127
|
+
if encoding == ENCODING_OTHER || matches!(self, Self::Unsupported) {
|
|
128
|
+
return Unsupported;
|
|
129
|
+
}
|
|
130
|
+
if encoding != ENCODING_SEVEN_BIT
|
|
131
|
+
&& ((encoding == ENCODING_ASCII && !bytes.is_ascii())
|
|
132
|
+
|| std::str::from_utf8(bytes).is_err())
|
|
133
|
+
{
|
|
134
|
+
return Unsupported;
|
|
135
|
+
}
|
|
136
|
+
let matched = match self {
|
|
137
|
+
Self::Dfa(dfa) => match dfa
|
|
138
|
+
.try_search_fwd(&Input::new(bytes).anchored(Anchored::Yes).earliest(true))
|
|
139
|
+
{
|
|
140
|
+
Ok(matched) => matched.is_some(),
|
|
141
|
+
Err(_) => return Unsupported,
|
|
142
|
+
},
|
|
143
|
+
Self::Compiled(regex) => regex.is_match(bytes),
|
|
144
|
+
Self::Unsupported => return Unsupported,
|
|
145
|
+
};
|
|
146
|
+
if matched { Valid } else { Invalid }
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
fn scan_items(
|
|
151
|
+
required: i32,
|
|
152
|
+
values: &[Value],
|
|
153
|
+
mut invalid: impl FnMut(usize, Value) -> Verdict,
|
|
154
|
+
mut matches: impl FnMut(&[u8]) -> Verdict,
|
|
155
|
+
) -> Verdict {
|
|
156
|
+
for (index, &value) in values.iter().enumerate() {
|
|
157
|
+
let mut data = std::ptr::null();
|
|
158
|
+
let mut len = 0;
|
|
159
|
+
let encoding = unsafe { sn_pattern_item_view(value, &mut data, &mut len) };
|
|
160
|
+
if encoding == -TYPE_UNSUPPORTED {
|
|
161
|
+
return Unsupported;
|
|
162
|
+
}
|
|
163
|
+
if encoding < 0 {
|
|
164
|
+
if required != 0 {
|
|
165
|
+
let result = invalid(index, value);
|
|
166
|
+
if result != Valid {
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if encoding == ENCODING_OTHER {
|
|
173
|
+
return Unsupported;
|
|
174
|
+
}
|
|
175
|
+
let bytes = unsafe { std::slice::from_raw_parts(data, len) };
|
|
176
|
+
if encoding != ENCODING_SEVEN_BIT
|
|
177
|
+
&& ((encoding == ENCODING_ASCII && !bytes.is_ascii())
|
|
178
|
+
|| std::str::from_utf8(bytes).is_err())
|
|
179
|
+
{
|
|
180
|
+
return Unsupported;
|
|
181
|
+
}
|
|
182
|
+
let result = matches(bytes);
|
|
183
|
+
match result {
|
|
184
|
+
Unsupported => return Unsupported,
|
|
185
|
+
Invalid => {
|
|
186
|
+
let result = invalid(index, value);
|
|
187
|
+
if result != Valid {
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
Valid => {}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
Valid
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// This is a VM compatibility translation, not a regex parser. Unsupported
|
|
198
|
+
// escapes, class set operations and special groups retain Ruby's behavior.
|
|
199
|
+
fn translate(source: &str) -> Option<String> {
|
|
200
|
+
let mut output = String::with_capacity(source.len());
|
|
201
|
+
let mut characters = source.chars().peekable();
|
|
202
|
+
let mut in_class = false;
|
|
203
|
+
while let Some(character) = characters.next() {
|
|
204
|
+
match character {
|
|
205
|
+
'\\' => {
|
|
206
|
+
let escaped = characters.next()?;
|
|
207
|
+
let class = match escaped {
|
|
208
|
+
'd' => Some("0-9"),
|
|
209
|
+
'D' => Some("^0-9"),
|
|
210
|
+
'w' => Some("A-Za-z0-9_"),
|
|
211
|
+
'W' => Some("^A-Za-z0-9_"),
|
|
212
|
+
's' => Some(
|
|
213
|
+
r"\x{0009}-\x{000D}\x{0020}\x{00A0}\x{1680}\x{2000}-\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}\x{FEFF}",
|
|
214
|
+
),
|
|
215
|
+
'S' => Some(
|
|
216
|
+
r"^\x{0009}-\x{000D}\x{0020}\x{00A0}\x{1680}\x{2000}-\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}\x{FEFF}",
|
|
217
|
+
),
|
|
218
|
+
_ => None,
|
|
219
|
+
};
|
|
220
|
+
if let Some(class) = class {
|
|
221
|
+
if !in_class {
|
|
222
|
+
output.push('[');
|
|
223
|
+
}
|
|
224
|
+
output.push_str(class);
|
|
225
|
+
if !in_class {
|
|
226
|
+
output.push(']');
|
|
227
|
+
}
|
|
228
|
+
} else if ".^$?+*(){}[]|\\-afnrtv".contains(escaped)
|
|
229
|
+
|| (!in_class && matches!(escaped, 'A' | 'z'))
|
|
230
|
+
{
|
|
231
|
+
output.push('\\');
|
|
232
|
+
output.push(escaped);
|
|
233
|
+
} else {
|
|
234
|
+
return None;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
'[' => {
|
|
238
|
+
if in_class {
|
|
239
|
+
return None;
|
|
240
|
+
}
|
|
241
|
+
in_class = true;
|
|
242
|
+
output.push(character);
|
|
243
|
+
}
|
|
244
|
+
']' => {
|
|
245
|
+
in_class = false;
|
|
246
|
+
output.push(character);
|
|
247
|
+
}
|
|
248
|
+
'&' if in_class => return None,
|
|
249
|
+
'-' | '~' if in_class && characters.peek() == Some(&character) => return None,
|
|
250
|
+
'(' if !in_class && characters.peek() == Some(&'?') => {
|
|
251
|
+
characters.next();
|
|
252
|
+
if characters.next() != Some(':') {
|
|
253
|
+
return None;
|
|
254
|
+
}
|
|
255
|
+
output.push_str("(?:");
|
|
256
|
+
}
|
|
257
|
+
'^' if !in_class => output.push_str(r"\A"),
|
|
258
|
+
'$' if !in_class => output.push_str(r"\z"),
|
|
259
|
+
_ => output.push(character),
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
Some(output)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
fn compatible(ast: &Ast) -> bool {
|
|
266
|
+
match ast {
|
|
267
|
+
Ast::Repetition(repetition) => {
|
|
268
|
+
// Ruby treats adjacent repetition operators as possessive or
|
|
269
|
+
// rejects them, while regex-syntax can parse nested repetitions.
|
|
270
|
+
if matches!(*repetition.ast, Ast::Repetition(_)) {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
let bounded = match repetition.op.kind {
|
|
274
|
+
RepetitionKind::Range(
|
|
275
|
+
RepetitionRange::Exactly(n) | RepetitionRange::AtLeast(n),
|
|
276
|
+
) => n <= 4096,
|
|
277
|
+
RepetitionKind::Range(RepetitionRange::Bounded(_, max)) => max <= 4096,
|
|
278
|
+
_ => true,
|
|
279
|
+
};
|
|
280
|
+
bounded && compatible(&repetition.ast)
|
|
281
|
+
}
|
|
282
|
+
Ast::Group(group) => compatible(&group.ast),
|
|
283
|
+
Ast::Alternation(alternation) => alternation.asts.iter().all(compatible),
|
|
284
|
+
Ast::Concat(concat) => concat.asts.iter().all(compatible),
|
|
285
|
+
Ast::Flags(_) | Ast::ClassUnicode(_) | Ast::ClassPerl(_) => false,
|
|
286
|
+
_ => true,
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
//! Resource/anchor lookup is compiled once. Evaluation only follows native
|
|
2
|
+
//! tables and stack frames, including when speculative branches collect annotations.
|
|
3
|
+
use super::*;
|
|
4
|
+
use tracking::JsonKey;
|
|
5
|
+
#[derive(Clone, Copy, Default)]
|
|
6
|
+
pub(super) struct Metadata {
|
|
7
|
+
pub resource: Value,
|
|
8
|
+
pub enters_scope: bool,
|
|
9
|
+
pub recursive_anchor: bool,
|
|
10
|
+
pub dynamic_anchor: Option<Value>,
|
|
11
|
+
pub primary_binding: Option<usize>,
|
|
12
|
+
}
|
|
13
|
+
#[derive(Clone, Copy, Debug)]
|
|
14
|
+
enum ReferenceKind {
|
|
15
|
+
Static,
|
|
16
|
+
Recursive,
|
|
17
|
+
Dynamic(usize),
|
|
18
|
+
}
|
|
19
|
+
#[derive(Clone, Copy, Debug)]
|
|
20
|
+
pub(super) struct Reference {
|
|
21
|
+
target: usize,
|
|
22
|
+
kind: ReferenceKind,
|
|
23
|
+
}
|
|
24
|
+
#[derive(Clone, Copy, Default)]
|
|
25
|
+
pub(super) struct Context<'a> {
|
|
26
|
+
pub active: Option<&'a RefFrame<'a>>,
|
|
27
|
+
pub scope: Option<&'a ScopeFrame<'a>>,
|
|
28
|
+
}
|
|
29
|
+
pub(super) struct ScopeFrame<'a> {
|
|
30
|
+
pub resource: Value,
|
|
31
|
+
pub parent: Option<&'a ScopeFrame<'a>>,
|
|
32
|
+
pub primary_binding: Option<usize>,
|
|
33
|
+
}
|
|
34
|
+
impl Engine {
|
|
35
|
+
pub(super) fn compile_scope(
|
|
36
|
+
&mut self,
|
|
37
|
+
program: Value,
|
|
38
|
+
index: usize,
|
|
39
|
+
flags: i64,
|
|
40
|
+
) -> Result<(), Value> {
|
|
41
|
+
let node = self.field(program, b"node\0")?;
|
|
42
|
+
let resource = self.field(node, b"resource\0")?;
|
|
43
|
+
let recursive = self.field(program, b"recursive_anchor?\0")?;
|
|
44
|
+
let dynamic = self.field(program, b"dynamic_anchor\0")?;
|
|
45
|
+
self.metadata[index] = Metadata {
|
|
46
|
+
resource,
|
|
47
|
+
enters_scope: flags & i64::from(TRACKS_DYNAMIC_SCOPE) != 0,
|
|
48
|
+
recursive_anchor: unsafe { sn_true(recursive) } != 0,
|
|
49
|
+
primary_binding: None,
|
|
50
|
+
dynamic_anchor: (unsafe { sn_same(dynamic, sn_nil()) } == 0).then_some(dynamic),
|
|
51
|
+
};
|
|
52
|
+
if !self.resources.contains_key(&resource) {
|
|
53
|
+
// Reserve the resource before compiling its root: roots can reach
|
|
54
|
+
// the current child through a recursive or dynamic reference.
|
|
55
|
+
self.resources.insert(resource, usize::MAX);
|
|
56
|
+
let mut root = 0;
|
|
57
|
+
if unsafe { sn_resource_program(self.compiler, resource, &mut root) } != 0 {
|
|
58
|
+
return Err(root);
|
|
59
|
+
}
|
|
60
|
+
self.retain(root);
|
|
61
|
+
let root = self.compile(root)?;
|
|
62
|
+
self.resources.insert(resource, root);
|
|
63
|
+
}
|
|
64
|
+
Ok(())
|
|
65
|
+
}
|
|
66
|
+
pub(super) fn compile_reference(
|
|
67
|
+
&mut self,
|
|
68
|
+
operand: Value,
|
|
69
|
+
opcode: i32,
|
|
70
|
+
target: usize,
|
|
71
|
+
) -> Result<Op, Value> {
|
|
72
|
+
let fragment = self.field(operand, b"fragment\0")?;
|
|
73
|
+
let kind = if opcode == OP_RECURSIVE_REF
|
|
74
|
+
&& unsafe { sn_same(fragment, sn_nil()) } == 0
|
|
75
|
+
&& unsafe { sn_string_bytes(fragment) } == 0
|
|
76
|
+
&& self.metadata[target].recursive_anchor
|
|
77
|
+
{
|
|
78
|
+
ReferenceKind::Recursive
|
|
79
|
+
} else if opcode == OP_DYNAMIC_REF {
|
|
80
|
+
let anchor = self.metadata[target].dynamic_anchor;
|
|
81
|
+
match anchor {
|
|
82
|
+
Some(anchor)
|
|
83
|
+
if unsafe { sn_same(fragment, sn_nil()) } == 0
|
|
84
|
+
&& unsafe { sn_string_bytes(fragment) } != 0
|
|
85
|
+
&& unsafe { *sn_string_data(fragment) } != b'/'
|
|
86
|
+
&& unsafe { sn_string_equal(fragment, anchor) } != 0 =>
|
|
87
|
+
{
|
|
88
|
+
let name = JsonKey::new(fragment);
|
|
89
|
+
let index = self
|
|
90
|
+
.anchor_names
|
|
91
|
+
.iter()
|
|
92
|
+
.position(|&candidate| candidate == name)
|
|
93
|
+
.unwrap_or_else(|| {
|
|
94
|
+
let index = self.anchor_names.len();
|
|
95
|
+
self.anchor_names.push(name);
|
|
96
|
+
index
|
|
97
|
+
});
|
|
98
|
+
ReferenceKind::Dynamic(index)
|
|
99
|
+
}
|
|
100
|
+
_ => ReferenceKind::Static,
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
ReferenceKind::Static
|
|
104
|
+
};
|
|
105
|
+
Ok(Op::Ref(Reference { target, kind }))
|
|
106
|
+
}
|
|
107
|
+
pub(super) fn bind_dynamic_anchors(&mut self) -> Result<(), Value> {
|
|
108
|
+
loop {
|
|
109
|
+
let pending: Vec<_> = self
|
|
110
|
+
.resources
|
|
111
|
+
.keys()
|
|
112
|
+
.flat_map(|&resource| {
|
|
113
|
+
(0..self.anchor_names.len()).map(move |name| (resource, name))
|
|
114
|
+
})
|
|
115
|
+
.filter(|key| !self.dynamic_bindings.contains_key(key))
|
|
116
|
+
.collect();
|
|
117
|
+
if pending.is_empty() {
|
|
118
|
+
self.anchor_tables = vec![vec![]; self.anchor_names.len()];
|
|
119
|
+
for (&(resource, name), &target) in &self.dynamic_bindings {
|
|
120
|
+
if let Some(target) = target {
|
|
121
|
+
self.anchor_tables[name].push((resource, target));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if self.anchor_tables.len() == 1 {
|
|
125
|
+
for metadata in &mut self.metadata {
|
|
126
|
+
metadata.primary_binding =
|
|
127
|
+
self.anchor_tables[0]
|
|
128
|
+
.iter()
|
|
129
|
+
.find_map(|&(resource, target)| {
|
|
130
|
+
(resource == metadata.resource).then_some(target)
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return Ok(());
|
|
135
|
+
}
|
|
136
|
+
for (resource, name) in pending {
|
|
137
|
+
let mut target = 0;
|
|
138
|
+
if unsafe {
|
|
139
|
+
sn_anchor_program(
|
|
140
|
+
self.compiler,
|
|
141
|
+
resource,
|
|
142
|
+
self.anchor_names[name].0,
|
|
143
|
+
&mut target,
|
|
144
|
+
)
|
|
145
|
+
} != 0
|
|
146
|
+
{
|
|
147
|
+
return Err(target);
|
|
148
|
+
}
|
|
149
|
+
self.retain(target);
|
|
150
|
+
let target = if unsafe { sn_same(target, sn_nil()) } != 0 {
|
|
151
|
+
None
|
|
152
|
+
} else {
|
|
153
|
+
Some(self.compile(target)?)
|
|
154
|
+
};
|
|
155
|
+
self.dynamic_bindings.insert((resource, name), target);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
pub(super) fn enter_scope<'a>(
|
|
160
|
+
&self,
|
|
161
|
+
node: usize,
|
|
162
|
+
active: Context<'a>,
|
|
163
|
+
frame: &'a ScopeFrame<'a>,
|
|
164
|
+
) -> Context<'a> {
|
|
165
|
+
if self.metadata[node].enters_scope
|
|
166
|
+
&& active
|
|
167
|
+
.scope
|
|
168
|
+
.is_none_or(|scope| scope.resource != frame.resource)
|
|
169
|
+
{
|
|
170
|
+
Context {
|
|
171
|
+
scope: Some(frame),
|
|
172
|
+
..active
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
active
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
pub(super) fn reference_target(&self, reference: Reference, context: Context<'_>) -> usize {
|
|
179
|
+
if matches!(reference.kind, ReferenceKind::Dynamic(0)) && self.anchor_tables.len() == 1 {
|
|
180
|
+
return context
|
|
181
|
+
.scope
|
|
182
|
+
.and_then(|frame| frame.primary_binding)
|
|
183
|
+
.unwrap_or(reference.target);
|
|
184
|
+
}
|
|
185
|
+
let mut target = reference.target;
|
|
186
|
+
let mut current = context.scope;
|
|
187
|
+
// Linked frames walk inner to outer. Keep replacing a match so the
|
|
188
|
+
// outermost matching resource wins, just as the VM's scope.each does.
|
|
189
|
+
while let Some(frame) = current {
|
|
190
|
+
match reference.kind {
|
|
191
|
+
ReferenceKind::Static => break,
|
|
192
|
+
ReferenceKind::Recursive => {
|
|
193
|
+
if let Some(&root) = self.resources.get(&frame.resource)
|
|
194
|
+
&& self.metadata[root].recursive_anchor
|
|
195
|
+
{
|
|
196
|
+
target = root;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
ReferenceKind::Dynamic(name) => {
|
|
200
|
+
if let Some(Some(binding)) = self.dynamic_bindings.get(&(frame.resource, name))
|
|
201
|
+
{
|
|
202
|
+
target = *binding;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
current = frame.parent;
|
|
207
|
+
}
|
|
208
|
+
target
|
|
209
|
+
}
|
|
210
|
+
}
|