Uses Mill 1.1.2. Cross-built for Scala 3.3.7, 2.13.18, and 2.12.21 (JVM only).
# Compile (JVM, Scala 3)
./mill 'sjsonnet.jvm[3.3.7]'.compile
# Run all JVM tests (Scala 3)
./mill 'sjsonnet.jvm[3.3.7]'.test
# Run all tests across all platforms
./mill __.test
# Build assembly JAR
./mill 'sjsonnet.jvm[3.3.7]'.assembly
# GraalVM native image
./mill sjsonnet.graal.nativeImageThe assembly JAR is at out/sjsonnet/jvm/3.3.7/assembly.dest/out.jar. Run it with java -Xss100m -jar out.jar.
We follow the Databricks Scala style guide.
Scala sources are formatted with scalafmt (config in .scalafmt.conf). The SjsonnetCrossModule and bench modules mix in ScalafmtModule.
# Format all sources
./mill __.reformat
# Check formatting without changing files
./mill __.checkFormatCI checks formatting on PRs, so run reformat before committing.
- Framework: uTest
- Test suites (
sjsonnet/test/resources/):test_suite/— From C++ Jsonnet (upstream). ~183.jsonnetfiles with.goldenexpected output.go_test_suite/— From go-jsonnet.new_test_suite/— sjsonnet-specific tests (deep nesting, regex, etc.).
- Test sources:
test/src/— Shared tests (parser, evaluator, renderer, stdlib).test/src-jvm/— JVM-only (MainTests, file I/O, XxHash64).test/src-jvm-native/— JVM + Native (FileTests,ConfigTests, gzip, md5, sha).test/src-js/— Scala.js (FileTests).
Each .jsonnet test file has a .golden file containing expected output. Files named error.* expect non-zero exit and their .golden holds the expected stderr (including stack traces).
Refresh golden outputs:
./sjsonnet/test/resources/refresh_golden_outputs.shThe .sync_ignore files in test_suite/ and go_test_suite/ list tests to skip when syncing from upstream (e.g. differing YAML parser behavior).
Every bug fix should include a regression test:
- Create
new_test_suite/<descriptive_name>.jsonnetwith a minimal reproducer. - Create
new_test_suite/<descriptive_name>.jsonnet.goldenwith expected output.- Success tests: JSON output followed by a newline, using
std.assertEqualchains ending intrue. - Error tests (filename starts with
error.): expected stderr including stack traces.
- Success tests: JSON output followed by a newline, using
- Run
./mill 'sjsonnet.jvm[3.3.7]'.testto verify.
The --debug-stats flag prints runtime counters and timing to stderr after evaluation:
sjsonnet --debug-stats myfile.jsonnetOutput includes thunk creation counts, function/builtin call counts, comprehension iterations, import/parse counts, and phase timing (eval, materialize). Counters are formatted with stable labels for machine parsing. The DebugStats class (sjsonnet/src/sjsonnet/DebugStats.scala) is wired through Interpreter -> Evaluator, with parse counting handled by CountingParseCache.
JMH benchmarks live in bench/. Benchmark suites in bench/resources/: bug_suite/, cpp_suite/, go_suite/, sjsonnet_suite/.
# Run all regression tests
./mill bench.runRegressions
# Run specific regressions
./mill bench.runRegressions bench/resources/bug_suite/assertions.jsonnet
# List all benchmark regression files
./mill bench.listRegressions
# Refresh benchmark golden outputs
./bench/resources/refresh_golden_outputs.shFor ad-hoc benchmarking, hyperfine is available on the PATH.
Source String → Parser → Expr (AST) → StaticOptimizer → Evaluator → Val → Materializer → ujson.Value → Renderer → Output
- Parser (
Parser.scala): Fastparse-based, producesExprAST. - StaticOptimizer (
StaticOptimizer.scala): Constant folding, arity specialization,ValidIdindexing, static checks. - Evaluator (
Evaluator.scala): WalksExpr, produces lazyValruntime values. Handles imports, function application, tail-call optimization. - Materializer (
Materializer.scala): WalksVal, producesujson.Value. Hybrid recursive/iterative to avoid stack overflow. - Interpreter (
Interpreter.scala): Orchestrates the full pipeline; main programmatic entry point.
Expr(Expr.scala): AST nodes — literals, binary ops, function defs/calls, objects, arrays, comprehensions, imports.ExprTagsprovides byte tags for fast pattern matching.Val(Val.scala): Runtime values —Str,Num,Bool(True/False),Null,Arr,Obj,Func,Builtin.Val.Literalextends bothValandExpr.Evaltrait:def value: Val. Implemented byLazy(deferred, cached) andVal(immediate). Arrays and objects holdEval, notVal.ValScope(ValScope.scala): Array-based lexical scope with copy-on-write extension.TailCall: Sentinel value for tail-call optimization; resolved via trampoline inTailCall.resolve.
Implemented as Scala builtins (not Jsonnet code) in sjsonnet/src/sjsonnet/stdlib/:
StdLibModule— builds thestdobject combining all modules.ArrayModule,StringModule,ObjectModule,MathModule,TypeModule,EncodingModule,ManifestModule,SetModule,NativeRegex.
Platform-specific stdlib extensions:
- JVM:
NativeXz(xz compression),NativeGzip, regex viare2j. - Native:
NativeGzip, crypto. - JS:
scala-yamlfor YAML parsing.
Shared and platform-specific sources are split across directories:
| Directory | Contents |
|---|---|
sjsonnet/src/ |
Shared core (parser, evaluator, materializer, stdlib) |
sjsonnet/src-jvm/ |
JVM main class, Platform.scala |
sjsonnet/src-jvm-native/ |
Shared JVM + Native (SjsonnetMainBase, Config) |
sjsonnet/src-js/ |
Scala.js main class, Platform.scala |
sjsonnet/src-native/ |
Scala Native main class, Platform.scala |
sjsonnet.jvm— JVM library + CLI (SjsonnetMain)sjsonnet.js— Scala.js (CommonJS)sjsonnet.wasm— WebAssembly (via Scala.js)sjsonnet.native— Scala Nativesjsonnet.graal— GraalVM native imagesjsonnet.jvm.client/sjsonnet.jvm.server— Client-server mode (background daemon)bench— JMH benchmarksplayground— Browser-based Jsonnet playground
Error— Main exception with message, stack frames (Error.Frame), optional cause.ParseError/StaticError— Parse-time and static analysis failures.Error.fail(msg, pos)throws with position info;Error.withStackFrame(expr)adds frames to exceptions.- Public APIs (e.g.
Interpreter.interpret) returnEither[String, ujson.Value].
- Lazy evaluation: Arrays and objects hold
Eval(notVal).Lazywraps a thunk, caches the result, and clears the closure after first evaluation. - Tail-call optimization:
TailCallsentinel + trampoline loop inTailCall.resolve. - Hybrid materialization: Recursive up to a depth limit, then switches to an explicit stack to avoid
StackOverflowError. - Static optimization:
StaticOptimizerdoes constant folding, resolves variable names to scope indices (ValidId), and specializes function application by arity (Apply0–Apply3,ApplyBuiltin0–ApplyBuiltin4). - Parse caching:
ParseCache/DefaultParseCachecaches parsed ASTs to avoid re-parsing imports. - Platform abstraction:
PathandImportertraits abstract filesystem access across JVM/JS/Native.
Key runtime dependencies:
fastparse— Parser combinatorsujson— JSON AST and renderingpprint— Pretty printingscalatags— HTML generation (for error output)os-lib— Filesystem (JVM/Native)mainargs— CLI argument parsing (JVM/Native)snakeyaml— YAML parsing (JVM)re2j— Regular expressions (JVM)xz/lz4-java— Compression (JVM)
GitHub Actions workflows in .github/workflows/:
pr-build.yaml— PR validation: compile + test across JVM/JS/WASM/Native × JDK 17/21/25, format check, GraalVM native tests.release-build.yaml— Release builds (JAR, JS, WASM, playground, native binaries).