Skip to content

Commit c23b2d4

Browse files
popematttgregg
authored andcommitted
Macro implementations and conformance test fixes (#1003)
1 parent a626a8e commit c23b2d4

14 files changed

+340
-53
lines changed

ion-tests

Submodule ion-tests updated 63 files

src/main/java/com/amazon/ion/impl/IonCursorBinary.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -2348,7 +2348,7 @@ private void uncheckedReadMacroInvocationHeader(IonTypeID valueTid, Marker marke
23482348
private boolean uncheckedReadHeader(final int typeIdByte, final boolean isAnnotated, final Marker markerToSet) {
23492349
IonTypeID valueTid = typeIds[typeIdByte];
23502350
if (!valueTid.isValid) {
2351-
throw new IonException("Invalid type ID.");
2351+
throw new IonException("Invalid type ID: " + valueTid.theByte);
23522352
} else if (valueTid.type == IonTypeID.ION_TYPE_ANNOTATION_WRAPPER) {
23532353
if (isAnnotated) {
23542354
throw new IonException("Nested annotation wrappers are invalid.");

src/main/java/com/amazon/ion/impl/IonReaderContinuableCoreBinary.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -1638,8 +1638,7 @@ protected void readParameter(Macro.Parameter parameter, long parameterPresence,
16381638
case OneOrMore:
16391639
if (parameterPresence == PresenceBitmap.EXPRESSION) {
16401640
readSingleExpression(parameter, expressions);
1641-
}
1642-
if (parameterPresence == PresenceBitmap.GROUP) {
1641+
} else if (parameterPresence == PresenceBitmap.GROUP) {
16431642
readGroupExpression(parameter, expressions, false);
16441643
} else {
16451644
throw new IonException(String.format(

src/main/java/com/amazon/ion/impl/IonReaderTextSystemX.java

+3
Original file line numberDiff line numberDiff line change
@@ -1237,6 +1237,9 @@ private class TextEExpressionArgsReader extends EExpressionArgsReader {
12371237
@Override
12381238
protected void readParameter(Macro.Parameter parameter, long parameterPresence, List<Expression.EExpressionBodyExpression> expressions, boolean isTrailing) {
12391239
if (IonReaderTextSystemX.this.nextRaw() == null) {
1240+
// Add an empty expression group if nothing present.
1241+
int index = expressions.size() + 1;
1242+
expressions.add(new Expression.ExpressionGroup(index, index));
12401243
return;
12411244
}
12421245
readValueAsExpression(isTrailing && parameter.getCardinality().canBeMulti, expressions);

src/main/java/com/amazon/ion/impl/bin/IonManagedWriter_1_1.kt

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ internal class IonManagedWriter_1_1(
3434
private val onClose: () -> Unit,
3535
) : _Private_IonWriter, MacroAwareIonWriter {
3636

37+
internal fun getRawUserWriter(): IonRawWriter_1_1 = userData
38+
3739
companion object {
3840
private val ION_VERSION_MARKER_REGEX = Regex("^\\\$ion_\\d+_\\d+$")
3941

src/main/java/com/amazon/ion/impl/macro/Expression.kt

+3
Original file line numberDiff line numberDiff line change
@@ -95,18 +95,21 @@ sealed interface Expression {
9595

9696
sealed interface IntValue : DataModelValue {
9797
val bigIntegerValue: BigInteger
98+
val longValue: Long
9899
}
99100

100101
data class LongIntValue(override val annotations: List<SymbolToken> = emptyList(), val value: Long) : IntValue {
101102
override val type: IonType get() = IonType.INT
102103
override fun withAnnotations(annotations: List<SymbolToken>) = copy(annotations = annotations)
103104
override val bigIntegerValue: BigInteger get() = BigInteger.valueOf(value)
105+
override val longValue: Long get() = value
104106
}
105107

106108
data class BigIntValue(override val annotations: List<SymbolToken> = emptyList(), val value: BigInteger) : IntValue {
107109
override val type: IonType get() = IonType.INT
108110
override fun withAnnotations(annotations: List<SymbolToken>) = copy(annotations = annotations)
109111
override val bigIntegerValue: BigInteger get() = value
112+
override val longValue: Long get() = value.longValueExact()
110113
}
111114

112115
data class FloatValue(override val annotations: List<SymbolToken> = emptyList(), val value: Double) : DataModelValue {

src/main/java/com/amazon/ion/impl/macro/MacroCompiler.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ internal class MacroCompiler(
258258
}
259259
else -> throw IonException("macro invocation must start with an id (int) or identifier (symbol); found ${encodingType() ?: "nothing"}\"")
260260
}
261-
val m = if (isQualifiedSystemMacro) SystemMacro.get(macroRef) else getMacro(macroRef)
261+
val m = if (isQualifiedSystemMacro) SystemMacro.getMacroOrSpecialForm(macroRef) else getMacro(macroRef)
262262
return m ?: throw IonException("Unrecognized macro: $macroRef")
263263
}
264264

src/main/java/com/amazon/ion/impl/macro/MacroEvaluator.kt

+140-14
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
// SPDX-License-Identifier: Apache-2.0
33
package com.amazon.ion.impl.macro
44

5-
import com.amazon.ion.IonException
6-
import com.amazon.ion.SymbolToken
5+
import com.amazon.ion.*
76
import com.amazon.ion.impl._Private_RecyclingStack
87
import com.amazon.ion.impl._Private_Utils.newSymbolToken
98
import com.amazon.ion.impl.macro.Expression.*
109
import java.io.ByteArrayOutputStream
1110
import java.math.BigDecimal
11+
import java.math.BigInteger
1212

1313
/**
1414
* Evaluates an EExpression from a List of [EExpressionBodyExpression] and the [TemplateBodyExpression]s
@@ -209,6 +209,119 @@ class MacroEvaluator {
209209
}
210210
}
211211

212+
private object MakeTimestampExpander : Expander {
213+
private fun readOptionalIntArg(
214+
signatureIndex: Int,
215+
expansionInfo: ExpansionInfo,
216+
macroEvaluator: MacroEvaluator
217+
): Int? {
218+
if (expansionInfo.i == expansionInfo.endExclusive) return null
219+
val parameterName = SystemMacro.MakeTimestamp.signature[signatureIndex].variableName
220+
val arg = readZeroOrOneExpandedArgumentValues(expansionInfo, macroEvaluator, parameterName)
221+
return arg?.let {
222+
it as? IntValue ?: throw IonException("$parameterName must be an integer")
223+
it.longValue.toInt()
224+
}
225+
}
226+
227+
override fun nextExpression(expansionInfo: ExpansionInfo, macroEvaluator: MacroEvaluator): Expression {
228+
val year = readExactlyOneExpandedArgumentValue(expansionInfo, macroEvaluator, SystemMacro.MakeTimestamp.signature[0].variableName)
229+
.let { it as? IntValue ?: throw IonException("year must be an integer") }
230+
.longValue.toInt()
231+
val month = readOptionalIntArg(1, expansionInfo, macroEvaluator)
232+
val day = readOptionalIntArg(2, expansionInfo, macroEvaluator)
233+
val hour = readOptionalIntArg(3, expansionInfo, macroEvaluator)
234+
val minute = readOptionalIntArg(4, expansionInfo, macroEvaluator)
235+
val second = if (expansionInfo.i == expansionInfo.endExclusive) {
236+
null
237+
} else when (val arg = readZeroOrOneExpandedArgumentValues(expansionInfo, macroEvaluator, SystemMacro.MakeTimestamp.signature[5].variableName)) {
238+
null -> null
239+
is DecimalValue -> arg.value
240+
is IntValue -> arg.longValue.toBigDecimal()
241+
else -> throw IonException("second must be a decimal")
242+
}
243+
val offsetMinutes = readOptionalIntArg(6, expansionInfo, macroEvaluator)
244+
245+
try {
246+
val ts = if (second != null) {
247+
month ?: throw IonException("make_timestamp: month is required when second is present")
248+
day ?: throw IonException("make_timestamp: day is required when second is present")
249+
hour ?: throw IonException("make_timestamp: hour is required when second is present")
250+
minute ?: throw IonException("make_timestamp: minute is required when second is present")
251+
Timestamp.forSecond(year, month, day, hour, minute, second, offsetMinutes)
252+
} else if (minute != null) {
253+
month ?: throw IonException("make_timestamp: month is required when minute is present")
254+
day ?: throw IonException("make_timestamp: day is required when minute is present")
255+
hour ?: throw IonException("make_timestamp: hour is required when minute is present")
256+
Timestamp.forMinute(year, month, day, hour, minute, offsetMinutes)
257+
} else if (hour != null) {
258+
throw IonException("make_timestamp: minute is required when hour is present")
259+
} else {
260+
if (offsetMinutes != null) throw IonException("make_timestamp: offset_minutes is prohibited when hours and minute are not present")
261+
if (day != null) {
262+
month ?: throw IonException("make_timestamp: month is required when day is present")
263+
Timestamp.forDay(year, month, day)
264+
} else if (month != null) {
265+
Timestamp.forMonth(year, month)
266+
} else {
267+
Timestamp.forYear(year)
268+
}
269+
}
270+
return TimestampValue(value = ts)
271+
} catch (e: IllegalArgumentException) {
272+
throw IonException(e.message)
273+
}
274+
}
275+
}
276+
277+
private object SumExpander : Expander {
278+
override fun nextExpression(expansionInfo: ExpansionInfo, macroEvaluator: MacroEvaluator): Expression {
279+
val a = readExactlyOneExpandedArgumentValue(expansionInfo, macroEvaluator, "a")
280+
val b = readExactlyOneExpandedArgumentValue(expansionInfo, macroEvaluator, "b")
281+
if (a !is IntValue || b !is IntValue) throw IonException("operands of sum must be integers")
282+
// TODO: Use LongIntValue when possible.
283+
return BigIntValue(value = a.bigIntegerValue + b.bigIntegerValue)
284+
}
285+
}
286+
287+
private object DeltaExpander : Expander {
288+
override fun nextExpression(expansionInfo: ExpansionInfo, macroEvaluator: MacroEvaluator): Expression {
289+
// TODO: Optimize to use Long and only fallback to BigInteger if needed.
290+
// TODO: Optimize for lazy evaluation
291+
if (expansionInfo.additionalState == null) {
292+
val position = expansionInfo.i
293+
var runningTotal = BigInteger.ZERO
294+
val values = ArrayDeque<BigInteger>()
295+
readExpandedArgumentValues(expansionInfo, macroEvaluator) {
296+
when (it) {
297+
is IntValue -> {
298+
runningTotal += it.bigIntegerValue
299+
values += runningTotal
300+
}
301+
is DataModelValue -> throw IonException("Invalid argument type for 'delta': ${it.type}")
302+
is FieldName -> TODO("Unreachable. We shouldn't be able to get here without first encountering a StructValue.")
303+
}
304+
true // continue expansion
305+
}
306+
307+
if (values.isEmpty()) {
308+
// Return fake, empty expression group
309+
return ExpressionGroup(position, position)
310+
}
311+
312+
expansionInfo.additionalState = values
313+
expansionInfo.i = position
314+
}
315+
316+
val valueQueue = expansionInfo.additionalState as ArrayDeque<BigInteger>
317+
val nextValue = valueQueue.removeFirst()
318+
if (valueQueue.isEmpty()) {
319+
expansionInfo.i = expansionInfo.endExclusive
320+
}
321+
return BigIntValue(value = nextValue)
322+
}
323+
}
324+
212325
private enum class IfExpander(private val minInclusive: Int, private val maxExclusive: Int) : Expander {
213326
IF_NONE(0, 1),
214327
IF_SOME(1, -1),
@@ -254,11 +367,10 @@ class MacroEvaluator {
254367
}
255368
nExpression.value.intValueExact()
256369
}
257-
else -> throw IonException("The first argument of repeat must be a positive integer")
370+
else -> throw IonException("The first argument of repeat must be a non-negative integer")
258371
}
259-
if (iterationsRemaining <= 0) {
260-
// TODO: Confirm https://github.com/amazon-ion/ion-docs/issues/350
261-
throw IonException("The first argument of repeat must be a positive integer")
372+
if (iterationsRemaining < 0) {
373+
throw IonException("The first argument of repeat must be a non-negative integer")
262374
}
263375
// Decrement because we're starting the first iteration right away.
264376
iterationsRemaining--
@@ -267,21 +379,27 @@ class MacroEvaluator {
267379
}
268380

269381
override fun nextExpression(expansionInfo: ExpansionInfo, macroEvaluator: MacroEvaluator): Expression {
270-
val repeatsRemaining = expansionInfo.additionalState as? Int
382+
val repeatsRemainingAfterTheCurrentOne = expansionInfo.additionalState as? Int
271383
?: init(expansionInfo, macroEvaluator)
272384

385+
if (repeatsRemainingAfterTheCurrentOne < 0) {
386+
expansionInfo.nextSourceExpression()
387+
return ExpressionGroup(0, 0)
388+
}
389+
273390
val repeatedExpressionIndex = expansionInfo.i
274391
val next = readFirstExpandedArgumentValue(expansionInfo, macroEvaluator)
275-
next ?: throw IonException("repeat macro requires at least one value for value parameter")
276-
if (repeatsRemaining > 0) {
277-
expansionInfo.additionalState = repeatsRemaining - 1
392+
next ?: return ExpressionGroup(0, 0)
393+
if (repeatsRemainingAfterTheCurrentOne > 0) {
394+
expansionInfo.additionalState = repeatsRemainingAfterTheCurrentOne - 1
278395
expansionInfo.i = repeatedExpressionIndex
279396
}
280397
return next
281398
}
282399
}
283400

284401
private object MakeFieldExpander : Expander {
402+
// This is wrong!
285403
override fun nextExpression(expansionInfo: ExpansionInfo, macroEvaluator: MacroEvaluator): Expression {
286404
/**
287405
* Uses [ExpansionInfo.additionalState] to track whether the expansion is on the field name or value.
@@ -317,7 +435,10 @@ class MacroEvaluator {
317435
MakeSymbol(MakeSymbolExpander),
318436
MakeBlob(MakeBlobExpander),
319437
MakeDecimal(MakeDecimalExpander),
438+
MakeTimestamp(MakeTimestampExpander),
320439
MakeField(MakeFieldExpander),
440+
Sum(SumExpander),
441+
Delta(DeltaExpander),
321442
IfNone(IfExpander.IF_NONE),
322443
IfSome(IfExpander.IF_SOME),
323444
IfSingle(IfExpander.IF_SINGLE),
@@ -328,23 +449,28 @@ class MacroEvaluator {
328449
companion object {
329450
@JvmStatic
330451
fun forSystemMacro(macro: SystemMacro): ExpansionKind {
331-
return if (macro.body != null) {
332-
TemplateBody
333-
} else when (macro) {
452+
return when (macro) {
334453
SystemMacro.None -> Values // "none" takes no args, so we can treat it as an empty "values" expansion
335454
SystemMacro.Values -> Values
336455
SystemMacro.Annotate -> Annotate
337456
SystemMacro.MakeString -> MakeString
338457
SystemMacro.MakeSymbol -> MakeSymbol
339458
SystemMacro.MakeBlob -> MakeBlob
340459
SystemMacro.MakeDecimal -> MakeDecimal
460+
SystemMacro.MakeTimestamp -> MakeTimestamp
341461
SystemMacro.IfNone -> IfNone
342462
SystemMacro.IfSome -> IfSome
343463
SystemMacro.IfSingle -> IfSingle
344464
SystemMacro.IfMulti -> IfMulti
345465
SystemMacro.Repeat -> Repeat
346466
SystemMacro.MakeField -> MakeField
347-
else -> throw IllegalStateException("Unreachable. All other macros have a template body.")
467+
SystemMacro.Sum -> Sum
468+
SystemMacro.Delta -> Delta
469+
else -> if (macro.body != null) {
470+
TemplateBody
471+
} else {
472+
TODO("System macro ${macro.macroName} needs either a template body or a hard-coded expander.")
473+
}
348474
}
349475
}
350476
}

src/main/java/com/amazon/ion/impl/macro/ParameterFactory.kt

-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,4 @@ object ParameterFactory {
1616
fun oneToManyTagged(name: String) = Parameter(name, ParameterEncoding.Tagged, ParameterCardinality.OneOrMore)
1717
@JvmStatic
1818
fun exactlyOneTagged(name: String) = Parameter(name, ParameterEncoding.Tagged, ParameterCardinality.ExactlyOne)
19-
@JvmStatic
20-
fun exactlyOneFlexInt(name: String) = Parameter(name, ParameterEncoding.FlexInt, ParameterCardinality.ExactlyOne)
2119
}

src/main/java/com/amazon/ion/impl/macro/SystemMacro.kt

+27-8
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ package com.amazon.ion.impl.macro
55
import com.amazon.ion.impl.*
66
import com.amazon.ion.impl.SystemSymbols_1_1.*
77
import com.amazon.ion.impl.macro.ExpressionBuilderDsl.Companion.templateBody
8-
import com.amazon.ion.impl.macro.ParameterFactory.exactlyOneFlexInt
98
import com.amazon.ion.impl.macro.ParameterFactory.exactlyOneTagged
10-
import com.amazon.ion.impl.macro.ParameterFactory.oneToManyTagged
119
import com.amazon.ion.impl.macro.ParameterFactory.zeroOrOneTagged
1210
import com.amazon.ion.impl.macro.ParameterFactory.zeroToManyTagged
1311

@@ -34,7 +32,22 @@ enum class SystemMacro(
3432
MakeString(3, MAKE_STRING, listOf(zeroToManyTagged("text"))),
3533
MakeSymbol(4, MAKE_SYMBOL, listOf(zeroToManyTagged("text"))),
3634
MakeBlob(5, MAKE_BLOB, listOf(zeroToManyTagged("bytes"))),
37-
MakeDecimal(6, MAKE_DECIMAL, listOf(exactlyOneFlexInt("coefficient"), exactlyOneFlexInt("exponent"))),
35+
MakeDecimal(6, MAKE_DECIMAL, listOf(exactlyOneTagged("coefficient"), exactlyOneTagged("exponent"))),
36+
MakeTimestamp(
37+
7, MAKE_TIMESTAMP,
38+
listOf(
39+
exactlyOneTagged("year"),
40+
zeroOrOneTagged("month"),
41+
zeroOrOneTagged("day"),
42+
zeroOrOneTagged("hour"),
43+
zeroOrOneTagged("minute"),
44+
zeroOrOneTagged("second"),
45+
zeroOrOneTagged("offset_minutes"),
46+
)
47+
),
48+
// TODO: make_list
49+
// TODO: make_sexp
50+
// TODO: make_struct
3851

3952
/**
4053
* ```ion
@@ -176,8 +189,11 @@ enum class SystemMacro(
176189
}
177190
}
178191
),
179-
180-
Repeat(17, REPEAT, listOf(exactlyOneTagged("n"), oneToManyTagged("value"))),
192+
// TODO: parse_ion
193+
Repeat(17, REPEAT, listOf(exactlyOneTagged("n"), zeroToManyTagged("value"))),
194+
Delta(18, DELTA, listOf(zeroToManyTagged("deltas"))),
195+
// TODO: flatten
196+
Sum(20, SUM, listOf(exactlyOneTagged("a"), exactlyOneTagged("b"))),
181197
Comment(21, META, listOf(zeroToManyTagged("values")), templateBody { macro(None) {} }),
182198
MakeField(
183199
22, MAKE_FIELD,
@@ -196,8 +212,6 @@ enum class SystemMacro(
196212
}
197213
}
198214
),
199-
200-
// TODO: Other system macros
201215
;
202216

203217
val macroName: String get() = this.systemSymbol.text
@@ -239,7 +253,12 @@ enum class SystemMacro(
239253

240254
/** Gets a [SystemMacro] by name, including those which are not in the system table (i.e. special forms) */
241255
@JvmStatic
242-
fun getMacroOrSpecialForm(name: String): SystemMacro? = MACROS_BY_NAME[name]
256+
fun getMacroOrSpecialForm(ref: MacroRef): SystemMacro? {
257+
return when (ref) {
258+
is MacroRef.ById -> get(ref.id)
259+
is MacroRef.ByName -> MACROS_BY_NAME[ref.name]
260+
}
261+
}
243262

244263
@JvmStatic
245264
val SYSTEM_MACRO_TABLE = this

0 commit comments

Comments
 (0)