Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion sjsonnet/src-jvm-native/sjsonnet/stdlib/NativeGzip.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import sjsonnet.{Error, Eval, EvalScope, Platform, Position, Val}
object NativeGzip extends AbstractFunctionModule {
def name = "gzip"

val functions: Seq[(String, Val.Builtin)] = Seq(
val functionNames: Array[String] = Array("gzip")

lazy val functions: Seq[(String, Val.Builtin)] = Seq(
"gzip" -> new Val.Builtin1("gzip", "v") {
override def evalRhs(v: Eval, ev: EvalScope, pos: Position): Val = v.value match {
case Val.Str(_, value) => Val.Str(pos, Platform.gzipString(value))
Expand Down
4 changes: 3 additions & 1 deletion sjsonnet/src-jvm/sjsonnet/stdlib/NativeXz.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import sjsonnet.{Error, Eval, EvalScope, Platform, Position, Val}
object NativeXz extends AbstractFunctionModule {
def name = "xz"

val functions: Seq[(String, Val.Builtin)] = Seq(
val functionNames: Array[String] = Array("xz")

lazy val functions: Seq[(String, Val.Builtin)] = Seq(
"xz" -> new Val.Builtin2(
"xz",
"v",
Expand Down
14 changes: 14 additions & 0 deletions sjsonnet/src/sjsonnet/Val.scala
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,20 @@ object Val {
def invoke(self: Obj, sup: Obj, fs: FileScope, ev: EvalScope): Val = v
}

/**
* A member whose value is created lazily on first access. Used by stdlib to defer builtin
* function instantiation until actually needed, reducing startup overhead.
*/
final class LazyConstMember(add2: Boolean, visibility2: Visibility, private var init: () => Val)
extends Member(add2, visibility2, cached = true, deprecatedSkipAsserts = true) {
private var _val: Val = _
def invoke(self: Obj, sup: Obj, fs: FileScope, ev: EvalScope): Val = {
var v = _val
if (v eq null) { v = init(); _val = v; init = null }
v
}
}

def mk(pos: Position, members: (String, Obj.Member)*): Obj = {
val m = Util.preSizedJavaLinkedHashMap[String, Obj.Member](members.length)
for ((k, v) <- members) m.put(k, v)
Expand Down
25 changes: 22 additions & 3 deletions sjsonnet/src/sjsonnet/functions/AbstractFunctionModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,31 @@ import sjsonnet.Val
abstract class AbstractFunctionModule extends FunctionModule {

/**
* module's functions
* Module function names — cheap string array for lazy registration. No Val.Func objects created.
*/
val functions: Seq[(String, Val.Func)]
def functionNames: Array[String]

/**
* the hodler of the module object
* module's functions — override as lazy val in concrete modules to defer Val.Builtin creation.
*/
def functions: Seq[(String, Val.Func)]

/**
* Unsynchronized lazy function lookup map — single-threaded, no synchronization needed.
*/
private var _functionMap: Map[String, Val.Func] = _

/**
* Get a function by name, triggering module initialization if needed.
*/
def getFunction(name: String): Val.Func = {
var m = _functionMap
if (m eq null) { m = functions.toMap; _functionMap = m }
m(name)
}

/**
* the holder of the module object
*/
override final lazy val module: Val.Obj = moduleFromFunctions(functions: _*)
}
30 changes: 29 additions & 1 deletion sjsonnet/src/sjsonnet/stdlib/ArrayModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,35 @@ object ArrayModule extends AbstractFunctionModule {
Val.Arr(pos, chars)
}

val functions: Seq[(String, Val.Func)] = Seq(
val functionNames: Array[String] = Array(
"minArray",
"maxArray",
"all",
"any",
"count",
"filter",
"map",
"mapWithIndex",
"find",
"flattenArrays",
"flattenDeepArray",
"reverse",
"member",
"range",
"foldl",
"foldr",
"flatMap",
"filterMap",
"repeat",
"makeArray",
"contains",
"remove",
"removeAt",
"sum",
"avg"
)

lazy val functions: Seq[(String, Val.Func)] = Seq(
builtin(MinArray),
builtin(MaxArray),
builtin(All),
Expand Down
13 changes: 12 additions & 1 deletion sjsonnet/src/sjsonnet/stdlib/EncodingModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,18 @@ object EncodingModule extends AbstractFunctionModule {
Val.Str(pos, Platform.md5(s.value.asString))
}

val functions: Seq[(String, Val.Func)] = Seq(
val functionNames: Array[String] = Array(
"md5",
"base64",
"base64Decode",
"base64DecodeBytes",
"sha1",
"sha256",
"sha512",
"sha3"
)

lazy val functions: Seq[(String, Val.Func)] = Seq(
builtin(MD5),
builtin("base64", "input") { (_, _, input: Val) =>
input match {
Expand Down
20 changes: 19 additions & 1 deletion sjsonnet/src/sjsonnet/stdlib/ManifestModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,25 @@ object ManifestModule extends AbstractFunctionModule {
}
}

val functions: Seq[(String, Val.Func)] = Seq(
val functionNames: Array[String] = Array(
"manifestJson",
"manifestJsonMinified",
"manifestJsonEx",
"parseJson",
"parseYaml",
"manifestTomlEx",
"lines",
"deepJoin",
"manifestToml",
"manifestYamlDoc",
"manifestYamlStream",
"manifestIni",
"manifestPython",
"manifestPythonVars",
"manifestXmlJsonml"
)

lazy val functions: Seq[(String, Val.Func)] = Seq(
builtin(ManifestJson),
builtin(ManifestJsonMinified),
builtin(ManifestJsonEx),
Expand Down
39 changes: 38 additions & 1 deletion sjsonnet/src/sjsonnet/stdlib/MathModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,44 @@ import sjsonnet.functions.AbstractFunctionModule
object MathModule extends AbstractFunctionModule {
def name = "math"

val functions: Seq[(String, Val.Func)] = Seq(
val functionNames: Array[String] = Array(
"sqrt",
"max",
"min",
"mod",
"modulo",
"clamp",
"pow",
"floor",
"round",
"ceil",
"abs",
"sign",
"sin",
"cos",
"tan",
"isEven",
"isInteger",
"isOdd",
"isDecimal",
"asin",
"acos",
"atan",
"atan2",
"hypot",
"deg2rad",
"rad2deg",
"log",
"log2",
"log10",
"exp",
"mantissa",
"exponent",
"xor",
"xnor"
)

lazy val functions: Seq[(String, Val.Func)] = Seq(
builtin("sqrt", "x") { (pos, ev, x: Double) =>
math.sqrt(x)
},
Expand Down
10 changes: 9 additions & 1 deletion sjsonnet/src/sjsonnet/stdlib/NativeRegex.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,15 @@ object NativeRegex extends AbstractFunctionModule {
}
}

val functions: Seq[(String, Val.Builtin)] = Seq(
val functionNames: Array[String] = Array(
"regexPartialMatch",
"regexFullMatch",
"regexGlobalReplace",
"regexReplace",
"regexQuoteMeta"
)

lazy val functions: Seq[(String, Val.Builtin)] = Seq(
"regexPartialMatch" -> new Val.Builtin2("regexPartialMatch", "pattern", "str") {
override def evalRhs(pattern: Eval, str: Eval, ev: EvalScope, pos: Position): Val = {
regexPartialMatch(pos, pattern.value.asString, str.value.asString)
Expand Down
20 changes: 19 additions & 1 deletion sjsonnet/src/sjsonnet/stdlib/ObjectModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,25 @@ object ObjectModule extends AbstractFunctionModule {
Val.Arr(pos, result)
}

val functions: Seq[(String, Val.Func)] = Seq(
val functionNames: Array[String] = Array(
"objectHas",
"objectHasAll",
"objectHasEx",
"objectFields",
"objectFieldsAll",
"objectFieldsEx",
"objectValues",
"objectValuesAll",
"get",
"mapWithKey",
"objectKeysValues",
"objectKeysValuesAll",
"objectRemoveKey",
"mergePatch",
"prune"
)

lazy val functions: Seq[(String, Val.Func)] = Seq(
builtin(ObjectHas),
builtin(ObjectHasAll),
builtin("objectHasEx", "o", "k", "inc_hidden") {
Expand Down
13 changes: 12 additions & 1 deletion sjsonnet/src/sjsonnet/stdlib/SetModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,18 @@ object SetModule extends AbstractFunctionModule {
Val.Arr(pos, chars)
}

val functions: Seq[(String, Val.Func)] = Seq(
val functionNames: Array[String] = Array(
"set",
"slice",
"uniq",
"sort",
"setUnion",
"setInter",
"setDiff",
"setMember"
)

lazy val functions: Seq[(String, Val.Func)] = Seq(
builtin(Set_),
builtin("slice", "indexable", "index", "end", "step") {
(pos, ev, indexable: Val, index: Option[Int], _end: Option[Int], _step: Option[Int]) =>
Expand Down
106 changes: 74 additions & 32 deletions sjsonnet/src/sjsonnet/stdlib/StdLibModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package sjsonnet.stdlib

import sjsonnet._
import sjsonnet.Expr.Member.Visibility
import sjsonnet.functions.FunctionModule
import sjsonnet.functions.{AbstractFunctionModule, FunctionModule}

/**
* Main standard library module that combines all the individual stdlib modules
* Main standard library module that combines all the individual stdlib modules.
*
* Uses lazy initialization: only function names (cheap string arrays) are registered at startup.
* The actual Val.Builtin objects are created on first access, per-module granularity.
*/
final class StdLibModule(
private val nativeFunctions: Map[String, Val.Func] = Map.empty,
Expand All @@ -21,39 +24,78 @@ final class StdLibModule(
nativeFunctions.getOrElse(name.value.asString, Val.Null(pos))
}

// All functions including native and additional functions
val functions: Map[String, Val.Func] = allModuleFunctions ++
additionalStdFunctions +
("native" -> nativeFunction) +
("trace" -> traceFunction) +
("extVar" -> extVarFunction)

val module: Val.Obj = Val.Obj.mk(
null,
functions.size + additionalStdMembers.size,
functions.view.map { case (k, v) =>
(
k,
new Val.Obj.ConstMember(false, Visibility.Hidden, v)
)
},
additionalStdMembers
)
val module: Val.Obj = {
// Estimate total size: module functions + additional std functions + native/trace/extVar + pi/thisFile
val totalSize =
sharedLazyMembers.size + additionalStdFunctions.size + 3 + additionalStdMembers.size
val entries = Util.preSizedJavaLinkedHashMap[String, Val.Obj.Member](totalSize)

// Shared lazy members — created once in companion object, reused across all instances.
// After first StdLibModule evaluation, LazyConstMember._val is populated and subsequent
// instances get the cached values directly (single null-check fast path).
entries.putAll(sharedLazyMembers)

// Additional std functions (eager — typically empty or small)
for ((k, v) <- additionalStdFunctions)
entries.put(k, new Val.Obj.ConstMember(false, Visibility.Hidden, v))

// Core functions (eager — always needed, only 3)
entries.put("native", new Val.Obj.ConstMember(false, Visibility.Hidden, nativeFunction))
entries.put("trace", new Val.Obj.ConstMember(false, Visibility.Hidden, traceFunction))
entries.put("extVar", new Val.Obj.ConstMember(false, Visibility.Hidden, extVarFunction))

// Non-function members
for ((k, v) <- additionalStdMembers) entries.put(k, v)

new Val.Obj(null, entries, false, null, null)
}
}

object StdLibModule {
// Combine all functions from all modules
private val allModuleFunctions: Map[String, Val.Func] = (
ArrayModule.functions ++
StringModule.functions ++
ObjectModule.functions ++
MathModule.functions ++
TypeModule.functions ++
EncodingModule.functions ++
ManifestModule.functions ++
SetModule.functions ++
NativeRegex.functions
).toMap
// All stdlib modules — referenced but NOT initialized (functions are lazy val)
private val modules: Array[AbstractFunctionModule] = Array(
ArrayModule,
StringModule,
ObjectModule,
MathModule,
TypeModule,
EncodingModule,
ManifestModule,
SetModule,
NativeRegex
)

// Build name→module index using only cheap functionNames arrays (no Val.Builtin created)
private val nameToModule: java.util.LinkedHashMap[String, AbstractFunctionModule] = {
val m = new java.util.LinkedHashMap[String, AbstractFunctionModule](256)
var i = 0
while (i < modules.length) {
val mod = modules(i)
val names = mod.functionNames
var j = 0
while (j < names.length) {
m.put(names(j), mod)
j += 1
}
i += 1
}
m
}

// Shared LazyConstMember objects — created once at class loading, reused across all
// StdLibModule instances. After first evaluation, each member caches its resolved Val.Func,
// so subsequent instances pay only a null-check per member access (no closure or Map lookup).
private val sharedLazyMembers: java.util.LinkedHashMap[String, Val.Obj.Member] = {
val m = new java.util.LinkedHashMap[String, Val.Obj.Member](256)
val iter = nameToModule.entrySet().iterator()
while (iter.hasNext) {
val e = iter.next()
val n = e.getKey
val mod = e.getValue
m.put(n, new Val.Obj.LazyConstMember(false, Visibility.Hidden, () => mod.getFunction(n)))
}
m
}

// Core std library functions that belong directly in StdLibModule
private val traceFunction = new Val.Builtin2("trace", "str", "rest") {
Expand Down
Loading
Loading