multiline string literals

This commit is contained in:
Sergey Chernov 2025-08-10 23:31:40 +03:00
parent ed0a21cb06
commit 63de82393a
8 changed files with 109 additions and 18 deletions

View File

@ -48,6 +48,7 @@ $$ \ln(1+x)=x-{\dfrac {x^{2}}{2}}+{\dfrac {x^{3}}{3}}-\cdots =\sum \limits _{n=0
} }
assert( почти_равны( 0.0005, 0.000501 ) ) assert( почти_равны( 0.0005, 0.000501 ) )
Во многих случаях вычисление $n+1$ члена значительно проще cчитается от предыдущего члена, в нашем случае это можно было бы записать через итератор, что мы вскоре добавим. Во многих случаях вычисление $n+1$ члена значительно проще cчитается от предыдущего члена, в нашем случае это можно было бы записать через итератор, см [Iterable] и `flow` в [parallelism].
(продолжение следует) [Iterable]: ../Iterable.md
[parallelism]: ../parallelism.md

View File

@ -1259,7 +1259,19 @@ String literal could be multiline:
"Hello "Hello
World" World"
though multiline literals is yet work in progress. In this case, it will be passed literally ot "hello\n World". But, if there are
several lines with common left indent, it will be removed, also, forst and last lines,
if blank, will be removed too, for example:
println("
This is a multiline text.
This is a second line.
")
>>> This is a multiline text.
>>> This is a second line.
>>> void
- as expected, empty lines and common indent were removed. It is much like kotlin's `""" ... """.trimIndent()` technique, but simpler ;)
# Built-in functions # Built-in functions

View File

@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
group = "net.sergeych" group = "net.sergeych"
version = "0.8.4-SNAPSHOT" version = "0.8.5-SNAPSHOT"
buildscript { buildscript {
repositories { repositories {

View File

@ -375,6 +375,32 @@ private class Parser(fromPos: Pos) {
private val currentChar: Char get() = pos.currentChar private val currentChar: Char get() = pos.currentChar
private fun fixMultilineStringLiteral(source: String): String {
val sizes = mutableListOf<Int>()
val lines = source.lines().toMutableList()
if( lines.size == 0 ) return ""
if( lines[0].isBlank() ) lines.removeFirst()
if( lines.isEmpty()) return ""
if( lines.last().isBlank() ) lines.removeLast()
val normalized = lines.map { l ->
if( l.isBlank() ) {
sizes.add(-1)
""
}
else {
val margin = leftMargin(l)
sizes += margin
" ".repeat(margin) + l.trim()
}
}
val commonMargin = sizes.filter { it >= 0 }.min()
val fixed = if( commonMargin < 1 ) lines else normalized.map {
if( it.isBlank() ) "" else it.drop(commonMargin)
}
return fixed.joinToString("\n")
}
private fun loadStringToken(): Token { private fun loadStringToken(): Token {
val start = currentPos val start = currentPos
@ -383,6 +409,7 @@ private class Parser(fromPos: Pos) {
// start = start.back() // start = start.back()
val sb = StringBuilder() val sb = StringBuilder()
var newlineDetected = false
while (currentChar != '"') { while (currentChar != '"') {
if (pos.end) throw ScriptError(start, "unterminated string started there") if (pos.end) throw ScriptError(start, "unterminated string started there")
when (currentChar) { when (currentChar) {
@ -397,6 +424,12 @@ private class Parser(fromPos: Pos) {
} }
} }
'\n', '\r'-> {
newlineDetected = true
sb.append(currentChar)
pos.advance()
}
else -> { else -> {
sb.append(currentChar) sb.append(currentChar)
pos.advance() pos.advance()
@ -404,7 +437,10 @@ private class Parser(fromPos: Pos) {
} }
} }
pos.advance() pos.advance()
return Token(sb.toString(), start, Token.Type.STRING)
val result = sb.toString().let { if( newlineDetected ) fixMultilineStringLiteral(it) else it }
return Token(result, start, Token.Type.STRING)
} }
/** /**

View File

@ -0,0 +1,13 @@
package net.sergeych.lyng
fun leftMargin(s: String): Int {
var cnt = 0
for (c in s) {
when (c) {
' ' -> cnt++
'\t' -> cnt = (cnt / 4.0 + 0.9).toInt() * 4
else -> break
}
}
return cnt
}

View File

@ -2794,4 +2794,45 @@ class ScriptTest {
println(y.list) println(y.list)
} }
@Test
fun testMultilineStrings() = runTest {
assertEquals(
"""
This is a multiline text.
This is a second line.
""".trimIndent(), eval(
"""
"
This is a multiline text.
This is a second line.
"
""".trimIndent()
).toString()
)
assertEquals(
"""
This is a multiline text.
""".trimIndent(), eval(
"""
"
This is a multiline text.
"
""".trimIndent()
).toString()
)
assertEquals(
"""
This is a multiline text.
""".trimIndent(), eval(
"""
"
This is a multiline text.
"
""".trimIndent()
).toString()
)
}
} }

View File

@ -6,6 +6,7 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Script import net.sergeych.lyng.Script
import net.sergeych.lyng.leftMargin
import net.sergeych.lyng.obj.ObjVoid import net.sergeych.lyng.obj.ObjVoid
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Files.readAllLines import java.nio.file.Files.readAllLines
@ -16,18 +17,6 @@ import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.fail import kotlin.test.fail
fun leftMargin(s: String): Int {
var cnt = 0
for (c in s) {
when (c) {
' ' -> cnt++
'\t' -> cnt = (cnt / 4.0 + 0.9).toInt() * 4
else -> break
}
}
return cnt
}
data class DocTest( data class DocTest(
val fileName: String, val fileName: String,
val line: Int, val line: Int,

View File

@ -13,7 +13,6 @@ dependencyResolutionManagement {
maven("https://maven.universablockchain.com/") maven("https://maven.universablockchain.com/")
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven") maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
mavenLocal() mavenLocal()
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
} }
} }