+ String.replace
This commit is contained in:
1 parent
368ce2ce8c
commit
12fb4fe0ba
7 files changed
+322
-6
No files matched your search
@@ -21,10 +21,7 @@ import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.PerfFlags
|
||||
import net.sergeych.lyng.Pos
|
||||
import net.sergeych.lyng.RegexCache
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.*
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lyng.requireScope
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
@@ -132,6 +129,46 @@ data class ObjString(val value: String) : Obj() {
|
||||
return JsonPrimitive(value)
|
||||
}
|
||||
|
||||
private fun replaceLiteralChar(oldValue: Char, newValue: String, firstOnly: Boolean): ObjString {
|
||||
if (!firstOnly) return ObjString(value.replace(oldValue.toString(), newValue))
|
||||
val index = value.indexOf(oldValue)
|
||||
if (index < 0) return this
|
||||
return ObjString(value.substring(0, index) + newValue + value.substring(index + 1))
|
||||
}
|
||||
|
||||
private fun replaceLiteralString(oldValue: String, newValue: String, firstOnly: Boolean): ObjString =
|
||||
ObjString(
|
||||
if (firstOnly) value.replaceFirst(oldValue, newValue)
|
||||
else value.replace(oldValue, newValue)
|
||||
)
|
||||
|
||||
private suspend fun replaceRegex(
|
||||
scope: ScopeFacade,
|
||||
pattern: Regex,
|
||||
firstOnly: Boolean,
|
||||
replacementProvider: suspend (ObjRegexMatch) -> String
|
||||
): ObjString {
|
||||
val firstMatch = pattern.find(value) ?: return this
|
||||
if (firstOnly) {
|
||||
val replacement = replacementProvider(ObjRegexMatch(firstMatch))
|
||||
val start = firstMatch.range.first
|
||||
val endExclusive = start + firstMatch.value.length
|
||||
return ObjString(value.substring(0, start) + replacement + value.substring(endExclusive))
|
||||
}
|
||||
|
||||
val result = StringBuilder(value.length)
|
||||
var lastIndex = 0
|
||||
for (match in pattern.findAll(value)) {
|
||||
val start = match.range.first
|
||||
val endExclusive = start + match.value.length
|
||||
result.append(value, lastIndex, start)
|
||||
result.append(replacementProvider(ObjRegexMatch(match)))
|
||||
lastIndex = endExclusive
|
||||
}
|
||||
result.append(value, lastIndex, value.length)
|
||||
return ObjString(result.toString())
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = object : ObjClass("String", ObjCollection) {
|
||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
|
||||
@@ -341,6 +378,190 @@ data class ObjString(val value: String) : Obj() {
|
||||
}
|
||||
)
|
||||
}
|
||||
addFnDoc(
|
||||
name = "replace",
|
||||
doc = "Return a copy of this string with all literal or regex matches replaced. String arguments are treated literally; use Regex for regular expressions.",
|
||||
params = listOf(ParamDoc("old"), ParamDoc("new")),
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val source = thisAs<ObjString>()
|
||||
val oldValue = requiredArg<Obj>(0)
|
||||
val newValue = requiredArg<Obj>(1)
|
||||
when (oldValue) {
|
||||
is ObjChar -> {
|
||||
val replacement = when (newValue) {
|
||||
is ObjChar -> newValue.value.toString()
|
||||
is ObjString -> newValue.value
|
||||
else -> raiseIllegalArgument("String.replace(Char, ...) requires Char or String replacement")
|
||||
}
|
||||
source.replaceLiteralChar(oldValue.value, replacement, firstOnly = false)
|
||||
}
|
||||
|
||||
is ObjString -> {
|
||||
val replacement = (newValue as? ObjString)?.value
|
||||
?: raiseIllegalArgument("String.replace(String, ...) requires String replacement")
|
||||
source.replaceLiteralString(oldValue.value, replacement, firstOnly = false)
|
||||
}
|
||||
|
||||
is ObjRegex -> when {
|
||||
newValue is ObjString ->
|
||||
source.replaceRegex(this, oldValue.regex, firstOnly = false) { newValue.value }
|
||||
|
||||
newValue.isInstanceOf("Callable") ->
|
||||
source.replaceRegex(this, oldValue.regex, firstOnly = false) { match ->
|
||||
val transformed = call(newValue, Arguments(match), ObjVoid)
|
||||
(transformed as? ObjString)?.value
|
||||
?: raiseIllegalArgument("String.replace(Regex, transform) callback must return String")
|
||||
}
|
||||
|
||||
else ->
|
||||
raiseIllegalArgument("String.replace(Regex, ...) requires String replacement or a callable")
|
||||
}
|
||||
|
||||
else ->
|
||||
raiseIllegalArgument("String.replace requires Char, String, or Regex as the first argument")
|
||||
}
|
||||
}
|
||||
addFnDoc(
|
||||
name = "replaceFirst",
|
||||
doc = "Return a copy of this string with the first literal or regex match replaced. String arguments are treated literally; use Regex for regular expressions.",
|
||||
params = listOf(ParamDoc("old"), ParamDoc("new")),
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val source = thisAs<ObjString>()
|
||||
val oldValue = requiredArg<Obj>(0)
|
||||
val newValue = requiredArg<Obj>(1)
|
||||
when (oldValue) {
|
||||
is ObjChar -> {
|
||||
val replacement = when (newValue) {
|
||||
is ObjChar -> newValue.value.toString()
|
||||
is ObjString -> newValue.value
|
||||
else -> raiseIllegalArgument("String.replaceFirst(Char, ...) requires Char or String replacement")
|
||||
}
|
||||
source.replaceLiteralChar(oldValue.value, replacement, firstOnly = true)
|
||||
}
|
||||
|
||||
is ObjString -> {
|
||||
val replacement = (newValue as? ObjString)?.value
|
||||
?: raiseIllegalArgument("String.replaceFirst(String, ...) requires String replacement")
|
||||
source.replaceLiteralString(oldValue.value, replacement, firstOnly = true)
|
||||
}
|
||||
|
||||
is ObjRegex -> when {
|
||||
newValue is ObjString ->
|
||||
source.replaceRegex(this, oldValue.regex, firstOnly = true) { newValue.value }
|
||||
|
||||
newValue.isInstanceOf("Callable") ->
|
||||
source.replaceRegex(this, oldValue.regex, firstOnly = true) { match ->
|
||||
val transformed = call(newValue, Arguments(match), ObjVoid)
|
||||
(transformed as? ObjString)?.value
|
||||
?: raiseIllegalArgument("String.replaceFirst(Regex, transform) callback must return String")
|
||||
}
|
||||
|
||||
else ->
|
||||
raiseIllegalArgument("String.replaceFirst(Regex, ...) requires String replacement or a callable")
|
||||
}
|
||||
|
||||
else ->
|
||||
raiseIllegalArgument("String.replaceFirst requires Char, String, or Regex as the first argument")
|
||||
}
|
||||
}
|
||||
BuiltinDocRegistry.module("lyng.stdlib") {
|
||||
classDoc("String", doc = "") {
|
||||
method(
|
||||
name = "replace",
|
||||
doc = "Replace all occurrences of the given character with another character.",
|
||||
params = listOf(
|
||||
ParamDoc("old", type("lyng.Char")),
|
||||
ParamDoc("new", type("lyng.Char"))
|
||||
),
|
||||
returns = type("lyng.String")
|
||||
)
|
||||
method(
|
||||
name = "replace",
|
||||
doc = "Replace all occurrences of the given character with another character or string.",
|
||||
params = listOf(
|
||||
ParamDoc("old", type("lyng.Char")),
|
||||
ParamDoc("new", type("lyng.String"))
|
||||
),
|
||||
returns = type("lyng.String")
|
||||
)
|
||||
method(
|
||||
name = "replace",
|
||||
doc = "Replace all literal occurrences of the given string with another string.",
|
||||
params = listOf(
|
||||
ParamDoc("old", type("lyng.String")),
|
||||
ParamDoc("new", type("lyng.String"))
|
||||
),
|
||||
returns = type("lyng.String")
|
||||
)
|
||||
method(
|
||||
name = "replace",
|
||||
doc = "Replace all regular-expression matches with the given replacement string.",
|
||||
params = listOf(
|
||||
ParamDoc("pattern", type("lyng.Regex")),
|
||||
ParamDoc("new", type("lyng.String"))
|
||||
),
|
||||
returns = type("lyng.String")
|
||||
)
|
||||
method(
|
||||
name = "replace",
|
||||
doc = "Replace all regular-expression matches using the callback result for each RegexMatch.",
|
||||
params = listOf(
|
||||
ParamDoc("pattern", type("lyng.Regex")),
|
||||
ParamDoc("transform", funType(listOf(type("lyng.RegexMatch")), type("lyng.String")))
|
||||
),
|
||||
returns = type("lyng.String")
|
||||
)
|
||||
method(
|
||||
name = "replaceFirst",
|
||||
doc = "Replace the first occurrence of the given character with another character.",
|
||||
params = listOf(
|
||||
ParamDoc("old", type("lyng.Char")),
|
||||
ParamDoc("new", type("lyng.Char"))
|
||||
),
|
||||
returns = type("lyng.String")
|
||||
)
|
||||
method(
|
||||
name = "replaceFirst",
|
||||
doc = "Replace the first occurrence of the given character with another character or string.",
|
||||
params = listOf(
|
||||
ParamDoc("old", type("lyng.Char")),
|
||||
ParamDoc("new", type("lyng.String"))
|
||||
),
|
||||
returns = type("lyng.String")
|
||||
)
|
||||
method(
|
||||
name = "replaceFirst",
|
||||
doc = "Replace the first literal occurrence of the given string with another string.",
|
||||
params = listOf(
|
||||
ParamDoc("old", type("lyng.String")),
|
||||
ParamDoc("new", type("lyng.String"))
|
||||
),
|
||||
returns = type("lyng.String")
|
||||
)
|
||||
method(
|
||||
name = "replaceFirst",
|
||||
doc = "Replace the first regular-expression match with the given replacement string.",
|
||||
params = listOf(
|
||||
ParamDoc("pattern", type("lyng.Regex")),
|
||||
ParamDoc("new", type("lyng.String"))
|
||||
),
|
||||
returns = type("lyng.String")
|
||||
)
|
||||
method(
|
||||
name = "replaceFirst",
|
||||
doc = "Replace the first regular-expression match using the callback result for the RegexMatch.",
|
||||
params = listOf(
|
||||
ParamDoc("pattern", type("lyng.Regex")),
|
||||
ParamDoc("transform", funType(listOf(type("lyng.RegexMatch")), type("lyng.String")))
|
||||
),
|
||||
returns = type("lyng.String")
|
||||
)
|
||||
}
|
||||
}
|
||||
createField(
|
||||
name = "re",
|
||||
initialValue = ObjProperty(
|
||||
|
||||
@@ -345,9 +345,30 @@ object LyngLanguageTools {
|
||||
|
||||
fun docAt(analysis: LyngAnalysisResult, offset: Int): LyngSymbolInfo? {
|
||||
StdlibDocsBootstrap.ensure()
|
||||
val target = definitionAt(analysis, offset) ?: return null
|
||||
val mini = analysis.mini
|
||||
val imported = analysis.importedModules
|
||||
val target = definitionAt(analysis, offset) ?: run {
|
||||
val word = DocLookupUtils.wordRangeAt(analysis.text, offset) ?: return null
|
||||
val name = analysis.text.substring(word.first, word.second)
|
||||
val dotPos = DocLookupUtils.prevNonWs(analysis.text, word.first - 1)
|
||||
if (dotPos >= 0 && analysis.text[dotPos] == '.') {
|
||||
val receiverClass = DocLookupUtils.guessReceiverClassViaMini(
|
||||
mini,
|
||||
analysis.text,
|
||||
dotPos,
|
||||
imported,
|
||||
analysis.binding
|
||||
) ?: DocLookupUtils.guessReceiverClass(analysis.text, dotPos, imported, mini)
|
||||
if (receiverClass != null) {
|
||||
LyngSymbolTarget(
|
||||
name = name,
|
||||
kind = SymbolKind.Function,
|
||||
range = TextRange(word.first, word.second),
|
||||
containerName = receiverClass
|
||||
)
|
||||
} else null
|
||||
} else null
|
||||
} ?: return null
|
||||
val name = target.name
|
||||
|
||||
val local = mini?.let { findLocalDecl(it, analysis.text, name, target.range.start) }
|
||||
|
||||
Reference in new issue
Block a user