///|
/// `NodeObjectKey = QuotedText / Identifier`.
fn Parser::parse_object_key(
self : Parser,
) -> @ast.Node[String] raise ParseError {
match self.peek() {
Some(t) if t.kind == Identifier => {
ignore(self.advance())
@ast.Node::new(t.lexeme, t.span)
}
Some(t) if t.kind == @scanner.TokenKind::String => {
ignore(self.advance())
@ast.Node::new(@scanner.unescape(t.lexeme), t.span)
}
_ =>
self.fail(
"SM-PARSE-001",
"UNEXPECTED TOKEN",
"I was expecting a key (an identifier or a quoted string) but found \{self.found()}",
self.here(),
)
}
}
///|
/// Whether `c` is an ASCII digit.
fn is_number_digit(c : Char) -> Bool {
c >= '0' && c <= '9'
}
///|
/// The digits of `chars` from `start`, and the index after them.
fn number_digits(chars : Array[Char], start : Int32) -> (String, Int32) {
let n = chars.length()
let mut i = start
while i < n && is_number_digit(chars[i]) {
i += 1
}
(String::from_array(chars[start:i]), i)
}
///|
/// The largest exponent magnitude a number lexeme may have. Past it the
/// exact value would need `10^n` digits for a huge `n`, so the parser
/// reports the number instead of computing it.
let max_number_exponent : Int32 = 4096
///|
/// Split a scanner-validated `Number` lexeme (`[-] int [. frac]
/// [e|E [+|-] digits]`) into its sign, integer digits, fraction digits
/// (empty when there is no `.`) and exponent (`0` when there is no
/// `e`/`E`). The exponent saturates: once its magnitude passes
/// `max_number_exponent` it stops growing, so a long run of exponent
/// digits never overflows.
fn split_number_lexeme(text : String) -> (String, String, String, Int32) {
let chars = text.to_array()
let n = chars.length()
let mut i = 0
let sign = if i < n && chars[i] == '-' {
i += 1
"-"
} else {
""
}
let (int_digits, after_int) = number_digits(chars, i)
i = after_int
let frac_digits = if i < n && chars[i] == '.' {
let (digits, after_frac) = number_digits(chars, i + 1)
i = after_frac
digits
} else {
""
}
let exponent = if i < n && (chars[i] == 'e' || chars[i] == 'E') {
i += 1
let exp_sign = if i < n && chars[i] == '-' {
i += 1
-1
} else {
if i < n && chars[i] == '+' {
i += 1
}
1
}
let (exp_digits, _) = number_digits(chars, i)
let mut exp_value = 0
for ch in exp_digits {
if exp_value > max_number_exponent {
break
}
exp_value = exp_value * 10 + (ch.to_int() - '0'.to_int())
}
exp_sign * exp_value
} else {
0
}
(sign, int_digits, frac_digits, exponent)
}
///|
/// The exact decimal value of a lexeme with a `.` or an exponent (or
/// both), as `coefficient * 10^(-scale)`: no `Double` involved, so no
/// lexeme the scanner accepts can lose precision. `span` is the number
/// token's span, for the diagnostic when the scale is unrepresentable.
fn Parser::decimal_from_lexeme(
self : Parser,
text : String,
span : @span.Span,
) -> @decimal.Decimal raise ParseError {
let (sign, int_digits, frac_digits, exponent) = split_number_lexeme(text)
if exponent > max_number_exponent || exponent < -max_number_exponent {
self.fail(
"SM-PARSE-001",
"NUMBER TOO LARGE",
"I cannot represent `\{text}` exactly: its exponent is beyond \{max_number_exponent}",
span,
hint="Use an exponent between -\{max_number_exponent} and \{max_number_exponent}.",
)
}
let mut coefficient = BigInt::from_string(sign + int_digits + frac_digits)
let mut scale = frac_digits.length() - exponent
if scale < 0 {
coefficient *= BigInt::from_int(10).pow(BigInt::from_int(-scale))
scale = 0
}
match @decimal.Decimal::new(coefficient, scale) {
Some(d) => d
// `Decimal::new` rejects a negative scale (never produced here) or
// one past `max_scale` (28): more decimal places than Smithy numbers
// can hold.
None =>
self.fail(
"SM-PARSE-001",
"NUMBER TOO PRECISE",
"I cannot represent `\{text}` exactly: it needs more than 28 decimal places",
span,
hint="Round the number to at most 28 decimal places.",
)
}
}
///|
/// The exact value of a `Number` token.
fn Parser::parse_number(
self : Parser,
t : @scanner.Token,
) -> @ast.NumberLiteral raise ParseError {
let text = t.lexeme
let value : @ast.NumberValue = if @scanner.is_integer_lexeme(text) {
Integer(BigInt::from_string(text))
} else {
match @decimal.Decimal::from_string(text) {
Some(d) => Decimal(d)
// The scanner validated the digits; a lexeme the decimal parser
// rejects (an exponent form it does not take) is parsed exactly
// from its digits instead.
None => Decimal(self.decimal_from_lexeme(text, t.span))
}
}
{ text, value, }
}
///|
/// `NodeObjectKvp *(WS NodeObjectKvp)` up to `close` (not consumed).
fn Parser::parse_object_kvps(
self : Parser,
close : @scanner.TokenKind,
what : String,
) -> Array[@ast.NodeKvp] raise ParseError {
let kvps : Array[@ast.NodeKvp] = []
while !self.at_kind(close) {
if self.at_end() {
self.fail(
"SM-PARSE-002",
"UNFINISHED \{what.to_upper()}",
"I reached the end of the file while reading \{what}",
self.here(),
hint="Add the closing bracket.",
)
}
let key = self.parse_object_key()
guard self.at_kind(Colon) else {
self.fail(
"SM-PARSE-001",
"UNEXPECTED TOKEN",
"I was expecting `:` after the key `\{key.value}` but found \{self.found()}",
self.here(),
example="{ \{key.value}: \"value\" }",
)
}
ignore(self.advance())
let value = self.parse_node_value()
kvps.push({ key, value, span: key.span.join(value.span()), })
}
kvps
}
///|
/// `NodeValue = NodeArray / NodeObject / Number / NodeKeyword / NodeStringValue`.
fn Parser::parse_node_value(self : Parser) -> @ast.NodeValue raise ParseError {
match self.peek() {
None =>
self.fail(
"SM-PARSE-002",
"UNEXPECTED END OF FILE",
"I was expecting a value but reached the end of the file",
self.here_anchored(),
)
Some(t) =>
match t.kind {
LBracket => {
ignore(self.advance())
self.enter()
self.push_context("an array", t.span)
let items : Array[@ast.NodeValue] = []
while !self.at_kind(RBracket) {
if self.at_end() {
self.fail(
"SM-PARSE-002",
"UNFINISHED ARRAY",
"I reached the end of the file while reading an array",
self.here(),
hint="Add the closing `]`.",
)
}
items.push(self.parse_node_value())
}
let close = self.expect_kind(RBracket, "`]`")
self.pop_context()
self.leave()
Array(items[:], t.span.join(close.span))
}
LBrace => {
ignore(self.advance())
self.enter()
self.push_context("an object", t.span)
let kvps = self.parse_object_kvps(RBrace, "an object")
let close = self.expect_kind(RBrace, "`}`")
self.pop_context()
self.leave()
Object(kvps[:], t.span.join(close.span))
}
Number => {
ignore(self.advance())
Number(self.parse_number(t), t.span)
}
String => {
ignore(self.advance())
String(@scanner.unescape(t.lexeme), t.span)
}
TextBlock => {
ignore(self.advance())
TextBlock(@scanner.text_block_value(t.lexeme), t.span)
}
Identifier =>
match t.lexeme {
"true" => {
ignore(self.advance())
Keyword(True, t.span)
}
"false" => {
ignore(self.advance())
Keyword(False, t.span)
}
"null" => {
ignore(self.advance())
Keyword(Null, t.span)
}
_ => {
let id = self.parse_shape_id()
ShapeId(id.value, id.span)
}
}
_ =>
self.fail(
"SM-PARSE-001",
"UNEXPECTED TOKEN",
"I was expecting a value but found \{self.found()}",
self.here(),
notes=[
"A value is a string, a number, `true`, `false`, `null`, a shape ID, an array `[...]` or an object `{...}`.",
],
)
}
}
}