-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
449071e
commit 8703bfb
Showing
9 changed files
with
280 additions
and
77 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
use std::{collections::VecDeque, sync::OnceLock}; | ||
|
||
use bevy_utils::HashMap; | ||
use gramatika::{ArcStr, Position, SpannedError, Substr}; | ||
use parser::Span; | ||
use regex::{CaptureLocations, Regex}; | ||
|
||
static BRACED_PAT: OnceLock<Regex> = OnceLock::new(); | ||
static UNBRACED_PAT: OnceLock<Regex> = OnceLock::new(); | ||
|
||
fn braced_pat() -> &'static Regex { | ||
BRACED_PAT.get_or_init(|| Regex::new(r"#\{([a-zA-Z_][a-zA-Z0-9_]*)\}").unwrap()) | ||
} | ||
|
||
fn unbraced_pat() -> &'static Regex { | ||
UNBRACED_PAT.get_or_init(|| Regex::new(r"#([a-zA-Z_][a-zA-Z0-9_]*)").unwrap()) | ||
} | ||
|
||
#[derive(Clone, Debug)] | ||
pub enum SourceSlice { | ||
Verbatim(Substr, Span), | ||
Replacement(SourceReplacement), | ||
} | ||
|
||
#[derive(Clone, Debug)] | ||
pub struct SourceReplacement { | ||
pub original: (Substr, Span), | ||
pub replacement: String, | ||
} | ||
|
||
pub(super) struct InterpolationStream<'a> { | ||
full_source: ArcStr, | ||
source: &'a [(Substr, Span)], | ||
defs: &'a HashMap<String, String>, | ||
index: usize, | ||
buffer: VecDeque<SourceSlice>, | ||
braced_locs: CaptureLocations, | ||
unbraced_locs: CaptureLocations, | ||
errors: Vec<SpannedError>, | ||
} | ||
|
||
impl<'a> InterpolationStream<'a> { | ||
pub fn new( | ||
full_source: ArcStr, | ||
source: &'a [(Substr, Span)], | ||
defs: &'a HashMap<String, String>, | ||
) -> Self { | ||
Self { | ||
full_source, | ||
source, | ||
defs, | ||
index: 0, | ||
buffer: VecDeque::new(), | ||
braced_locs: braced_pat().capture_locations(), | ||
unbraced_locs: unbraced_pat().capture_locations(), | ||
errors: vec![], | ||
} | ||
} | ||
|
||
pub fn into_inner(self) -> Vec<SpannedError> { | ||
self.errors | ||
} | ||
} | ||
|
||
impl<'a> Iterator for InterpolationStream<'a> { | ||
type Item = SourceSlice; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
use SourceSlice::*; | ||
|
||
if !self.buffer.is_empty() { | ||
return self.buffer.pop_front(); | ||
} | ||
|
||
if self.index >= self.source.len() { | ||
return None; | ||
} | ||
|
||
let (mut substr, mut span) = self.source[self.index].clone(); | ||
|
||
while let Some(m) = None | ||
.or_else(|| braced_pat().captures_read(&mut self.braced_locs, &substr)) | ||
.or_else(|| unbraced_pat().captures_read(&mut self.unbraced_locs, &substr)) | ||
{ | ||
// Gather the data we need about the match before going any further | ||
// so we don't keep the `substr` borrowed for longer than necessary | ||
let start = m.start(); | ||
let (cap_start, cap_end) = self | ||
.braced_locs | ||
.get(1) | ||
.or_else(|| self.unbraced_locs.get(1)) | ||
.unwrap(); | ||
|
||
let symbol = substr.substr(cap_start..cap_end); | ||
let match_len = m.len(); | ||
|
||
// Slice off the text before the match as verbatim | ||
if let Some((before, before_span)) = split_at(&substr, span, start) { | ||
self.buffer.push_back(Verbatim(before, before_span)); | ||
|
||
// Buffalo buffalo Buffalo buffalo... | ||
substr = substr.substr(start..); | ||
span.start = before_span.end; | ||
} | ||
|
||
// Substr and Span for the match range | ||
let replaced_slice = substr.substr(..match_len); | ||
let replaced_span = Span { | ||
start: span.start, | ||
end: Position { | ||
line: span.start.line, | ||
character: span.start.character + match_len, | ||
}, | ||
}; | ||
|
||
// Ignore other directives that may be part of the pruned text | ||
if symbol == "import" || symbol == "define_import_path" { | ||
self.buffer | ||
.push_back(Verbatim(replaced_slice, replaced_span)); | ||
} | ||
// Emit an error if the symbol is undefined and push the slice as verbatim | ||
else if !self.defs.contains_key(symbol.as_str()) { | ||
self.errors.push(SpannedError { | ||
message: format!("`{symbol}` is undefined"), | ||
source: self.full_source.clone(), | ||
span: Some(replaced_span), | ||
}); | ||
|
||
self.buffer | ||
.push_back(Verbatim(replaced_slice.clone(), replaced_span)); | ||
} | ||
// Push the #define replaement | ||
else { | ||
self.buffer.push_back(Replacement(SourceReplacement { | ||
original: (replaced_slice, replaced_span), | ||
replacement: self.defs[symbol.as_str()].clone(), | ||
})); | ||
} | ||
|
||
// Advance the "cursor" to the position just past the match | ||
substr = substr.substr(match_len..); | ||
span = Span { | ||
start: replaced_span.end, | ||
end: span.end, | ||
}; | ||
} | ||
|
||
// We're finished processing interpolation matches in this slice | ||
self.index += 1; | ||
|
||
// Handle the remainder of the slice after the last match | ||
if !substr.is_empty() { | ||
if self.buffer.is_empty() { | ||
return Some(Verbatim(substr, span)); | ||
} else { | ||
self.buffer.push_back(Verbatim(substr, span)); | ||
} | ||
} | ||
|
||
self.buffer.pop_front() | ||
} | ||
} | ||
|
||
fn split_at(substr: &Substr, span: Span, idx: usize) -> Option<(Substr, Span)> { | ||
if idx == 0 { | ||
return None; | ||
} | ||
|
||
let before = substr.substr(..idx); | ||
|
||
let mut line_inc = 0_usize; | ||
let mut remaining = before.as_str(); | ||
while let Some(i) = remaining.find('\n') { | ||
line_inc += 1; | ||
remaining = &remaining[i + 1..]; | ||
} | ||
let char_inc = remaining.len(); | ||
|
||
let start = span.start; | ||
let mut end = start; | ||
|
||
end.line += line_inc; | ||
|
||
if line_inc > 0 { | ||
end.character = char_inc; | ||
} else { | ||
end.character += char_inc; | ||
} | ||
|
||
Some((before, Span { start, end })) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.