Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
cfede30
Clarify ETS modifier arguments and test blocks
CaelmBleidd Oct 14, 2026
c063cad
Clarify ETS array type test stages
CaelmBleidd Oct 14, 2026
62ed030
Clarify IR file test setup and logger import
CaelmBleidd Oct 14, 2026
8854322
Separate local rendering branches
CaelmBleidd Oct 14, 2026
d4dda98
Align expression spacing and location sentinel arguments
CaelmBleidd Oct 14, 2026
e4830eb
Attach nested namespace classes to their declaring file
CaelmBleidd Oct 14, 2026
4083e6d
Preserve alias and type-only export rendering
CaelmBleidd Oct 14, 2026
29da736
Validate CFG edges against actual block indices
CaelmBleidd Oct 14, 2026
b3c890f
Align immediate constructor DTOs and remove inactive schema comments
CaelmBleidd Oct 14, 2026
b5750f5
Correct DSL and namespace DTO graph rendering
CaelmBleidd Oct 14, 2026
a2213f4
Decode canonical Raw DTO envelopes without losing metadata
CaelmBleidd Oct 14, 2026
ae25251
Avoid generated local name collisions in DTO normalization
CaelmBleidd Oct 14, 2026
95f9e94
Retain field decorators during DTO conversion
CaelmBleidd Oct 14, 2026
157faaa
Preserve DSL static owners and reserve generated local names
CaelmBleidd Oct 14, 2026
dca74e7
Keep fluent conditional chains exclusive and snapshot built programs
CaelmBleidd Oct 14, 2026
f7a1bc5
Resolve CFG jumps by occurrence and preserve empty branch edges
CaelmBleidd Oct 14, 2026
dfe522f
Make identity set operations consistent
CaelmBleidd Oct 14, 2026
a697c54
Verify observed descendant cleanup and decode frontend diagnostics as…
CaelmBleidd Oct 14, 2026
44745cb
Clarify the recursive visitor contract
CaelmBleidd Oct 14, 2026
c3b8cb8
Render all model classes with distinct IDs and context-specific escaping
CaelmBleidd Oct 14, 2026
5c0488b
Give DTO method overloads distinct DOT identities
CaelmBleidd Oct 14, 2026
bb6082b
Fix interprocedural rendering for empty and colliding callees
CaelmBleidd Oct 14, 2026
cbda44d
Check DOT rendering failures and preserve process arguments
CaelmBleidd Oct 14, 2026
7aab19d
Name frontend AST branch predicates
CaelmBleidd Oct 14, 2026
6175856
Clarify unsupported frontend fallback contract
CaelmBleidd Oct 14, 2026
ff6dae7
Clarify CFG successor and throwing-effect predicates
CaelmBleidd Oct 14, 2026
f2340df
Name frontend IR validation predicates
CaelmBleidd Oct 14, 2026
5eff0f2
Preserve Array type identity and generic callable metadata
CaelmBleidd Oct 14, 2026
6335126
Clean generated IR after convenience loader consumption
CaelmBleidd Oct 14, 2026
ef9c6e5
Preserve live generic binders in inferred function types
CaelmBleidd Oct 14, 2026
0f53ade
Share scope function type construction with generic binders
CaelmBleidd Oct 14, 2026
ea8e9fa
Respect shadowed undefined bindings during expression lowering
CaelmBleidd Oct 14, 2026
86c9a78
Preserve explicit superclass call metadata
CaelmBleidd Oct 14, 2026
9ea5d16
Separate iterator protocol operations into readable blocks
CaelmBleidd Oct 14, 2026
2223a29
Clarify property reference and namespace predicates
CaelmBleidd Oct 14, 2026
577c75b
Snapshot compound local values before lowering the right operand
CaelmBleidd Oct 14, 2026
41066e3
Mark optional superclass property calls as explicit unsupported lowering
CaelmBleidd Oct 14, 2026
5124674
Fix external corpus preparation lifecycle and failures
CaelmBleidd Oct 14, 2026
b633b1d
Reject unsupported direct superclass getter calls
CaelmBleidd Oct 14, 2026
b92df43
Clarify class expression guards and remove unused test helper
CaelmBleidd Oct 14, 2026
7d66db5
Reject unsupported superclass property reads
CaelmBleidd Oct 14, 2026
24542b7
Remove unreachable array spread fallback
CaelmBleidd Oct 14, 2026
2c9062d
Reject lifted functions with unrepresented recursive bindings
CaelmBleidd Oct 14, 2026
405b06d
Reject object methods without callable own-property semantics
CaelmBleidd Oct 14, 2026
dde308e
Reject tagged superclass property reads before lowering effects
CaelmBleidd Oct 14, 2026
fe283e1
Remove an unreachable type guard from regex literal lowering
CaelmBleidd Oct 14, 2026
a2f6b4a
Preserve closure hoist timing and preflight unsupported bindings
CaelmBleidd Oct 14, 2026
c4b39e1
Reuse synchronous iterator protocol and close abrupt for-of exits
CaelmBleidd Oct 15, 2026
5d2d4eb
Share chained loop labels and reject unsound for-in lowering
CaelmBleidd Oct 15, 2026
391b0e3
fix(frontend): bind destructured catch parameters through cleanup tar…
CaelmBleidd Oct 15, 2026
cf409e6
fix(frontend): preflight unsupported binding patterns before effects
CaelmBleidd Oct 15, 2026
9332239
fix(ets-tests): keep model and DTO graph output paths distinct
CaelmBleidd Oct 15, 2026
dedbb22
fix(frontend): reject unsupported class definitions and preserve infe…
CaelmBleidd Oct 15, 2026
97a9d75
fix(ts-frontend): forward inherited default constructor parameters
CaelmBleidd Oct 15, 2026
22f853b
fix(ts-frontend): preserve generic interface method binders
CaelmBleidd Oct 15, 2026
c71377f
fix(ts-frontend): preserve local export metadata
CaelmBleidd Oct 15, 2026
565f7b9
fix(ts-frontend): preserve dotted namespace containment
CaelmBleidd Oct 15, 2026
ccb4265
fix(ts-frontend): preserve escaped CLI paths and emission status
CaelmBleidd Oct 15, 2026
c2ac9aa
fix(ts-frontend-tests): resolve static methods by declaring class
CaelmBleidd Oct 15, 2026
ee7dbc8
refactor(ts-frontend-tests): clarify and share concrete runtime helpers
CaelmBleidd Oct 15, 2026
c5cf530
Fix TypeScript narrowing in namespace and catch lowering
CaelmBleidd Oct 15, 2026
ec09fcf
Fix frontend regression fixtures and oracle assertions
CaelmBleidd Oct 15, 2026
eecc751
Preserve immediate constraints in inferred generic binders
CaelmBleidd Oct 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 87 additions & 41 deletions jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@

