Skip to content
This repository was archived by the owner on Jun 11, 2026. It is now read-only.

Commit 638ffbf

Browse files
feat: implement bool/integer/list/pair/unit casing
Signed-off-by: Jonathan Lim <jonathan.lim.222@gmail.com>
1 parent 0ce5c52 commit 638ffbf

2 files changed

Lines changed: 107 additions & 1 deletion

File tree

crates/uplc/src/machine/cek.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use bumpalo::{collections::Vec as BumpVec, Bump};
22

33
use crate::{
44
binder::Eval,
5+
constant::Constant,
56
machine::{context::Context, env::Env, state::MachineState},
67
term::Term,
78
};
@@ -15,6 +16,9 @@ use super::{
1516
CostModel, ExBudget, MachineError,
1617
};
1718

19+
pub const CONS_BRANCH: usize = 0;
20+
pub const NILS_BRANCH: usize = 1;
21+
1822
pub struct Machine<'a> {
1923
pub(super) arena: &'a Bump,
2024
ex_budget: ExBudget,
@@ -248,6 +252,106 @@ impl<'a> Machine<'a> {
248252
Err(MachineError::MissingCaseBranch(branches, value))
249253
}
250254
}
255+
Value::Con(constant) => match constant {
256+
Constant::Integer(scrutinee) => match usize::try_from(*scrutinee) {
257+
Ok(scrutinee_usize) => branches
258+
.get(scrutinee_usize)
259+
.map(|branch| MachineState::compute(self.arena, context, env, branch))
260+
.ok_or(MachineError::MissingCaseBranch(branches, value)),
261+
Err(_) => Err(MachineError::ExplicitErrorTerm),
262+
},
263+
Constant::Boolean(scrutinee) => {
264+
if branches.len() > 2 {
265+
return Err(MachineError::TooManyCaseBranches(branches, value));
266+
}
267+
branches
268+
.get(*scrutinee as usize)
269+
.map(|branch| MachineState::compute(self.arena, context, env, branch))
270+
.ok_or(MachineError::MissingCaseBranch(branches, value))
271+
}
272+
Constant::Unit => {
273+
if branches.len() > 1 {
274+
return Err(MachineError::TooManyCaseBranches(branches, value));
275+
}
276+
branches
277+
.get(CONS_BRANCH)
278+
.map(|branch| MachineState::compute(self.arena, context, env, branch))
279+
.ok_or(MachineError::MissingCaseBranch(branches, value))
280+
}
281+
// Caseing on pairs expects a single branch that takes two arguments for each values of the pair.
282+
Constant::ProtoPair(_, _, left_constant, right_constant) => {
283+
if branches.len() > 1 {
284+
return Err(MachineError::TooManyCaseBranches(branches, value));
285+
}
286+
branches
287+
.get(CONS_BRANCH)
288+
.map(|branch| {
289+
let right_value: &Value<'_, V> =
290+
Value::con(self.arena, right_constant);
291+
let right_frame: &Context<'_, V> = Context::frame_await_fun_value(
292+
self.arena,
293+
right_value,
294+
context,
295+
);
296+
let left_value = Value::con(self.arena, left_constant);
297+
let left_frame = Context::frame_await_fun_value(
298+
self.arena,
299+
left_value,
300+
right_frame,
301+
);
302+
MachineState::compute(self.arena, left_frame, env, branch)
303+
})
304+
.ok_or(MachineError::MissingCaseBranch(branches, value))
305+
}
306+
// When matching (case-ing) on a builtin list, exactly one or two branches are allowed:
307+
// - With a single branch, it is assumed the list is non-empty; the branch receives the head and tail as arguments.
308+
// If the list is actually empty, script evaluation will fail.
309+
// - With two branches, the nils branch is selected for the empty list (receiving no arguments),
310+
// and the cons branch is selected for a non-empty list (receiving the head and tail as arguments).
311+
//
312+
// Note: In the Haskell implementation, when a list contains only a single element,
313+
// the tail argument passed to the branch is an empty list.
314+
Constant::ProtoList(list_type, list) => {
315+
if branches.len() > 2 {
316+
return Err(MachineError::TooManyCaseBranches(branches, value));
317+
}
318+
if list.is_empty() {
319+
branches
320+
.get(NILS_BRANCH)
321+
.map(|branch| {
322+
let frame = self.transfer_arg_stack(&[], context);
323+
MachineState::compute(self.arena, frame, env, branch)
324+
})
325+
.ok_or(MachineError::MissingCaseBranch(branches, value))
326+
} else if let Some((head, tail)) = list.split_first() {
327+
branches
328+
.get(CONS_BRANCH)
329+
.map(|branch| {
330+
let tail_value = if tail.is_empty() {
331+
let empty_list_const =
332+
Constant::proto_list(self.arena, list_type, &[]);
333+
Value::con(self.arena, empty_list_const)
334+
} else {
335+
Value::con(self.arena, tail.last().unwrap())
336+
};
337+
338+
let tail_frame = Context::frame_await_fun_value(
339+
self.arena, tail_value, context,
340+
);
341+
let head_value = Value::con(self.arena, head);
342+
let head_frame = Context::frame_await_fun_value(
343+
self.arena, head_value, tail_frame,
344+
);
345+
346+
MachineState::compute(self.arena, head_frame, env, branch)
347+
})
348+
.ok_or(MachineError::MissingCaseBranch(branches, value))
349+
} else {
350+
Err(MachineError::ExplicitErrorTerm)
351+
}
352+
}
353+
_ => Err(MachineError::ExplicitErrorTerm),
354+
},
251355
v => Err(MachineError::NonConstrScrutinized(v)),
252356
},
253357
Context::NoFrame => {

crates/uplc/src/machine/error.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ where
3434
BuiltinTermArgumentExpected(&'a Term<'a, V>),
3535
#[error("Non-constructor scrutinized")]
3636
NonConstrScrutinized(&'a Value<'a, V>),
37-
#[error("Non-integer index")]
37+
#[error("Missing case branch")]
3838
MissingCaseBranch(&'a [&'a Term<'a, V>], &'a Value<'a, V>),
39+
#[error("Too many case branches")]
40+
TooManyCaseBranches(&'a [&'a Term<'a, V>], &'a Value<'a, V>),
3941
#[error(transparent)]
4042
Runtime(RuntimeError<'a>),
4143
#[error("Max constr tag exceeded")]

0 commit comments

Comments
 (0)