Skip to content

Commit 8f8c257

Browse files
committed
Fix #110: add parsing of quoted library arguments
1 parent 8d67044 commit 8f8c257

File tree

2 files changed

+96
-7
lines changed

2 files changed

+96
-7
lines changed

src/main/kotlin/org/jetbrains/kotlin/jupyter/libraries/util.kt

+76-7
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,88 @@ fun diagFailure(message: String): ResultWithDiagnostics.Failure {
6060
return ResultWithDiagnostics.Failure(ScriptDiagnostic(ScriptDiagnostic.unspecifiedError, message))
6161
}
6262

63-
fun parseLibraryArgument(str: String): Variable {
64-
val eq = str.indexOf('=')
65-
return if (eq == -1) Variable("", str.trim())
66-
else Variable(str.substring(0, eq).trim(), str.substring(eq + 1).trim())
63+
data class ArgParseResult(
64+
val variable: Variable,
65+
val end: Int
66+
)
67+
68+
fun parseLibraryArgument(str: String, argEndChars: List<Char>, begin: Int): ArgParseResult? {
69+
val eq = str.indexOf('=', begin)
70+
val untrimmedName = if (eq < 0) "" else str.substring(begin, eq)
71+
val name = untrimmedName.trim()
72+
73+
var argBegan = false
74+
var argEnded = false
75+
var quoteOpened = false
76+
var escape = false
77+
78+
val builder = StringBuilder()
79+
80+
var i = if (eq < 0) begin - 1 else eq
81+
while ((++i) < str.length) {
82+
val c = str[i]
83+
84+
if (escape) {
85+
builder.append(c)
86+
escape = false
87+
continue
88+
}
89+
90+
when (c) {
91+
'\\' -> {
92+
if (quoteOpened) escape = true
93+
else builder.append(c)
94+
}
95+
'"' -> {
96+
if (argBegan) {
97+
quoteOpened = false
98+
argEnded = true
99+
} else {
100+
quoteOpened = true
101+
argBegan = true
102+
}
103+
}
104+
in argEndChars -> {
105+
if (quoteOpened) builder.append(c)
106+
else break
107+
}
108+
else -> {
109+
if (!c.isWhitespace()) {
110+
if (argEnded) {
111+
throw ReplCompilerException(
112+
"Cannot parse library arguments: unexpected char '$c' " +
113+
"on position $i " +
114+
"in arguments string '$str'"
115+
)
116+
}
117+
argBegan = true
118+
builder.append(c)
119+
}
120+
}
121+
}
122+
}
123+
124+
val value = builder.toString().trim()
125+
if (eq == -1 && value.isEmpty()) return null
126+
127+
val nextIndex = if (i == str.length) i else i + 1
128+
return ArgParseResult(Variable(name, value), nextIndex)
67129
}
68130

69131
fun parseCall(str: String, brackets: Brackets): Pair<String, List<Variable>> {
70132
val openBracketIndex = str.indexOf(brackets.open)
71133
if (openBracketIndex == -1) return str.trim() to emptyList()
72134
val name = str.substring(0, openBracketIndex).trim()
73-
val args = str.substring(openBracketIndex + 1, str.indexOf(brackets.close, openBracketIndex))
74-
.split(',')
75-
.map(::parseLibraryArgument)
135+
val argsString = str.substring(openBracketIndex + 1, str.indexOfLast { it == brackets.close })
136+
137+
val endChars = listOf(brackets.close, ',')
138+
val firstArg = parseLibraryArgument(argsString, endChars, 0)
139+
val args = generateSequence(firstArg) {
140+
parseLibraryArgument(argsString, endChars, it.end)
141+
}.map {
142+
it.variable
143+
}.toList()
144+
76145
return name to args
77146
}
78147

src/test/kotlin/org/jetbrains/kotlin/jupyter/test/parseMagicsTests.kt

+20
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,26 @@ class ParseArgumentsTests {
4848
assertEquals("val2", args[1].value)
4949
}
5050

51+
@Test
52+
fun test4() {
53+
val (ref, args) = libraryFactory.parseReferenceWithArgs("""lets-plot(api="[1.0,)")""")
54+
assertEquals("lets-plot", ref.name)
55+
assertEquals(1, args.count())
56+
assertEquals("api", args[0].name)
57+
assertEquals("[1.0,)", args[0].value)
58+
}
59+
60+
@Test
61+
fun test5() {
62+
val (ref, args) = libraryFactory.parseReferenceWithArgs("""lets-plot(api = "[1.0,)" , lib=1.5.3 )""")
63+
assertEquals("lets-plot", ref.name)
64+
assertEquals(2, args.count())
65+
assertEquals("api", args[0].name)
66+
assertEquals("[1.0,)", args[0].value)
67+
assertEquals("lib", args[1].name)
68+
assertEquals("1.5.3", args[1].value)
69+
}
70+
5171
@Test
5272
fun testInfo1() {
5373
val requestUrl = "https://raw.githubusercontent.com/Kotlin/kotlin-jupyter/master/libraries/default.json"

0 commit comments

Comments
 (0)