aboutsummaryrefslogtreecommitdiffstats
path: root/src/parse
diff options
context:
space:
mode:
authorJonas Maier <jonas@x77.dev>2026-05-22 21:26:41 +0200
committerJonas Maier <jonas@x77.dev>2026-05-22 21:26:41 +0200
commiteeb267c46340d5d47f41cc2440f0b281f9ae9261 (patch)
treeabcbc6624e0903cc1c7cd919d15a42ebb970692a /src/parse
parent07daff9331dbdc607584edbf1a8fb3e415c338ea (diff)
downloadpish-eeb267c46340d5d47f41cc2440f0b281f9ae9261.tar.gz
basic syntax highlighting
Diffstat (limited to 'src/parse')
-rw-r--r--src/parse/mod.rs84
-rw-r--r--src/parse/span.rs86
2 files changed, 166 insertions, 4 deletions
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index 102b334..97a6e4a 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -3,6 +3,8 @@ use crate::{BString, PushAll, bstr};
#[cfg(test)]
mod test;
+mod span;
+
pub trait Stage: PartialEq {
type Str: std::fmt::Debug + Clone + PartialEq;
}
@@ -908,6 +910,7 @@ impl Parse for ExpString {
let mut last_delim = StringDelimiter::None;
'outer: loop {
+ let begin = b.loc();
let Some(delim) = StringDelimiter::try_begin(b) else {
break;
};
@@ -1034,6 +1037,14 @@ impl Parse for ExpString {
add_char(p, x);
}
}
+
+ if !delim.is_none() {
+ let end = b.loc_u32();
+ b.highlights.push(Highlight {
+ span: begin.to(end),
+ kind: HighlightKind::String,
+ });
+ }
}
if already_parsed {
@@ -1285,6 +1296,18 @@ pub enum ParseMode {
Completion,
}
+#[derive(Copy, Clone)]
+pub enum HighlightKind {
+ Keyword(Keyword),
+ String,
+ None,
+}
+
+pub struct Highlight {
+ pub span: span::Span,
+ pub kind: HighlightKind,
+}
+
pub struct Cursor<'a> {
buf: &'a [u8],
mode: ParseMode,
@@ -1293,6 +1316,13 @@ pub struct Cursor<'a> {
spaced: bool,
pub backtrace: bool,
+
+ pub highlights: Vec<Highlight>,
+
+ file: span::FileId,
+
+ buf_start: u64,
+ buf_len: u32,
}
#[derive(Default)]
@@ -1305,11 +1335,20 @@ struct SpaceStats {
impl<'a> Cursor<'a> {
pub fn new(buf: &'a [u8], mode: ParseMode) -> Self {
+ assert!(
+ buf.len() < u32::MAX as usize,
+ "cannot support larger parse buffers for now - what are you even doing."
+ );
+
Self {
buf,
mode,
spaced: false,
backtrace: false,
+ highlights: Vec::new(),
+ file: span::FileId::new(),
+ buf_start: buf.as_ptr() as u64,
+ buf_len: buf.len() as u32,
}
}
@@ -1377,6 +1416,18 @@ impl<'a> Cursor<'a> {
}
}
+ fn loc_u32(&self) -> u32 {
+ let now_loc = self.buf.as_ptr() as u64;
+ assert!(now_loc >= self.buf_start, "not the original buffer");
+ let relative_loc = (now_loc - self.buf_start) as u32;
+ assert!(relative_loc <= self.buf_len, "not the original buffer");
+ relative_loc
+ }
+
+ fn loc(&self) -> span::SpanFrom {
+ self.file.from(self.loc_u32())
+ }
+
fn spaces_stats(&mut self) -> SpaceStats {
let mut stats = SpaceStats::default();
while self.has() && b" \t\n\r".contains(&self.buf[0]) {
@@ -1418,7 +1469,8 @@ impl<'a> Cursor<'a> {
}
self.spaces();
- if self.buf.starts_with(bytes) {
+ let span = self.loc().with_len(bytes.len() as u32);
+ let result = if self.buf.starts_with(bytes) {
if kw.requires_space() {
if self.buf.len() > bytes.len() && self.buf[bytes.len()].is_ascii_whitespace() {
self.buf = &self.buf[bytes.len() + 1..];
@@ -1439,11 +1491,20 @@ impl<'a> Cursor<'a> {
}
} else {
Err(ParseError::ExpectedKeyword(kw))
+ };
+
+ if result.is_ok() {
+ self.highlights.push(Highlight {
+ span,
+ kind: HighlightKind::Keyword(kw),
+ })
}
+
+ result
}
}
-#[derive(Debug, PartialEq)]
+#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Keyword {
If,
While,
@@ -1546,8 +1607,8 @@ impl Parse for While {
}
}
-impl Parse for Ast<PreExpansion> {
- fn parse(b: &mut Cursor<'_>) -> Result<Self> {
+impl Ast<PreExpansion> {
+ fn parse_inner(b: &mut Cursor<'_>) -> Result<Self> {
b.spaces();
let orig_len = b.buf.len();
@@ -1586,6 +1647,21 @@ impl Parse for Ast<PreExpansion> {
}
}
+impl Parse for Ast<PreExpansion> {
+ fn parse(b: &mut Cursor<'_>) -> Result<Self> {
+ let begin = b.loc();
+ let result = Ast::parse_inner(b);
+ let span = begin.to(b.loc_u32());
+ if result.is_ok() {
+ b.highlights.push(Highlight {
+ span,
+ kind: HighlightKind::None,
+ });
+ }
+ result
+ }
+}
+
impl Parse for Command<PreExpansion> {
fn parse(b: &mut Cursor<'_>) -> Result<Self> {
let path: ExpString = b.parse()?;
diff --git a/src/parse/span.rs b/src/parse/span.rs
new file mode 100644
index 0000000..340a078
--- /dev/null
+++ b/src/parse/span.rs
@@ -0,0 +1,86 @@
+use std::sync::atomic::AtomicU32;
+
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct FileId {
+ id: u32,
+}
+
+impl FileId {
+ pub fn new() -> Self {
+ static GEN: AtomicU32 = AtomicU32::new(0);
+ Self {
+ id: GEN.fetch_add(1, std::sync::atomic::Ordering::SeqCst),
+ }
+ }
+
+ pub fn from(self, start: u32) -> SpanFrom {
+ SpanFrom::new(self, start)
+ }
+
+ pub fn span(self, start: u32, end: u32) -> Span {
+ Span::new(self, start, end)
+ }
+}
+
+#[derive(Copy, Clone)]
+pub struct SpanFrom {
+ pub file: FileId,
+ pub start: u32,
+}
+
+impl SpanFrom {
+ pub fn new(file: FileId, start: u32) -> Self {
+ Self { file, start }
+ }
+
+ pub fn to(self, end: u32) -> Span {
+ Span::new(self.file, self.start, end)
+ }
+
+ pub fn with_len(self, len: u32) -> Span {
+ self.to(self.start + len)
+ }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub struct Span {
+ pub file: FileId,
+ pub start: u32,
+ pub end: u32,
+}
+
+/// manual implementation of PartialOrd to ensure shorter Spans are first
+impl PartialOrd for Span {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ match self.file.partial_cmp(&other.file) {
+ Some(core::cmp::Ordering::Equal) => {}
+ ord => return ord,
+ }
+ match self.start.partial_cmp(&other.start) {
+ Some(core::cmp::Ordering::Equal) => {}
+ ord => return ord,
+ }
+ other.end.partial_cmp(&self.end)
+ }
+}
+
+/// manual implementation of Ord to ensure shorter Spans are first
+impl Ord for Span {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ match self.file.cmp(&other.file) {
+ core::cmp::Ordering::Equal => {}
+ ord => return ord,
+ }
+ match self.start.cmp(&other.start) {
+ core::cmp::Ordering::Equal => {}
+ ord => return ord,
+ }
+ other.end.cmp(&self.end)
+ }
+}
+
+impl Span {
+ pub fn new(file: FileId, start: u32, end: u32) -> Self {
+ Self { file, start, end }
+ }
+}