weavatrix_parse/script/
mod.rs1use crate::facts::{
13 Declaration, DeclarationKind, Facts, Import, ImportBinding, Reference, ReferenceKind, Span,
14};
15use crate::syntax::Language;
16use crate::token::{Mode, Token, TokenKind, Tokenizer};
17use std::collections::BTreeMap;
18
19#[must_use]
21pub fn extract(source: &str, language: Language) -> Facts {
22 let tokens = Tokenizer::new(source, language)
23 .mode(Mode::Lite)
24 .collect::<Vec<_>>();
25 Extractor {
26 source,
27 tokens: &tokens,
28 language,
29 facts: Facts::default(),
30 scopes: Vec::new(),
31 import_bindings: BTreeMap::new(),
32 depth: 0,
33 paren_depth: 0,
34 bracket_depth: 0,
35 }
36 .run()
37}
38
39struct Scope {
41 name: String,
42 depth: Option<i32>,
46 declaration: Option<usize>,
47 member_body: bool,
49 fields: bool,
51 paren_depth: i32,
53 bracket_depth: i32,
54}
55
56struct Extractor<'source, 'tokens> {
57 source: &'source str,
58 tokens: &'tokens [Token],
59 language: Language,
60 facts: Facts,
61 scopes: Vec<Scope>,
62 import_bindings: BTreeMap<String, (String, bool, String)>,
63 depth: i32,
64 paren_depth: i32,
65 bracket_depth: i32,
66}
67
68mod calls;
69mod declarations;
70mod modules;
71mod traversal;
72mod types;
73
74fn is_method(name: &str) -> bool {
76 matches!(
77 name,
78 "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" | "ALL"
79 )
80}
81
82fn template_interpolation_ranges(template: &str, language: Language) -> Vec<(usize, usize)> {
86 let bytes = template.as_bytes();
87 let mut ranges = Vec::new();
88 let mut cursor = usize::from(bytes.first() == Some(&b'`'));
89 while cursor + 1 < bytes.len() {
90 if bytes[cursor] == b'\\' {
91 cursor = (cursor + 2).min(bytes.len());
92 continue;
93 }
94 if bytes[cursor] == b'`' {
95 break;
96 }
97 if bytes[cursor] != b'$' || bytes[cursor + 1] != b'{' {
98 cursor += 1;
99 continue;
100 }
101 let expression_start = cursor + 2;
102 let tail = &template[expression_start..];
103 let tokens = Tokenizer::new(tail, language)
104 .mode(Mode::Lite)
105 .collect::<Vec<_>>();
106 let mut depth = 1_i32;
107 let mut expression_end = None;
108 for token in tokens {
109 if token.kind != TokenKind::Punctuation {
110 continue;
111 }
112 match token.text(tail) {
113 "{" => depth += 1,
114 "}" => {
115 depth -= 1;
116 if depth == 0 {
117 expression_end = Some(expression_start + token.start);
118 cursor = expression_start + token.end;
119 break;
120 }
121 }
122 _ => {}
123 }
124 }
125 let Some(expression_end) = expression_end else {
126 break;
127 };
128 ranges.push((expression_start, expression_end));
129 }
130 ranges
131}
132
133fn position_at(source: &str, offset: usize) -> (u32, u32) {
134 let prefix = source.get(..offset).unwrap_or(source);
135 let line = u32::try_from(prefix.bytes().filter(|byte| *byte == b'\n').count())
136 .unwrap_or(u32::MAX)
137 .saturating_add(1);
138 let column = u32::try_from(
139 prefix
140 .rsplit_once('\n')
141 .map_or(prefix, |(_, suffix)| suffix)
142 .chars()
143 .count(),
144 )
145 .unwrap_or(u32::MAX)
146 .saturating_add(1);
147 (line, column)
148}
149
150#[cfg(test)]
151mod tests;