diff --git a/docs/Range.md b/docs/Range.md index cf92d20..909d0e3 100644 --- a/docs/Range.md +++ b/docs/Range.md @@ -98,10 +98,11 @@ Exclusive end char ranges are supported too: | isEndInclusive | true for '..' | Bool | | isOpen | at any end | Bool | | isIntRange | both start and end are Int | Bool | -| start | | Bool | -| end | | Bool | +| start | | Any? | +| end | | Any? | | size | for finite ranges, see above | Long | | [] | see above | | -| | | | + +Ranges are also used with the `clamp(value, range)` function and the `value.clamp(range)` extension method to limit values within boundaries. [Iterable]: Iterable.md \ No newline at end of file diff --git a/docs/Real.md b/docs/Real.md index 2f63c7a..0e9b484 100644 --- a/docs/Real.md +++ b/docs/Real.md @@ -19,6 +19,7 @@ you can use it's class to ensure type: |-----------------|-------------------------------------------------------------|------| | `.roundToInt()` | round to nearest int like round(x) | Int | | `.toInt()` | convert integer part of real to `Int` dropping decimal part | Int | +| `.clamp(range)` | clamp value within range boundaries | Real | | | | | | | | | | | | | diff --git a/docs/math.md b/docs/math.md index 743d54a..d8a67a9 100644 --- a/docs/math.md +++ b/docs/math.md @@ -92,6 +92,7 @@ or transformed `Real` otherwise. | pow(x, y) | ${x^y}$ | | sqrt(x) | $ \sqrt {x}$ | | abs(x) | absolute value of x. Int if x is Int, Real otherwise | +| clamp(x, range) | limit x to be inside range boundaries | For example: @@ -102,6 +103,11 @@ For example: // abs() keeps the argument type: assert( abs(-1) is Int) assert( abs(-2.21) == 2.21 ) + + // clamp() limits value to the range: + assert( clamp(15, 0..10) == 10 ) + assert( clamp(-5, 0..10) == 0 ) + assert( 5.clamp(0..10) == 5 ) >>> void ## Scientific constant diff --git a/docs/whats_new.md b/docs/whats_new.md index e8ecf24..59bcddb 100644 --- a/docs/whats_new.md +++ b/docs/whats_new.md @@ -186,6 +186,26 @@ Key features: - **Structural Equality**: Transient fields are automatically ignored during `==` equality checks. - **Deserialization**: Transient constructor parameters with default values are correctly restored to those defaults upon restoration. +### Value Clamping (`clamp`) +A new `clamp()` function has been added to the standard library to limit a value within a specified range. It is available as both a global function and an extension method on all objects. + +```lyng +// Global function +clamp(15, 0..10) // returns 10 +clamp(-5, 0..10) // returns 0 + +// Extension method +val x = 15 +x.clamp(0..10) // returns 10 + +// Exclusive and open-ended ranges +15.clamp(0..<10) // returns 9 +15.clamp(..10) // returns 10 +-5.clamp(0..) // returns 0 +``` + +`clamp()` correctly handles inclusive (`..`) and exclusive (`..<`) ranges. For discrete types like `Int` and `Char`, clamping to an exclusive upper bound returns the previous value. + ## Tooling and Infrastructure ### CLI: Formatting Command diff --git a/lyng-idea/src/main/kotlin/net/sergeych/lyng/idea/docs/LyngDocumentationProvider.kt b/lyng-idea/src/main/kotlin/net/sergeych/lyng/idea/docs/LyngDocumentationProvider.kt index abe7a72..82e4cef 100644 --- a/lyng-idea/src/main/kotlin/net/sergeych/lyng/idea/docs/LyngDocumentationProvider.kt +++ b/lyng-idea/src/main/kotlin/net/sergeych/lyng/idea/docs/LyngDocumentationProvider.kt @@ -139,7 +139,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() { val e: Int = miniSource.offsetOf(d.range.end) if (offset >= s && offset <= e) { // For enum constant, we don't have detailed docs in MiniAst yet, but we can render a title - return "
enum constant ${d.name}.${name}
" + return renderTitle("enum constant ${d.name}.${name}") } } } @@ -361,7 +361,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() { "Print values to the standard output and append a newline. Accepts any number of arguments." else "Print values to the standard output without a trailing newline. Accepts any number of arguments." val title = "function $ident(values)" - return "
${htmlEscape(title)}
" + styledMarkdown(htmlEscape(fallback)) + return renderTitle(title) + styledMarkdown(htmlEscape(fallback)) } // 4b) try class members like ClassName.member with inheritance fallback val lhs = previousWordBefore(text, idRange.startOffset) @@ -519,7 +519,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() { } } val sb = StringBuilder() - sb.append("
").append(htmlEscape(title)).append("
") + sb.append(renderTitle(title)) sb.append(renderDocBody(d.doc)) return sb.toString() } @@ -527,7 +527,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() { private fun renderParamDoc(fn: MiniFunDecl, p: MiniParam): String { val title = "parameter ${p.name}${typeOf(p.type)} in ${fn.name}${signatureOf(fn)}" val sb = StringBuilder() - sb.append("
").append(htmlEscape(title)).append("
") + sb.append(renderTitle(title)) // Find matching @param tag fn.doc?.tags?.get("param")?.forEach { tag -> @@ -549,7 +549,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() { val staticStr = if (m.isStatic) "static " else "" val title = "${staticStr}method $className.${m.name}(${params})${ret}" val sb = StringBuilder() - sb.append("
").append(htmlEscape(title)).append("
") + sb.append(renderTitle(title)) sb.append(renderDocBody(m.doc)) return sb.toString() } @@ -560,7 +560,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() { val staticStr = if (m.isStatic) "static " else "" val title = "${staticStr}${kind} $className.${m.name}${ts}" val sb = StringBuilder() - sb.append("
").append(htmlEscape(title)).append("
") + sb.append(renderTitle(title)) sb.append(renderDocBody(m.doc)) return sb.toString() } @@ -579,6 +579,10 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() { return "(${params})${ret}" } + private fun renderTitle(title: String): String { + return "
${htmlEscape(title)}
" + } + private fun htmlEscape(s: String): String = buildString(s.length) { for (ch in s) append( when (ch) { @@ -645,7 +649,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() { private fun renderOverloads(name: String, overloads: List): String { val sb = StringBuilder() - sb.append("
Overloads for ").append(htmlEscape(name)).append("
") + sb.append(renderTitle("Overloads for $name")) sb.append("