///|
pub struct AnalysisSession {
priv db : @ripple.Database
priv sources : @ripple.Input[String, String]
priv types : @ripple.CycleQuery[String, TypecheckResult]
priv evals : @ripple.Query[String, EvalResult]
priv counters : AnalysisCounters
}
///|
priv struct AnalysisCounters {
mut parse_count : Int
mut typecheck_count : Int
mut eval_count : Int
}
///|
pub(all) struct AnalysisStats {
parse_count : Int
typecheck_count : Int
eval_count : Int
} derive(Eq, Debug)
///|
pub fn AnalysisSession::new() -> AnalysisSession {
let db = @ripple.Database::new()
let rt = db.runtime()
let sources : @ripple.Input[String, String] = db.input()
sources.register(rt)
let counters = AnalysisCounters::{
parse_count: 0,
typecheck_count: 0,
eval_count: 0,
}
let parses : @ripple.Query[String, ParseResult?] = db.query(fn(rt, path) {
counters.parse_count = counters.parse_count + 1
match get_module_type_source(sources, rt, path) {
Some(source) => Some(parse_source(source))
None => None
}
})
parses.register(rt)
let types_ref : Ref[@ripple.CycleQuery[String, TypecheckResult]?] = {
val: None,
}
let types : @ripple.CycleQuery[String, TypecheckResult] = db.cycle_query_with_recover(
fn(rt, path) {
counters.typecheck_count = counters.typecheck_count + 1
typecheck_module_path(parses, types_ref, rt, path)
},
fn(_rt, path) { TypeError([diag("cyclic import \{path}")]) },
)
types_ref.val = Some(types)
types.register(rt)
let evals : @ripple.Query[String, EvalResult] = db.query(fn(rt, path) {
counters.eval_count = counters.eval_count + 1
eval_module_path(sources, rt, path, [])
})
evals.register(rt)
AnalysisSession::{ db, sources, types, evals, counters }
}
///|
pub fn AnalysisSession::set_source(
self : AnalysisSession,
path : String,
source : String,
) -> Unit {
ignore(self.sources.set(self.db.runtime(), path, source))
}
///|
pub fn AnalysisSession::typecheck_path(
self : AnalysisSession,
path : String,
) -> TypecheckResult {
self.types.fetch(self.db.runtime(), path)
}
///|
pub fn AnalysisSession::eval_path(
self : AnalysisSession,
path : String,
) -> EvalResult {
// PKL-118: parallel to `eval_source`'s filter — strip the
// hidden-prefixed function members the evaluator adds for cross-
// module dispatch. The session's cached EvalResult keeps the full
// (with-hidden) shape so subsequent import lookups still see the
// function exports; only this returning boundary strips them. The
// recursive helper also clears the `@hidden$__class` tag that
// universal class tagging now writes (PKL-148bh).
match self.evals.fetch(self.db.runtime(), path) {
EvalOk(value) => EvalOk(strip_invisible_recursive(value))
other => other
}
}
///|
/// Evaluate a module while retaining hidden runtime metadata such as class
/// tags and reflection kinds. Renderers and CLI output projection need these
/// markers; callers displaying ordinary Pkl values should use `eval_path`.
pub fn AnalysisSession::eval_path_with_runtime_metadata(
self : AnalysisSession,
path : String,
) -> EvalResult {
self.evals.fetch(self.db.runtime(), path)
}
///|
pub fn AnalysisSession::reset_stats(self : AnalysisSession) -> Unit {
self.counters.parse_count = 0
self.counters.typecheck_count = 0
self.counters.eval_count = 0
}
///|
pub fn AnalysisSession::stats(self : AnalysisSession) -> AnalysisStats {
AnalysisStats::{
parse_count: self.counters.parse_count,
typecheck_count: self.counters.typecheck_count,
eval_count: self.counters.eval_count,
}
}
///|
fn stack_contains(stack : Array[String], path : String) -> Bool {
for item in stack {
if item == path {
return true
}
}
false
}
///|
fn push_stack(stack : Array[String], path : String) -> Array[String] {
let next : Array[String] = []
for item in stack {
next.push(item)
}
next.push(path)
next
}
///|
fn resolve_import_path(from : String, uri : String) -> String {
if uri == "..." {
match lookup_triple_dot_resolution(from, uri) {
Some(resolved) => return resolved
None => return uri
}
}
// PKL-148r: triple-dot import — the CLI loader's FS-aware walk has
// already cached the resolution into the sandbox registry. Consult
// it here so the analysis layer can produce the same absolute path
// without re-doing FS access. Falling through to the bare uri keeps
// the existing "Cannot find module" diagnostic shape for the case
// where the CLI never registered a resolution (e.g., embedder
// bypassing the CLI loader).
if uri.has_prefix(".../") {
match lookup_triple_dot_resolution(from, uri) {
Some(resolved) => return resolved
None => return uri
}
}
// PKL-148bb: `modulepath:/` — consult the same cache the CLI
// populates when it walks the importer's directory for a basename
// match (basic/import1b).
if uri.has_prefix("modulepath:/") {
match lookup_triple_dot_resolution(from, uri) {
Some(resolved) => return resolved
None => return uri
}
}
// PKL-148ad: `@/` project-dependency imports — the CLI
// loader resolves the local dep via `PklProject.deps.json` and
// registers the absolute path in the same sandbox cache the triple-
// dot path uses. Consult it here so eval-side resolution lands on
// the same file the loader read. Falling through to the bare uri
// keeps the existing "Cannot find module" diagnostic for the case
// where the CLI never registered a resolution (e.g. remote-type dep
// — PKL-129 package download still deferred).
if uri.has_prefix("@") {
match lookup_triple_dot_resolution(from, uri) {
Some(resolved) => return resolved
None => return uri
}
}
if uri.has_prefix("/") || uri.find(":") is Some(_) {
return normalize_path_segments(uri)
}
let combined = match from.rev_find("/") {
Some(idx) => String::unsafe_substring(from, start=0, end=idx + 1) + uri
None => uri
}
// PKL-152: collapse `..` / `.` segments so two imports that name the
// same file via different paths normalize to the same string. Cycle
// detection in `eval_module_path` matches on the resolved path, so
// the previous lexical (parent + uri) form let recursive imports
// bypass the guard and recurse until the path string ballooned past
// the FS lookup ceiling.
normalize_path_segments(combined)
}
///|
/// PKL-152: walk `..` / `.` segments out of a slash-separated path. A
/// leading `/` (or `://`) is preserved verbatim; the segment
/// pass collapses interior `.` and pops one preceding non-`..` segment
/// per `..`. Used by `resolve_import_path` to make cycle-detection
/// path equality work regardless of which side of a recursive import
/// pair built the string.
fn normalize_path_segments(path : String) -> String {
// Split scheme prefix (`file:///`, `pkl:`) off — leave it untouched.
let scheme_end = match path.find("://") {
Some(idx) => idx + 3
None => 0
}
let head = String::unsafe_substring(path, start=0, end=scheme_end)
let body = String::unsafe_substring(path, start=scheme_end, end=path.length())
let leading_slash = body.has_prefix("/")
let raw_segments : Array[String] = []
let mut start = if leading_slash { 1 } else { 0 }
let mut i = start
while i < body.length() {
if body.unsafe_get(i) == '/' {
raw_segments.push(String::unsafe_substring(body, start~, end=i))
start = i + 1
}
i = i + 1
}
if start <= body.length() {
raw_segments.push(String::unsafe_substring(body, start~, end=body.length()))
}
let resolved : Array[String] = []
for seg in raw_segments {
if seg == "" || seg == "." {
continue
}
if seg == ".." {
if resolved.length() > 0 && resolved[resolved.length() - 1] != ".." {
let _ = resolved.pop()
} else if !leading_slash {
resolved.push("..")
}
continue
}
resolved.push(seg)
}
let buf = StringBuilder::new()
buf.write_string(head)
if leading_slash {
buf.write_char('/')
}
for k = 0; k < resolved.length(); k = k + 1 {
if k > 0 {
buf.write_char('/')
}
buf.write_string(resolved[k])
}
buf.to_string()
}
///|
fn builtin_stdlib_source(uri : String) -> String? {
match uri {
"pkl:math" => {
// The math module exposes Int range constants, pure Int helpers,
// and (PKL-120) the Float-side helpers `sqrt` / `pow` / `log` /
// `exp` / `floor` / `ceil` / `round` / `sin` / `cos` / `tan` /
// `atan` / `atan2`, plus the constants `pi` and `e`. The Float
// operations forward to runtime intrinsics declared as
// `_pkl_math_` global identifiers; the CallExpr dispatch in
// `eval.mbt` intercepts those names and computes the result via
// MoonBit's `math` module.
//
// `maxInt` / `minInt` track Apple Pkl's 64-bit Int range.
//
// The helpers are declared as top-level lambda bindings rather
// than `function` declarations because the parser marks
// `function ... = ...` forms as `exported: false`, which keeps
// them out of the imported module's `ObjectValue`. Lambda-valued
// bindings round-trip through the regular `exported: true` path.
let source =
#|minInt8 = 0 - 128
#|minInt16 = 0 - 32768
#|maxInt8 = 127
#|maxInt16 = 32767
#|maxInt32 = 2147483647
#|minInt32 = 0 - 2147483647 - 1
#|maxInt = 9223372036854775807
#|minInt = 0 - 9223372036854775807 - 1
#|maxUInt = 9223372036854775807
#|maxUInt8 = 255
#|maxUInt16 = 65535
#|maxUInt32 = 4294967295
#|pi = 3.141592653589793
#|e = 2.718281828459045
#|maxFiniteFloat = 1.7976931348623157E308
#|minFiniteFloat = 0.0 - 1.7976931348623157E308
#|abs = (x) -> if (x < 0) 0 - x else x
#|min = (a, b) -> _pkl_math_min(a, b)
#|max = (a, b) -> _pkl_math_max(a, b)
#|sqrt = (x) -> _pkl_math_sqrt(x)
#|cbrt = (x) -> _pkl_math_cbrt(x)
#|pow = (x, y) -> _pkl_math_pow(x, y)
#|log = (x) -> _pkl_math_log(x)
#|log2 = (x) -> _pkl_math_log2(x)
#|log10 = (x) -> _pkl_math_log10(x)
#|exp = (x) -> _pkl_math_exp(x)
#|floor = (x) -> _pkl_math_floor(x)
#|ceil = (x) -> _pkl_math_ceil(x)
#|round = (x) -> _pkl_math_round(x)
#|sin = (x) -> _pkl_math_sin(x)
#|cos = (x) -> _pkl_math_cos(x)
#|tan = (x) -> _pkl_math_tan(x)
#|asin = (x) -> _pkl_math_asin(x)
#|acos = (x) -> _pkl_math_acos(x)
#|atan = (x) -> _pkl_math_atan(x)
#|atan2 = (y, x) -> _pkl_math_atan2(y, x)
#|gcd = (a, b) -> _pkl_math_gcd(a, b)
#|lcm = (a, b) -> _pkl_math_lcm(a, b)
#|isPowerOfTwo = (x) -> _pkl_math_is_power_of_two(x)
Some(source)
}
"pkl:platform" => {
// PKL-123: read-only stub. Apple Pkl's `pkl:platform` reads the
// host VM (`System.getProperty("os.name")` etc.); we hard-code
// portable stub values so fixtures are deterministic across CI
// hosts. A future slice can swap in host-detected values via
// intrinsics without breaking the existing API shape.
let source =
#|class OperatingSystem {
#| name: String
#| version: String = "0.0.0"
#|}
#|class Language { version: String }
#|class Runtime { name: String; version: String }
#|class VirtualMachine { name: String; version: String }
#|class Processor { architecture: String }
#|
#|current = new {
#| operatingSystem = new {
#| name = "stub-os"
#| version = "0.0.0"
#| }
#| architecture = new {
#| name = "stub-arch"
#| }
#| language = new {
#| version = "0.30.0"
#| }
#| runtime = new {
#| name = "stub-runtime"
#| version = "0.0.0"
#| }
#| virtualMachine = new {
#| name = "stub-vm"
#| version = "0.0.0"
#| }
#| processor = new {
#| architecture = "stub-arch"
#| }
#|}
Some(source)
}
"pkl:release" => {
// PKL-152: deterministic release metadata stub. The upstream
// fixture only validates the public shape and simple predicates,
// not the exact host build metadata.
let source =
#|import "pkl:semver" as semver
#|current = new {
#| version = semver.parse("0.30.0")
#| versionInfo = "Linux pkl-mbt"
#| commitId = "abcdef0"
#| sourceCode = new {
#| homepage = "https://github.com/apple/pkl/"
#| }
#| documentation = new {
#| homepage = "https://pkl-lang.org/"
#| }
#| standardLibrary = new {
#| modules = List(
#| "pkl:analyze",
#| "pkl:base",
#| "pkl:Benchmark",
#| "pkl:Command",
#| "pkl:DocPackageInfo",
#| "pkl:DocsiteInfo",
#| "pkl:EvaluatorSettings",
#| "pkl:json",
#| "pkl:jsonnet",
#| "pkl:math",
#| "pkl:pklbinary",
#| "pkl:platform",
#| "pkl:Project",
#| "pkl:protobuf",
#| "pkl:reflect",
#| "pkl:release",
#| "pkl:semver",
#| "pkl:settings",
#| "pkl:shell",
#| "pkl:test",
#| "pkl:xml",
#| "pkl:yaml",
#| )
#| }
#|}
Some(source)
}
"pkl:Benchmark" => {
// PKL-152: benchmark fixtures assert the result object shape and
// monotonic Duration relationships. Use deterministic samples
// instead of measuring wall-clock time.
let source =
#|iterations = 1
#|iterationTime = 1.ms
#|isVerbose = false
#|microbenchmarks = new Mapping {}
#|outputBenchmarks = new Mapping {}
#|parserBenchmarks = new Mapping {}
#|output = new {
#| text = "[\"micro\"]\n[\"output\"]\n[\"parser\"]"
#|}
#|
#|class Microbenchmark {
#| expression: Any
#| iterations: Int = 1
#| iterationTime: Duration = 1.ms
#| isVerbose: Boolean = false
#| function run() = new {
#| iterations = this.iterations
#| repetitions = 1
#| samples = null
#| min = 1.ms
#| max = 1.ms
#| mean = 1.ms
#| }
#|}
#|
#|class OutputBenchmark {
#| sourceModule: Any
#| iterations: Int = 1
#| iterationTime: Duration = 1.ms
#| isVerbose: Boolean = false
#| function run() = new {
#| iterations = this.iterations
#| repetitions = 1
#| samples = if (this.isVerbose) List(1.ms, 1.ms, 1.ms) else null
#| min = 1.ms
#| max = 1.ms
#| mean = 1.ms
#| }
#|}
#|
#|class ParserBenchmark {
#| sourceText: String
#| iterations: Int = 1
#| iterationTime: Duration = 1.ms
#| isVerbose: Boolean = false
#| function run() = new {
#| iterations = this.iterations
#| repetitions = 1
#| samples = null
#| min = 1.ms
#| max = 1.ms
#| mean = 1.ms
#| }
#|}
Some(source)
}
"pkl:EvaluatorSettings" => {
// PKL-154 / #27: keep the evaluator-settings contract embedded so
// JS/Wasm consumers can load PklProject files without reaching into
// the vendored Apple Pkl checkout. These values are configuration
// data; applying them to the host evaluator remains the caller's job.
let source =
#|externalProperties: Mapping? = null
#|env: Mapping? = null
#|allowedModules: Listing? = null
#|allowedResources: Listing? = null
#|color: String? = null
#|noCache: Boolean? = null
#|modulePath: Listing? = null
#|timeout: Duration? = null
#|moduleCacheDir: String? = null
#|rootDir: String? = null
#|http: Http? = null
#|externalModuleReaders: Mapping? = null
#|externalResourceReaders: Mapping? = null
#|traceMode: String? = null
#|
#|class Http {
#| proxy: Proxy? = null
#| rewrites: Mapping? = null
#| headers: Mapping? = null
#|}
#|
#|class Proxy {
#| address: String? = null
#| noProxy: Listing = new Listing {}
#|}
#|
#|class ExternalReader {
#| executable: String
#| arguments: Listing? = null
#| workingDir: String? = null
#|}
#|
#|local const isNotReservedHeaderName = (header: String) ->
#| header.toLowerCase() != "connection" &&
#| header.toLowerCase() != "content-length" &&
#| header.toLowerCase() != "expect" &&
#| header.toLowerCase() != "host" &&
#| header.toLowerCase() != "keep-alive" &&
#| header.toLowerCase() != "te" &&
#| header.toLowerCase() != "trailer" &&
#| header.toLowerCase() != "transfer-encoding" &&
#| header.toLowerCase() != "upgrade"
#|local const doesNotStartWithReservedPrefix = (header: String) ->
#| !header.toLowerCase().startsWith("proxy-") &&
#| !header.toLowerCase().startsWith("sec-")
#|local const hasValidHeaderNameSyntax = (header: String) ->
#| header.matches(Regex(#"[a-zA-Z0-9!#$%&'*+-.^_`|~]+"#))
#|
#|typealias HttpHeaderName = String(
#| isNotReservedHeaderName,
#| doesNotStartWithReservedPrefix,
#| hasValidHeaderNameSyntax,
#| isNotBlank,
#|)
#|typealias HttpHeaderValue =
#| String(matches(Regex(#"[\t\u0020-\u007E\u0080-\u00FF]*"#)), length < 4096)
#|typealias HttpHeaders =
#| Mapping | HttpHeaderValue>
#|
#|function resolveForOs(enclosingUri: String, os: Dynamic): Dynamic =
#| _pkl_evaluator_settings_resolve(this, enclosingUri, os.name == "Windows")
#|function resolve(enclosingUri: String): Dynamic =
#| _pkl_evaluator_settings_resolve(this, enclosingUri, false)
Some(source)
}
"pkl:Project" => {
// PKL-154 / #27: public PklProject data model. The standard module
// imports EvaluatorSettings through the regular module graph so an
// amended project keeps the same nested object semantics as Apple Pkl.
let source =
#|import "pkl:EvaluatorSettings" as EvaluatorSettingsModule
#|
#|package: Package? = null
#|tests: Listing = new Listing {}
#|dependencies: Mapping = new Mapping {}
#|evaluatorSettings: EvaluatorSettingsModule = (EvaluatorSettingsModule) {}
#|projectFileUri: String = "pkl:Project"
#|
#|class RemoteDependency {
#| uri: String
#| checksums: Checksums? = null
#|}
#|
#|class Checksums {
#| sha256: String
#|}
#|
#|class Package {
#| name: String
#| baseUri: String
#| version: String
#| packageZipUrl: String
#| description: String? = null
#| authors: Listing = new Listing {}
#| website: String? = null
#| documentation: String? = null
#| sourceCode: String? = null
#| sourceCodeUrlScheme: String? = null
#| license: String? = null
#| licenseText: String? = null
#| issueTracker: String? = null
#| apiTests: Listing = new Listing {}
#| exclude: Listing = new Listing {
#| "PklProject"
#| "PklProject.deps.json"
#| ".**"
#| }
#| uri: String = "\(baseUri)@\(version)"
#|}
Some(source)
}
"pkl:pklbinary" => {
// PKL-154 / #27: the public renderer surface is embedded here;
// MessagePack emission itself lives in eval_render_pklbinary.mbt.
let source =
#|class Renderer extends BytesRenderer {
#| hidden __rendererFormat: String = "pklbinary"
#| converters: Mapping = new Mapping {}
#| convertPropertyTransformers: Mapping = new Mapping {}
#|}
Some(source)
}
"pkl:Command" => {
// PKL-154 / #27: command authoring contract. Parsing argv and
// injecting options are host responsibilities; this embedded module
// provides the module/class/annotation shapes command sources extend.
let source =
#|hidden command: CommandInfo = new {
#| name = "Command"
#|}
#|hidden options: Typed = new Dynamic {}
#|hidden parent: Any? = null
#|hidden root: Any? = null
#|
#|class CommandInfo {
#| name: String
#| description: String? = null
#| hide: Boolean = false
#| noOp: Boolean = false
#| subcommands: Listing = new Listing {}
#|}
#|
#|abstract class BaseFlag extends Annotation {
#| shortName: String? = null
#| hide: Boolean = false
#|}
#|
#|class Flag extends BaseFlag {
#| metavar: String? = null
#| convert: Any? = null
#| multiple: Boolean? = null
#| transformAll: Any? = null
#| completionCandidates: Any = new Listing {}
#|}
#|
#|class BooleanFlag extends BaseFlag {}
#|class CountedFlag extends BaseFlag {}
#|
#|class Argument extends Annotation {
#| convert: Any? = null
#| multiple: Boolean? = null
#| transformAll: Any? = null
#| completionCandidates: Any = new Listing {}
#|}
#|
#|class Import {
#| uri: String
#| glob: Boolean = false
#|}
Some(source)
}
"pkl:DocPackageInfo" => {
// PKL-154 / #27: descriptor contract used by pkldoc. Reflection-
// derived overview metadata is nullable here; user-authored package
// fields and nested dependencies retain their standard shapes.
let source =
#|name: String
#|version: String
#|importUri: String
#|uri: String? = null
#|authors: Listing = new Listing {}
#|sourceCode: String? = null
#|sourceCodeUrlScheme: String? = null
#|issueTracker: String
#|dependencies: Listing = new Listing {}
#|extraAttributes: Mapping = new Mapping {}
#|overview: String? = null
#|overviewImports: Map = Map()
#|annotations: List = List()
#|
#|class PackageDependency {
#| name: String
#| version: String
#| sourceCode: String
#| sourceCodeUrlScheme: String? = null
#| documentation: String? = null
#|}
Some(source)
}
"pkl:DocsiteInfo" => {
let source =
#|title: String? = null
#|overview: String? = null
#|overviewImports: Map = Map()
Some(source)
}
"pkl:settings" => {
let source =
#|import "pkl:EvaluatorSettings"
#|
#|editor: Editor = new Editor {
#| urlScheme = "%{url}, line %{line}"
#|}
#|http: EvaluatorSettings.Http? = null
#|
#|class Editor {
#| urlScheme: String
#|}
Some(source)
}
"pkl:semver" => {
// PKL-123: `Version` constructor builds the canonical record
// (major / minor / patch / preRelease / build). `parse` and
// `parseOrNull` forward to the `_pkl_semver_parse(_or_null)`
// intrinsics so the lexer logic stays in MoonBit; `parse` raises
// a diagnostic on bad input while `parseOrNull` returns null.
// Comparison helpers wrap `_pkl_semver_compare` (`<0` / `0` /
// `>0` style) so pre-release ordering follows the SemVer spec
// without re-implementing it in pkl. Apple Pkl's public
// `comparator` is the Boolean shape expected by `sortWith`.
let source =
#|Version = (s) -> _pkl_semver_parse(s)
#|parse = (s) -> _pkl_semver_parse(s)
#|parseOrNull = (s) -> _pkl_semver_parse_or_null(s)
#|compare = (a, b) -> _pkl_semver_compare(a, b)
#|comparator = (a, b) -> _pkl_semver_compare(a, b) < 0
#|isLessThan = (a, b) -> _pkl_semver_compare(a, b) < 0
#|isGreaterThan = (a, b) -> _pkl_semver_compare(a, b) > 0
#|isEqualTo = (a, b) -> _pkl_semver_compare(a, b) == 0
Some(source)
}
"pkl:test" => Some("catch = null")
"pkl:analyze" => {
// PKL-148bo: forward `importGraph(moduleUris)` to the
// `_pkl_analyze_import_graph` intrinsic. The intrinsic walks the
// sandbox-recorded per-module import edges (populated by the
// CLI's `load_path` pre-walk) and returns a value that already
// matches the `ImportGraph { imports, resolvedImports }` shape;
// we still declare the class so type annotations / `is
// ImportGraph` checks line up.
let source =
#|class ImportGraph {
#| imports: Mapping = new {}
#| resolvedImports: Mapping = new {}
#|}
#|class Import {
#| uri: String
#|}
#|importGraph = (moduleUris) -> _pkl_analyze_import_graph(moduleUris)
Some(source)
}
"pkl:jsonnet" => {
// PKL-148bm: `pkl:jsonnet` is a Pkl-side stub used by the
// jsonnet renderer. The actual Jsonnet emission lives in
// `eval_render_jsonnet.mbt` — this stub exposes the public
// `Renderer` class for `new jsonnet.Renderer {}` instantiation
// and the `ExtVar` / `ImportStr` constructors that produce
// tagged ObjectValues the renderer recognises and projects as
// `std.extVar(...)` / `importstr ...`. `renderValue` /
// `renderDocument` are intercepted by `eval_value_renderer_method`
// in eval_expr.mbt so the function bodies here are never run.
// PKL-148bm: pkl:jsonnet is a Pkl-side stub. The renderer body
// lives in eval_render_jsonnet.mbt; the stub only needs to
// expose the `Renderer` class plus tagged `ExtVar` / `ImportStr`
// record shapes. Apple Pkl uses dedicated classes for the
// latter, but because user-typed `new T { ... }` results don't
// currently carry the class tag through lambda return paths,
// the constructors stamp a hidden `@__jsonnet_kind` marker that
// the renderer recognises in `jsonnet_special_object_text`.
let source =
#|class Renderer {
#| indent: String = " "
#| omitNullProperties: Boolean = true
#| converters: Mapping = new {}
#|}
#|ExtVar = (_name) -> new {
#| __jsonnet_kind__ = "ExtVar"
#| name = _name
#|}
#|ImportStr = (_path) -> new {
#| __jsonnet_kind__ = "ImportStr"
#| path = _path
#|}
Some(source)
}
// PKL-148bh: `pkl:shell.escapeWithSingleQuotes(str)` — wraps the
// string in single quotes and escapes internal `'` via the
// `'\''` shell-quote idiom (api/shellModule).
"pkl:shell" => {
let source =
#|escapeWithSingleQuotes = (str) -> _pkl_shell_escape_single_quote(str)
Some(source)
}
// PKL-148bh: `pkl:base` is the implicit-imported root stdlib.
// Apple Pkl allows explicit `import "pkl:base"` (modules/equality
// does it twice — once bare, once `as base2`) so the binding can
// be inspected for object-equality. The contents are vacuously
// empty here — every pkl:base name already resolves through the
// bare-identifier path so the import only needs to provide an
// ObjectValue that satisfies equality comparisons.
"pkl:base" => Some("")
"pkl:json" => {
// PKL-124 / PKL-144 / PKL-145: `pkl:json` carries `Parser` and
// the annotation class `Property`. The Parser stamps a hidden
// `__kind = "JsonParser"` class property (PKL-145 added the
// `hidden` modifier on class properties) so the evaluator's
// CallExpr path intercepts `parser.parse(source)` by checking
// the marker rather than relying on member-shape coincidence.
// The Json→Value conversion routes through MoonBit core's
// `@json.parse` and honours `useMapping`. The user-facing
// `JsonRenderer` lives in `pkl:base`.
let source =
#|class Parser {
#| useMapping: Boolean = false
#| converters: Any = new Mapping {}
#| hidden __kind: String = "JsonParser"
#|}
#|class Property extends ConvertProperty {
#| name: String
#| render = (prop, _) -> Pair(name, prop.value)
#|}
Some(source)
}
"pkl:yaml" => {
// PKL-124 / PKL-146: parallel to `pkl:json` — Parser + Property
// surface. The Parser stamps a hidden `__kind = "YamlParser"`
// marker (PKL-145 enabled `hidden` on class properties) so the
// evaluator intercepts `parser.parse(source)` and routes the
// YAML body through `moonbit-community/yaml`'s
// `Yaml::load_from_string`, projecting each document onto a
// Pkl Value via `yaml_to_value`. The YAML renderer itself is a
// `pkl:base` re-export.
let source =
#|class Parser {
#| mode: String = "compat"
#| useMapping: Boolean = false
#| converters: Any = new Mapping {}
#| maxCollectionAliases: Int = 50
#| hidden __kind: String = "YamlParser"
#|}
#|class Property {
#| name: String
#|}
Some(source)
}
"pkl:xml" => {
// PKL-124: the `pkl:xml` module exposes the renderer under its
// own qualified name `xml.Renderer`. The class shape is also
// consumed by the AST-driven renderer detection and XML helper
// constructors.
let source =
#|class Renderer {
#| indent: String = " "
#| xmlVersion: String = "1.0"
#| rootElementName: String = "root"
#|}
#|class Element {
#| hidden __xmlKind: String = "Element"
#| name: String
#| isBlockFormat: Boolean = true
#|}
#|class Inline {
#| hidden __xmlKind: String = "Inline"
#| value: Any
#|}
#|class CData {
#| hidden __xmlKind: String = "CData"
#| text: String
#|}
#|class Comment {
#| hidden __xmlKind: String = "Comment"
#| text: String
#|}
#|class Property {
#| name: String
#|}
Some(source)
}
"pkl:protobuf" => {
// PKL-124: protobuf renderer surface lives in the `pkl:protobuf`
// module under the qualified `protobuf.Renderer` name.
let source =
#|class Renderer {
#| indent: String = " "
#|}
#|class Property {
#| name: String
#|}
Some(source)
}
"pkl:ref" => {
let source =
#|abstract class Domain {
#| abstract function renderReference(reference: Reference): String
#|}
#|
#|class Access {
#| fixed isProperty: Boolean
#| fixed isSubscript: Boolean
#| property: String?
#| key: Any
#|}
#|
#|class Reference {
#| function getDomain(): Dynamic = _pkl_ref_get_domain(this)
#| function getData(): Any = _pkl_ref_get_data(this)
#| function getPath(): List = _pkl_ref_get_path(this)
#| function toString(): String = _pkl_ref_to_string(this)
#|}
Some(source)
}
"pkl:reflect" => {
// PKL-080 minimal slice. Apple's `pkl:reflect` is ~460 lines and built
// around `external` declarations that bind directly to VM internals
// (Class / Module / Property / TypeAlias / Type mirrors). A faithful
// implementation would require a `ClassValue` variant in the value
// model and a way to surface module / class members at runtime — neither
// of which exists yet.
//
// The stub below covers what fixtures actually reach for first:
// - String-tagged mirror constants for the common base types.
// The tag format `"pkl.base#"` is internal to this stub; it is
// intentionally distinct from any user-visible string so a real
// mirror type can replace it later without ambiguity.
// - `Class` / `Module` / `TypeAlias` / `Property` factories that take
// a string identifier and return a container exposing `reflectee`.
// This degrades the upstream "pass the class itself" API to a
// "pass the class name" API, traded for being implementable today.
// - `DeclaredType(referent)` wrapping a mirror so nested `referent`
// walks line up with upstream usage.
// - `isSubclassOf(other)` is intentionally absent from this slice; the
// naive name-equality check it would collapse to is misleading enough
// that a follow-up should add a real subclass relation table.
let source =
#|class Type {}
#|class Property {}
#|class Method {}
#|
#|anyType = _pkl_reflect_type("Any")
#|booleanType = _pkl_reflect_type("Boolean")
#|intType = _pkl_reflect_type("Int")
#|floatType = _pkl_reflect_type("Float")
#|numberType = _pkl_reflect_type("Number")
#|stringType = _pkl_reflect_type("String")
#|durationType = _pkl_reflect_type("Duration")
#|dataSizeType = _pkl_reflect_type("DataSize")
#|bytesType = _pkl_reflect_type("Bytes")
#|pairType = _pkl_reflect_type("Pair")
#|listType = _pkl_reflect_type("List")
#|setType = _pkl_reflect_type("Set")
#|mapType = _pkl_reflect_type("Map")
#|listingType = _pkl_reflect_type("Listing")
#|mappingType = _pkl_reflect_type("Mapping")
#|objectType = _pkl_reflect_type("Object")
#|dynamicType = _pkl_reflect_type("Dynamic")
#|typedType = _pkl_reflect_type("Typed")
#|moduleType = _pkl_reflect_type("Module")
#|unknownType = _pkl_reflect_type("unknown")
#|nothingType = _pkl_reflect_type("nothing")
// PKL-143: Class / Module mirrors carry a hidden `__kind`
// marker that the evaluator uses to hijack `.properties` /
// `.methods` / `.supertype` / `.classes` / `.isSubclassOf`
// member access. The marker doesn't render (hidden modifier)
// and the user-facing surface still exposes `reflectee`.
#|Class = (name) -> _pkl_reflect_class(name)
#|Module = (name) -> _pkl_reflect_module(name)
#|moduleOf = (name) -> _pkl_reflect_module(name)
#|TypeAlias = (name) -> _pkl_reflect_type_alias(name)
#|Property = (name) -> new { name = name }
#|DeclaredType = (referent) -> _pkl_reflect_declared_type(referent)
#|UnionType = (types) -> _pkl_reflect_union_type(types)
#|StringLiteralType = (value) -> _pkl_reflect_string_literal_type(value)
#|TypeVariable = (parameter) -> _pkl_reflect_type_variable(parameter)
Some(source)
}
_ => None
}
}
///|
fn builtin_stdlib_type_supplement(uri : String) -> String? {
match uri {
"pkl:Benchmark" =>
Some(
(
#|abstract class Benchmark {}
#|class BenchmarkResult {}
#|class BenchmarkReport {}
),
)
"pkl:DocPackageInfo" =>
Some(
(
#|typealias PackageName = String
#|typealias PackageVersion = String
),
)
"pkl:EvaluatorSettings" => Some("typealias HttpRewrite = String")
"pkl:Project" =>
Some(
(
#|typealias PackageUri = String
#|typealias EmailAddress = String
#|typealias EvaluatorSettings = Any
#|typealias CommonSpdxLicenseIdentifier = String
),
)
"pkl:base" =>
Some(
(
#|abstract class Any {}
#|class Null extends Any {}
#|typealias NonNull = Any
#|class Class {}
#|class TypeAlias {}
#|class Module {}
#|class Annotation {}
#|class Since extends Annotation {}
#|class Deprecated extends Annotation {}
#|class AlsoKnownAs extends Annotation {}
#|class Unlisted extends Annotation {}
#|class DocExample extends Annotation {}
#|class SourceCode extends Annotation {}
#|class ModuleInfo extends Annotation {}
#|class FileOutput {}
#|class ModuleOutput extends FileOutput {}
#|class BaseValueRenderer {}
#|class ConvertProperty {}
#|class ValueRenderer extends BaseValueRenderer {}
#|class BytesRenderer {}
#|class PcfRenderer extends ValueRenderer {}
#|class RenderDirective {}
#|class PcfRenderDirective extends RenderDirective {}
#|class JsonRenderer extends ValueRenderer {}
#|class YamlRenderer extends ValueRenderer {}
#|class PListRenderer extends ValueRenderer {}
#|class PropertiesRenderer extends ValueRenderer {}
#|class Resource {}
#|abstract class Number {}
#|class Int extends Number {}
#|typealias Int8 = Int
#|typealias Int16 = Int
#|typealias Int32 = Int
#|typealias UInt8 = Int
#|typealias UInt16 = Int
#|typealias UInt32 = Int
#|typealias UInt = Int
#|typealias Comparable = Any
#|class Float extends Number {}
#|NaN = 0.0
#|Infinity = 0.0
#|class Boolean {}
#|typealias Char = String
#|class String {}
#|typealias Charset = String
#|typealias Uri = String
#|Regex = (_pattern) -> new { pattern = _pattern }
#|class Regex {}
#|class RegexMatch {}
#|typealias DurationUnit = String
#|class Duration {}
#|typealias DataSizeUnit = String
#|class DataSize {}
#|class Object {}
#|class Typed extends Object {}
#|class Dynamic extends Object {}
#|class Listing extends Object {}
#|class Mapping extends Object {}
#|class Function {}
#|class Function0 extends Function {}
#|class Function1 extends Function {}
#|class Function2 extends Function {}
#|class Function3 extends Function {}
#|class Function4 extends Function {}
#|class Function5 extends Function {}
#|typealias Mixin = Function1
#|Undefined = () -> throw("Undefined")
#|TODO = () -> throw("TODO")
#|Null = (_default) -> null
#|Pair = (_first, _second) -> new { first = _first; second = _second }
#|class Pair {}
#|abstract class Collection {}
#|IntSeq = (_start, _end) -> new { start = _start; end = _end; step = 1 }
#|class IntSeq extends Collection {}
#|class VarArgs extends Collection {}
#|List = (_element) -> new Listing { _element }
#|class List extends Collection {}
#|Set = (_element) -> new Listing { _element }
#|class Set extends Collection {}
#|Map = (_key, _value) -> new Mapping { [_key] = _value }
#|class Map extends Collection {}
#|Bytes = (_value) -> new Listing { _value }
#|class Bytes extends Collection {}
),
)
"pkl:json" | "pkl:yaml" => Some("typealias Value = Any")
"pkl:jsonnet" =>
Some(
(
#|class Property {}
#|class ImportStr {}
#|class ExtVar {}
),
)
"pkl:math" => Some("minPositiveFloat = 4.9E-324")
"pkl:platform" => Some("class Platform {}")
"pkl:reflect" =>
Some(
(
#|abstract class Declaration {}
#|class Module extends Declaration {}
#|abstract class TypeDeclaration extends Declaration {}
#|class Class extends TypeDeclaration {}
#|class TypeAlias extends TypeDeclaration {}
#|class MethodParameter {}
#|class TypeParameter {}
#|typealias Modifier = String
#|typealias Variance = String
#|class DeclaredType extends Type {}
#|class StringLiteralType extends Type {}
#|class UnionType extends Type {}
#|class NullableType extends Type {}
#|class FunctionType extends Type {}
#|class ModuleType extends Type {}
#|class UnknownType extends Type {}
#|class NothingType extends Type {}
#|class TypeVariable extends Type {}
#|class SourceLocation {}
),
)
"pkl:release" =>
Some(
(
#|class Release {}
#|class SourceCode {}
#|class Documentation {}
#|class StandardLibrary {}
),
)
"pkl:semver" =>
Some(
(
#|class Version {}
#|isValid = (s) -> _pkl_semver_parse_or_null(s) != null
),
)
"pkl:test" =>
Some(
(
#|facts = null
#|examples = null
#|catchOrNull = (fun) -> _pkl_test_catch_or_null(fun)
),
)
"pkl:xml" =>
Some(
(
#|Element = (_name) -> new { name = _name; isBlockFormat = true }
#|Inline = (_value) -> new { value = _value }
#|Comment = (_text) -> new { text = _text }
#|CData = (_text) -> new { text = _text }
),
)
_ => None
}
}
///|
fn get_module_type_source(
sources : @ripple.Input[String, String],
rt : @ripple.Runtime,
path : String,
) -> String? {
match builtin_stdlib_source(path) {
Some(runtime_source) =>
match builtin_stdlib_type_supplement(path) {
Some(supplement) => Some(runtime_source + "\n" + supplement)
None => Some(runtime_source)
}
None => sources.get(rt, path)
}
}
///|
fn get_module_source(
sources : @ripple.Input[String, String],
rt : @ripple.Runtime,
path : String,
) -> String? {
match builtin_stdlib_source(path) {
Some(source) => Some(source)
None => sources.get(rt, path)
}
}
///|
fn typecheck_module_path(
parses : @ripple.Query[String, ParseResult?],
types_ref : Ref[@ripple.CycleQuery[String, TypecheckResult]?],
rt : @ripple.Runtime,
path : String,
) -> TypecheckResult {
match parses.fetch(rt, path) {
Some(parsed) =>
typecheck_parsed_with_import_details(
parsed,
fn(uri) {
let resolved = resolve_import_path(path, uri)
Some(types_ref.val.unwrap().fetch(rt, resolved))
},
fn(uri) {
let resolved = resolve_import_path(path, uri)
match parses.fetch(rt, resolved) {
Some(imported) => Some(type_exports_from_parse_result(imported))
None => None
}
},
)
None => TypeError([diag("Cannot find module `\{path}`.")])
}
}
///|
/// PKL-148bh: derive a module name from a file path the same way Apple
/// Pkl does — strip the parent directories and the trailing `.pkl`
/// extension, return `None` for paths that don't end in `.pkl` (the
/// reflect mirror falls back to the bare simple name).
fn derive_module_name_from_path(path : String) -> String? {
let basename = match path.rev_find("/") {
Some(idx) =>
String::unsafe_substring(path, start=idx + 1, end=path.length())
None => path
}
if basename.has_suffix(".pkl") {
Some(String::unsafe_substring(basename, start=0, end=basename.length() - 4))
} else if basename == "" {
None
} else {
Some(basename)
}
}
///|
fn eval_module_path(
sources : @ripple.Input[String, String],
rt : @ripple.Runtime,
path : String,
stack : Array[String],
) -> EvalResult {
if stack_contains(stack, path) {
// PKL-148bb: a cyclic import where the cycle only travels through
// type-position references (`class Bar { foo: M1.Foo }` inside M2
// imported back into M1) is legal in Apple Pkl — the type info is
// extracted through the parallel `resolve_import_classes` walk
// which parses without evaluating. Return an empty module value
// so the value-side import binding doesn't fail; if any actual
// value reference closes the cycle, the downstream `Cannot find
// property` diagnostic will surface there. (`modules/recursiveModule1`.)
return EvalOk(ObjectValue([]))
}
match get_module_source(sources, rt, path) {
Some(source) => {
let evaluated = eval_source_with_import_details_named_at(
source,
derive_module_name_from_path(path),
Some(path),
fn(uri) {
let resolved = resolve_import_path(path, uri)
Some(eval_module_path(sources, rt, resolved, push_stack(stack, path)))
},
fn(uri) {
let resolved = resolve_import_path(path, uri)
match get_module_source(sources, rt, resolved) {
Some(imported_source) => {
// Walk `extends "parent.pkl"` so an importer of this
// module also sees classes inherited from its parent —
// Apple Pkl's extends is class inheritance, not a
// namespaced import (PKL-148f).
let visited : Array[String] = []
let exports : Array[ClassExport] = []
let mut current_path = resolved
let mut current_parsed = parse_source(imported_source)
let mut keep_going = true
while keep_going {
if visited.contains(current_path) {
keep_going = false
} else {
visited.push(current_path)
for
class_export in class_exports_from_parse_result(
current_parsed,
) {
exports.push(class_export)
}
// pkspec Spec-layer gap: an `amends`/`extends` parent
// that itself does `import "X.pkl" as base` and uses
// `base.Type` in its own functions / typed fields must
// keep those qualified types resolvable when the child
// re-runs the parent's bodies in the merged context.
// Surface each ancestor's imported classes under their
// `alias.ClassName` form so the importer's `class_env`
// and declaration set see them by qualified name.
for import_decl in current_parsed.program.imports {
if !import_decl.is_glob {
let import_resolved = resolve_import_path(
current_path,
import_decl.uri,
)
match get_module_source(sources, rt, import_resolved) {
Some(import_source) =>
for
imported_export in class_exports_from_parse_result(
parse_source(import_source),
) {
exports.push({
..imported_export,
name: "\{import_decl.import_name}.\{imported_export.name}",
})
}
None => ()
}
}
}
match current_parsed.program.module_relation {
Some(relation) => {
let parent_resolved = resolve_import_path(
current_path,
relation.uri,
)
match get_module_source(sources, rt, parent_resolved) {
Some(parent_source) => {
current_path = parent_resolved
current_parsed = parse_source(parent_source)
}
None => keep_going = false
}
}
None => keep_going = false
}
}
}
Some(exports)
}
None => None
}
},
fn(uri) {
// PKL-153: parent's raw Binding[] (including locals) so the
// re-eval path can resolve identifiers like a parent-local
// `duplicateNames` that the parent's visible `output` body
// references. We re-parse the parent module (cheap; this
// resolver is only called when the derived module amends or
// extends — i.e. once per module relation) and surface the
// raw bindings.
//
// PKL-158: walk the whole `amends`/`extends` chain (mirroring
// the class-export resolver above) so a type declared on a
// grandparent (e.g. `workflowTests: Listing`)
// is still reachable when an intermediate module re-amends
// without re-declaring it. Ancestors are collected farthest-
// first so the nearest module's binding lands last; consumers
// reverse-walk via `find_binding` (last match wins), so the
// closest declaration still shadows ancestors.
let resolved = resolve_import_path(path, uri)
match get_module_source(sources, rt, resolved) {
Some(parent_source) => {
let chain : Array[Array[Binding]] = []
let visited : Array[String] = []
let mut current_path = resolved
let mut current_parsed = parse_source(parent_source)
let mut keep_going = true
while keep_going {
if visited.contains(current_path) {
keep_going = false
} else {
visited.push(current_path)
// Include function declarations as synthetic bindings as
// well as properties. A grandparent `output.value` body
// can call a local helper after several amend/extend
// layers; re-evaluating that body at the leaf needs the
// original function binding even when the hidden runtime
// member was not retained by an intermediate module.
chain.push(all_eval_bindings(current_parsed.program))
match current_parsed.program.module_relation {
Some(relation) => {
let parent_resolved = resolve_import_path(
current_path,
relation.uri,
)
match get_module_source(sources, rt, parent_resolved) {
Some(ancestor_source) => {
current_path = parent_resolved
current_parsed = parse_source(ancestor_source)
}
None => keep_going = false
}
}
None => keep_going = false
}
}
}
// Flatten farthest-ancestor → nearest-parent so the
// nearest binding is last (reverse-walk picks it first).
let collected : Array[Binding] = []
let mut i = chain.length() - 1
while i >= 0 {
for binding in chain[i] {
collected.push(binding)
}
i = i - 1
}
Some(collected)
}
None => None
}
},
)
evaluated
}
None => EvalError([diag("Cannot find module `\{path}`.")])
}
}