1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use winnow::{
ModalResult, Parser,
combinator::{alt, cut_err, delimited, fail, repeat, separated_pair, terminated},
};
use crate::{
StatefulInput,
context::Context,
expr::Expr,
function::Fn,
ident::Ident,
macros::{exp_char, exp_desc, label},
state::{EvalState, FmtState},
utils::{delimited_multispace0, write_indent},
value::Value,
};
/// Block expression with scoped variables.
#[derive(Debug, Clone)]
pub struct Block {
ctx_idx: usize,
return_expr: Box<Expr>,
}
impl Block {
pub(crate) fn parse(input: &mut StatefulInput) -> ModalResult<Expr> {
// Store the current active context index before parsing the block
let current_ctx_idx = input.state.active_ctx_idx();
// Get the next available context index
// This will be the new block's context index
let ctx_idx = input.state.avail_ctx_idx();
// Increment the available context index for nested expressions
input.state.increment_avail_ctx_idx();
// Set this new block's context as active
// This ensures that the nested blocks have this block as their parent
input.state.set_active_ctx(ctx_idx);
// Parse the block without unwrapping the result
// This allows restoring the state variables later
let parse_result = delimited(
'{',
(
// Assignments
delimited_multispace0(alt((
repeat(
// One or more assignments
1..,
delimited_multispace0(terminated(
separated_pair(
Ident::parse_ident,
delimited_multispace0('='),
alt((
Expr::parse,
// Allow function definitions as well
Fn::parse,
cut_err(fail).context(exp_desc!("an expression")),
)),
),
cut_err(';').context(exp_char!(';')),
)),
),
cut_err(fail).context(exp_desc!("at least one assignment")),
))),
// Required final expression in the block
Expr::require_parse.map(Box::new),
),
cut_err('}').context(exp_char!('}')),
)
.context(label!("block expression"))
.parse_next(input);
// Restore active context to previous one
input.state.set_active_ctx(current_ctx_idx);
let (assignments, return_expr): (Vec<(Ident, Expr)>, Box<Expr>) = parse_result
.inspect_err(|_| {
// Returned backtrack error during parsing
// Decrement avail_ctx_idx to avoid skipping indices
input.state.decrement_avail_ctx_idx();
})?;
// Create a new context for this block with the parsed assignments
let ctx = Context::from_iter(Some(current_ctx_idx), assignments);
// Place the new context at its index in the state's contexts
input.state.place_ctx(ctx_idx, ctx);
Ok(Expr::Block(Self {
ctx_idx,
return_expr,
}))
}
pub(crate) fn evaluate(self, state: &mut EvalState) -> Value {
// Save the index of the current ctx
let current_ctx_idx = state.active_ctx_idx();
// Set self as the active context
state.set_active_ctx(self.ctx_idx);
// Evaluate the expression in the context of this block
let value = self.return_expr.evaluate(state);
// Reset active context to parent context
state.set_active_ctx(current_ctx_idx);
value
}
pub(crate) fn format<W: std::fmt::Write>(
&self,
writer: &mut W,
state: FmtState,
) -> std::fmt::Result {
let pretty = state.pretty();
write!(writer, "{{")?;
if pretty {
writeln!(writer)?;
}
let ctx = &state[self.ctx_idx];
ctx.format(writer, state.indented())?;
if pretty {
write_indent(writer, state.indented().indent_level())?;
}
self.return_expr.format(writer, state.indented())?;
if pretty {
writeln!(writer)?;
write_indent(writer, state.indent_level())?;
}
write!(writer, "}}")
}
}