Skip to content
Merged
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
21 changes: 21 additions & 0 deletions common/src/commonMain/kotlin/lang/temper/common/SplitLines.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package lang.temper.common

private val crlfOrLfPattern = Regex("""\r\n?|\n""")

fun CharSequence.splitLinesPreservingTerminators(): List<String> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kotlin's split lines drops terminators so I just thought I'd write one that's good.

val matches = crlfOrLfPattern.findAll(this)
var pos = 0
var listBuilder: MutableList<String>? = null
for (match in matches) {
val lines = listBuilder ?: (mutableListOf<String>().also { listBuilder = it })
val endExclusive = match.range.last + 1
lines.add(substring(pos, endExclusive))
pos = endExclusive
}
return if (listBuilder == null) {
listOf(this.toString())
} else {
listBuilder.add(substring(pos, length))
listBuilder.toList()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package lang.temper.common.json

import lang.temper.common.structure.Hints
import kotlin.math.min

class JsonNestedObjectBuilder {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I combine a bunch of test files from the expect directory into a JSON object so that we can still get one-diff-to-expose-all-problems style failure.

To that end, I keep a mapping from relative file paths to JSON property chains (and a bit of other info) and feed them into this.

The set of small file definitions are in the README added in this which is probably a good place to start any review.

private val chainsToLeaves = mutableListOf<Pair<List<String>, JsonValue>>()

fun property(propertyChain: List<String>, value: JsonValue) {
chainsToLeaves.add(propertyChain to value)
}

fun toJsonObject(): JsonObject {
// sort the property chains in lexicographic order
// Perhaps we have these once we're ordered:
// - ["a", "b"]
// - ["a", "c", "d"]
// - ["a", "c", "e"]
// - ["f", "g"]
// At depth 0, we can identify a run of "a" and a run of "f",
// so we know for one JsonObject, what it's properties are.
// Then at depth 1 for the "a" object, we have a run of one "b" and one "c".
// So simple linear search lets us build objects.
chainsToLeaves.sortWith { (a), (b) ->
lexicographicTupleComparison(a, b)
}

fun build(range: IntRange, depth: Int): JsonObject = JsonObject(
buildList {
var i = range.first
val limit = range.last
while (i <= limit) {
val (chainI, valueI) = chainsToLeaves[i]
val propertyName = chainI.getOrNull(depth)
if (propertyName == null) {
i += 1
continue
}
// Find the i..<j range for the property.
var j = i + 1
while (j <= limit) {
if (chainsToLeaves[j].first.getOrNull(depth) != propertyName) {
break
}
j += 1
}
// The property either has a singleton value or a nested object value.
val value = if (i + 1 == j && chainI.size == depth + 1) {
valueI
} else {
build(i..<j, depth + 1)
}
add(JsonProperty(propertyName, value, Hints.empty))
i = j
}
},
)
return build(chainsToLeaves.indices, 0)
}
}

fun buildJsonNestedObject(
body: (JsonNestedObjectBuilder).() -> Unit,
): JsonObject {
val builder = JsonNestedObjectBuilder()
builder.body()
return builder.toJsonObject()
}

private fun lexicographicTupleComparison(a: List<String>, b: List<String>): Int {
val aSize = a.size
val bSize = b.size
val minSize = min(aSize, bSize)
for (i in 0..<minSize) {
val aStr = a[i]
val bStr = b[i]
val delta = aStr.compareTo(bStr)
if (delta != 0) { return delta }
}

return aSize - bSize
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,37 @@ class JsonValueTest {
}
}
}

@Test
fun jsonNestedObjectBuilder() {
assertEquals(
"""
|{
| "a": {
| "b": {
| "c": null,
| "d": "d"
| },
| "e": 1
| },
| "f": {
| "g": null,
| "h": true
| },
| "i": [
| null,
| null
| ]
|}
""".trimMargin(),
buildJsonNestedObject {
property(listOf("a", "b", "c"), JsonNull)
property(listOf("a", "e"), JsonLong(1))
property(listOf("i"), JsonArray(listOf(JsonNull, JsonNull)))
property(listOf("f", "h"), JsonBoolean(true))
property(listOf("f", "g"), JsonNull)
property(listOf("a", "b", "d"), JsonString("d"))
}.toJsonString(),
)
}
}
4 changes: 2 additions & 2 deletions frontend/src/commonMain/kotlin/lang/temper/frontend/Module.kt
Original file line number Diff line number Diff line change
Expand Up @@ -814,14 +814,14 @@ internal fun destructureTreeMultipleRepresentations(
ast: Tree,
pseudoCodeDetail: PseudoCodeDetail,
) {
sink.key("code", Hints.s) {
sink.key("code", Hints.su) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make it more flexible as to which of lispy/pseudocode form is required.

val code = ast.toPseudoCode(
singleLine = false,
detail = pseudoCodeDetail,
)
value(code)
}
sink.key("tree", Hints.s) { value(ast) }
sink.key("tree", Hints.su) { value(ast) }
}

fun Iterable<Module>.mergedNamingContext(mergedLoc: ModuleLocation): NamingContext {
Expand Down
Loading
Loading