forked from amazon-ion/ion-java
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMacroCompiler.kt
312 lines (272 loc) · 15.2 KB
/
MacroCompiler.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package com.amazon.ion.impl.macro
import com.amazon.ion.*
import com.amazon.ion.impl.*
import com.amazon.ion.impl.bin.Ion_1_1_Constants.*
import com.amazon.ion.impl.macro.Expression.*
import com.amazon.ion.util.*
/**
* [MacroCompiler] wraps an Ion reader. When directed to do so, it will take over advancing and getting values from the
* reader in order to read one [TemplateMacro].
*/
internal class MacroCompiler(
private val getMacro: (MacroRef) -> Macro?,
private val reader: ReaderAdapter
) {
/** The name of the macro that was read. Returns `null` if no macro name is available. */
var macroName: String? = null
private set // Only mutable internally
private val signature: MutableList<Macro.Parameter> = mutableListOf()
private val expressions: MutableList<TemplateBodyExpression> = mutableListOf()
/**
* Compiles a template macro definition from the reader. Caller is responsible for positioning the reader at—but not
* stepped into—the macro template s-expression.
*/
fun compileMacro(): TemplateMacro {
macroName = null
signature.clear()
expressions.clear()
confirm(reader.encodingType() == IonType.SEXP) { "macro compilation expects a sexp starting with the keyword `macro`" }
reader.confirmNoAnnotations("a macro definition sexp")
reader.readContainer {
reader.nextValue()
confirm(reader.encodingType() == IonType.SYMBOL && reader.stringValue() == "macro") { "macro compilation expects a sexp starting with the keyword `macro`" }
reader.nextAndCheckType(IonType.SYMBOL, IonType.NULL, "macro name")
reader.confirmNoAnnotations("macro name")
if (reader.encodingType() != IonType.NULL) {
macroName = reader.stringValue().also { confirm(isIdentifierSymbol(it)) { "invalid macro name: '$it'" } }
}
reader.nextAndCheckType(IonType.SEXP, "macro signature")
reader.confirmNoAnnotations("macro signature")
reader.readSignature()
confirm(reader.nextValue()) { "Macro definition is missing a template body expression." }
reader.compileTemplateBodyExpression()
confirm(!reader.nextValue()) { "Unexpected ${reader.encodingType()} after template body expression." }
}
return TemplateMacro(signature.toList(), expressions.toList())
}
/**
* Reads the macro signature, populating parameters in [signature].
* Caller is responsible for making sure that the reader is positioned on (but not stepped into) the signature sexp.
*/
private fun ReaderAdapter.readSignature() {
var pendingParameter: Macro.Parameter? = null
forEachInContainer {
if (encodingType() != IonType.SYMBOL) throw IonException("parameter must be a symbol; found ${encodingType()}")
val symbolText = stringValue()
val cardinality = Macro.ParameterCardinality.fromSigil(symbolText)
if (cardinality != null) {
confirmNoAnnotations("cardinality sigil")
// The symbol is a cardinality modifier
if (pendingParameter == null) {
throw IonException("Found an orphaned cardinality in macro signature")
} else {
signature.add(pendingParameter!!.copy(cardinality = cardinality))
pendingParameter = null
return@forEachInContainer
}
}
// If we have a pending parameter, add it to the signature before we read the next parameter
if (pendingParameter != null) signature.add(pendingParameter!!)
// Read the next parameter name
val annotations = getTypeAnnotationSymbols()
val parameterEncoding = when (annotations.size) {
0 -> Macro.ParameterEncoding.Tagged
1 -> {
val encodingText = annotations[0].text
val encoding = Macro.ParameterEncoding.entries.singleOrNull { it.ionTextName == encodingText }
if (encoding == null) {
// TODO: Check for macro-shaped parameter encodings, and only if it's still null, we throw.
throw IonException("Unknown parameter encoding: $encodingText")
}
encoding
}
2 -> TODO("Qualified references for macro-shaped parameters")
else -> throw IonException("unsupported parameter encoding $annotations")
}
confirm(isIdentifierSymbol(symbolText)) { "invalid parameter name: '$symbolText'" }
confirm(signature.none { it.variableName == symbolText }) { "redeclaration of parameter '$symbolText'" }
pendingParameter = Macro.Parameter(symbolText, parameterEncoding, Macro.ParameterCardinality.ExactlyOne)
}
// If we have a pending parameter than hasn't been added to the signature, add it here.
if (pendingParameter != null) signature.add(pendingParameter!!)
}
private fun isIdentifierSymbol(symbol: String): Boolean {
if (symbol.isEmpty()) return false
// If the symbol's text matches an Ion keyword, it's not an identifier symbol.
// Eg, the symbol 'false' must be quoted and is not an identifier symbol.
if (_Private_IonTextAppender.isIdentifierKeyword(symbol)) return false
if (!_Private_IonTextAppender.isIdentifierStart(symbol[0].code)) return false
return symbol.all { c -> _Private_IonTextAppender.isIdentifierPart(c.code) }
}
/**
* Compiles the current value on the reader into a [TemplateBodyExpression] and adds it to [expressions].
* Caller is responsible for ensuring that the reader is positioned on a value.
*
* If called when the reader is not positioned on any value, throws [IllegalStateException].
*/
private fun ReaderAdapter.compileTemplateBodyExpression() {
// NOTE: `toList()` does not allocate for an empty list.
val annotations: List<SymbolToken> = getTypeAnnotationSymbols()
if (isNullValue()) {
expressions.add(NullValue(annotations, encodingType()!!))
} else when (encodingType()) {
IonType.BOOL -> expressions.add(BoolValue(annotations, booleanValue()))
IonType.INT -> expressions.add(
when (integerSize()!!) {
IntegerSize.INT,
IntegerSize.LONG -> LongIntValue(annotations, longValue())
IntegerSize.BIG_INTEGER -> BigIntValue(annotations, bigIntegerValue())
}
)
IonType.FLOAT -> expressions.add(FloatValue(annotations, doubleValue()))
IonType.DECIMAL -> expressions.add(DecimalValue(annotations, decimalValue()))
IonType.TIMESTAMP -> expressions.add(TimestampValue(annotations, timestampValue()))
IonType.STRING -> expressions.add(StringValue(annotations, stringValue()))
IonType.BLOB -> expressions.add(BlobValue(annotations, newBytes()))
IonType.CLOB -> expressions.add(ClobValue(annotations, newBytes()))
IonType.SYMBOL -> expressions.add(SymbolValue(annotations, symbolValue()))
IonType.LIST -> compileList(annotations)
IonType.SEXP -> compileSExpression(annotations)
IonType.STRUCT -> compileStruct(annotations)
// IonType.NULL, IonType.DATAGRAM, null
else -> throw IllegalStateException("Found ${encodingType()}; this should be unreachable.")
}
}
/**
* Compiles a struct in a macro template.
* When calling, the reader should be positioned at the struct, but not stepped into it.
* If this function returns normally, it will be stepped out of the struct.
* Caller will need to call [IonReader.next] to get the next value.
*/
private fun ReaderAdapter.compileStruct(annotations: List<SymbolToken>) {
val start = expressions.size
expressions.add(Placeholder)
val templateStructIndex = mutableMapOf<String, ArrayList<Int>>()
forEachInContainer {
val fieldName: SymbolToken = fieldNameSymbol()
expressions.add(FieldName(fieldName))
fieldName.text?.let {
val valueIndex = expressions.size
// Default is an array list with capacity of 1, since the most common case is that a field name occurs once.
templateStructIndex.getOrPut(it) { ArrayList(1) } += valueIndex
}
compileTemplateBodyExpression()
}
val end = expressions.size
expressions[start] = StructValue(annotations, start, end, templateStructIndex)
}
/**
* Compiles a list or sexp in a macro template.
* When calling, the reader should be positioned at the sequence, but not stepped into it.
* If this function returns normally, it will be stepped out of the sequence.
* Caller will need to call [IonReader.next] to get the next value.
*/
private fun ReaderAdapter.compileList(annotations: List<SymbolToken>) {
val start = expressions.size
stepIntoContainer()
expressions.add(Placeholder)
compileExpressionTail(start) { end -> ListValue(annotations, start, end) }
}
/**
* Compiles an unclassified S-Expression in a template body expression.
* When calling, the reader should be positioned at the sexp, but not stepped into it.
* If this function returns normally, it will be stepped out of the sexp.
* Caller will need to call [IonReader.next] to get the next value.
*/
private fun ReaderAdapter.compileSExpression(sexpAnnotations: List<SymbolToken>) {
val start = expressions.size
stepIntoContainer()
expressions.add(Placeholder)
if (nextValue()) {
if (encodingType() == IonType.SYMBOL) {
when (stringValue()) {
TDL_VARIABLE_EXPANSION_SIGIL -> {
confirm(sexpAnnotations.isEmpty()) { "Variable expansion may not be annotated" }
confirmNoAnnotations("Variable expansion operator")
compileVariableExpansion(start)
return
}
TDL_EXPRESSION_GROUP_SIGIL -> {
confirm(sexpAnnotations.isEmpty()) { "Expression group may not be annotated" }
confirmNoAnnotations("Expression group operator")
compileExpressionTail(start) { end -> ExpressionGroup(start, end) }
return
}
TDL_MACRO_INVOCATION_SIGIL -> {
confirm(sexpAnnotations.isEmpty()) { "Macro invocation may not be annotated" }
confirmNoAnnotations("Macro invocation operator")
nextValue()
val macro = readMacroReference()
compileExpressionTail(start) { end -> MacroInvocation(macro, start, end) }
return
}
}
}
// Compile the value we're already positioned on before compiling the rest of the s-expression
compileTemplateBodyExpression()
}
compileExpressionTail(start) { end -> SExpValue(sexpAnnotations, start, end) }
}
/**
* Must be positioned on the (expected) macro reference.
*/
private fun ReaderAdapter.readMacroReference(): Macro {
val annotations = getTypeAnnotationSymbols()
val isQualifiedSystemMacro = annotations.size == 1 && SystemSymbols_1_1.ION.text == annotations[0].getText()
val macroRef = when (encodingType()) {
IonType.SYMBOL -> {
val macroName = stringValue()
// TODO: Come up with a consistent strategy for handling special forms.
MacroRef.ByName(macroName)
}
IonType.INT -> {
val sid = intValue()
if (sid < 0) throw IonException("Macro ID must be non-negative: $sid")
MacroRef.ById(intValue())
}
else -> throw IonException("macro invocation must start with an id (int) or identifier (symbol); found ${encodingType() ?: "nothing"}\"")
}
val m = if (isQualifiedSystemMacro) SystemMacro.getMacroOrSpecialForm(macroRef) else getMacro(macroRef)
return m ?: throw IonException("Unrecognized macro: $macroRef")
}
private fun ReaderAdapter.compileVariableExpansion(placeholderIndex: Int) {
nextValue()
confirm(encodingType() == IonType.SYMBOL) { "Variable names must be symbols" }
val name = stringValue()
confirmNoAnnotations("on variable reference '$name'")
val index = signature.indexOfFirst { it.variableName == name }
confirm(index >= 0) { "variable '$name' is not recognized" }
expressions[placeholderIndex] = VariableRef(index)
confirm(!nextValue()) { "Variable expansion should contain only the variable name." }
stepOutOfContainer()
}
private inline fun ReaderAdapter.compileExpressionTail(seqStart: Int, constructor: (Int) -> TemplateBodyExpression) {
forEachRemaining { compileTemplateBodyExpression() }
val seqEnd = expressions.size
expressions[seqStart] = constructor(seqEnd)
stepOutOfContainer()
}
// Helper functions
/** Utility method for checking that annotations are empty or a single array with the given annotations */
private fun List<SymbolToken>.isEmptyOr(text: String): Boolean = isEmpty() || (size == 1 && this[0].assumeText() == text)
/** Throws [IonException] if any annotations are on the current value in this [IonReader]. */
private fun ReaderAdapter.confirmNoAnnotations(location: String) {
confirm(!hasAnnotations()) { "found annotations on $location" }
}
/** Moves to the next type and throw [IonException] if it is not the `expected` [IonType]. */
private fun ReaderAdapter.nextAndCheckType(expected: IonType, location: String) {
confirm(nextValue() && encodingType() == expected) { "$location must be a $expected; found ${encodingType() ?: "nothing"}" }
}
/** Moves to the next type and throw [IonException] if it is not the `expected` [IonType]. */
private fun ReaderAdapter.nextAndCheckType(expected0: IonType, expected1: IonType, location: String) {
confirm(nextValue() && (encodingType() == expected0 || encodingType() == expected1)) { "$location must be a $expected0 or $expected1; found ${encodingType() ?: "nothing"}" }
}
/** Steps into a container, executes [block], and steps out. */
private inline fun ReaderAdapter.readContainer(block: () -> Unit) { stepIntoContainer(); block(); stepOutOfContainer() }
/** Executes [block] for each remaining value at the current reader depth. */
private inline fun ReaderAdapter.forEachRemaining(block: (IonType) -> Unit) { while (nextValue()) { block(encodingType()!!) } }
/** Steps into a container, executes [block] for each value at that reader depth, and steps out. */
private inline fun ReaderAdapter.forEachInContainer(block: (IonType) -> Unit) = readContainer { forEachRemaining(block) }
}