This commit is contained in:
ge
2025-12-26 22:38:52 +03:00
commit b9b57ad93f
8 changed files with 466 additions and 0 deletions

206
ranges.v Normal file
View File

@@ -0,0 +1,206 @@
module ranges
import strconv
import math.big
struct Range[T] {
limit T
step T
is_neg bool
mut:
cur T
}
// next returns the new element from range or none if range end is reached.
pub fn (mut r Range[T]) next() ?T {
if (r.is_neg && r.cur < r.limit) || (!r.is_neg && r.cur > r.limit) {
return none
}
defer {
r.cur += r.step
}
return r.cur
}
@[params]
pub struct RangeConfig {
pub:
// If true exclude the end value from range.
exclusive bool
}
// range creates new Range iterator with given start, end and step values.
//
// Generally numbers are expected. If type is a struct the following operators
// must be overloaded to perform comparisons and arithmetics: `+`, `-`, `<`, `==`.
// See https://docs.vlang.io/limited-operator-overloading.html for details.
//
// By default, the range includes the end value. This behavior can be changed
// by enabling the 'exclusive' option.
//
// Note: Zero step value will cause an infitite loop!
pub fn range[T](start T, end T, step T, config RangeConfig) Range[T] {
mut limit := end
if config.exclusive {
limit -= step
}
return Range[T]{
limit: limit
step: step
cur: start
is_neg: start > end
}
}
@[params]
pub struct RangeFromStringConfig {
RangeConfig
pub:
sep string = '-'
group_sep string = ','
}
// from_string parses the input string and returns an array of iterators.
// This function supports only V native number types and big.Integer.
// Use from_string_custom if you want to use custom type with special string
// convertion rules.
//
// Supported string formats are `start-end`, `start[:step]end`. start and end
// values are sepatared by 'sep' which is hypen (`-`) by default. Single number
// will be interpreted as range of one element. Several ranges can be specified
// in a line, separated by 'group_sep' (comma by default). 'sep' and 'group_sep'
// can be overrided by user.
//
// Some example input strings:
//
// * `5` - range from 5 to 5 (single element).
// * `0-10` - range from 0 to 10.
// * `15:-1:0` - range in MathLab-style syntax from 15 to 0 with negative step -1.
// * `1..8` - range from 1 to 8 with '..' sep.
// * `0-7,64-71` - multiple ranges: from 0 to 7 and from 64 to 71.
//
// Only MathLab-style syntax allows you to specify a step directly in the string.
// For all other cases, the step is equal to one.
//
// Example: assert ranges.from_string[int]('1-7')! == [ranges.range(1, 7, 1)]
pub fn from_string[T](s string, config RangeFromStringConfig) ![]Range[T] {
mut result := []Range[T]{}
for i in s.split(config.group_sep) {
range_str := parse_string(i, config.sep)!
// vfmt off
result << range[T](
convert_string[T](range_str[0])!,
convert_string[T](range_str[1])!,
convert_string[T](range_str[2])!,
config.RangeConfig)
// vfmt on
}
return result
}
pub type StringConvertFn[T] = fn (s string) !T
// from_string_custom parses the input string using `conv` function to convert
// string values into numbers and returns an array of iterators. This is an extended
// version of from_string with the same semanthics.
// Example:
// ```v
// import math.big
// import ranges
//
// conv := fn (s string) !big.Integer {
// return big.integer_from_string(s)!
// }
//
// for range in ranges.from_string_custom('0-3,8,11-13', conv)! {
// for i in range {
// println(i)
// }
// }
// // 0
// // 1
// // 2
// // 3
// // 8
// // 11
// // 12
// // 13
// ```
pub fn from_string_custom[T](s string, conv StringConvertFn[T], config RangeFromStringConfig) ![]Range[T] {
mut result := []Range[T]{}
for i in s.split(config.group_sep) {
range_str := parse_string(i, config.sep)!
start := conv[T](range_str[0])!
end := conv[T](range_str[1])!
step := conv[T](range_str[2])!
result << range(start, end, step, config.RangeConfig)
}
return result
}
fn parse_string(s string, sep string) ![]string {
parts := s.split(sep)
if parts.any(|x| x.is_blank()) || parts.len !in [1, 2, 3] {
return error('`start${sep}end` or `start[:step]:end`' +
"formatted string expected, not '${s}'")
}
if parts.len == 1 {
return [parts[0], parts[0], '1']
} else if parts.len == 2 {
return [parts[0], parts[1], '1']
} else if sep == ':' && parts.len == 3 {
return [parts[0], parts[2], parts[1]]
}
return error('invalid range string: ${s}')
}
fn convert_string[T](s string) !T {
$match T {
int {
return strconv.atoi(s)!
}
i8 {
return strconv.atoi8(s)!
}
i16 {
return strconv.atoi16(s)!
}
i32 {
return strconv.atoi32(s)!
}
i64 {
return strconv.atoi64(s)!
}
isize {
return isize(strconv.atoi64(s)!)
}
u8 {
return strconv.atou8(s)!
}
u16 {
return strconv.atou16(s)!
}
u32 {
return strconv.atou32(s)!
}
u64 {
return strconv.atou64(s)!
}
usize {
return usize(strconv.atou64(s)!)
}
f32 {
return f32(strconv.atof64(s)!)
}
f64 {
return strconv.atof64(s)!
}
big.Integer {
return big.integer_from_string(s)!
}
$else {
return error("cannot convert '${s}' to ${typeof[T]().name}")
}
}
return error('unexpected string convert error')
}