feat(tvix/eval): Support builtins.substring

Nix's `builtins.substring`:
- doesn't accept negative indexes for `beg` or `end`. Compare the error messages
  for:
  - `builtins.substring -3  5 "testing"`
  - `builtins.substring  3 -5 "testing"`
  - `builtins.substring -3 -5 "testing"`
- returns an empty string when `beg >= end`
- allows `end` to exceed the length of the input string

Change-Id: I83af7a099d81b6d537ebe5029d946c7031cb7113
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6555
Reviewed-by: wpcarro <wpcarro@gmail.com>
Autosubmit: wpcarro <wpcarro@gmail.com>
Reviewed-by: tazjin <tazjin@tvl.su>
Tested-by: BuildkiteCI
This commit is contained in:
William Carroll 2022-09-06 14:33:10 -07:00 committed by clbot
parent 0c75ce2d3d
commit 2fe18e4486
4 changed files with 61 additions and 0 deletions

View file

@ -4,6 +4,7 @@
//! available builtins in Nix.
use std::{
cmp,
collections::{BTreeMap, HashMap},
path::PathBuf,
rc::Rc,
@ -192,6 +193,34 @@ fn pure_builtins() -> Vec<Builtin> {
let a = args.pop().unwrap();
arithmetic_op!(a, b, -)
}),
Builtin::new("substring", 3, |args, vm| {
let beg = args[0].force(vm)?.as_int()?;
let len = args[1].force(vm)?.as_int()?;
let x = args[2].force(vm)?.to_str()?;
if beg < 0 {
return Err(ErrorKind::IndexOutOfBounds { index: beg });
}
let beg = beg as usize;
// Nix doesn't assert that the length argument is
// non-negative when the starting index is GTE the
// string's length.
if beg >= x.as_str().len() {
return Ok(Value::String("".into()));
}
if len < 0 {
return Err(ErrorKind::NegativeLength { length: len });
}
let len = len as usize;
let end = cmp::min(beg + len, x.as_str().len());
Ok(Value::String(
x.as_str()[(beg as usize)..(end as usize)].into(),
))
}),
Builtin::new("throw", 1, |mut args, _| {
return Err(ErrorKind::Throw(
args.pop().unwrap().to_str()?.as_str().to_owned(),