use crate::ast::{Atom, LogicOperator, MathOperator, Precedence};
use crate::parse::{LexContext, ParseError, Token};
use crate::play::{PlayContext, PlayResult, RunContext};
use crate::Value;
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShortCircuit {
And,
Or,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expression {
Atom(Atom),
Math(MathOperator, Box<Self>, Box<Self>),
Logic(LogicOperator, Box<Self>, Box<Self>),
Assignment(String, Option<MathOperator>, Box<Self>),
ShortCircuitAssignment(String, ShortCircuit, Box<Self>),
ShortCircuit(ShortCircuit, Box<Self>, Box<Self>),
}
impl Expression {
pub fn parse_toplevel(lctx: &mut LexContext) -> Result<Self, ParseError> {
Ok(Self::parse(lctx, true, Precedence::default())?
.expect("Todo: error for no valid expression"))
}
pub fn parse(
lctx: &mut LexContext,
comma_is_and: bool,
prec: Precedence,
) -> Result<Option<Self>, ParseError> {
let Some(begin) = Atom::parse(lctx)? else {
return Ok(None);
};
let mut lhs = Self::Atom(begin);
while let Some(token) = lctx.next()? {
let token_prec = match Precedence::of(&token, comma_is_and) {
Some(p) if p <= prec => p,
_ => {
lctx.push_token(token);
break;
}
};
let rhs =
Self::parse(lctx, comma_is_and, token_prec)?.ok_or(ParseError::MissingRhsToOp)?;
if token == Token::Assign {
let Self::Atom(Atom::Variable(var)) = lhs else {
return Err(ParseError::AssignToNonVariable);
};
lhs = Self::Assignment(var, None, rhs.into());
continue;
};
if let Some((math, assign)) = MathOperator::from_token(&token) {
if assign {
let Self::Atom(Atom::Variable(var)) = lhs else {
return Err(ParseError::AssignToNonVariable);
};
lhs = Self::Assignment(var, None, rhs.into());
} else {
lhs = Self::Math(math, lhs.into(), rhs.into());
}
continue;
}
if let Some(logic) = LogicOperator::from_token(&token) {
lhs = Self::Logic(logic, lhs.into(), rhs.into());
continue;
}
if token == Token::And || token == Token::Comma && comma_is_and {
lhs = Self::ShortCircuit(ShortCircuit::And, lhs.into(), rhs.into());
continue;
}
if token == Token::Or {
lhs = Self::ShortCircuit(ShortCircuit::Or, lhs.into(), rhs.into());
continue;
}
lctx.push_token(token);
break;
}
Ok(Some(lhs))
}
pub fn parse_until1(lctx: &mut LexContext, until: Token) -> Result<Self, ParseError> {
todo!();
}
pub fn begin_position(&self) -> Vec<PathBuf> {
match self {
Self::Atom(Atom::Value(Value::Path(path))) => vec![path.to_path_buf()],
Self::Atom(Atom::Value(Value::PathGlob(pathglob))) => vec![pathglob.begin_position()],
Self::ShortCircuit(ShortCircuit::And, lhs, rhs) => {
let mut beginnings = lhs.begin_position();
'out: for new in rhs.begin_position() {
for idx in 0..beginnings.len() {
if beginnings[idx] == new {
continue 'out;
}
if beginnings[idx].starts_with(&new) {
continue 'out;
}
if new.starts_with(&beginnings[idx]) {
beginnings[idx] = new;
continue 'out;
}
for ancestor in beginnings[idx].ancestors() {
if new.starts_with(&ancestor) {
beginnings[idx] = ancestor.into();
continue 'out;
}
}
panic!("when does this happen?: {new:?} {old:?}", old = beginnings[idx]);
}
}
beginnings
}
Self::ShortCircuit(ShortCircuit::Or, lhs, rhs) => {
let mut beginnings = lhs.begin_position();
'out: for new in rhs.begin_position() {
for current in beginnings.iter_mut() {
if current.starts_with(&new) {
continue 'out;
}
if new.starts_with(¤t) {
*current = new;
continue 'out;
}
}
beginnings.push(new)
}
beginnings
}
_ => vec![],
}
}
}
impl Expression {
pub fn run(&self, ctx: &mut PlayContext, rctx: RunContext) -> PlayResult<Value> {
match self {
Self::Atom(atom) => atom.run(ctx, rctx),
Self::Math(op, lhs, rhs) => {
op.run(&lhs.run(ctx, RunContext::Any)?, &rhs.run(ctx, RunContext::Any)?)
}
Self::Logic(op, lhs, rhs) => op
.run(&lhs.run(ctx, RunContext::Any)?, &rhs.run(ctx, RunContext::Any)?)
.map(Value::from),
Self::Assignment(name, op, rhs) => {
let value = if let Some(op) = op {
let old = ctx.lookup_var(name)?;
op.run(&old, &rhs.run(ctx, RunContext::Any)?)?
} else {
rhs.run(ctx, RunContext::Any)?
};
ctx.assign_var(name, value.clone());
Ok(value)
}
Self::ShortCircuitAssignment(sc, lhs, rhs) => {
todo!()
}
Self::ShortCircuit(sc, lhs, rhs) => {
let lhs = lhs.run(ctx, RunContext::Logical)?;
if lhs.is_truthy() == (*sc == ShortCircuit::And) {
rhs.run(ctx, RunContext::Logical)
} else {
Ok(lhs)
}
} }
}
}