pharos_corefile/
cursor.rs1use crate::{Error, Token};
2
3pub 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 #[must_use]
14 pub fn has_next(&self) -> bool {
15 self.position.map_or(0, |position| position + 1) < self.tokens.len()
16 }
17
18 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 #[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 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 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 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 pub fn val(&self) -> &str {
109 self.position
110 .map_or("", |position| &self.tokens[position].text)
111 }
112
113 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 pub fn line(&self) -> usize {
123 self.position
124 .map_or(0, |position| self.tokens[position].line)
125 }
126
127 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 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 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 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}