package org.jacodb.ets.dsl

import org.jacodb.ets.utils.IdentityHashSet
import java.util.IdentityHashMap

data class Block(
val id: Int,
val statements: List<BlockStmt>,
Expand All @@ -29,41 +26,73 @@ data class BlockCfg(
val successors: Map<Int, List<Int>>, // for IF stmt, successors are (true, false) branches
)

private class NodeOccurrence(
val node: Node,
val thenBranch: List<NodeOccurrence> = emptyList(),
val elseBranch: List<NodeOccurrence> = emptyList(),
)

fun Program.toBlockCfg(): BlockCfg {
val labelToNode: MutableMap<String, Node> = hashMapOf()
val targets: MutableSet<Node> = IdentityHashSet()
fun collectOccurrences(nodes: List<Node>): List<NodeOccurrence> = nodes.map { node ->
if (node is If) {
NodeOccurrence(
node = node,
thenBranch = collectOccurrences(node.thenBranch),
elseBranch = collectOccurrences(node.elseBranch),
)
} else {
NodeOccurrence(node)
}
}

val occurrences = collectOccurrences(nodes)
val labelToOccurrence: MutableMap<String, NodeOccurrence> = hashMapOf()
val targets: MutableSet<NodeOccurrence> = hashSetOf()

fun findLabels(nodes: List<Node>) {
if (nodes.lastOrNull() is Label) {
fun findLabels(nodes: List<NodeOccurrence>) {
if (nodes.lastOrNull()?.node is Label) {
error("Label at the end of the block: $nodes")
}
for ((stmt, next) in nodes.zipWithNext()) {
if (stmt is Label) {
check(next !is Label) { "Two labels in a row: $stmt, $next" }
check(next !is Goto) { "Label followed by goto: $stmt, $next" }
check(stmt.name !in labelToNode) { "Duplicate label: ${stmt.name}" }
labelToNode[stmt.name] = next
}
for ((occurrence, next) in nodes.zipWithNext()) {
val label = occurrence.node as? Label ?: continue

check(next.node !is Label) { "Two labels in a row: $label, ${next.node}" }
check(next.node !is Goto) { "Label followed by goto: $label, ${next.node}" }
check(label.name !in labelToOccurrence) { "Duplicate label: ${label.name}" }
labelToOccurrence[label.name] = next
}
for (node in nodes) {
if (node is If) {
findLabels(node.thenBranch)
findLabels(node.elseBranch)
}
if (node is Goto) {
targets += labelToNode[node.targetLabel] ?: error("Unknown label: ${node.targetLabel}")
for (occurrence in nodes) {
findLabels(occurrence.thenBranch)
findLabels(occurrence.elseBranch)
}
}

findLabels(occurrences)

fun findTargets(nodes: List<NodeOccurrence>) {
for (occurrence in nodes) {
when (val node = occurrence.node) {
is If -> {
findTargets(occurrence.thenBranch)
findTargets(occurrence.elseBranch)
}

is Goto -> targets += labelToOccurrence[node.targetLabel]
?: error("Unknown label: ${node.targetLabel}")

else -> Unit
}
}
}

findLabels(nodes)
findTargets(occurrences)

val blocks: MutableList<Block> = mutableListOf()
val successors: MutableMap<Int, List<Int>> = hashMapOf()
val stmtToBlock: MutableMap<BlockStmt, Int> = IdentityHashMap()
val nodeToStmt: MutableMap<Node, BlockStmt> = IdentityHashMap()
val occurrenceToBlock: MutableMap<NodeOccurrence, Int> = hashMapOf()
val pendingGotos: MutableList<Pair<Int, String>> = mutableListOf()

fun buildBlocks(nodes: List<Node>): Pair<Int, Int>? {
fun buildBlocks(nodes: List<NodeOccurrence>): Pair<Int, Int>? {
if (nodes.isEmpty()) return null

lateinit var currentBlock: MutableList<BlockStmt>
Expand All @@ -75,14 +104,20 @@ fun Program.toBlockCfg(): BlockCfg {
return block
}

fun newEmptyBranch(): Block {
val branch = Block(blocks.size, emptyList())
blocks += branch
return branch
}

var block = newBlock()
val firstBlockId = block.id

for (node in nodes) {
for ((index, occurrence) in nodes.withIndex()) {
val node = occurrence.node
if (node is Label) continue

if (node in targets && currentBlock.isNotEmpty()) {
block.statements.forEach { stmtToBlock[it] = block.id }
if (occurrence in targets && currentBlock.isNotEmpty()) {
val prevBlock = block
block = newBlock()
successors[prevBlock.id] = listOf(block.id)
Expand All @@ -97,17 +132,16 @@ fun Program.toBlockCfg(): BlockCfg {
is CustomEts -> BlockCustomEts(node.toEts)
else -> error("Unexpected node: $node")
}
nodeToStmt[node] = stmt
occurrenceToBlock[occurrence] = block.id
currentBlock += stmt
}

if (node is If) {
block.statements.forEach { stmtToBlock[it] = block.id }
val ifBlock = block
block = newBlock()

val thenBlocks = buildBlocks(node.thenBranch)
val elseBlocks = buildBlocks(node.elseBranch)
val thenBlocks = buildBlocks(occurrence.thenBranch)
val elseBlocks = buildBlocks(occurrence.elseBranch)

when {
thenBlocks != null && elseBlocks != null -> {
Expand Down Expand Up @@ -147,29 +181,41 @@ fun Program.toBlockCfg(): BlockCfg {
}

else -> {
successors[ifBlock.id] = listOf(block.id)
val trueBranch = newEmptyBranch()
val falseBranch = newEmptyBranch()
successors[ifBlock.id] = listOf(trueBranch.id, falseBranch.id)
successors[trueBranch.id] = listOf(block.id)
successors[falseBranch.id] = listOf(block.id)
}
}
} else if (node is Goto) {
val targetNode = labelToNode[node.targetLabel] ?: error("Unknown label: ${node.targetLabel}")
val target = nodeToStmt[targetNode] ?: error("No statement for $targetNode")
val targetBlockId = stmtToBlock[target] ?: error("No block for $target")
successors[block.id] = listOf(targetBlockId)
block.statements.forEach { stmtToBlock[it] = block.id }
pendingGotos += block.id to node.targetLabel
block = newBlock()
} else if (node is Return) {
successors[block.id] = emptyList()
break
if (index < nodes.lastIndex) {
block = newBlock()
}
}
}

block.statements.forEach { stmtToBlock[it] = block.id }
val lastBlockId = block.id

return Pair(firstBlockId, lastBlockId)
}

buildBlocks(nodes)
buildBlocks(occurrences)

for ((sourceBlockId, label) in pendingGotos) {
val targetOccurrence = labelToOccurrence.getValue(label)
val targetBlockId = occurrenceToBlock[targetOccurrence]
?: error("No block for ${targetOccurrence.node}")
successors[sourceBlockId] = listOf(targetBlockId)
}

for (block in blocks) {
successors.putIfAbsent(block.id, emptyList())
}

return BlockCfg(blocks, successors)
}
9 changes: 9 additions & 0 deletions jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Expr.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package org.jacodb.ets.dsl

import org.jacodb.ets.model.EtsClassSignature

sealed interface Expr

data class Local(val name: String) : LValue {
Expand Down Expand Up @@ -96,7 +98,14 @@ data class FieldRef(
data class StaticFieldRef(
val className: String,
val fieldName: String,
val classSignature: EtsClassSignature? = null,
) : LValue {
init {
require(classSignature == null || classSignature.name == className) {
"Static field class name does not match its signature"
}
}

override fun toString() = "$className.$fieldName"
}

Expand Down
23 changes: 17 additions & 6 deletions jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/ProgramBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package org.jacodb.ets.dsl

import org.jacodb.ets.model.EtsClassSignature
import org.jacodb.ets.model.EtsEntity
import org.jacodb.ets.model.EtsStmt
import org.jacodb.ets.model.EtsStmtLocation
Expand All @@ -43,6 +44,8 @@ fun ProgramBuilder.const(value: String) = ConstantString(value)

fun ProgramBuilder.fieldRef(instance: Expr, fieldName: String) = FieldRef(instance, fieldName)
fun ProgramBuilder.staticFieldRef(className: String, fieldName: String) = StaticFieldRef(className, fieldName)
fun ProgramBuilder.staticFieldRef(owner: EtsClassSignature, fieldName: String) =
StaticFieldRef(owner.name, fieldName, classSignature = owner)
fun ProgramBuilder.arrayAccess(array: Expr, index: Expr) = ArrayAccess(array, index)

fun Expr.field(name: String) = FieldRef(this, name)
Expand Down Expand Up @@ -84,13 +87,23 @@ class CustomBinaryExpr(
val toEts: (left: EtsEntity, right: EtsEntity) -> EtsEntity,
) : Expr

private fun List<Node>.snapshot(): List<Node> = map { node ->
when (node) {
is If -> node.copy(
thenBranch = node.thenBranch.snapshot(),
elseBranch = node.elseBranch.snapshot(),
)

else -> node
}
}

class ProgramBuilderImpl : ProgramBuilder {
private val _nodes: MutableList<Node> = mutableListOf()
val nodes: List<Node> get() = _nodes

fun build(): Program {
return Program(nodes)
}
/** Capture the completed node tree; later builder calls cannot change this program. */
fun build(): Program = Program(nodes.snapshot())

override fun nop() {
_nodes += Nop
Expand Down Expand Up @@ -138,10 +151,8 @@ class IfBuilder : ProgramBuilder {
elseBuilder.apply(block)
}

fun elseIf(condition: Expr, block: ProgramBuilder.() -> Unit): IfBuilder {
fun elseIf(condition: Expr, block: ProgramBuilder.() -> Unit): IfBuilder =
elseBuilder.ifStmt(condition, block)
return this
}

override fun ifStmt(condition: Expr, block: ProgramBuilder.() -> Unit): IfBuilder =
thenBuilder.ifStmt(condition, block)
Expand Down
Loading
Loading