// Construct a writer context from symbols originating in different streams.
///|
/// Whether any unknown symbol value, field name, or annotation retains a
/// shared-table import location.
pub fn IonValue::has_imported_symbols(self : IonValue) -> Bool {
let token = fn(found : Bool, token : SymbolToken) -> FoldAction[Bool] {
if token.text is None && token.import_location is Some(_) {
Stop(true)
} else {
Continue(found)
}
}
self.fold(false, {
visit_value: fn(found, value) {
match value.kind() {
Symbol(symbol) => token(found, symbol)
_ => Continue(found)
}
},
visit_annotation: token,
visit_field_name: token,
})
}
///|
/// Build a symbol table for serializing `values`. Unknown imported symbols
/// retain their shared-table name, version, and slot; their stream-local IDs
/// may change. Unknown local IDs are reserved after the imports.
/// Conflicting versions of the same unknown shared table in one context are
/// rejected because a catalog could resolve their null slots differently.
pub fn SymbolTable::for_values(
values : Array[IonValue],
) -> SymbolTable raise IonError {
let tokens : Array[SymbolToken] = []
for value in values {
collect_writer_tokens(value, tokens)
}
let table = SymbolTable::system()
let mut highest_local = 9
for token in tokens {
if token.text is Some(_) {
continue
}
match token.import_location {
Some(location) => {
let version = token.import_version.unwrap_or(1)
if location.name == "" ||
location.name == "$ion" ||
location.sid < 1 ||
version < 1 {
raise DataModel("invalid shared-symbol import location")
}
let mut found = false
for index in 0.. {
let sid = token.sid.unwrap_or(0)
if sid < 0 {
raise DataModel("a symbol ID cannot be negative")
}
highest_local = @cmp.maximum(highest_local, sid)
}
}
}
for import_ in table.imports {
if import_.count > 0x7FFF_FFFF - 9 - table.imported {
raise DataModel("writer symbol table exceeds the symbol ID range")
}
table.imported += import_.count
}
if highest_local > 0x7FFF_FFFF - table.imported {
raise DataModel("writer symbol table exceeds the symbol ID range")
}
table.reserved = highest_local - 9
for token in tokens {
ignore(table.intern_token(token))
}
table
}
///|
/// Match encoder traversal: annotations, then value; struct names precede
/// their values. This gives batch and incremental writers the same IDs.
fn collect_writer_tokens(value : IonValue, tokens : Array[SymbolToken]) -> Unit {
value.each_token(token => tokens.push(token))
}
///|
/// The ID a writer context assigns to `token`. Imported unknown symbols are
/// located by table name and slot; local unknown IDs follow this context's
/// imported IDs. Known text is interned as usual.
pub fn SymbolTable::intern_token(
self : SymbolTable,
token : SymbolToken,
) -> Int32 raise IonError {
if token.text is Some(text) {
if self.find(text) is None && self.max_id() == 0x7FFF_FFFF {
raise DataModel("writer symbol table exceeds the symbol ID range")
}
return self.intern(text)
}
if token.import_location is Some(location) {
let mut offset = 9
for import_ in self.imports {
if import_.name == location.name &&
Some(import_.version) == token.import_version &&
location.sid >= 1 &&
location.sid <= import_.count {
return offset + location.sid
}
offset += import_.count
}
raise Unsupported(
"writer context does not declare the symbol's shared import",
)
}
match token.sid {
Some(sid) if sid >= 0 => {
if sid > 0x7FFF_FFFF - self.imported {
raise DataModel("writer symbol table exceeds the symbol ID range")
}
if sid > 9 {
sid + self.imported
} else {
sid
}
}
_ => raise DataModel("a symbol has no valid text or ID")
}
}
///|
/// Assign a writer ID, rejecting unknown tokens that would alias known text
/// or refer to an undeclared import or local slot.
pub fn SymbolTable::writer_token_id(
self : SymbolTable,
token : SymbolToken,
) -> Int32 raise IonError {
let sid = self.intern_token(token)
if token.text is None &&
(sid > self.max_id() || !self.token(sid).equivalent(token)) {
raise Unsupported(
"unknown symbol requires a matching declared writer context",
)
}
sid
}
///|
/// Replace unknown tokens with this context's IDs for text rendering. Emit
/// this context's local symbol table before the result.
pub fn SymbolTable::localize_for_text(
self : SymbolTable,
value : IonValue,
) -> IonValue raise IonError {
value.map_tokens(token => {
if token.text is Some(_) {
token
} else {
SymbolToken::from_sid(self.writer_token_id(token))
}
})
}