Skip to main content

pharos_corefile/
cursor.rs

1use crate::{Error, Token};
2
3/// A source-aware cursor over one Directive's tokens during setup.
4pub struct Cursor<'a> {
5    file: &'a str,
6    tokens: &'a [Token],
7    position: Option<usize>,
8    nesting: usize,
9}
10
11impl<'a> Cursor<'a> {
12    /// Whether another token remains after the current token.
13    #[must_use]
14    pub fn has_next(&self) -> bool {
15        self.position.map_or(0, |position| position + 1) < self.tokens.len()
16    }
17
18    /// Start before the first token.
19    pub fn new(file: &'a str, tokens: &'a [Token]) -> Self {
20        Self {
21            file,
22            tokens,
23            position: None,
24            nesting: 0,
25        }
26    }
27
28    /// Load the next token without regard to line boundaries.
29    #[allow(clippy::should_implement_trait)]
30    pub fn next(&mut self) -> bool {
31        let next = self.position.map_or(0, |position| position + 1);
32        if next < self.tokens.len() {
33            self.position = Some(next);
34            true
35        } else {
36            false
37        }
38    }
39
40    /// Load the next token only if it is on the current line.
41    pub fn next_arg(&mut self) -> bool {
42        match self.position {
43            None => self.next(),
44            Some(position) => {
45                let Some(next) = self.tokens.get(position + 1) else {
46                    return false;
47                };
48                let current = &self.tokens[position];
49                if current.file == next.file
50                    && current.line + current.text.matches('\n').count() == next.line
51                {
52                    self.next()
53                } else {
54                    false
55                }
56            }
57        }
58    }
59
60    /// Load the next token only if it starts a new source line.
61    pub fn next_line(&mut self) -> bool {
62        match self.position {
63            None => self.next(),
64            Some(position) => {
65                let Some(next) = self.tokens.get(position + 1) else {
66                    return false;
67                };
68                let current = &self.tokens[position];
69                if crate::parser::new_line(current, next) {
70                    self.next()
71                } else {
72                    false
73                }
74            }
75        }
76    }
77
78    /// Enter or continue a nested brace-delimited block.
79    pub fn next_block(&mut self) -> bool {
80        if self.nesting > 0 {
81            if !self.next() {
82                return false;
83            }
84            if self.val() == "{" {
85                self.position = self.position.and_then(|position| position.checked_sub(1));
86            } else if self.val() == "}" {
87                self.nesting -= 1;
88                return false;
89            } else {
90                return true;
91            }
92        }
93        if !self.next_arg() {
94            return false;
95        }
96        if self.val() != "{" {
97            self.position = self.position.and_then(|position| position.checked_sub(1));
98            return false;
99        }
100        if !self.next() || self.val() == "}" {
101            return false;
102        }
103        self.nesting += 1;
104        true
105    }
106
107    /// Current token text, or an empty string before the first token.
108    pub fn val(&self) -> &str {
109        self.position
110            .map_or("", |position| &self.tokens[position].text)
111    }
112
113    /// Current source file, falling back to the initial filename.
114    pub fn file(&self) -> &str {
115        self.position.map_or(self.file, |position| {
116            let file = &self.tokens[position].file;
117            if file.is_empty() { self.file } else { file }
118        })
119    }
120
121    /// Current source line, or zero before the first token.
122    pub fn line(&self) -> usize {
123        self.position
124            .map_or(0, |position| self.tokens[position].line)
125    }
126
127    /// Fill targets with same-line arguments, leaving missing targets unchanged.
128    pub fn args(&mut self, targets: &mut [&mut String]) -> bool {
129        for target in targets {
130            if !self.next_arg() {
131                return false;
132            }
133            **target = self.val().to_owned();
134        }
135        true
136    }
137
138    /// Consume the remaining same-line arguments, stopping before an opening brace.
139    pub fn remaining_args(&mut self) -> Vec<String> {
140        let mut args = Vec::new();
141        while self.next_arg() {
142            if self.val() == "{" {
143                self.position = self.position.and_then(|position| position.checked_sub(1));
144                break;
145            }
146            args.push(self.val().to_owned());
147        }
148        args
149    }
150
151    /// Create an error at the current token.
152    pub fn error(&self, message: impl Into<String>) -> Error {
153        Error {
154            file: self.file().to_owned(),
155            line: self.line(),
156            message: message.into(),
157        }
158    }
159
160    /// Create the standard missing or unexpected argument error.
161    pub fn arg_err(&self) -> Error {
162        if self.val() == "{" {
163            self.error("Unexpected token '{', expecting argument")
164        } else {
165            self.error(format!(
166                "Wrong argument count or unexpected line ending after '{}'",
167                self.val()
168            ))
169        }
170    }
171}