@@ -105,11 +105,12 @@ class KotlinJmespathExpressionVisitor(
105105 return VisitedExpression (outerName, leftShape, innerResult.shape)
106106 }
107107
108- private fun subfield (expression : FieldExpression , parentName : String ): VisitedExpression {
108+ private fun subfield (expression : FieldExpression , parentName : String , isObject : Boolean = false ): VisitedExpression {
109109 val member = currentShape.targetOrSelf(ctx.model).getMember(expression.name).getOrNull()
110110
111111 val name = expression.name.toCamelCase()
112- val nameExpr = ensureNullGuard(currentShape, name)
112+ // User created objects are represented as hash maps in code-gen and are marked by `isObject`
113+ val nameExpr = if (isObject) " [\" $name \" ]" else ensureNullGuard(currentShape, name)
113114
114115 val unwrapExpr = member?.let {
115116 val memberTarget = ctx.model.expectShape(member.target)
@@ -210,17 +211,20 @@ class KotlinJmespathExpressionVisitor(
210211 codegenReq(arguments.size == 1 ) { " Unexpected number of arguments to $this " }
211212 return acceptSubexpression(this .arguments[0 ])
212213 }
213-
214214 private fun FunctionExpression.twoArgs (): Pair <VisitedExpression , VisitedExpression > {
215215 codegenReq(arguments.size == 2 ) { " Unexpected number of arguments to $this " }
216216 return acceptSubexpression(this .arguments[0 ]) to acceptSubexpression(this .arguments[1 ])
217217 }
218218
219- private fun VisitedExpression.dotFunction (expression : FunctionExpression , expr : String , elvisExpr : String? = null): VisitedExpression {
219+ private fun FunctionExpression.args (): List <VisitedExpression > =
220+ this .arguments.map { acceptSubexpression(it) }
221+
222+ private fun VisitedExpression.dotFunction (expression : FunctionExpression , expr : String , elvisExpr : String? = null, isObject : Boolean = false): VisitedExpression {
220223 val dotFunctionExpr = ensureNullGuard(shape, expr, elvisExpr)
221- val ident = addTempVar(expression.name, " $identifier$dotFunctionExpr " )
224+ val ident = addTempVar(expression.name.toCamelCase() , " $identifier$dotFunctionExpr " )
222225
223- return VisitedExpression (ident, shape)
226+ shape?.let { shapeCursor.addLast(shape) }
227+ return VisitedExpression (ident, shape, isObject = isObject)
224228 }
225229
226230 override fun visitFunction (expression : FunctionExpression ): VisitedExpression = when (expression.name) {
@@ -267,6 +271,21 @@ class KotlinJmespathExpressionVisitor(
267271 subject.dotFunction(expression, " endsWith(${suffix.identifier} )" )
268272 }
269273
274+ " keys" -> {
275+ val obj = expression.singleArg()
276+ VisitedExpression (addTempVar(" keys" , obj.getKeys()))
277+ }
278+
279+ " values" -> {
280+ val obj = expression.singleArg()
281+ VisitedExpression (addTempVar(" values" , obj.getValues()))
282+ }
283+
284+ " merge" -> {
285+ val objects = expression.args()
286+ VisitedExpression (addTempVar(" merge" , objects.mergeProperties()), isObject = true )
287+ }
288+
270289 else -> throw CodegenException (" Unknown function type in $expression " )
271290 }
272291
@@ -391,22 +410,22 @@ class KotlinJmespathExpressionVisitor(
391410 }
392411
393412 override fun visitSubexpression (expression : Subexpression ): VisitedExpression {
394- val leftName = expression.left.accept(this ).identifier
395- return processRightSubexpression(expression.right, leftName )
413+ val left = expression.left.accept(this )
414+ return processRightSubexpression(expression.right, left.identifier, left.isObject )
396415 }
397416
398417 private fun subexpression (expression : Subexpression , parentName : String ): VisitedExpression {
399- val leftName = when (val left = expression.left) {
400- is FieldExpression -> subfield(left, parentName).identifier
401- is Subexpression -> subexpression(left, parentName).identifier
418+ val left = when (val left = expression.left) {
419+ is FieldExpression -> subfield(left, parentName)
420+ is Subexpression -> subexpression(left, parentName)
402421 else -> throw CodegenException (" Subexpression type $left is unsupported" )
403422 }
404- return processRightSubexpression(expression.right, leftName )
423+ return processRightSubexpression(expression.right, left.identifier, left.isObject )
405424 }
406425
407- private fun processRightSubexpression (expression : JmespathExpression , leftName : String ): VisitedExpression =
426+ private fun processRightSubexpression (expression : JmespathExpression , leftName : String , isObject : Boolean = false ): VisitedExpression =
408427 when (expression) {
409- is FieldExpression -> subfield(expression, leftName)
428+ is FieldExpression -> subfield(expression, leftName, isObject )
410429 is IndexExpression -> index(expression, leftName)
411430 is Subexpression -> subexpression(expression, leftName)
412431 is ProjectionExpression -> projection(expression, leftName)
@@ -415,10 +434,7 @@ class KotlinJmespathExpressionVisitor(
415434
416435 private fun index (expression : IndexExpression , parentName : String ): VisitedExpression {
417436 val index = if (expression.index < 0 ) " $parentName .size${expression.index} " else expression.index
418-
419- shapeCursor.addLast(currentShape.targetOrSelf(ctx.model).targetMemberOrSelf)
420- val indexExpr = ensureNullGuard(currentShape, " get($index )" )
421- shapeCursor.removeLast()
437+ val indexExpr = ensureNullGuard(currentShape.targetMemberOrSelf, " get($index )" )
422438
423439 return VisitedExpression (addTempVar(" index" , " $parentName$indexExpr " ), currentShape)
424440 }
@@ -439,6 +455,33 @@ class KotlinJmespathExpressionVisitor(
439455 " .$expr "
440456 }
441457
458+ private fun VisitedExpression.getKeys (): String {
459+ val keys = this .shape?.targetOrSelf(ctx.model)?.allMembers
460+ ?.keys?.joinToString(" , " , " listOf(" , " )" ) { " \" $it \" " }
461+ return keys ? : " listOf<String>()"
462+ }
463+
464+ private fun VisitedExpression.getValues (): String {
465+ val values = this .shape?.targetOrSelf(ctx.model)?.allMembers?.keys
466+ ?.joinToString(" , " , " listOf(" , " )" ) { " ${this .identifier}${ensureNullGuard(this .shape, it)} " }
467+ return values ? : " listOf<String>()"
468+ }
469+
470+ private fun List<VisitedExpression>.mergeProperties (): String {
471+ val union = addTempVar(" union" , " HashMap<String, Any?>()" )
472+
473+ forEach { obj ->
474+ val keys = addTempVar(" keys" , obj.getKeys())
475+ val values = addTempVar(" values" , obj.getValues())
476+
477+ writer.withBlock(" for(i in $keys .indices){" , " }" ) {
478+ write(" union[$keys [i]] = $values [i]" )
479+ }
480+ }
481+
482+ return union
483+ }
484+
442485 private val Shape .isNullable: Boolean
443486 get() = this is MemberShape &&
444487 ctx.model.expectShape(target).let { ! it.hasTrait<OperationInput >() && ! it.hasTrait<OperationOutput >() }
@@ -461,5 +504,9 @@ class KotlinJmespathExpressionVisitor(
461504 * `foo[].bar[].baz.qux`, the shape that backs the identifier (and therefore determines overall nullability)
462505 * is `foo`, but the shape that needs carried through to subfield expressions in the following projection
463506 * is the target of `bar`, such that its subfields `baz` and `qux` can be recognized.
507+ * @param nullable Boolean to indicate that a visited expression is nullable. Shape is used for this mostly but sometimes an
508+ * expression is nullable for reasons that are not shape related
509+ * @param isObject Boolean to indicate that a visited expression results in an object. Objects are represented as hash maps
510+ * because it is not possible to construct a class at runtime
464511 */
465- data class VisitedExpression (val identifier : String , val shape : Shape ? = null , val projected : Shape ? = null , val nullable : Boolean = false )
512+ data class VisitedExpression (val identifier : String , val shape : Shape ? = null , val projected : Shape ? = null , val nullable : Boolean = false , val isObject : Boolean = false )
0 commit comments