///|
fn is_pkl_math_intrinsic_call(
callee : Expr,
bindings : Array[Binding],
stack : Array[String],
) -> Bool {
match callee {
Identifier(name) =>
pkl_math_intrinsic_arity(name) is Some(_) &&
!stack_contains_binding(stack, name) &&
find_binding(bindings, name) is None
_ => false
}
}
///|
fn pkl_math_intrinsic_arity(name : String) -> Int? {
match name {
"_pkl_math_sqrt"
| "_pkl_math_cbrt"
| "_pkl_math_log"
| "_pkl_math_log2"
| "_pkl_math_log10"
| "_pkl_math_exp"
| "_pkl_math_floor"
| "_pkl_math_ceil"
| "_pkl_math_round"
| "_pkl_math_sin"
| "_pkl_math_cos"
| "_pkl_math_tan"
| "_pkl_math_asin"
| "_pkl_math_acos"
| "_pkl_math_is_power_of_two"
| "_pkl_math_atan" => Some(1)
"_pkl_math_pow"
| "_pkl_math_atan2"
| "_pkl_math_gcd"
| "_pkl_math_lcm"
| "_pkl_math_min"
| "_pkl_math_max" => Some(2)
_ => None
}
}
///|
fn pkl_math_numeric_arg(value : Value) -> Double? {
match value {
FloatValue(d) => Some(d)
IntValue(n) => Some(n.to_double())
_ => None
}
}
///|
fn pkl_math_int_arg(value : Value, diagnostics : Array[Diagnostic]) -> Int64? {
match value {
IntValue(n) =>
if n < 0L {
diagnostics.push(
diag("Expected a positive number, but got `\{n.to_string()}`."),
)
None
} else {
Some(n)
}
_ => {
diagnostics.push(
diag(
"Expected value of type `Int`, but got type `\{eval_value_type_name(value)}`. Value: \{render_pcf_value_inline(value)}",
),
)
None
}
}
}
///|
fn int64_gcd(a : Int64, b : Int64) -> Int64 {
let mut x = a
let mut y = b
while y != 0L {
let r = x % y
x = y
y = r
}
if x < 0L {
0L - x
} else {
x
}
}
///|
fn int64_is_power_of_two(n : Int64) -> Bool {
n > 0L && (n & (n - 1L)) == 0L
}
///|
fn double_is_negative_zero(d : Double) -> Bool {
d == 0.0 && (1.0 / d).is_neg_inf()
}
///|
fn pkl_math_minmax(name : String, left : Value, right : Value) -> Value? {
match (left, right) {
(IntValue(a), IntValue(b)) =>
if name == "_pkl_math_min" {
Some(IntValue(if a < b { a } else { b }))
} else {
Some(IntValue(if a > b { a } else { b }))
}
_ => {
let a = match pkl_math_numeric_arg(left) {
Some(v) => v
None => return None
}
let b = match pkl_math_numeric_arg(right) {
Some(v) => v
None => return None
}
if a.is_nan() || b.is_nan() {
return Some(FloatValue(0.0 / 0.0))
}
if name == "_pkl_math_min" {
if a == 0.0 &&
b == 0.0 &&
(double_is_negative_zero(a) || double_is_negative_zero(b)) {
Some(FloatValue(-0.0))
} else {
Some(FloatValue(if a < b { a } else { b }))
}
} else if a == 0.0 && b == 0.0 {
Some(FloatValue(0.0))
} else {
Some(FloatValue(if a > b { a } else { b }))
}
}
}
}
///|
fn eval_pkl_math_intrinsic(
callee : Expr,
arguments : Array[Expr],
bindings : Array[Binding],
env : Array[ValueBinding],
class_env : Array[ClassBinding],
cache : Array[ValueBinding],
stack : Array[String],
declarations : Array[Declaration],
diagnostics : Array[Diagnostic],
resolve_import : (String) -> EvalResult?,
) -> Value? {
let name = match callee {
Identifier(n) => n
_ => return None
}
let arity = match pkl_math_intrinsic_arity(name) {
Some(n) => n
None => return None
}
if arguments.length() != arity {
diagnostics.push(
diag("\{name} expects \{arity} arguments, got \{arguments.length()}"),
)
return None
}
let raw_values : Array[Value] = []
for argument in arguments {
match
eval_expr_with_bindings(
argument, bindings, env, class_env, cache, stack, declarations, diagnostics,
resolve_import,
) {
Some(value) => raw_values.push(value)
None => return None
}
}
let arg_values : Array[Double] = []
if name != "_pkl_math_gcd" &&
name != "_pkl_math_lcm" &&
name != "_pkl_math_is_power_of_two" &&
name != "_pkl_math_min" &&
name != "_pkl_math_max" {
for value in raw_values {
match pkl_math_numeric_arg(value) {
Some(d) => arg_values.push(d)
None => {
diagnostics.push(diag("\{name} expects numeric arguments"))
return None
}
}
}
}
match name {
"_pkl_math_sqrt" => Some(FloatValue(arg_values[0].sqrt()))
"_pkl_math_cbrt" => Some(FloatValue(@math.cbrt(arg_values[0])))
"_pkl_math_pow" => Some(FloatValue(@math.pow(arg_values[0], arg_values[1])))
"_pkl_math_log" => Some(FloatValue(@math.ln(arg_values[0])))
// PKL-148bo: `@math.log2(x)` computes `ln(f)/LN2 + e` after
// `frexp(x) = (f, e)`. The intermediate `1/LN2` multiplication
// loses a couple of ulps on inputs whose `f` is not a clean
// power of two — `log2(2.34)` for example comes out
// `1.2265085298086795` versus Apple Pkl / fdlibm's correctly-
// rounded `1.2265085298086797`. `ln(x) / ln(2.0)` keeps the
// computation in a single division and round-trips through the
// same `ln`, matching the upstream gold for `api/mathModule`.
"_pkl_math_log2" =>
Some(FloatValue(@math.ln(arg_values[0]) / @math.ln(2.0)))
"_pkl_math_log10" => Some(FloatValue(@math.log10(arg_values[0])))
"_pkl_math_exp" => Some(FloatValue(@math.exp(arg_values[0])))
"_pkl_math_floor" => Some(FloatValue(arg_values[0].floor()))
"_pkl_math_ceil" => Some(FloatValue(arg_values[0].ceil()))
"_pkl_math_round" => Some(FloatValue(arg_values[0].round()))
// PKL-148bp: macOS `libm`'s `sin` / `cos` (the path
// `@math.sin` / `@math.cos` take on native) disagree with
// Apple Pkl by 1–2 ulps for inputs like 2.34 because
// Apple's GraalVM native-image binary routes through
// `StrictMath`, itself a port of Sun's fdlibm. Route through
// the small fdlibm kernel + Cody-Waite reduction in
// `eval_math_fdlibm.mbt` so the upstream `api/mathModule`
// gold (`sin(2.34) == 0.7184647930691261`) matches byte for
// byte.
"_pkl_math_sin" => Some(FloatValue(fdlibm_sin(arg_values[0])))
"_pkl_math_cos" => Some(FloatValue(fdlibm_cos(arg_values[0])))
"_pkl_math_tan" => Some(FloatValue(fdlibm_tan(arg_values[0])))
"_pkl_math_asin" => Some(FloatValue(@math.asin(arg_values[0])))
"_pkl_math_acos" => Some(FloatValue(@math.acos(arg_values[0])))
"_pkl_math_atan" => Some(FloatValue(@math.atan(arg_values[0])))
"_pkl_math_atan2" =>
Some(FloatValue(@math.atan2(arg_values[0], arg_values[1])))
"_pkl_math_gcd" => {
let a = match pkl_math_int_arg(raw_values[0], diagnostics) {
Some(n) => n
None => return None
}
let b = match pkl_math_int_arg(raw_values[1], diagnostics) {
Some(n) => n
None => return None
}
Some(IntValue(int64_gcd(a, b)))
}
"_pkl_math_lcm" => {
let a = match pkl_math_int_arg(raw_values[0], diagnostics) {
Some(n) => n
None => return None
}
let b = match pkl_math_int_arg(raw_values[1], diagnostics) {
Some(n) => n
None => return None
}
let g = int64_gcd(a, b)
if a == 0L || b == 0L {
Some(IntValue(0L))
} else {
match checked_int64_mul(a / g, b) {
Some(n) => Some(IntValue(n))
None => {
diagnostics.push(diag("Integer overflow."))
None
}
}
}
}
"_pkl_math_is_power_of_two" =>
match raw_values[0] {
IntValue(n) => Some(BoolValue(int64_is_power_of_two(n)))
FloatValue(d) =>
if d.is_nan() || d.is_inf() || d <= 0.0 {
Some(BoolValue(false))
} else {
let rounded = d.round()
Some(
BoolValue(
rounded == d && @math.pow(2.0, @math.log2(d).round()) == d,
),
)
}
_ => {
diagnostics.push(diag("\{name} expects numeric arguments"))
None
}
}
"_pkl_math_min" | "_pkl_math_max" =>
match pkl_math_minmax(name, raw_values[0], raw_values[1]) {
Some(value) => Some(value)
None => {
diagnostics.push(diag("\{name} expects numeric arguments"))
None
}
}
_ => None
}
}
///|
/// PKL-123: detect `_pkl_semver_` intrinsic calls and report
/// their declared arity. Same routing pattern as `pkl:math`.
fn is_pkl_semver_intrinsic_call(
callee : Expr,
bindings : Array[Binding],
stack : Array[String],
) -> Bool {
match callee {
Identifier(name) =>
pkl_semver_intrinsic_arity(name) is Some(_) &&
!stack_contains_binding(stack, name) &&
find_binding(bindings, name) is None
_ => false
}
}
///|
fn pkl_semver_intrinsic_arity(name : String) -> Int? {
match name {
"_pkl_semver_parse" | "_pkl_semver_parse_or_null" => Some(1)
"_pkl_semver_compare" => Some(2)
_ => None
}
}
///|
fn is_pkl_reflect_intrinsic_call(
callee : Expr,
bindings : Array[Binding],
stack : Array[String],
) -> Bool {
match callee {
Identifier(name) =>
pkl_reflect_intrinsic_arity(name) is Some(_) &&
!stack_contains_binding(stack, name) &&
find_binding(bindings, name) is None
_ => false
}
}
///|
fn pkl_reflect_intrinsic_arity(name : String) -> Int? {
match name {
"_pkl_reflect_type"
| "_pkl_reflect_class"
| "_pkl_reflect_type_alias"
| "_pkl_reflect_module"
| "_pkl_reflect_declared_type"
| "_pkl_reflect_union_type"
| "_pkl_reflect_string_literal_type"
| "_pkl_reflect_type_variable" => Some(1)
_ => None
}
}
///|
/// Parse a SemVer 2.0 string `MAJOR.MINOR.PATCH[-PRE][+BUILD]` into
/// its five components. Numeric core fields must be ASCII digits and
/// non-negative; pre-release / build segments may contain ASCII
/// alphanumerics, dots, and hyphens. Returns None on bad input.
fn pkl_semver_decompose(s : String) -> (Int, Int, Int, String?, String?)? {
let (head, build) = match s.find("+") {
None => (s, None)
Some(idx) =>
(
String::unsafe_substring(s, start=0, end=idx),
Some(String::unsafe_substring(s, start=idx + 1, end=s.length())),
)
}
let (core, pre) = match head.find("-") {
None => (head, None)
Some(idx) =>
(
String::unsafe_substring(head, start=0, end=idx),
Some(String::unsafe_substring(head, start=idx + 1, end=head.length())),
)
}
let parts = core.split(".").collect()
if parts.length() != 3 {
return None
}
let major = parse_semver_int(parts[0].to_owned()) catch { _ => return None }
let minor = parse_semver_int(parts[1].to_owned()) catch { _ => return None }
let patch = parse_semver_int(parts[2].to_owned()) catch { _ => return None }
match pre {
Some(p) => if !is_valid_semver_identifier_list(p) { return None }
None => ()
}
match build {
Some(b) => if !is_valid_semver_identifier_list(b) { return None }
None => ()
}
Some((major, minor, patch, pre, build))
}
///|
fn parse_semver_int(s : String) -> Int raise {
if s.length() == 0 {
fail("empty numeric segment")
}
if s.length() > 1 && s[0].to_int().unsafe_to_char() == '0' {
fail("leading zero in numeric segment")
}
let mut acc = 0
for i in 0.. '9' {
fail("non-digit in numeric segment")
}
acc = acc * 10 + (c.to_int() - '0'.to_int())
}
acc
}
///|
fn is_valid_semver_identifier_list(s : String) -> Bool {
if s.length() == 0 {
return false
}
for part in s.split(".") {
if part.length() == 0 {
return false
}
for c in part.iter() {
let alpha = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
let digit = c >= '0' && c <= '9'
if !alpha && !digit && c != '-' {
return false
}
}
}
true
}
///|
fn semver_value(
major : Int,
minor : Int,
patch : Int,
pre : String?,
build : String?,
) -> Value {
let members : Array[ValueMember] = [
{
name: "major",
value: IntValue(major.to_int64()),
source: None,
annotations: [],
},
{
name: "minor",
value: IntValue(minor.to_int64()),
source: None,
annotations: [],
},
{
name: "patch",
value: IntValue(patch.to_int64()),
source: None,
annotations: [],
},
{
name: hidden_member_name("__semver_version"),
value: BoolValue(true),
source: None,
annotations: [],
},
]
match pre {
Some(p) =>
members.push({
name: "preRelease",
value: StringValue(p),
source: None,
annotations: [],
})
None => ()
}
match build {
Some(b) =>
members.push({
name: "build",
value: StringValue(b),
source: None,
annotations: [],
})
None => ()
}
ObjectValue(members)
}
///|
/// SemVer pre-release ordering: dot-separated identifiers compared
/// pairwise. Numeric identifiers compare numerically; alphanumeric
/// identifiers compare lexicographically; numeric < alphanumeric;
/// fewer identifiers < more identifiers (left-aligned).
fn compare_semver_prerelease(a : String?, b : String?) -> Int {
match (a, b) {
(None, None) => 0
// Version with pre-release ranks below version without pre-release.
(Some(_), None) => -1
(None, Some(_)) => 1
(Some(left), Some(right)) => {
let left_parts = left.split(".").collect()
let right_parts = right.split(".").collect()
let len = if left_parts.length() < right_parts.length() {
left_parts.length()
} else {
right_parts.length()
}
for i in 0.. right_parts.length() {
1
} else {
0
}
}
}
}
///|
fn is_numeric_identifier(s : String) -> Bool {
if s.length() == 0 {
return false
}
for c in s.iter() {
if c < '0' || c > '9' {
return false
}
}
true
}
///|
fn compare_semver_identifier(a : String, b : String) -> Int {
let a_num = is_numeric_identifier(a)
let b_num = is_numeric_identifier(b)
if a_num && b_num {
let ai = parse_semver_int(a) catch { _ => return 0 }
let bi = parse_semver_int(b) catch { _ => return 0 }
if ai < bi {
-1
} else if ai > bi {
1
} else {
0
}
} else if a_num && !b_num {
-1
} else if !a_num && b_num {
1
} else {
a.lexical_compare(b)
}
}
///|
fn compare_semver_values(a : Value, b : Value) -> Int? {
let (a_major, a_minor, a_patch, a_pre) = match extract_semver_core(a) {
Some(t) => t
None => return None
}
let (b_major, b_minor, b_patch, b_pre) = match extract_semver_core(b) {
Some(t) => t
None => return None
}
if a_major != b_major {
return Some(if a_major < b_major { -1 } else { 1 })
}
if a_minor != b_minor {
return Some(if a_minor < b_minor { -1 } else { 1 })
}
if a_patch != b_patch {
return Some(if a_patch < b_patch { -1 } else { 1 })
}
Some(compare_semver_prerelease(a_pre, b_pre))
}
///|
fn extract_semver_core(v : Value) -> (Int, Int, Int, String?)? {
match v {
ObjectValue(members) => {
let major = match find_int_member(members, "major") {
Some(n) => n
None => return None
}
let minor = match find_int_member(members, "minor") {
Some(n) => n
None => return None
}
let patch = match find_int_member(members, "patch") {
Some(n) => n
None => return None
}
let pre = match find_member(members, "preRelease") {
Some(StringValue(s)) => Some(s)
Some(NullValue) | None => None
Some(_) => return None
}
Some((major, minor, patch, pre))
}
_ => None
}
}
///|
fn is_semver_value_members(members : Array[ValueMember]) -> Bool {
match lookup_member(members, "__semver_version") {
Some(BoolValue(true)) => true
_ => false
}
}
///|
fn semver_build_from_members(members : Array[ValueMember]) -> String? {
match find_member(members, "build") {
Some(StringValue(s)) => Some(s)
_ => None
}
}
///|
fn semver_to_string(members : Array[ValueMember]) -> String? {
let major = match find_int_member(members, "major") {
Some(n) => n
None => return None
}
let minor = match find_int_member(members, "minor") {
Some(n) => n
None => return None
}
let patch = match find_int_member(members, "patch") {
Some(n) => n
None => return None
}
let mut out = "\{major}.\{minor}.\{patch}"
match find_member(members, "preRelease") {
Some(StringValue(s)) => out = out + "-" + s
_ => ()
}
match semver_build_from_members(members) {
Some(s) => out = out + "+" + s
None => ()
}
Some(out)
}
///|
fn is_semver_version_method_name(name : String) -> Bool {
name == "toString" ||
name == "equals" ||
name == "isLessThan" ||
name == "isLessThanOrEquals" ||
name == "isGreaterThan" ||
name == "isGreaterThanOrEquals" ||
name == "isNormal" ||
name == "isStable"
}
///|
fn eval_semver_version_method(
receiver_members : Array[ValueMember],
method_name : String,
arguments : Array[Expr],
bindings : Array[Binding],
env : Array[ValueBinding],
class_env : Array[ClassBinding],
cache : Array[ValueBinding],
stack : Array[String],
declarations : Array[Declaration],
diagnostics : Array[Diagnostic],
resolve_import : (String) -> EvalResult?,
) -> Value? {
if !is_semver_value_members(receiver_members) {
return None
}
if method_name == "toString" {
if arguments.length() != 0 {
diagnostics.push(diag("Version.toString takes no arguments"))
return None
}
match semver_to_string(receiver_members) {
Some(text) => return Some(StringValue(text))
None => return None
}
}
if method_name == "isNormal" || method_name == "isStable" {
if arguments.length() != 0 {
diagnostics.push(diag("Version.\{method_name} takes no arguments"))
return None
}
let pre = match find_member(receiver_members, "preRelease") {
Some(StringValue(_)) => true
_ => false
}
let build = match find_member(receiver_members, "build") {
Some(StringValue(_)) => true
_ => false
}
if method_name == "isNormal" {
return Some(BoolValue(!pre && !build))
}
let major = match find_int_member(receiver_members, "major") {
Some(n) => n
None => return Some(BoolValue(false))
}
return Some(BoolValue(major > 0 && !pre))
}
if arguments.length() != 1 {
diagnostics.push(diag("Version.\{method_name} expects 1 argument"))
return None
}
let other = match
eval_expr_with_bindings(
arguments[0],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(value) => value
None => return None
}
let cmp = match compare_semver_values(ObjectValue(receiver_members), other) {
Some(n) => n
None => {
diagnostics.push(
diag("Version.\{method_name} expects a Version argument"),
)
return None
}
}
match method_name {
"equals" => Some(BoolValue(cmp == 0))
"isLessThan" => Some(BoolValue(cmp < 0))
"isLessThanOrEquals" => Some(BoolValue(cmp <= 0))
"isGreaterThan" => Some(BoolValue(cmp > 0))
"isGreaterThanOrEquals" => Some(BoolValue(cmp >= 0))
_ => None
}
}
///|
fn find_member(members : Array[ValueMember], name : String) -> Value? {
for m in members {
if m.name == name {
return Some(m.value)
}
}
None
}
///|
fn find_int_member(members : Array[ValueMember], name : String) -> Int? {
match find_member(members, name) {
Some(IntValue(n)) => Some(n.to_int())
_ => None
}
}
///|
fn eval_pkl_semver_intrinsic(
callee : Expr,
arguments : Array[Expr],
bindings : Array[Binding],
env : Array[ValueBinding],
class_env : Array[ClassBinding],
cache : Array[ValueBinding],
stack : Array[String],
declarations : Array[Declaration],
diagnostics : Array[Diagnostic],
resolve_import : (String) -> EvalResult?,
) -> Value? {
let name = match callee {
Identifier(n) => n
_ => return None
}
let arity = match pkl_semver_intrinsic_arity(name) {
Some(n) => n
None => return None
}
if arguments.length() != arity {
diagnostics.push(
diag("\{name} expects \{arity} arguments, got \{arguments.length()}"),
)
return None
}
let arg_values : Array[Value] = []
for argument in arguments {
match
eval_expr_with_bindings(
argument, bindings, env, class_env, cache, stack, declarations, diagnostics,
resolve_import,
) {
Some(v) => arg_values.push(v)
None => return None
}
}
match name {
"_pkl_semver_parse" => {
let s = match arg_values[0] {
StringValue(s) => s
_ => {
diagnostics.push(diag("semver.parse expects a String argument"))
return None
}
}
match pkl_semver_decompose(s) {
Some((major, minor, patch, pre, build)) =>
Some(semver_value(major, minor, patch, pre, build))
None => {
diagnostics.push(
diag("`\{s}` is not a valid semantic version number."),
)
None
}
}
}
"_pkl_semver_parse_or_null" => {
let s = match arg_values[0] {
StringValue(s) => s
_ => {
diagnostics.push(diag("semver.parseOrNull expects a String argument"))
return None
}
}
match pkl_semver_decompose(s) {
Some((major, minor, patch, pre, build)) =>
Some(semver_value(major, minor, patch, pre, build))
None => Some(NullValue)
}
}
"_pkl_semver_compare" =>
match compare_semver_values(arg_values[0], arg_values[1]) {
Some(n) => Some(IntValue(n.to_int64()))
None => {
diagnostics.push(
diag("semver.compare expects two parsed Version objects"),
)
None
}
}
_ => None
}
}
///|
fn eval_pkl_reflect_intrinsic(
callee : Expr,
arguments : Array[Expr],
bindings : Array[Binding],
env : Array[ValueBinding],
class_env : Array[ClassBinding],
cache : Array[ValueBinding],
stack : Array[String],
declarations : Array[Declaration],
diagnostics : Array[Diagnostic],
resolve_import : (String) -> EvalResult?,
) -> Value? {
let name = match callee {
Identifier(n) => n
_ => return None
}
let arity = match pkl_reflect_intrinsic_arity(name) {
Some(n) => n
None => return None
}
if arguments.length() != arity {
diagnostics.push(
diag("\{name} expects \{arity} arguments, got \{arguments.length()}"),
)
return None
}
let arg = match
eval_expr_with_bindings(
arguments[0],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(v) => v
None => return None
}
match name {
"_pkl_reflect_type" =>
match arg {
StringValue(type_name) => Some(reflect_type_value(type_name, []))
_ => {
diagnostics.push(diag("reflect type constructor expects a String"))
None
}
}
"_pkl_reflect_class" =>
if arg is ObjectValue(_) {
Some(
reflect_class_gold_factory(
arg, bindings, env, class_env, declarations, cache, resolve_import,
),
)
} else {
Some(reflect_class_factory_value(arg))
}
"_pkl_reflect_type_alias" =>
Some(reflect_type_alias_gold_factory(arg, declarations, cache))
"_pkl_reflect_module" => Some(reflect_module_factory_value(arg))
"_pkl_reflect_declared_type" => Some(reflect_declared_type_value(arg, []))
"_pkl_reflect_union_type" =>
match arg {
ListingValue(types) | ListValue(types) =>
Some(reflect_union_type_value(types))
_ => {
diagnostics.push(diag("reflect.UnionType expects a List argument"))
None
}
}
"_pkl_reflect_string_literal_type" =>
match arg {
StringValue(s) => Some(reflect_string_literal_type_value(s))
_ => {
diagnostics.push(
diag("reflect.StringLiteralType expects a String argument"),
)
None
}
}
"_pkl_reflect_type_variable" =>
match arg {
ObjectValue(members) =>
match lookup_member(members, "name") {
Some(StringValue(name)) => Some(reflect_type_variable_value(name))
_ => {
diagnostics.push(
diag("reflect.TypeVariable expects a type parameter"),
)
None
}
}
StringValue(name) => Some(reflect_type_variable_value(name))
_ => {
diagnostics.push(
diag("reflect.TypeVariable expects a type parameter"),
)
None
}
}
_ => None
}
}
///|
/// PKL-148bo: `_pkl_analyze_import_graph(Set)` builds the
/// `pkl:analyze.importGraph` result by BFS-walking the per-module
/// adjacency lists the CLI registered during the load step. The
/// intrinsic is library-side (no file IO) — every URI it consults
/// must already sit in `sandbox_module_imports`. The CLI's
/// `load_path` pre-walks each `import "..."` declaration of the
/// program being evaluated, so any URI passed in here through a
/// transitive import is covered; URIs passed in as bare strings
/// (e.g. `Set("package://...")`) get an entry too because
/// `load_path` registers every module URI it sees.
fn is_pkl_analyze_intrinsic_call(
callee : Expr,
bindings : Array[Binding],
stack : Array[String],
) -> Bool {
match callee {
Identifier("_pkl_analyze_import_graph") =>
!stack_contains_binding(stack, "_pkl_analyze_import_graph") &&
find_binding(bindings, "_pkl_analyze_import_graph") is None
_ => false
}
}
///|
fn is_pkl_evaluator_settings_intrinsic_call(
callee : Expr,
bindings : Array[Binding],
stack : Array[String],
) -> Bool {
match callee {
Identifier("_pkl_evaluator_settings_resolve") =>
!stack_contains_binding(stack, "_pkl_evaluator_settings_resolve") &&
find_binding(bindings, "_pkl_evaluator_settings_resolve") is None
_ => false
}
}
///|
fn is_pkl_ref_intrinsic_call(
callee : Expr,
bindings : Array[Binding],
stack : Array[String],
) -> Bool {
match callee {
Identifier(name) =>
(
name == "_pkl_ref_get_domain" ||
name == "_pkl_ref_get_data" ||
name == "_pkl_ref_get_path" ||
name == "_pkl_ref_to_string"
) &&
!stack_contains_binding(stack, name) &&
find_binding(bindings, name) is None
_ => false
}
}
///|
fn is_pkl_ref_constructor_call(
callee : Expr,
env : Array[ValueBinding],
cache : Array[ValueBinding],
) -> Bool {
let module_alias = match callee {
MemberAccess(Identifier(name), "Reference") => name
_ => return false
}
let target = match lookup_value(env, module_alias) {
Some(value) => Some(value)
None => lookup_value(cache, module_alias)
}
match target {
Some(ObjectValue(members)) =>
module_members_path(members) is Some("pkl:ref")
_ => false
}
}
///|
fn eval_pkl_ref_constructor(
arguments : Array[Expr],
bindings : Array[Binding],
env : Array[ValueBinding],
class_env : Array[ClassBinding],
cache : Array[ValueBinding],
stack : Array[String],
declarations : Array[Declaration],
diagnostics : Array[Diagnostic],
resolve_import : (String) -> EvalResult?,
) -> Value? {
if arguments.length() != 3 {
diagnostics.push(
diag("ref.Reference expects 3 arguments, got \{arguments.length()}"),
)
return None
}
let values : Array[Value] = []
for argument in arguments {
match
eval_expr_with_bindings(
argument, bindings, env, class_env, cache, stack, declarations, diagnostics,
resolve_import,
) {
Some(value) => values.push(value)
None => return None
}
}
let type_name = match class_name_from_mirror_value(values[1]) {
Some(name) => reference_normalize_type(name, class_env, declarations, cache)
None => {
diagnostics.push(
diag("ref.Reference expects a Class as its second argument"),
)
return None
}
}
Some(reference_value(values[0], type_name, values[2], []))
}
///|
fn eval_pkl_ref_intrinsic(
callee : Expr,
arguments : Array[Expr],
bindings : Array[Binding],
env : Array[ValueBinding],
class_env : Array[ClassBinding],
cache : Array[ValueBinding],
stack : Array[String],
declarations : Array[Declaration],
diagnostics : Array[Diagnostic],
resolve_import : (String) -> EvalResult?,
) -> Value? {
let name = match callee {
Identifier(name) => name
_ => return None
}
if arguments.length() != 1 {
diagnostics.push(diag("\{name} expects 1 argument"))
return None
}
let reference = match
eval_expr_with_bindings(
arguments[0],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(ObjectValue(members)) if is_reference_value_members(members) => members
Some(value) => {
diagnostics.push(
diag(
"\{name} expects a ref.Reference, got \{eval_value_type_name(value)}: \{render_pcf_value_inline(value)}",
),
)
return None
}
None => return None
}
match name {
"_pkl_ref_get_domain" => reference_hidden_value(reference, "domain")
"_pkl_ref_get_data" => reference_hidden_value(reference, "data")
"_pkl_ref_get_path" => reference_hidden_value(reference, "path")
"_pkl_ref_to_string" =>
eval_reference_to_string(
reference, bindings, env, class_env, cache, stack, declarations, diagnostics,
resolve_import,
)
_ => None
}
}
///|
fn evaluator_path_join(parts : Array[String], separator : String) -> String {
let out = StringBuilder::new()
for i, part in parts {
if i > 0 {
out.write_string(separator)
}
out.write_string(part)
}
out.to_string()
}
///|
fn evaluator_normalize_segments(path : String) -> Array[String] {
let resolved : Array[String] = []
for part in path.split("/") {
if part == "" || part == "." {
continue
}
if part == ".." {
if resolved.length() > 0 {
let _ = resolved.pop()
}
continue
}
resolved.push(part.to_owned())
}
resolved
}
///|
fn evaluator_normalize_posix_path(path : String) -> String {
let absolute = path.has_prefix("/")
let parts = evaluator_normalize_segments(path)
let body = evaluator_path_join(parts, "/")
if absolute {
if body == "" {
"/"
} else {
"/" + body
}
} else {
body
}
}
///|
fn evaluator_windows_drive_path(path : String) -> Bool {
path.length() >= 2 && path[1] == ':'
}
///|
fn evaluator_normalize_windows_path(raw : String) -> String {
let path = raw.replace_all(old="\\", new="/")
let normalized = if path.has_prefix("//") {
let rest = String::unsafe_substring(path, start=2, end=path.length())
let raw_parts = rest.split("/").collect()
let server = if raw_parts.length() > 0 {
raw_parts[0].to_owned()
} else {
""
}
let share = if raw_parts.length() > 1 {
raw_parts[1].to_owned()
} else {
""
}
let parts : Array[String] = []
for i = 2; i < raw_parts.length(); i = i + 1 {
let part = raw_parts[i]
if part == "" || part == "." {
continue
}
if part == ".." {
if parts.length() > 0 {
let _ = parts.pop()
}
} else {
parts.push(part.to_owned())
}
}
let root = "//" + server + "/" + share
if parts.length() == 0 {
root + "/"
} else {
root + "/" + evaluator_path_join(parts, "/")
}
} else if evaluator_windows_drive_path(path) {
let drive = String::unsafe_substring(path, start=0, end=2)
let absolute = path.length() >= 3 && path[2] == '/'
let rest = if absolute {
String::unsafe_substring(path, start=3, end=path.length())
} else {
String::unsafe_substring(path, start=2, end=path.length())
}
let parts = evaluator_normalize_segments(rest)
if parts.length() == 0 {
if absolute {
drive + "/"
} else {
drive
}
} else if absolute {
drive + "/" + evaluator_path_join(parts, "/")
} else {
drive + evaluator_path_join(parts, "/")
}
} else {
let absolute = path.has_prefix("/")
let parts = evaluator_normalize_segments(path)
let body = evaluator_path_join(parts, "/")
if absolute {
if body == "" {
"/"
} else {
"/" + body
}
} else {
body
}
}
normalized.replace_all(old="/", new="\\")
}
///|
fn evaluator_file_path_from_uri(uri : String, windows : Bool) -> String {
let decoded = sandbox_percent_decode(uri)
if decoded.has_prefix("file://") {
let rest = String::unsafe_substring(
decoded,
start="file://".length(),
end=decoded.length(),
)
if windows {
if rest.has_prefix("/") {
if rest.length() >= 3 && rest[2] == ':' {
String::unsafe_substring(rest, start=1, end=rest.length())
} else {
rest
}
} else {
"//" + rest
}
} else if rest.has_prefix("/") {
rest
} else {
"/" + rest
}
} else if decoded.has_prefix("file:") {
String::unsafe_substring(
decoded,
start="file:".length(),
end=decoded.length(),
)
} else {
decoded
}
}
///|
fn evaluator_path_dirname(path : String) -> String {
let normalized = path.replace_all(old="\\", new="/")
if normalized.has_suffix("/") {
normalized
} else {
match normalized.rev_find("/") {
Some(index) =>
if index == 0 {
"/"
} else {
String::unsafe_substring(normalized, start=0, end=index)
}
None => if evaluator_windows_drive_path(normalized) { "/" } else { "" }
}
}
}
///|
fn evaluator_windows_unc_root(path : String) -> String? {
if !path.has_prefix("//") {
return None
}
let rest = String::unsafe_substring(path, start=2, end=path.length())
let parts = rest.split("/").collect()
if parts.length() == 0 {
Some("//")
} else if parts.length() == 1 {
Some("//" + parts[0].to_owned() + "/")
} else {
Some("//" + parts[0].to_owned() + "/" + parts[1].to_owned())
}
}
///|
fn evaluator_resolve_path(
base_uri : String,
raw_path : String,
windows : Bool,
) -> String {
let base_path = evaluator_file_path_from_uri(base_uri, windows)
let base_dir = evaluator_path_dirname(base_path)
if !windows {
let combined = if raw_path.has_prefix("/") {
raw_path
} else if base_dir.has_suffix("/") || base_dir == "" {
base_dir + raw_path
} else {
base_dir + "/" + raw_path
}
return evaluator_normalize_posix_path(combined)
}
let path = raw_path.replace_all(old="\\", new="/")
let combined = if path.has_prefix("//") || evaluator_windows_drive_path(path) {
path
} else if path.has_prefix("/") {
match evaluator_windows_unc_root(base_dir) {
Some(root) => root + path
None if evaluator_windows_drive_path(base_dir) =>
String::unsafe_substring(base_dir, start=0, end=2) + path
None if base_path == "C:" => "/" + path
None => path
}
} else if base_dir.has_suffix("/") || base_dir == "" {
base_dir + path
} else {
base_dir + "/" + path
}
evaluator_normalize_windows_path(combined)
}
///|
fn evaluator_replace_value_member(
value_member : ValueMember,
value : Value,
) -> ValueMember {
{
name: value_member.name,
value,
source: value_member.source,
annotations: value_member.annotations,
}
}
///|
fn evaluator_resolve_reader(
value : Value,
base_uri : String,
windows : Bool,
) -> Value {
match value {
ObjectValue(members) => {
let resolved : Array[ValueMember] = []
let mut saw_working_dir = false
for value_member in members {
if value_member.name == "executable" {
let next = match value_member.value {
StringValue(path) =>
if path.contains("/") || path.contains("\\") {
StringValue(evaluator_resolve_path(base_uri, path, windows))
} else {
value_member.value
}
_ => value_member.value
}
resolved.push(evaluator_replace_value_member(value_member, next))
} else if value_member.name == "workingDir" {
saw_working_dir = true
let path = match value_member.value {
StringValue(path) => path
_ => "./"
}
resolved.push(
evaluator_replace_value_member(
value_member,
StringValue(evaluator_resolve_path(base_uri, path, windows)),
),
)
} else {
resolved.push(value_member)
}
}
if !saw_working_dir {
resolved.push({
name: "workingDir",
value: StringValue(evaluator_resolve_path(base_uri, "./", windows)),
source: None,
annotations: [],
})
}
ObjectValue(resolved)
}
_ => value
}
}
///|
fn evaluator_resolve_readers(
value : Value,
base_uri : String,
windows : Bool,
) -> Value {
let resolve_entries = fn(entries : Array[ValueEntry]) -> Array[ValueEntry] {
let next : Array[ValueEntry] = []
for entry in entries {
next.push({
key: entry.key,
value: evaluator_resolve_reader(entry.value, base_uri, windows),
})
}
next
}
match value {
MappingValue(entries) => MappingValue(resolve_entries(entries))
DefaultedMappingValue(_, entries, _) =>
MappingValue(resolve_entries(entries))
MapValue(entries) => MappingValue(resolve_entries(entries))
_ => value
}
}
///|
fn evaluator_resolve_settings(
value : Value,
base_uri : String,
windows : Bool,
) -> Value? {
match value {
ObjectValue(members) => {
let resolved : Array[ValueMember] = []
for value_member in members {
let next = match (value_member.name, value_member.value) {
("modulePath", ListingValue(paths)) => {
let values : Array[Value] = []
for item in paths {
match item {
StringValue(path) =>
values.push(
StringValue(evaluator_resolve_path(base_uri, path, windows)),
)
_ => values.push(item)
}
}
ListingValue(values)
}
("modulePath", DefaultedListingValue(_, paths, _)) => {
let values : Array[Value] = []
for item in paths {
match item {
StringValue(path) =>
values.push(
StringValue(evaluator_resolve_path(base_uri, path, windows)),
)
_ => values.push(item)
}
}
ListingValue(values)
}
("moduleCacheDir", StringValue(path))
| ("rootDir", StringValue(path)) =>
StringValue(evaluator_resolve_path(base_uri, path, windows))
("externalModuleReaders", readers)
| ("externalResourceReaders", readers) =>
evaluator_resolve_readers(readers, base_uri, windows)
_ => value_member.value
}
resolved.push(evaluator_replace_value_member(value_member, next))
}
Some(ObjectValue(resolved))
}
_ => None
}
}
///|
fn eval_pkl_evaluator_settings_intrinsic(
callee : Expr,
arguments : Array[Expr],
bindings : Array[Binding],
env : Array[ValueBinding],
class_env : Array[ClassBinding],
cache : Array[ValueBinding],
stack : Array[String],
declarations : Array[Declaration],
diagnostics : Array[Diagnostic],
resolve_import : (String) -> EvalResult?,
) -> Value? {
let name = match callee {
Identifier(name) => name
_ => return None
}
if arguments.length() != 3 {
diagnostics.push(
diag("\{name} expects 3 arguments, got \{arguments.length()}"),
)
return None
}
let values : Array[Value] = []
for argument in arguments {
match
eval_expr_with_bindings(
argument, bindings, env, class_env, cache, stack, declarations, diagnostics,
resolve_import,
) {
Some(value) => values.push(value)
None => return None
}
}
match (values[1], values[2]) {
(StringValue(base_uri), BoolValue(windows)) =>
match evaluator_resolve_settings(values[0], base_uri, windows) {
Some(value) => Some(value)
None => {
diagnostics.push(
diag("EvaluatorSettings.resolveForOs expects settings"),
)
None
}
}
_ => {
diagnostics.push(
diag("EvaluatorSettings.resolveForOs expects a URI and OS"),
)
None
}
}
}
///|
fn eval_pkl_analyze_intrinsic(
callee : Expr,
arguments : Array[Expr],
bindings : Array[Binding],
env : Array[ValueBinding],
class_env : Array[ClassBinding],
cache : Array[ValueBinding],
stack : Array[String],
declarations : Array[Declaration],
diagnostics : Array[Diagnostic],
resolve_import : (String) -> EvalResult?,
) -> Value? {
let name = match callee {
Identifier(n) => n
_ => return None
}
ignore(name)
if arguments.length() != 1 {
diagnostics.push(
diag(
"_pkl_analyze_import_graph expects 1 argument, got \{arguments.length()}",
),
)
return None
}
let arg = match
eval_expr_with_bindings(
arguments[0],
bindings,
env,
class_env,
cache,
stack,
declarations,
diagnostics,
resolve_import,
) {
Some(v) => v
None => return None
}
let seed_uris : Array[String] = []
let push_seed = fn(value : Value) -> Unit {
match value {
StringValue(s) => seed_uris.push(analyze_resolve_alias(s))
_ => ()
}
}
match arg {
SetValue(elements) =>
for v in elements {
push_seed(v)
}
ListingValue(elements)
| DefaultedListingValue(_, elements, _)
| ListValue(elements) =>
for v in elements {
push_seed(v)
}
StringValue(_) => push_seed(arg)
_ =>
diagnostics.push(
diag("_pkl_analyze_import_graph expects a Set / Listing of String URIs"),
)
}
// BFS walk via the sandbox-registered adjacency map. Cycle-safe via
// a "visited" set; missing URIs (i.e. not registered by the CLI's
// load step) emit an empty children list to keep the graph closed.
let visited : Array[String] = []
let imports_keys : Array[String] = []
let imports_lists : Array[Array[String]] = []
let queue : Array[String] = []
for uri in seed_uris {
queue.push(uri)
}
let mut head = 0
while head < queue.length() {
let uri = queue[head]
head = head + 1
if analyze_seen(visited, uri) {
continue
}
visited.push(uri)
let children = match sandbox_module_imports(uri) {
Some(list) => list
None => []
}
imports_keys.push(uri)
imports_lists.push(children)
for child in children {
if !analyze_seen(visited, child) && !analyze_seen(queue, child) {
queue.push(child)
}
}
}
// Sort the (key, list) pairs lexicographically — Apple Pkl renders
// `imports` keys in alphabetical order even when the BFS discovery
// order differs.
let order = analyze_sort_keys(imports_keys)
let imports_entries : Array[ValueEntry] = []
let resolved_entries : Array[ValueEntry] = []
for idx in order {
let key = imports_keys[idx]
let children = imports_lists[idx]
let import_objects : Array[Value] = []
for child in children {
import_objects.push(
ObjectValue([
{
name: "uri",
value: StringValue(child),
source: None,
annotations: [],
},
]),
)
}
imports_entries.push({
key: StringValue(key),
value: ListingValue(import_objects),
})
resolved_entries.push({ key: StringValue(key), value: StringValue(key) })
}
Some(
ObjectValue([
{
name: "imports",
value: MappingValue(imports_entries),
source: None,
annotations: [],
},
{
name: "resolvedImports",
value: MappingValue(resolved_entries),
source: None,
annotations: [],
},
]),
)
}
///|
fn analyze_resolve_alias(raw : String) -> String {
match sandbox_module_alias(raw) {
Some(resolved) => resolved
None => raw
}
}
///|
fn analyze_seen(seen : Array[String], uri : String) -> Bool {
for s in seen {
if s == uri {
return true
}
}
false
}
///|
fn analyze_sort_keys(keys : Array[String]) -> Array[Int] {
let n = keys.length()
let order : Array[Int] = []
for i in 0..= 0 && keys[order[j]] > keys[cur] {
order[j + 1] = order[j]
j = j - 1
}
order[j + 1] = cur
}
order
}