///|
/// A dummy span used for errors raised from built-in functions.
let builtin_span : @ast.Span = {
start: { file: "", line: 0, column: 0 },
end: { file: "", line: 0, column: 0 },
}
///|
/// Convert a Value to its Starlark string representation (like str()).
fn value_to_str(v : @value.Value) -> String {
match v {
None => "None"
Bool(b) => if b { "True" } else { "False" }
Int(n) => n.to_string()
String(s) => s
List(items) => {
let parts : Array[String] = []
for item in items.val {
parts.push(value_to_repr(item))
}
"[" + parts.join(", ") + "]"
}
Tuple(items) => {
let parts : Array[String] = []
for i = 0; i < items.length(); i = i + 1 {
parts.push(value_to_repr(items[i]))
}
if items.length() == 1 {
"(" + parts[0] + ",)"
} else {
"(" + parts.join(", ") + ")"
}
}
Dict(map) => {
let parts : Array[String] = []
for k, v2 in map.val {
parts.push(
value_to_repr(@value.Value::String(k)) + ": " + value_to_repr(v2),
)
}
"{" + parts.join(", ") + "}"
}
Function(name, _) => ""
BuiltinFunction(name, _) => ""
}
}
///|
/// Convert a Value to its Starlark repr representation (like repr()).
/// Strings get double-quoted with escapes.
fn value_to_repr(v : @value.Value) -> String {
match v {
String(s) => {
let buf = StringBuilder::new()
buf.write_char('"')
for i = 0; i < s.length(); i = i + 1 {
let code = s[i].to_int()
if code == '"'.to_int() {
buf.write_string("\\\"")
} else if code == '\\'.to_int() {
buf.write_string("\\\\")
} else if code == '\n'.to_int() {
buf.write_string("\\n")
} else if code == '\r'.to_int() {
buf.write_string("\\r")
} else if code == '\t'.to_int() {
buf.write_string("\\t")
} else {
buf.write_char(code.unsafe_to_char())
}
}
buf.write_char('"')
buf.to_string()
}
_ => value_to_str(v)
}
}
///|
/// Compare two values for sorting. Returns -1, 0, or 1.
fn builtin_compare(
a : @value.Value,
b : @value.Value,
) -> Int raise StarlarkError {
match (a, b) {
(Int(x), Int(y)) => if x < y { -1 } else if x > y { 1 } else { 0 }
(String(x), String(y)) => x.lexical_compare(y)
(Bool(x), Bool(y)) => {
let xi : Int64 = if x { 1L } else { 0L }
let yi : Int64 = if y { 1L } else { 0L }
if xi < yi {
-1
} else if xi > yi {
1
} else {
0
}
}
(List(xs), List(ys)) => {
let min_len = if xs.val.length() < ys.val.length() {
xs.val.length()
} else {
ys.val.length()
}
for i = 0; i < min_len; i = i + 1 {
let c = builtin_compare(xs.val[i], ys.val[i])
if c != 0 {
return c
}
}
if xs.val.length() < ys.val.length() {
-1
} else if xs.val.length() > ys.val.length() {
1
} else {
0
}
}
(Tuple(xs), Tuple(ys)) => {
let min_len = if xs.length() < ys.length() {
xs.length()
} else {
ys.length()
}
for i = 0; i < min_len; i = i + 1 {
let c = builtin_compare(xs[i], ys[i])
if c != 0 {
return c
}
}
if xs.length() < ys.length() {
-1
} else if xs.length() > ys.length() {
1
} else {
0
}
}
_ =>
raise StarlarkError::TypeError(
message="not supported between instances of '" +
a.type_name() +
"' and '" +
b.type_name() +
"'",
span=builtin_span,
)
}
}
///|
/// Simple string hash function.
fn string_hash(s : String) -> Int64 {
let mut h : Int64 = 0L
for i = 0; i < s.length(); i = i + 1 {
h = h * 31L + s[i].to_int().to_int64()
}
h
}
///|
/// Parse an integer string with a given base.
fn parse_int_with_base(s : String, base : Int) -> Int64 raise StarlarkError {
let mut str = s
// Trim whitespace
let mut start = 0
while start < str.length() &&
(
str[start].to_int() == ' '.to_int() ||
str[start].to_int() == '\t'.to_int()
) {
start = start + 1
}
let mut end = str.length()
while end > start &&
(
str[end - 1].to_int() == ' '.to_int() ||
str[end - 1].to_int() == '\t'.to_int()
) {
end = end - 1
}
if start > 0 || end < str.length() {
let buf = StringBuilder::new()
for i = start; i < end; i = i + 1 {
buf.write_char(str[i].to_int().unsafe_to_char())
}
str = buf.to_string()
}
if str.length() == 0 {
raise StarlarkError::ValueError(
message="int() argument must be a string or a number, not empty string",
span=builtin_span,
)
}
// Handle sign
let mut negative = false
let mut idx = 0
if str[0].to_int() == '-'.to_int() {
negative = true
idx = 1
} else if str[0].to_int() == '+'.to_int() {
idx = 1
}
let actual_base : Int = if base == 0 {
// Auto-detect
if idx + 1 < str.length() && str[idx].to_int() == '0'.to_int() {
let prefix_code = str[idx + 1].to_int()
if prefix_code == 'x'.to_int() || prefix_code == 'X'.to_int() {
idx = idx + 2
16
} else if prefix_code == 'o'.to_int() || prefix_code == 'O'.to_int() {
idx = idx + 2
8
} else if prefix_code == 'b'.to_int() || prefix_code == 'B'.to_int() {
idx = idx + 2
2
} else {
10
}
} else {
10
}
} else {
// If explicit base, still skip prefix if present
if base == 16 &&
idx + 1 < str.length() &&
str[idx].to_int() == '0'.to_int() &&
(
str[idx + 1].to_int() == 'x'.to_int() ||
str[idx + 1].to_int() == 'X'.to_int()
) {
idx = idx + 2
} else if base == 8 &&
idx + 1 < str.length() &&
str[idx].to_int() == '0'.to_int() &&
(
str[idx + 1].to_int() == 'o'.to_int() ||
str[idx + 1].to_int() == 'O'.to_int()
) {
idx = idx + 2
} else if base == 2 &&
idx + 1 < str.length() &&
str[idx].to_int() == '0'.to_int() &&
(
str[idx + 1].to_int() == 'b'.to_int() ||
str[idx + 1].to_int() == 'B'.to_int()
) {
idx = idx + 2
}
base
}
if idx >= str.length() {
raise StarlarkError::ValueError(
message="invalid literal for int() with base \{actual_base}: \{value_to_repr(@value.Value::String(s))}",
span=builtin_span,
)
}
let mut result : Int64 = 0L
let base64 = actual_base.to_int64()
while idx < str.length() {
let code = str[idx].to_int()
// Allow underscores as digit separators
if code == '_'.to_int() {
idx = idx + 1
continue
}
let digit : Int = if code >= '0'.to_int() && code <= '9'.to_int() {
code - '0'.to_int()
} else if code >= 'a'.to_int() && code <= 'z'.to_int() {
code - 'a'.to_int() + 10
} else if code >= 'A'.to_int() && code <= 'Z'.to_int() {
code - 'A'.to_int() + 10
} else {
raise StarlarkError::ValueError(
message="invalid literal for int() with base \{actual_base}: \{value_to_repr(@value.Value::String(s))}",
span=builtin_span,
)
}
if digit >= actual_base {
raise StarlarkError::ValueError(
message="invalid literal for int() with base \{actual_base}: \{value_to_repr(@value.Value::String(s))}",
span=builtin_span,
)
}
result = result * base64 + digit.to_int64()
idx = idx + 1
}
if negative {
-result
} else {
result
}
}
///|
/// Returns a map of all predeclared Starlark built-in functions.
pub fn predeclared_builtins() -> Map[String, @value.Value] {
let builtins : Map[String, @value.Value] = {}
// abs(x) - absolute value of int
builtins.set(
"abs",
@value.Value::BuiltinFunction("abs", fn(pos, _kw) raise {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="abs() takes exactly one argument (\{pos.length()} given)",
span=builtin_span,
)
}
match pos[0] {
Int(n) => @value.Value::Int(n.abs())
_ =>
raise StarlarkError::TypeError(
message="bad operand type for abs(): '\{pos[0].type_name()}'",
span=builtin_span,
)
}
}),
)
// any(x) - true if any element of iterable is truthy
builtins.set(
"any",
@value.Value::BuiltinFunction("any", fn(pos, _kw) raise {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="any() takes exactly one argument (\{pos.length()} given)",
span=builtin_span,
)
}
let items = iterable_to_array_by_name(pos[0], "any")
for item in items {
if item.is_truthy() {
return @value.Value::Bool(true)
}
}
@value.Value::Bool(false)
}),
)
// all(x) - true if all elements are truthy (true for empty)
builtins.set(
"all",
@value.Value::BuiltinFunction("all", fn(pos, _kw) raise {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="all() takes exactly one argument (\{pos.length()} given)",
span=builtin_span,
)
}
let items = iterable_to_array_by_name(pos[0], "all")
for item in items {
if not(item.is_truthy()) {
return @value.Value::Bool(false)
}
}
@value.Value::Bool(true)
}),
)
// bool(x?) - convert to bool; bool() -> false
builtins.set(
"bool",
@value.Value::BuiltinFunction("bool", fn(pos, _kw) raise {
if pos.length() == 0 {
return @value.Value::Bool(false)
}
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="bool() takes at most one argument (\{pos.length()} given)",
span=builtin_span,
)
}
@value.Value::Bool(pos[0].is_truthy())
}),
)
// dict(pairs?) - create dict from list of (key, value) tuples; dict() -> {}
builtins.set(
"dict",
@value.Value::BuiltinFunction("dict", fn(pos, kw) raise {
let map : Map[String, @value.Value] = {}
guard pos.length() <= 1 else {
raise StarlarkError::TypeError(
message="dict() takes at most one positional argument (\{pos.length()} given)",
span=builtin_span,
)
}
if pos.length() == 1 {
let items = iterable_to_array_by_name(pos[0], "dict")
for item in items {
match item {
Tuple(pair) =>
if pair.length() == 2 {
match pair[0] {
String(k) => map.set(k, pair[1])
_ =>
raise StarlarkError::TypeError(
message="dict keys must be strings",
span=builtin_span,
)
}
} else {
raise StarlarkError::ValueError(
message="dict() requires (key, value) pairs",
span=builtin_span,
)
}
List(pair) =>
if pair.val.length() == 2 {
match pair.val[0] {
String(k) => map.set(k, pair.val[1])
_ =>
raise StarlarkError::TypeError(
message="dict keys must be strings",
span=builtin_span,
)
}
} else {
raise StarlarkError::ValueError(
message="dict() requires (key, value) pairs",
span=builtin_span,
)
}
_ =>
raise StarlarkError::TypeError(
message="cannot convert dict() element to a pair",
span=builtin_span,
)
}
}
}
for k, v in kw {
map.set(k, v)
}
@value.Value::Dict(Ref::new(map))
}),
)
// dir(x) - sorted list of attribute names
builtins.set(
"dir",
@value.Value::BuiltinFunction("dir", fn(pos, _kw) raise {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="dir() takes exactly one argument (\{pos.length()} given)",
span=builtin_span,
)
}
let name_strs : Array[String] = []
match pos[0] {
String(_) =>
for name, _ in string_methods() {
name_strs.push(name)
}
List(_) =>
for name, _ in list_methods() {
name_strs.push(name)
}
Dict(_) =>
for name, _ in dict_methods() {
name_strs.push(name)
}
_ => ()
}
// Insertion sort with lexicographic comparison
// (MoonBit's built-in String < / > is not lexicographic)
for i = 1; i < name_strs.length(); i = i + 1 {
let key = name_strs[i]
let mut j = i - 1
while j >= 0 {
if name_strs[j].lexical_compare(key) > 0 {
name_strs[j + 1] = name_strs[j]
j = j - 1
} else {
break
}
}
name_strs[j + 1] = key
}
let names : Array[@value.Value] = []
for name in name_strs {
names.push(@value.Value::String(name))
}
@value.Value::List(Ref::new(names))
}),
)
// enumerate(x, start=0) - list of (index, value) tuples
builtins.set(
"enumerate",
@value.Value::BuiltinFunction("enumerate", fn(pos, kw) raise {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="enumerate() takes exactly one positional argument (\{pos.length()} given)",
span=builtin_span,
)
}
let start : Int64 = match kw.get("start") {
Some(Int(n)) => n
Some(other) =>
raise StarlarkError::TypeError(
message="enumerate() start must be an int, not '" +
other.type_name() +
"'",
span=builtin_span,
)
None => 0L
}
let items = iterable_to_array_by_name(pos[0], "enumerate")
let result : Array[@value.Value] = []
for i = 0; i < items.length(); i = i + 1 {
let idx = start + i.to_int64()
let tuple_items : FixedArray[@value.Value] = [
@value.Value::Int(idx),
items[i],
]
result.push(@value.Value::Tuple(tuple_items))
}
@value.Value::List(Ref::new(result))
}),
)
// fail(*args) - raise error with message
builtins.set(
"fail",
@value.Value::BuiltinFunction("fail", fn(pos, _kw) raise {
let parts : Array[String] = []
for arg in pos {
parts.push(value_to_str(arg))
}
let msg = if parts.length() > 0 { parts.join(" ") } else { "fail" }
raise StarlarkError::ValueError(message="fail: " + msg, span=builtin_span)
}),
)
// getattr(x, name, default?) - get attribute (stub)
builtins.set(
"getattr",
@value.Value::BuiltinFunction("getattr", fn(pos, _kw) raise {
guard pos.length() >= 2 && pos.length() <= 3 else {
raise StarlarkError::TypeError(
message="getattr() takes 2 or 3 arguments (\{pos.length()} given)",
span=builtin_span,
)
}
if pos.length() == 3 {
return pos[2]
}
let name = match pos[1] {
String(s) => s
_ =>
raise StarlarkError::TypeError(
message="getattr() attribute name must be a string",
span=builtin_span,
)
}
raise StarlarkError::TypeError(
message="'\{pos[0].type_name()}' has no attribute '\{name}'",
span=builtin_span,
)
}),
)
// hasattr(x, name) - check attribute (stub: always false)
builtins.set(
"hasattr",
@value.Value::BuiltinFunction("hasattr", fn(pos, _kw) raise {
guard pos.length() == 2 else {
raise StarlarkError::TypeError(
message="hasattr() takes exactly 2 arguments (\{pos.length()} given)",
span=builtin_span,
)
}
@value.Value::Bool(false)
}),
)
// hash(x) - hash of string
builtins.set(
"hash",
@value.Value::BuiltinFunction("hash", fn(pos, _kw) raise {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="hash() takes exactly one argument (\{pos.length()} given)",
span=builtin_span,
)
}
match pos[0] {
String(s) => @value.Value::Int(string_hash(s))
_ =>
raise StarlarkError::TypeError(
message="unhashable type: '\{pos[0].type_name()}'",
span=builtin_span,
)
}
}),
)
// int(x, base?) - convert to int
builtins.set(
"int",
@value.Value::BuiltinFunction("int", fn(pos, _kw) raise {
if pos.length() == 0 {
return @value.Value::Int(0L)
}
guard pos.length() <= 2 else {
raise StarlarkError::TypeError(
message="int() takes at most 2 arguments (\{pos.length()} given)",
span=builtin_span,
)
}
let base : Int = if pos.length() == 2 {
match pos[1] {
Int(n) => n.to_int()
_ =>
raise StarlarkError::TypeError(
message="int() second argument must be an int",
span=builtin_span,
)
}
} else {
10
}
match pos[0] {
Int(n) => {
if pos.length() == 2 {
raise StarlarkError::TypeError(
message="int() can't convert non-string with explicit base",
span=builtin_span,
)
}
@value.Value::Int(n)
}
Bool(b) => {
if pos.length() == 2 {
raise StarlarkError::TypeError(
message="int() can't convert non-string with explicit base",
span=builtin_span,
)
}
@value.Value::Int(if b { 1L } else { 0L })
}
String(s) => @value.Value::Int(parse_int_with_base(s, base))
_ =>
raise StarlarkError::TypeError(
message="int() argument must be a string or a number, not '" +
pos[0].type_name() +
"'",
span=builtin_span,
)
}
}),
)
// len(x) - length of string/list/tuple/dict
builtins.set(
"len",
@value.Value::BuiltinFunction("len", fn(pos, _kw) raise {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="len() takes exactly one argument (\{pos.length()} given)",
span=builtin_span,
)
}
match pos[0] {
String(s) => @value.Value::Int(s.length().to_int64())
List(items) => @value.Value::Int(items.val.length().to_int64())
Tuple(items) => @value.Value::Int(items.length().to_int64())
Dict(map) => @value.Value::Int(map.val.length().to_int64())
_ =>
raise StarlarkError::TypeError(
message="object of type '" + pos[0].type_name() + "' has no len()",
span=builtin_span,
)
}
}),
)
// list(x?) - create list from iterable; list() -> []
builtins.set(
"list",
@value.Value::BuiltinFunction("list", fn(pos, _kw) raise {
if pos.length() == 0 {
return @value.Value::List(Ref::new([]))
}
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="list() takes at most one argument (\{pos.length()} given)",
span=builtin_span,
)
}
let items = iterable_to_array_by_name(pos[0], "list")
@value.Value::List(Ref::new(items))
}),
)
// max(args) - maximum
builtins.set(
"max",
@value.Value::BuiltinFunction("max", fn(pos, _kw) raise {
guard pos.length() > 0 else {
raise StarlarkError::TypeError(
message="max() requires at least one argument",
span=builtin_span,
)
}
let items : Array[@value.Value] = if pos.length() == 1 {
iterable_to_array_by_name(pos[0], "max")
} else {
pos
}
guard items.length() > 0 else {
raise StarlarkError::ValueError(
message="max() arg is an empty sequence",
span=builtin_span,
)
}
let mut best = items[0]
for i = 1; i < items.length(); i = i + 1 {
if builtin_compare(items[i], best) > 0 {
best = items[i]
}
}
best
}),
)
// min(args) - minimum
builtins.set(
"min",
@value.Value::BuiltinFunction("min", fn(pos, _kw) raise {
guard pos.length() > 0 else {
raise StarlarkError::TypeError(
message="min() requires at least one argument",
span=builtin_span,
)
}
let items : Array[@value.Value] = if pos.length() == 1 {
iterable_to_array_by_name(pos[0], "min")
} else {
pos
}
guard items.length() > 0 else {
raise StarlarkError::ValueError(
message="min() arg is an empty sequence",
span=builtin_span,
)
}
let mut best = items[0]
for i = 1; i < items.length(); i = i + 1 {
if builtin_compare(items[i], best) < 0 {
best = items[i]
}
}
best
}),
)
// print(*args, sep=" ") - print to stderr, return None
builtins.set(
"print",
@value.Value::BuiltinFunction("print", fn(pos, kw) raise {
let sep = match kw.get("sep") {
Some(String(s)) => s
Some(_) =>
raise StarlarkError::TypeError(
message="print() sep must be a string",
span=builtin_span,
)
None => " "
}
let parts : Array[String] = []
for arg in pos {
parts.push(value_to_str(arg))
}
println(parts.join(sep))
@value.Value::None
}),
)
// range(stop) / range(start, stop) / range(start, stop, step)
builtins.set(
"range",
@value.Value::BuiltinFunction("range", fn(pos, _kw) raise {
guard pos.length() >= 1 && pos.length() <= 3 else {
raise StarlarkError::TypeError(
message="range() requires 1 to 3 arguments (\{pos.length()} given)",
span=builtin_span,
)
}
let params : (Int64, Int64, Int64) = if pos.length() == 1 {
match pos[0] {
Int(n) => (0L, n, 1L)
_ =>
raise StarlarkError::TypeError(
message="range() argument must be an int",
span=builtin_span,
)
}
} else if pos.length() == 2 {
match (pos[0], pos[1]) {
(Int(a), Int(b)) => (a, b, 1L)
_ =>
raise StarlarkError::TypeError(
message="range() arguments must be ints",
span=builtin_span,
)
}
} else {
match (pos[0], pos[1], pos[2]) {
(Int(a), Int(b), Int(c)) => {
if c == 0L {
raise StarlarkError::ValueError(
message="range() step argument must not be zero",
span=builtin_span,
)
}
(a, b, c)
}
_ =>
raise StarlarkError::TypeError(
message="range() arguments must be ints",
span=builtin_span,
)
}
}
let (start, stop, step) = params
let result : Array[@value.Value] = []
if step > 0L {
let mut i = start
while i < stop {
result.push(@value.Value::Int(i))
i = i + step
}
} else {
let mut i = start
while i > stop {
result.push(@value.Value::Int(i))
i = i + step
}
}
@value.Value::List(Ref::new(result))
}),
)
// repr(x) - string representation (strings get quoted)
builtins.set(
"repr",
@value.Value::BuiltinFunction("repr", fn(pos, _kw) raise {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="repr() takes exactly one argument (\{pos.length()} given)",
span=builtin_span,
)
}
@value.Value::String(value_to_repr(pos[0]))
}),
)
// reversed(x) - new reversed list
builtins.set(
"reversed",
@value.Value::BuiltinFunction("reversed", fn(pos, _kw) raise {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="reversed() takes exactly one argument (\{pos.length()} given)",
span=builtin_span,
)
}
let items = iterable_to_array_by_name(pos[0], "reversed")
let len = items.length()
for i = 0; i < len / 2; i = i + 1 {
let tmp = items[i]
items[i] = items[len - 1 - i]
items[len - 1 - i] = tmp
}
@value.Value::List(Ref::new(items))
}),
)
// sorted(x, reverse=False) - new sorted list (stable sort)
builtins.set(
"sorted",
@value.Value::BuiltinFunction("sorted", fn(pos, kw) raise {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="sorted() takes exactly one positional argument (\{pos.length()} given)",
span=builtin_span,
)
}
let reverse = match kw.get("reverse") {
Some(Bool(b)) => b
Some(Int(n)) => n != 0L
Some(_) =>
raise StarlarkError::TypeError(
message="sorted() reverse must be a bool",
span=builtin_span,
)
None => false
}
let items = iterable_to_array_by_name(pos[0], "sorted")
// Insertion sort for stability
for i = 1; i < items.length(); i = i + 1 {
let key = items[i]
let mut j = i - 1
while j >= 0 {
let cmp = builtin_compare(items[j], key)
let should_swap = if reverse { cmp < 0 } else { cmp > 0 }
if should_swap {
items[j + 1] = items[j]
j = j - 1
} else {
break
}
}
items[j + 1] = key
}
@value.Value::List(Ref::new(items))
}),
)
// str(x) - convert to string (no quotes on strings)
builtins.set(
"str",
@value.Value::BuiltinFunction("str", fn(pos, _kw) raise {
if pos.length() == 0 {
return @value.Value::String("")
}
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="str() takes at most one argument (\{pos.length()} given)",
span=builtin_span,
)
}
@value.Value::String(value_to_str(pos[0]))
}),
)
// tuple(x?) - create tuple from iterable; tuple() -> ()
builtins.set(
"tuple",
@value.Value::BuiltinFunction("tuple", fn(pos, _kw) raise {
if pos.length() == 0 {
return @value.Value::Tuple(FixedArray::make(0, @value.Value::None))
}
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="tuple() takes at most one argument (\{pos.length()} given)",
span=builtin_span,
)
}
let items = iterable_to_array_by_name(pos[0], "tuple")
@value.Value::Tuple(FixedArray::from_array(items))
}),
)
// type(x) - type name string
builtins.set(
"type",
@value.Value::BuiltinFunction("type", fn(pos, _kw) raise {
guard pos.length() == 1 else {
raise StarlarkError::TypeError(
message="type() takes exactly one argument (\{pos.length()} given)",
span=builtin_span,
)
}
@value.Value::String(pos[0].type_name())
}),
)
// zip(*iterables) - list of tuples, length = shortest input
builtins.set(
"zip",
@value.Value::BuiltinFunction("zip", fn(pos, _kw) raise {
if pos.length() == 0 {
return @value.Value::List(Ref::new([]))
}
let arrays : Array[Array[@value.Value]] = []
for arg in pos {
arrays.push(iterable_to_array_by_name(arg, "zip"))
}
let mut min_len = arrays[0].length()
for i = 1; i < arrays.length(); i = i + 1 {
if arrays[i].length() < min_len {
min_len = arrays[i].length()
}
}
let result : Array[@value.Value] = []
for i = 0; i < min_len; i = i + 1 {
let tuple_items : Array[@value.Value] = []
for j = 0; j < arrays.length(); j = j + 1 {
tuple_items.push(arrays[j][i])
}
result.push(@value.Value::Tuple(FixedArray::from_array(tuple_items)))
}
@value.Value::List(Ref::new(result))
}),
)
builtins
}