From 08ded2cbc3030d08ff3d6760ba1f89975056bdce Mon Sep 17 00:00:00 2001 From: Alex Rosengarten Date: Fri, 29 Jan 2021 17:02:56 -0800 Subject: [PATCH 1/6] Cleaned up structure of DependencyNodes to be more succinct. Introduced an AnalysisResult class to track nodes within the visitor. Passes all existing unit tests. --- java/arcs/core/analysis/DependencyNode.kt | 153 +++++++++++---- .../analysis/ExpressionDependencyAnalyzer.kt | 177 ++++++++++++------ .../ExpressionDependencyAnalyzerTest.kt | 18 +- 3 files changed, 248 insertions(+), 100 deletions(-) diff --git a/java/arcs/core/analysis/DependencyNode.kt b/java/arcs/core/analysis/DependencyNode.kt index 71ec5509c69..bad80711cf5 100644 --- a/java/arcs/core/analysis/DependencyNode.kt +++ b/java/arcs/core/analysis/DependencyNode.kt @@ -24,7 +24,7 @@ private typealias Path = List * map to output connections in particle specs with Paxel [Expression]s. * * - [DependencyNode.Input] represents an input handle connection and access path. - * - [DependencyNode.DerivedFrom] indicates that an input has been modified in the Paxel expression. + * - [DependencyNode.Derived] indicates that an input has been modified in the Paxel expression. * - [DependencyNode.AssociationNode] connects fields to other nodes in the graph. These are used to * form left-hand-side / right-hand-side relations between handle connections. * @@ -52,7 +52,7 @@ private typealias Path = List * DependencyNode.AssociationNode( * "ratio" to DependencyNode.AssociationNode( * "trained" to DependencyNode.Input("input", "cats"), - * "total" to DependencyNode.DerivedFrom( + * "total" to DependencyNode.Derived( * DependencyNode.Input("input", "cats"), * DependencyNode.Input("input", "dogs") * ) @@ -84,56 +84,133 @@ private typealias Path = List */ sealed class DependencyNode { - /** An unmodified input (from a handle connection) used in a Paxel [Expression]. */ - data class Input(val path: Path = emptyList()) : DependencyNode() { - constructor(vararg fields: Identifier) : this(listOf(*fields)) - } + /** Path of [Identifier]s representing access to a handle connection. */ + abstract val path: Path - /** Represents derivation from a group of [Input]s in an [Expression]. */ - data class DerivedFrom(val inputs: Set = emptySet()) : DependencyNode() { - - constructor(vararg paths: Path) : this(paths.map { Input(it) }.toSet()) - - /** Produce a new [DerivedFrom] with a flattened set of [Input]s. */ - constructor(vararg nodes: DependencyNode) : this(flatten(*nodes)) - - companion object { - /** Flatten nested sets of [DependencyNode]s.*/ - private fun flatten(vararg nodes: DependencyNode): Set { - return nodes.flatMap { node -> - when (node) { - is Input -> setOf(node) - is DerivedFrom -> node.inputs - else -> throw IllegalArgumentException( - "Nodes must be a 'Input' or 'DerivedFrom'." - ) - } - }.toSet() - } - } - } + /** Set of [DependencyNode]s that the current node depends on. */ + abstract val dependency: Set + + /** Set of [DependencyNode]s that bear influence on the current node. */ + abstract val influencedBy: Set + + /** Expresses influence on to the current [DependencyNode]. */ + abstract fun influence(influenceBy: Set): DependencyNode /** Associates [Identifier]s with [DependencyNode]s. */ - data class AssociationNode( - val associations: Map = emptyMap() + open class AssociationNode( + override val path: Path = emptyList(), + override val dependency: Set = emptySet(), + override val influencedBy: Set = emptySet(), + val associations: Map ) : DependencyNode() { - /** Construct an [AssociationNode] from associations of [Identifier]s to [DependencyNode]s. */ - constructor(vararg pairs: Pair) : this(pairs.toMap()) + /** Constructor to express associations. */ + constructor( + vararg mappings: Pair, + path: Path = emptyList(), + dependency: Set = emptySet(), + influencedBy: Set = emptySet() + ) : this(path, dependency, influencedBy, mappings.toMap()) + + /** Expresses influence on to the current [DependencyNode]. */ + override fun influence(influenceBy: Set): DependencyNode = AssociationNode( + path, + dependency, + this.influencedBy + influenceBy, + associations.mapValues { (_, node) -> node.influence(influencedBy) } + ) /** Replace the associations of an [AssociationNode] with new mappings. */ - fun add(vararg other: Pair): DependencyNode = AssociationNode( + fun add(vararg other: Pair): AssociationNode = AssociationNode( + path, + dependency, + influencedBy, associations + other ) /** Returns the [DependencyNode] associated with the input [Identifier]. */ - fun lookup(key: Identifier): DependencyNode = requireNotNull(associations[key]) { - "Identifier '$key' is not found in AssociationNode." + operator fun get(key: Identifier): DependencyNode? = associations[key] + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is AssociationNode) return false + + if (path != other.path) return false + if (dependency != other.dependency) return false + if (influencedBy != other.influencedBy) return false + if (associations != other.associations) return false + + return true + } + + override fun hashCode(): Int { + var result = path.hashCode() + result = 31 * result + dependency.hashCode() + result = 31 * result + influencedBy.hashCode() + result = 31 * result + associations.hashCode() + return result + } + + override fun toString(): String { + return "AssociationNode(path=$path, dependency=$dependency, influencedBy=$influencedBy," + + " associations=$associations)" } } + /** An unmodified input (from a handle connection) used in a Paxel [Expression]. */ + class Input( + override val path: Path, + override val dependency: Set = emptySet(), + override val influencedBy: Set = emptySet() + ) : AssociationNode(path, dependency, influencedBy, emptyMap()) { + + /** Constructor to build input [Path]s. */ + constructor( + vararg identifier: Identifier, + dependency: Set = emptySet(), + influencedBy: Set = emptySet() + ) : this(listOf(*identifier), dependency, influencedBy) + } + + /** Represents derivation from a group of [Input]s in an [Expression]. */ + data class Derived( + override val path: Path, + override val dependency: Set = emptySet(), + override val influencedBy: Set = emptySet(), + val inputs: Set + ) : DependencyNode() { + + /** Constructor for Literals. */ + constructor() : this(emptyList(), emptySet(), emptySet(), emptySet()) + + /** Constructor to express derivation of [Input]s. */ + constructor(vararg inputs: DependencyNode) : this( + emptyList(), + emptySet(), + emptySet(), + setOf(*inputs).flatten() + ) + + /** Expresses influence on to the current [DependencyNode]. */ + override fun influence(influenceBy: Set): DependencyNode = Derived( + path, + dependency, + this.influencedBy + influenceBy, + inputs.map { node -> node.influence(influencedBy) }.toSet() + ) + } + companion object { - /** A [DependencyNode] case to represent literals. */ - val LITERAL = DerivedFrom() + val LITERAL = Derived() } } + +/** Flatten a collection of nested [DependencyNode]s. */ +fun Collection.flatten(): Set { + return this.flatMap { node -> + when (node) { + is DependencyNode.AssociationNode -> setOf(node) + node.associations.values.flatten() + is DependencyNode.Derived -> node.inputs + } + }.toSet() +} diff --git a/java/arcs/core/analysis/ExpressionDependencyAnalyzer.kt b/java/arcs/core/analysis/ExpressionDependencyAnalyzer.kt index 9eea46a4a15..f0ecb8410a9 100644 --- a/java/arcs/core/analysis/ExpressionDependencyAnalyzer.kt +++ b/java/arcs/core/analysis/ExpressionDependencyAnalyzer.kt @@ -10,92 +10,163 @@ */ package arcs.core.analysis -import arcs.core.data.Claim -import arcs.core.data.ParticleSpec import arcs.core.data.expression.Expression -private typealias Scope = DependencyNode.AssociationNode +/** A [DependencyNode] holder class to track visitor results, context, and influence. */ +private data class AnalysisResult( + val node: DependencyNode, + val ctx: DependencyNode.AssociationNode = DependencyNode.AssociationNode(), + val influencedBy: Set = emptySet() +) { + + /** Cache [DependencyNode]s that bear influence on other nodes. */ + fun addInfluence(influencer: AnalysisResult): AnalysisResult = + copy(influencedBy = influencedBy + influencer.node) +} /** * A visitor that parses Paxel [Expression]s to produce data flow dependencies. * - * For each [Expression], this visitor produces a [DependencyNode], which can be translated into a - * set of [Claim] relationships. + * For each [Expression], this visitor produces an [AnalysisResult], which can be translated into a + * set of claims relationships via the [analyze] function. * - * [DependencyNode]s are DAG structures that help map handle connections in a [ParticleSpec] to the - * target [Expression]. + * [AnalysisResult] structure [DependencyNode]s into a DAG. These map handle connections in a + * `ParticleSpec` to the target [Expression]. */ -class ExpressionDependencyAnalyzer : Expression.Visitor { - override fun visit(expr: Expression.UnaryExpression, ctx: Scope): DependencyNode = - DependencyNode.DerivedFrom(expr.expr.accept(this, ctx)) - - override fun visit(expr: Expression.BinaryExpression, ctx: Scope) = - DependencyNode.DerivedFrom( - expr.left.accept(this, ctx), - expr.right.accept(this, ctx) +private class ExpressionDependencyAnalyzer : Expression.Visitor { + override fun visit( + expr: Expression.UnaryExpression, + ctx: AnalysisResult + ): AnalysisResult { + val exprResult = expr.expr.accept(this, ctx) + return exprResult.copy(node = DependencyNode.Derived(exprResult.node)) + } + + override fun visit( + expr: Expression.BinaryExpression, + ctx: AnalysisResult + ): AnalysisResult { + val leftResult = expr.left.accept(this, ctx) + val rightResult = expr.right.accept(this, ctx) + return AnalysisResult( + node = DependencyNode.Derived(leftResult.node, rightResult.node), + ctx = ctx.ctx, + influencedBy = leftResult.influencedBy + rightResult.influencedBy ) + } - override fun visit(expr: Expression.FieldExpression, ctx: Scope): DependencyNode { - return when (val qualifier = expr.qualifier?.accept(this, ctx)) { - null -> ctx.associations[expr.field] ?: DependencyNode.Input(expr.field) + override fun visit(expr: Expression.FieldExpression, ctx: AnalysisResult): AnalysisResult { + val defaultNode = ctx.ctx[expr.field] ?: DependencyNode.Input(expr.field) + val qualifierResult = expr.qualifier?.accept(this, ctx) + ?: return AnalysisResult(defaultNode) + + val node = when (val qualifier = qualifierResult.node) { is DependencyNode.Input -> DependencyNode.Input( qualifier.path + expr.field ) - is DependencyNode.AssociationNode -> qualifier.lookup(expr.field) - is DependencyNode.DerivedFrom -> throw UnsupportedOperationException( - "Field access is not defined on a '${expr.qualifier}'." - ) + is DependencyNode.AssociationNode -> requireNotNull(qualifier[expr.field]) { + "Field ${expr.field} not found in ${expr.qualifier}." + } + is DependencyNode.Derived -> requireNotNull( + qualifier.inputs.find { it.path.last() == expr.field } + ) { + "Field ${expr.field} not found in ${expr.qualifier}." + } } + + return AnalysisResult(node, influencedBy = qualifierResult.influencedBy) } - override fun visit(expr: Expression.QueryParameterExpression, ctx: Scope): DependencyNode { + override fun visit( + expr: Expression.QueryParameterExpression, + ctx: AnalysisResult + ): AnalysisResult { TODO("Not yet implemented") } - override fun visit(expr: Expression.NumberLiteralExpression, ctx: Scope) = DependencyNode.LITERAL - - override fun visit(expr: Expression.TextLiteralExpression, ctx: Scope) = DependencyNode.LITERAL - - override fun visit(expr: Expression.BooleanLiteralExpression, ctx: Scope) = DependencyNode.LITERAL - - override fun visit(expr: Expression.NullLiteralExpression, ctx: Scope) = DependencyNode.LITERAL + override fun visit( + expr: Expression.NumberLiteralExpression, + ctx: AnalysisResult + ): AnalysisResult = + AnalysisResult(DependencyNode.LITERAL) + + override fun visit(expr: Expression.TextLiteralExpression, ctx: AnalysisResult): AnalysisResult = + AnalysisResult(DependencyNode.LITERAL) + + override fun visit( + expr: Expression.BooleanLiteralExpression, + ctx: AnalysisResult + ): AnalysisResult = + AnalysisResult(DependencyNode.LITERAL) + + override fun visit(expr: Expression.NullLiteralExpression, ctx: AnalysisResult): AnalysisResult = + AnalysisResult(DependencyNode.LITERAL) + + override fun visit(expr: Expression.FromExpression, ctx: AnalysisResult): AnalysisResult { + val scope = (expr.qualifier?.accept(this, ctx) ?: ctx) + return scope.copy( + ctx = scope.ctx.add( + expr.iterationVar to expr.source.accept( + this, + scope + ).node + ) + ) + } - override fun visit(expr: Expression.FromExpression, ctx: Scope): DependencyNode { - val scope = (expr.qualifier?.accept(this, ctx) ?: ctx) as Scope - return scope.add(expr.iterationVar to expr.source.accept(this, scope)) + override fun visit(expr: Expression.WhereExpression, ctx: AnalysisResult): AnalysisResult { + val qualifier = expr.qualifier.accept(this, ctx) + return qualifier.addInfluence(expr.expr.accept(this, qualifier)) } - override fun visit(expr: Expression.SelectExpression, ctx: Scope): DependencyNode { - val qualifier = expr.qualifier.accept(this, ctx) as Scope - return expr.expr.accept(this, qualifier) + override fun visit( + expr: Expression.SelectExpression, + ctx: AnalysisResult + ): AnalysisResult { + val qualifier = expr.qualifier.accept(this, ctx) + val result = expr.expr.accept(this, qualifier) + return result.copy(node = result.node.influence(qualifier.influencedBy)) } - override fun visit(expr: Expression.LetExpression, ctx: Scope): DependencyNode { - val qualifier = expr.qualifier.accept(this, ctx) as Scope - return qualifier.add( - expr.variableName to expr.variableExpr.accept(this, qualifier) + override fun visit(expr: Expression.LetExpression, ctx: AnalysisResult): AnalysisResult { + val scope = expr.qualifier.accept(this, ctx) + return scope.copy( + ctx = scope.ctx.add( + expr.variableName to expr.variableExpr.accept( + this, + scope + ).node + ) ) } - override fun visit(expr: Expression.FunctionExpression, ctx: Scope): DependencyNode { + override fun visit( + expr: Expression.FunctionExpression, + ctx: AnalysisResult + ): AnalysisResult { TODO("Not yet implemented") } - override fun visit(expr: Expression.NewExpression, ctx: Scope) = DependencyNode.AssociationNode( - expr.fields.associateBy({ it.first }, { it.second.accept(this, ctx) }) - ) - - override fun visit(expr: Expression.OrderByExpression, ctx: Scope): DependencyNode { - TODO("Not yet implemented") + override fun visit(expr: Expression.NewExpression, ctx: AnalysisResult): AnalysisResult { + return AnalysisResult( + DependencyNode.AssociationNode( + associations = expr.fields.associateBy({ it.first }, { it.second.accept(this, ctx).node }) + ) + ) } - override fun visit(expr: Expression.WhereExpression, ctx: Scope): DependencyNode { + override fun visit( + expr: Expression.OrderByExpression, + ctx: AnalysisResult + ): AnalysisResult { TODO("Not yet implemented") } } -/** Analyze data flow relationships in a Paxel [Expression]. */ -fun Expression.analyze() = this.accept( - ExpressionDependencyAnalyzer(), - DependencyNode.AssociationNode() -) +/** Analyze data flow relationships in an [Expression]. */ +fun Expression.analyze(): DependencyNode { + return this.accept( + ExpressionDependencyAnalyzer(), + AnalysisResult(DependencyNode.AssociationNode()) + ).node +} diff --git a/javatests/arcs/core/analysis/ExpressionDependencyAnalyzerTest.kt b/javatests/arcs/core/analysis/ExpressionDependencyAnalyzerTest.kt index 0d5263604a7..48838dfeb19 100644 --- a/javatests/arcs/core/analysis/ExpressionDependencyAnalyzerTest.kt +++ b/javatests/arcs/core/analysis/ExpressionDependencyAnalyzerTest.kt @@ -70,7 +70,7 @@ class ExpressionDependencyAnalyzerTest { val actual = expr.analyze() - assertThat(actual).isEqualTo(DependencyNode.DerivedFrom(DependencyNode.Input("x"))) + assertThat(actual).isEqualTo(DependencyNode.Derived(DependencyNode.Input("x"))) } @Test @@ -98,7 +98,7 @@ class ExpressionDependencyAnalyzerTest { val actual = expr.analyze() assertThat(actual).isEqualTo( - DependencyNode.DerivedFrom( + DependencyNode.Derived( DependencyNode.Input("x", "foo", "bar"), DependencyNode.Input("y", "foo", "bar", "baz"), DependencyNode.Input("z", "baz", "bar") @@ -122,7 +122,7 @@ class ExpressionDependencyAnalyzerTest { val actual = expr.analyze() assertThat(actual).isEqualTo( - DependencyNode.DerivedFrom( + DependencyNode.Derived( DependencyNode.Input("x", "foo", "bar"), DependencyNode.Input("y", "foo", "bar", "baz"), DependencyNode.Input("z", "baz", "bar") @@ -165,7 +165,7 @@ class ExpressionDependencyAnalyzerTest { val actual = expr.analyze() assertThat(actual).isEqualTo( - DependencyNode.DerivedFrom( + DependencyNode.Derived( DependencyNode.Input("x"), DependencyNode.Input("y") ) @@ -178,7 +178,7 @@ class ExpressionDependencyAnalyzerTest { val actual = expr.analyze() - assertThat(actual).isEqualTo(DependencyNode.DerivedFrom(listOf("x"))) + assertThat(actual).isEqualTo(DependencyNode.Derived(DependencyNode.Input("x"))) } @Test @@ -191,7 +191,7 @@ class ExpressionDependencyAnalyzerTest { DependencyNode.AssociationNode( "foo" to DependencyNode.Input("input", "foo"), "bar" to - DependencyNode.DerivedFrom( + DependencyNode.Derived( DependencyNode.Input("input", "foo", "bar"), DependencyNode.Input("input", "foo") ) @@ -215,7 +215,7 @@ class ExpressionDependencyAnalyzerTest { val actual = expr.analyze() assertThat(actual).isEqualTo( - DependencyNode.DerivedFrom( + DependencyNode.Derived( DependencyNode.Input("foo", "x"), DependencyNode.Input("foo", "y") ) @@ -307,7 +307,7 @@ class ExpressionDependencyAnalyzerTest { val actual = expr.analyze() assertThat(actual).isEqualTo( - DependencyNode.DerivedFrom( + DependencyNode.Derived( DependencyNode.Input("foo", "x"), DependencyNode.Input("foo", "y") ) @@ -399,7 +399,7 @@ class ExpressionDependencyAnalyzerTest { val actual = expr.analyze() assertThat(actual).isEqualTo( - DependencyNode.DerivedFrom( + DependencyNode.Derived( DependencyNode.Input("foo", "x"), DependencyNode.Input("foo", "y"), DependencyNode.Input("foo", "z") From d81df774494110379d5e56e8caf7b95f83adcc80 Mon Sep 17 00:00:00 2001 From: Alex Rosengarten Date: Fri, 29 Jan 2021 17:38:27 -0800 Subject: [PATCH 2/6] Added tests that illustrate the deducer tracks influence in dependency nodes. --- java/arcs/core/analysis/DependencyNode.kt | 13 +- .../ExpressionDependencyAnalyzerTest.kt | 222 ++++++++++++++++++ 2 files changed, 231 insertions(+), 4 deletions(-) diff --git a/java/arcs/core/analysis/DependencyNode.kt b/java/arcs/core/analysis/DependencyNode.kt index bad80711cf5..de00b487558 100644 --- a/java/arcs/core/analysis/DependencyNode.kt +++ b/java/arcs/core/analysis/DependencyNode.kt @@ -184,10 +184,15 @@ sealed class DependencyNode { constructor() : this(emptyList(), emptySet(), emptySet(), emptySet()) /** Constructor to express derivation of [Input]s. */ - constructor(vararg inputs: DependencyNode) : this( - emptyList(), - emptySet(), - emptySet(), + constructor( + vararg inputs: DependencyNode, + path: Path = emptyList(), + dependency: Set = emptySet(), + influencedBy: Set = emptySet() + ) : this( + path, + dependency, + influencedBy, setOf(*inputs).flatten() ) diff --git a/javatests/arcs/core/analysis/ExpressionDependencyAnalyzerTest.kt b/javatests/arcs/core/analysis/ExpressionDependencyAnalyzerTest.kt index 48838dfeb19..15fe93c316b 100644 --- a/javatests/arcs/core/analysis/ExpressionDependencyAnalyzerTest.kt +++ b/javatests/arcs/core/analysis/ExpressionDependencyAnalyzerTest.kt @@ -425,4 +425,226 @@ class ExpressionDependencyAnalyzerTest { assertThat(actual).isEqualTo(DependencyNode.Input("foo", "a")) } + + @Test + fun from_where_select() { + val expr = PaxelParser.parse( + """ + from f in foo + where f.x > 10 + select f.y + """.trimIndent() + ) + + val actual = expr.analyze() + + assertThat(actual).isEqualTo( + DependencyNode.Input( + "foo", "y", influencedBy = setOf( + DependencyNode.Derived( + DependencyNode.Input("foo", "x") + ) + ) + ) + ) + } + + @Test + fun from_where_binop_select() { + val expr = PaxelParser.parse( + """ + from f in foo + where (f.y + f.z) > 10 + select f.x + """.trimIndent() + ) + + val actual = expr.analyze() + + assertThat(actual).isEqualTo( + DependencyNode.Input( + "foo", "x", + influencedBy = setOf( + DependencyNode.Derived( + DependencyNode.Input("foo", "y"), + DependencyNode.Input("foo", "z") + ) + ) + ) + ) + } + + @Test + fun from_from_where_select() { + val expr = PaxelParser.parse( + """ + from f in foo + from b in bar + where (f.x + b.x) > 10 + select f.y + b.y + """.trimIndent() + ) + + val actual = expr.analyze() + + assertThat(actual).isEqualTo( + DependencyNode.Derived( + DependencyNode.Input("foo", "y"), + DependencyNode.Input("bar", "y"), + influencedBy = setOf( + DependencyNode.Derived( + DependencyNode.Input("foo", "x"), + DependencyNode.Input("bar", "x") + ) + ) + ) + ) + } + + @Test + fun from_where_where_select() { + val expr = PaxelParser.parse( + """ + from f in foo + where f.x > 10 + where f.z < 100 + select f.y + """.trimIndent() + ) + + val actual = expr.analyze() + + assertThat(actual).isEqualTo( + DependencyNode.Input( + "foo", "y", + influencedBy = setOf( + DependencyNode.Derived(DependencyNode.Input("foo", "x")), + DependencyNode.Derived(DependencyNode.Input("foo", "z")) + ) + ) + ) + } + + @Test + fun from_where_from_where_select() { + val expr = PaxelParser.parse( + """ + from f in foo + where f.x > 10 + from b in bar + where b.x < 10 + select f.y + b.y + """.trimIndent() + ) + + val actual = expr.analyze() + + val fooInfluencedBy = DependencyNode.Derived(DependencyNode.Input("foo", "x")) + val barInfluencedBy = DependencyNode.Derived(DependencyNode.Input("bar", "x")) + + assertThat(actual).isEqualTo( + DependencyNode.Derived( + DependencyNode.Input("foo", "y"), + DependencyNode.Input("bar", "y"), + influencedBy = setOf(fooInfluencedBy, barInfluencedBy) + ) + ) + } + + @Test + fun from_where_let_select() { + val expr = PaxelParser.parse( + """ + from f in foo + where f.x > 10 + let y = f.y + select y + """.trimIndent() + ) + + val actual = expr.analyze() + + assertThat(actual).isEqualTo( + DependencyNode.Input( + "foo", + "y", + influencedBy = setOf( + DependencyNode.Derived( + DependencyNode.Input("foo", "x") + ) + ) + ) + ) + } + + @Test + fun sub_from_where_select_expr() { + val expr = PaxelParser.parse( + """ + new Foo { + a: (from f in foo where f.y > 12 select f.x), + b: foo.z + } + """.trimIndent() + ) + + val actual = expr.analyze() + + assertThat(actual).isEqualTo( + DependencyNode.AssociationNode( + "a" to DependencyNode.Input( + "foo", "x", + influencedBy = setOf(DependencyNode.Derived(DependencyNode.Input("foo", "y"))) + ), + "b" to DependencyNode.Input("foo", "z") + ) + ) + } + + @Test + fun from_where_select_new() { + val expr = PaxelParser.parse( + """ + from f in foo + where f.x > 10 + select new Foo { a: f.y } + """.trimIndent() + ) + + val actual = expr.analyze() + + assertThat(actual).isEqualTo( + DependencyNode.AssociationNode( + "a" to DependencyNode.Input("foo", "y"), + influencedBy = setOf(DependencyNode.Derived(DependencyNode.Input("foo", "x"))) + ) + ) + } + + @Test + fun from_where_select_new_derived() { + val expr = PaxelParser.parse( + """ + from f in foo + where f.x > 10 + select new Bar { + a: f.x, + b: f.y + f.z + } + """.trimIndent() + ) + + val actual = expr.analyze() + + assertThat(actual).isEqualTo( + DependencyNode.AssociationNode( + "a" to DependencyNode.Input("foo", "x"), + "b" to DependencyNode.Derived( + DependencyNode.Input("foo", "y"), + DependencyNode.Input("foo", "z") + ), + influencedBy = setOf(DependencyNode.Derived(DependencyNode.Input("foo", "x"))) + ) + ) + } } From d7aaf9511c3ab79dfb044a8fc64cee4a31800248 Mon Sep 17 00:00:00 2001 From: Alex Rosengarten Date: Fri, 29 Jan 2021 17:44:09 -0800 Subject: [PATCH 3/6] Added toString method for Inputs. Removed term "Paxel" from comments. --- java/arcs/core/analysis/DependencyNode.kt | 10 +++++++--- .../arcs/core/analysis/ExpressionDependencyAnalyzer.kt | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/java/arcs/core/analysis/DependencyNode.kt b/java/arcs/core/analysis/DependencyNode.kt index de00b487558..618244699ab 100644 --- a/java/arcs/core/analysis/DependencyNode.kt +++ b/java/arcs/core/analysis/DependencyNode.kt @@ -21,10 +21,10 @@ private typealias Path = List /** * [DependencyNode]s make up a directed-acyclic-graph that describes how input handle connections - * map to output connections in particle specs with Paxel [Expression]s. + * map to output connections in particle specs with [Expression]s. * * - [DependencyNode.Input] represents an input handle connection and access path. - * - [DependencyNode.Derived] indicates that an input has been modified in the Paxel expression. + * - [DependencyNode.Derived] indicates that an input has been modified in the expression. * - [DependencyNode.AssociationNode] connects fields to other nodes in the graph. These are used to * form left-hand-side / right-hand-side relations between handle connections. * @@ -157,7 +157,7 @@ sealed class DependencyNode { } } - /** An unmodified input (from a handle connection) used in a Paxel [Expression]. */ + /** An unmodified input (from a handle connection) used in a [Expression]. */ class Input( override val path: Path, override val dependency: Set = emptySet(), @@ -170,6 +170,10 @@ sealed class DependencyNode { dependency: Set = emptySet(), influencedBy: Set = emptySet() ) : this(listOf(*identifier), dependency, influencedBy) + + override fun toString(): String { + return "Input(path=$path, dependency=$dependency, influencedBy=$influencedBy)" + } } /** Represents derivation from a group of [Input]s in an [Expression]. */ diff --git a/java/arcs/core/analysis/ExpressionDependencyAnalyzer.kt b/java/arcs/core/analysis/ExpressionDependencyAnalyzer.kt index f0ecb8410a9..ab3229978e3 100644 --- a/java/arcs/core/analysis/ExpressionDependencyAnalyzer.kt +++ b/java/arcs/core/analysis/ExpressionDependencyAnalyzer.kt @@ -25,7 +25,7 @@ private data class AnalysisResult( } /** - * A visitor that parses Paxel [Expression]s to produce data flow dependencies. + * A visitor that parses [Expression]s to produce data flow dependencies. * * For each [Expression], this visitor produces an [AnalysisResult], which can be translated into a * set of claims relationships via the [analyze] function. From aad62695f2a6bdda723357a7b06598ec05d2fffc Mon Sep 17 00:00:00 2001 From: Alex Rosengarten Date: Fri, 29 Jan 2021 17:44:48 -0800 Subject: [PATCH 4/6] type: s/a/an/d --- java/arcs/core/analysis/DependencyNode.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/arcs/core/analysis/DependencyNode.kt b/java/arcs/core/analysis/DependencyNode.kt index 618244699ab..69cd3208e44 100644 --- a/java/arcs/core/analysis/DependencyNode.kt +++ b/java/arcs/core/analysis/DependencyNode.kt @@ -157,7 +157,7 @@ sealed class DependencyNode { } } - /** An unmodified input (from a handle connection) used in a [Expression]. */ + /** An unmodified input (from a handle connection) used in an [Expression]. */ class Input( override val path: Path, override val dependency: Set = emptySet(), From 557c2da72854231f6734e976d4e3a9ecb17a2266 Mon Sep 17 00:00:00 2001 From: Alex Rosengarten Date: Fri, 29 Jan 2021 17:48:40 -0800 Subject: [PATCH 5/6] Added comment back for LITERAL. --- java/arcs/core/analysis/DependencyNode.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/java/arcs/core/analysis/DependencyNode.kt b/java/arcs/core/analysis/DependencyNode.kt index 69cd3208e44..fd72a21e053 100644 --- a/java/arcs/core/analysis/DependencyNode.kt +++ b/java/arcs/core/analysis/DependencyNode.kt @@ -210,6 +210,7 @@ sealed class DependencyNode { } companion object { + /** A [DependencyNode] case to represent literals. */ val LITERAL = Derived() } } From 5c1803472881be68b76393e937fb9b3ac3df5626 Mon Sep 17 00:00:00 2001 From: Alex Rosengarten Date: Thu, 4 Feb 2021 15:19:35 -0800 Subject: [PATCH 6/6] Reworded docs, adjusted whitespace, updated one return value to better track context. --- .../analysis/ExpressionDependencyAnalyzer.kt | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/java/arcs/core/analysis/ExpressionDependencyAnalyzer.kt b/java/arcs/core/analysis/ExpressionDependencyAnalyzer.kt index ab3229978e3..b3c4aa89f4a 100644 --- a/java/arcs/core/analysis/ExpressionDependencyAnalyzer.kt +++ b/java/arcs/core/analysis/ExpressionDependencyAnalyzer.kt @@ -30,8 +30,8 @@ private data class AnalysisResult( * For each [Expression], this visitor produces an [AnalysisResult], which can be translated into a * set of claims relationships via the [analyze] function. * - * [AnalysisResult] structure [DependencyNode]s into a DAG. These map handle connections in a - * `ParticleSpec` to the target [Expression]. + * [AnalysisResult] structures [DependencyNode]s into a DAG. These map `ParticleSpec` input fields + * to output fields as specified by the [Expression]. */ private class ExpressionDependencyAnalyzer : Expression.Visitor { override fun visit( @@ -74,7 +74,7 @@ private class ExpressionDependencyAnalyzer : Expression.Visitor visit( @@ -106,10 +106,7 @@ private class ExpressionDependencyAnalyzer : Expression.Visitor