findfile/ast/
expression.rs

1use crate::ast::{Atom, LogicOperator, MathOperator, Precedence};
2use crate::parse::{LexContext, ParseError, Token};
3use crate::play::{PlayContext, PlayResult, RunContext};
4use crate::Value;
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ShortCircuit {
9	And,
10	Or,
11}
12
13#[derive(Debug, Clone, PartialEq)]
14pub enum Expression {
15	Atom(Atom),
16	Math(MathOperator, Box<Self>, Box<Self>),
17	Logic(LogicOperator, Box<Self>, Box<Self>),
18	Assignment(String, Option<MathOperator>, Box<Self>),
19	ShortCircuitAssignment(String, ShortCircuit, Box<Self>),
20	ShortCircuit(ShortCircuit, Box<Self>, Box<Self>),
21}
22
23impl Expression {
24	pub fn parse_toplevel(lctx: &mut LexContext) -> Result<Self, ParseError> {
25		Ok(Self::parse(lctx, true, Precedence::default())?
26			.expect("Todo: error for no valid expression"))
27	}
28
29	pub fn parse(
30		lctx: &mut LexContext,
31		comma_is_and: bool,
32		prec: Precedence,
33	) -> Result<Option<Self>, ParseError> {
34		let Some(begin) = Atom::parse(lctx)? else {
35			return Ok(None);
36		};
37		let mut lhs = Self::Atom(begin);
38
39		while let Some(token) = lctx.next()? {
40			let token_prec = match Precedence::of(&token, comma_is_and) {
41				Some(p) if p <= prec => p,
42				_ => {
43					lctx.push_token(token);
44					break;
45				}
46			};
47
48			let rhs =
49				Self::parse(lctx, comma_is_and, token_prec)?.ok_or(ParseError::MissingRhsToOp)?;
50
51			if token == Token::Assign {
52				let Self::Atom(Atom::Variable(var)) = lhs else {
53					return Err(ParseError::AssignToNonVariable);
54				};
55
56				lhs = Self::Assignment(var, None, rhs.into());
57				continue;
58			};
59
60			if let Some((math, assign)) = MathOperator::from_token(&token) {
61				if assign {
62					let Self::Atom(Atom::Variable(var)) = lhs else {
63						return Err(ParseError::AssignToNonVariable);
64					};
65
66					lhs = Self::Assignment(var, None, rhs.into());
67				} else {
68					lhs = Self::Math(math, lhs.into(), rhs.into());
69				}
70				continue;
71			}
72
73			if let Some(logic) = LogicOperator::from_token(&token) {
74				lhs = Self::Logic(logic, lhs.into(), rhs.into());
75				continue;
76			}
77
78			if token == Token::And || token == Token::Comma && comma_is_and {
79				lhs = Self::ShortCircuit(ShortCircuit::And, lhs.into(), rhs.into());
80				continue;
81			}
82			if token == Token::Or {
83				lhs = Self::ShortCircuit(ShortCircuit::Or, lhs.into(), rhs.into());
84				continue;
85			}
86
87			lctx.push_token(token);
88			break;
89		}
90
91		Ok(Some(lhs))
92	}
93
94	pub fn parse_until1(lctx: &mut LexContext, until: Token) -> Result<Self, ParseError> {
95		todo!();
96	}
97
98	// im not really confident in this algorithm; in the future i'll make it more robust.
99	pub fn begin_position(&self) -> Vec<PathBuf> {
100		match self {
101			Self::Atom(Atom::Value(Value::Path(path))) => vec![path.to_path_buf()],
102			Self::Atom(Atom::Value(Value::PathGlob(pathglob))) => vec![pathglob.begin_position()],
103			Self::ShortCircuit(ShortCircuit::And, lhs, rhs) => {
104				let mut beginnings = lhs.begin_position();
105
106				'out: for new in rhs.begin_position() {
107					for idx in 0..beginnings.len() {
108						// `a/b && a/b` -> `a/b`
109						if beginnings[idx] == new {
110							continue 'out;
111						}
112
113						// `a/b && a/` -> keep `a/b`
114						if beginnings[idx].starts_with(&new) {
115							continue 'out;
116						}
117
118						// `a/ && a/b` -> replace with `a/b`
119						if new.starts_with(&beginnings[idx]) {
120							beginnings[idx] = new;
121							continue 'out;
122						}
123
124						// `a/b/c && a/b/d` -> replace with `a/b`
125						for ancestor in beginnings[idx].ancestors() {
126							if new.starts_with(&ancestor) {
127								beginnings[idx] = ancestor.into();
128								continue 'out;
129							}
130						}
131
132						panic!("when does this happen?: {new:?} {old:?}", old = beginnings[idx]);
133					}
134				}
135				beginnings
136			}
137
138			Self::ShortCircuit(ShortCircuit::Or, lhs, rhs) => {
139				let mut beginnings = lhs.begin_position();
140
141				'out: for new in rhs.begin_position() {
142					for current in beginnings.iter_mut() {
143						// we already have it, nothing to do
144						if current.starts_with(&new) {
145							continue 'out;
146						}
147
148						// it's more specific, let's add it in
149						if new.starts_with(&current) {
150							*current = new;
151							continue 'out;
152						}
153					}
154					// If nothing starts with it, let's add it to the list.
155					beginnings.push(new)
156				}
157				beginnings
158			}
159			_ => vec![],
160		}
161	}
162}
163
164// #[derive(Debug, Clone, PartialEq)]
165// pub enum Expression {
166// 	Atom(Atom),
167// 	Math(MathOperator, Box<Self>, Box<Self>),
168// 	Logic(LogicOperator, Box<Self>, Box<Self>),
169// 	Assignment(String, Option<MathOperator>, Box<Self>),
170// 	And(Box<Self>, Box<Self>),
171// 	Or(Box<Self>, Box<Self>),
172// }
173
174impl Expression {
175	pub fn run(&self, ctx: &mut PlayContext, rctx: RunContext) -> PlayResult<Value> {
176		match self {
177			Self::Atom(atom) => atom.run(ctx, rctx),
178			Self::Math(op, lhs, rhs) => {
179				op.run(&lhs.run(ctx, RunContext::Any)?, &rhs.run(ctx, RunContext::Any)?)
180			}
181			Self::Logic(op, lhs, rhs) => op
182				.run(&lhs.run(ctx, RunContext::Any)?, &rhs.run(ctx, RunContext::Any)?)
183				.map(Value::from),
184			Self::Assignment(name, op, rhs) => {
185				let value = if let Some(op) = op {
186					let old = ctx.lookup_var(name)?;
187					op.run(&old, &rhs.run(ctx, RunContext::Any)?)?
188				} else {
189					rhs.run(ctx, RunContext::Any)?
190				};
191
192				ctx.assign_var(name, value.clone());
193				Ok(value)
194			}
195			Self::ShortCircuitAssignment(sc, lhs, rhs) => {
196				todo!()
197				// let value = if let Some(op) = op {
198				// 	let old = ctx.lookup_var(name);
199				// 	op.run(&old, &rhs.run(ctx, RunContext::Any)?)?
200				// } else {
201				// 	rhs.run(ctx, RunContext::Any)?
202				// };
203
204				// ctx.assign_var(name, value.clone());
205				// Ok(value)
206			}
207
208			Self::ShortCircuit(sc, lhs, rhs) => {
209				let lhs = lhs.run(ctx, RunContext::Logical)?;
210				if lhs.is_truthy() == (*sc == ShortCircuit::And) {
211					rhs.run(ctx, RunContext::Logical)
212				} else {
213					Ok(lhs)
214				}
215			} // Self::Or(lhs, rhs) => {
216			  // 	let lhs = lhs.run(ctx, RunContext::Logical)?;
217			  // 	if lhs.is_truthy() {
218			  // 		Ok(lhs)
219			  // 	} else {
220			  // 		rhs.run(ctx, RunContext::Logical)
221			  // 	}
222			  // }
223		}
224	}
225
226	// pub fn matches(&self, ctx: &mut PlayContext) -> PlayResult<bool> {
227	// 	match self {
228	// 		Self::Atom(atom) => atom.matches(ctx, lctx),
229	// 		Self::And(lhs, rhs) => Ok(lhs.matches(ctx, lctx)? && rhs.matches(ctx, lctx)?),
230	// 		Self::Or(lhs, rhs) => Ok(lhs.matches(ctx, lctx)? || rhs.matches(ctx, lctx)?),
231	// 		_ => todo!(),
232	// 	}
233	// }
234}