Skip to content

Commit 3ab9ca7

Browse files
authored
feat: implement each and when block statements (#52)
feat: implement each and when blocks (closes #23)
1 parent c5b0777 commit 3ab9ca7

File tree

4 files changed

+406
-0
lines changed

4 files changed

+406
-0
lines changed

src/mapping/ast.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,18 @@ pub enum Statement {
110110
Where { condition: Expr, span: Span },
111111
/// `sort .field asc, .field2 desc` — sort array elements
112112
Sort { keys: Vec<SortKey>, span: Span },
113+
/// `each .path { ... }` — apply block to each array element
114+
Each {
115+
path: Path,
116+
body: Vec<Statement>,
117+
span: Span,
118+
},
119+
/// `when <condition> { ... }` — conditional block
120+
When {
121+
condition: Expr,
122+
body: Vec<Statement>,
123+
span: Span,
124+
},
113125
}
114126

115127
/// A sort direction.

src/mapping/eval.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ fn eval_statement(stmt: &Statement, value: &Value) -> error::Result<Value> {
2828
Statement::Nest { paths, target, .. } => eval_nest(value, paths, target),
2929
Statement::Where { condition, .. } => eval_where(value, condition),
3030
Statement::Sort { keys, .. } => eval_sort(value, keys),
31+
Statement::Each { path, body, .. } => eval_each(value, path, body),
32+
Statement::When {
33+
condition, body, ..
34+
} => eval_when(value, condition, body),
3135
}
3236
}
3337

@@ -334,6 +338,48 @@ fn eval_sort(value: &Value, keys: &[SortKey]) -> error::Result<Value> {
334338
}
335339
}
336340

341+
// ---------------------------------------------------------------------------
342+
// each
343+
// ---------------------------------------------------------------------------
344+
345+
fn eval_each(value: &Value, path: &Path, body: &[Statement]) -> error::Result<Value> {
346+
let target = resolve_path(value, &path.segments);
347+
match target {
348+
Some(Value::Array(arr)) => {
349+
let mut updated = Vec::with_capacity(arr.len());
350+
for item in &arr {
351+
let mut result = item.clone();
352+
for stmt in body {
353+
result = eval_statement(stmt, &result)?;
354+
}
355+
updated.push(result);
356+
}
357+
Ok(set_path(value, &path.segments, Value::Array(updated)))
358+
}
359+
Some(_) => Err(error::MorphError::mapping("each requires an array target")),
360+
None => Err(error::MorphError::mapping(
361+
"each target path does not exist",
362+
)),
363+
}
364+
}
365+
366+
// ---------------------------------------------------------------------------
367+
// when
368+
// ---------------------------------------------------------------------------
369+
370+
fn eval_when(value: &Value, condition: &Expr, body: &[Statement]) -> error::Result<Value> {
371+
let cond_result = eval_expr(condition, value)?;
372+
if is_truthy(&cond_result) {
373+
let mut result = value.clone();
374+
for stmt in body {
375+
result = eval_statement(stmt, &result)?;
376+
}
377+
Ok(result)
378+
} else {
379+
Ok(value.clone())
380+
}
381+
}
382+
337383
// ---------------------------------------------------------------------------
338384
// Expression evaluation
339385
// ---------------------------------------------------------------------------

src/mapping/parser.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ impl Parser {
101101
TokenKind::Nest => self.parse_nest(),
102102
TokenKind::Where => self.parse_where(),
103103
TokenKind::Sort => self.parse_sort(),
104+
TokenKind::Each => self.parse_each(),
105+
TokenKind::When => self.parse_when(),
104106
_ => {
105107
let suggestion = suggest_keyword(&token.kind);
106108
let msg = if let Some(s) = suggestion {
@@ -322,6 +324,49 @@ impl Parser {
322324
}
323325
}
324326

327+
fn parse_each(&mut self) -> error::Result<Statement> {
328+
let start = self.advance().unwrap(); // consume 'each'
329+
let path = self.parse_path()?;
330+
let body = self.parse_block()?;
331+
Ok(Statement::Each {
332+
path,
333+
body,
334+
span: start.span,
335+
})
336+
}
337+
338+
fn parse_when(&mut self) -> error::Result<Statement> {
339+
let start = self.advance().unwrap(); // consume 'when'
340+
let condition = self.parse_expr()?;
341+
let body = self.parse_block()?;
342+
Ok(Statement::When {
343+
condition,
344+
body,
345+
span: start.span,
346+
})
347+
}
348+
349+
fn parse_block(&mut self) -> error::Result<Vec<Statement>> {
350+
self.skip_newlines();
351+
self.expect_exact(&TokenKind::LBrace)?;
352+
self.skip_newlines();
353+
354+
let mut statements = Vec::new();
355+
while self.peek_kind() != Some(&TokenKind::RBrace) {
356+
if self.peek().is_none() {
357+
return Err(error::MorphError::mapping(
358+
"unexpected end of input, expected '}'",
359+
));
360+
}
361+
let stmt = self.parse_statement()?;
362+
statements.push(stmt);
363+
self.skip_newlines();
364+
}
365+
366+
self.expect_exact(&TokenKind::RBrace)?;
367+
Ok(statements)
368+
}
369+
325370
fn parse_path_list(&mut self) -> error::Result<Vec<Path>> {
326371
let mut paths = vec![self.parse_path()?];
327372
while let Some(TokenKind::Comma) = self.peek_kind() {

0 commit comments

Comments
 (0)