///|
fn literal_error(
code : String,
message : String,
span : SourceSpan,
) -> ParseError {
ParseError(code~, message~, span~)
}
///|
fn hex_value(value : UInt16) -> Int? {
match value {
'0'..='9' => Some(value.to_int() - '0')
'a'..='f' => Some(value.to_int() - 'a' + 10)
'A'..='F' => Some(value.to_int() - 'A' + 10)
_ => None
}
}
///|
fn decode_hex(
content : String,
start : Int,
digits : Int,
span : SourceSpan,
) -> Int raise ParseError {
if start + digits > content.length() {
raise literal_error(
"CEL-LIT-ESCAPE",
"hex escape is shorter than the required \{digits} digits",
span,
)
}
let mut offset = 0
let mut value = 0
while offset < digits {
match hex_value(content[start + offset]) {
Some(digit) => {
value = value * 16 + digit
offset += 1
}
None =>
raise literal_error(
"CEL-LIT-ESCAPE", "hex escape contains a non-hexadecimal digit", span,
)
}
}
value
}
///|
fn codepoint_utf8(codepoint : Int, span : SourceSpan) -> Bytes raise ParseError {
if codepoint < 0 ||
codepoint > 0x10ffff ||
(codepoint >= 0xd800 && codepoint <= 0xdfff) {
raise literal_error(
"CEL-LIT-UNICODE", "escape does not encode a Unicode scalar value", span,
)
}
let bytes : Array[Byte] = []
if codepoint <= 0x7f {
bytes.push(codepoint.to_byte())
} else if codepoint <= 0x7ff {
bytes.push((0xc0 | (codepoint >> 6)).to_byte())
bytes.push((0x80 | (codepoint & 0x3f)).to_byte())
} else if codepoint <= 0xffff {
bytes.push((0xe0 | (codepoint >> 12)).to_byte())
bytes.push((0x80 | ((codepoint >> 6) & 0x3f)).to_byte())
bytes.push((0x80 | (codepoint & 0x3f)).to_byte())
} else {
bytes.push((0xf0 | (codepoint >> 18)).to_byte())
bytes.push((0x80 | ((codepoint >> 12) & 0x3f)).to_byte())
bytes.push((0x80 | ((codepoint >> 6) & 0x3f)).to_byte())
bytes.push((0x80 | (codepoint & 0x3f)).to_byte())
}
Bytes::from_array(bytes)
}
///|
fn append_codepoint(
builder : StringBuilder,
codepoint : Int,
span : SourceSpan,
) -> Unit raise ParseError {
let encoded = codepoint_utf8(codepoint, span)
let decoded = @utf8.decode(encoded) catch {
_ =>
raise literal_error(
"CEL-LIT-UNICODE", "failed to decode Unicode escape", span,
)
}
builder.write_string(decoded)
}
///|
fn append_source_char(
builder : StringBuilder,
content : String,
index : Int,
) -> Int {
match content.get_char(index) {
Some(character) => {
builder.write_char(character)
character.utf16_len()
}
None => 1
}
}
///|
fn decode_quoted(
content : String,
raw : Bool,
span : SourceSpan,
) -> String raise ParseError {
if raw {
return content
}
let builder = StringBuilder(size_hint=content.length())
let mut index = 0
while index < content.length() {
if content[index] != '\\' {
index += append_source_char(builder, content, index)
continue
}
let escape_start = index
index += 1
if index >= content.length() {
raise literal_error(
"CEL-LIT-ESCAPE", "quoted literal ends with an incomplete escape", span,
)
}
let escape = content[index]
index += 1
match escape {
'a' => append_codepoint(builder, 7, span)
'b' => builder.write_char('\b')
'f' => builder.write_char('\f')
'n' => builder.write_char('\n')
'r' => builder.write_char('\r')
't' => builder.write_char('\t')
'v' => append_codepoint(builder, 11, span)
'\\' => builder.write_char('\\')
'\'' => builder.write_char('\'')
'"' => builder.write_char('"')
'?' => builder.write_char('?')
'x' | 'X' => {
let value = decode_hex(content, index, 2, span)
index += 2
append_codepoint(builder, value, span)
}
'u' => {
let high = decode_hex(content, index, 4, span)
index += 4
if high >= 0xd800 && high <= 0xdbff {
if index + 6 > content.length() ||
content[index] != '\\' ||
content[index + 1] != 'u' {
raise literal_error(
"CEL-LIT-UNICODE", "leading surrogate must be followed by a trailing surrogate escape",
span,
)
}
let low = decode_hex(content, index + 2, 4, span)
if low < 0xdc00 || low > 0xdfff {
raise literal_error(
"CEL-LIT-UNICODE", "leading surrogate is followed by an invalid trailing surrogate",
span,
)
}
index += 6
append_codepoint(
builder,
0x10000 + ((high - 0xd800) << 10) + low - 0xdc00,
span,
)
} else {
append_codepoint(builder, high, span)
}
}
'U' => {
let value = decode_hex(content, index, 8, span)
index += 8
append_codepoint(builder, value, span)
}
'0'..='7' => {
let mut value = escape.to_int() - '0'
let mut consumed = 1
while consumed < 3 &&
index < content.length() &&
content[index] >= '0' &&
content[index] <= '7' {
value = value * 8 + content[index].to_int() - '0'
index += 1
consumed += 1
}
if value > 255 {
raise literal_error(
"CEL-LIT-ESCAPE", "octal escape exceeds one byte", span,
)
}
append_codepoint(builder, value, span)
}
_ =>
raise literal_error(
"CEL-LIT-ESCAPE",
"unsupported escape at source offset \{span.start + escape_start}",
span,
)
}
}
builder.to_string()
}
///|
fn parse_int_literal(
text : String,
span : SourceSpan,
) -> Literal raise ParseError {
let value : Int64 = @string.parse_int64(text) catch {
_ =>
raise literal_error(
"CEL-LIT-INT", "integer literal is outside the Int64 range", span,
)
}
Literal::Int(value)
}
///|
fn parse_uint_literal(
text : String,
span : SourceSpan,
) -> Literal raise ParseError {
let body = text[0:text.length() - 1]
let value : UInt64 = @string.parse_uint64(body) catch {
_ =>
raise literal_error(
"CEL-LIT-UINT", "unsigned integer literal is outside the UInt64 range", span,
)
}
Literal::UInt(value)
}
///|
fn parse_double_literal(
text : String,
span : SourceSpan,
) -> Literal raise ParseError {
let value : Double = @string.parse_double(text) catch {
_ =>
raise literal_error(
"CEL-LIT-DOUBLE", "invalid floating-point literal", span,
)
}
Literal::Double(value)
}
///|
fn literal_from_token(token : Token) -> Literal raise ParseError {
match token.kind {
NullLiteral => Literal::Null
BoolLiteral(value) => Literal::Bool(value)
IntLiteral(text) => parse_int_literal(text, token.span)
UIntLiteral(text) => parse_uint_literal(text, token.span)
DoubleLiteral(text) => parse_double_literal(text, token.span)
StringLiteral(content, raw) =>
Literal::String(decode_quoted(content, raw, token.span))
BytesLiteral(content, raw) => {
let decoded = decode_quoted(content, raw, token.span)
Literal::Bytes(@utf8.encode(decoded))
}
_ =>
raise literal_error(
"CEL-LIT-EXPECTED",
"expected one CEL literal, found \{token.label()}",
token.span,
)
}
}
///|
/// Parse exactly one CEL literal without parsing a complete expression.
pub fn parse_literal(source : StringView) -> Literal raise ParseError {
let tokens = lex(source.to_owned())
if tokens.length() != 2 || tokens[1].kind != Eof {
let span = if tokens.length() > 1 {
tokens[1].span
} else {
SourceSpan::empty()
}
raise literal_error(
"CEL-LIT-TRAILING", "literal input contains trailing tokens", span,
)
}
literal_from_token(tokens[0])
}