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 member_body: bool,
48 fields: bool,
50 paren_depth: i32,
52 bracket_depth: i32,
53}
54
55struct Extractor<'source, 'tokens> {
56 source: &'source str,
57 tokens: &'tokens [Token],
58 language: Language,
59 facts: Facts,
60 scopes: Vec<Scope>,
61 import_bindings: BTreeMap<String, (String, bool, String)>,
62 depth: i32,
63 paren_depth: i32,
64 bracket_depth: i32,
65}
66
67mod calls;
68mod declarations;
69mod modules;
70mod traversal;
71mod types;
72
73fn is_method(name: &str) -> bool {
75 matches!(
76 name,
77 "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" | "ALL"
78 )
79}
80
81fn template_interpolation_ranges(template: &str, language: Language) -> Vec<(usize, usize)> {
85 let bytes = template.as_bytes();
86 let mut ranges = Vec::new();
87 let mut cursor = usize::from(bytes.first() == Some(&b'`'));
88 while cursor + 1 < bytes.len() {
89 if bytes[cursor] == b'\\' {
90 cursor = (cursor + 2).min(bytes.len());
91 continue;
92 }
93 if bytes[cursor] == b'`' {
94 break;
95 }
96 if bytes[cursor] != b'$' || bytes[cursor + 1] != b'{' {
97 cursor += 1;
98 continue;
99 }
100 let expression_start = cursor + 2;
101 let tail = &template[expression_start..];
102 let tokens = Tokenizer::new(tail, language)
103 .mode(Mode::Lite)
104 .collect::<Vec<_>>();
105 let mut depth = 1_i32;
106 let mut expression_end = None;
107 for token in tokens {
108 if token.kind != TokenKind::Punctuation {
109 continue;
110 }
111 match token.text(tail) {
112 "{" => depth += 1,
113 "}" => {
114 depth -= 1;
115 if depth == 0 {
116 expression_end = Some(expression_start + token.start);
117 cursor = expression_start + token.end;
118 break;
119 }
120 }
121 _ => {}
122 }
123 }
124 let Some(expression_end) = expression_end else {
125 break;
126 };
127 ranges.push((expression_start, expression_end));
128 }
129 ranges
130}
131
132fn position_at(source: &str, offset: usize) -> (u32, u32) {
133 let prefix = source.get(..offset).unwrap_or(source);
134 let line = u32::try_from(prefix.bytes().filter(|byte| *byte == b'\n').count())
135 .unwrap_or(u32::MAX)
136 .saturating_add(1);
137 let column = u32::try_from(
138 prefix
139 .rsplit_once('\n')
140 .map_or(prefix, |(_, suffix)| suffix)
141 .chars()
142 .count(),
143 )
144 .unwrap_or(u32::MAX)
145 .saturating_add(1);
146 (line, column)
147}
148
149#[cfg(test)]
150mod tests;