use alloc::{borrow::Cow, boxed::Box, format, string::String, vec::Vec};
use core::fmt::Write;
use crate::{
ast::{
AnonStructExpr, BoolExpr, BytesExpr, BytesKind, CharExpr, Document, Expr, FieldsBody,
Ident, MapEntry, MapExpr, NumberExpr, NumberKind, OptionExpr, OptionValue, SeqExpr,
SeqItem, StringExpr, StringKind, StructBody, StructExpr, StructField, Trivia, TupleBody,
TupleElement, TupleExpr, UnitExpr,
},
convert::parse_int_raw,
error::{Error, ErrorKind, Result, Span},
value::{F64, NamedContent, Number, StructFields, Value},
};
trait SpanExt<T> {
fn with_span(self, span: &Span) -> Result<T>;
}
impl<T> SpanExt<T> for Result<T> {
fn with_span(self, span: &Span) -> Result<T> {
self.map_err(|err| {
if err.span().is_synthetic() {
Error::with_span(err.kind().clone(), *span)
} else {
err
}
})
}
}
pub fn to_value(doc: &Document<'_>) -> Option<Result<Value>> {
doc.value.as_ref().map(expr_to_value)
}
pub fn into_value(doc: Document<'_>) -> Option<Result<Value>> {
doc.value.map(expr_into_value)
}
pub fn expr_to_value(expr: &Expr<'_>) -> Result<Value> {
match expr {
Expr::Unit(_) => Ok(Value::Unit),
Expr::Bool(b) => Ok(Value::Bool(b.value)),
Expr::Char(c) => Ok(Value::Char(c.value)),
Expr::Byte(b) => Ok(byte_to_value(b)),
Expr::Number(n) => number_to_value(n).with_span(&n.span),
Expr::String(s) => Ok(string_to_value(s)),
Expr::Bytes(b) => Ok(bytes_to_value(b)),
Expr::Option(opt) => option_to_value(opt),
Expr::Seq(seq) => seq_to_value(seq),
Expr::Map(map) => map_to_value(map),
Expr::Tuple(tuple) => tuple_to_value(tuple),
Expr::AnonStruct(s) => anon_struct_to_value(s),
Expr::Struct(s) => struct_to_value(s),
Expr::Error(err) => Err(Error::with_span(err.error.kind().clone(), err.span)),
}
}
pub fn expr_into_value(expr: Expr<'_>) -> Result<Value> {
match expr {
Expr::Unit(_) => Ok(Value::Unit),
Expr::Bool(b) => Ok(Value::Bool(b.value)),
Expr::Char(c) => Ok(Value::Char(c.value)),
Expr::Byte(b) => Ok(Value::Number(Number::U8(b.value))),
Expr::Number(n) => {
let span = n.span;
number_to_value(&n).with_span(&span)
}
Expr::String(s) => Ok(Value::String(s.value)),
Expr::Bytes(b) => Ok(Value::Bytes(b.value)),
Expr::Option(opt) => option_into_value(*opt),
Expr::Seq(seq) => seq_into_value(seq),
Expr::Map(map) => map_into_value(map),
Expr::Tuple(tuple) => tuple_into_value(tuple),
Expr::AnonStruct(s) => anon_struct_into_value(s),
Expr::Struct(s) => struct_into_value(s),
Expr::Error(err) => Err(Error::with_span(err.error.kind().clone(), err.span)),
}
}
fn byte_to_value(b: &crate::ast::ByteExpr<'_>) -> Value {
Value::Number(Number::U8(b.value))
}
fn number_to_value(n: &NumberExpr<'_>) -> Result<Value> {
let raw = n.raw.trim();
match n.kind {
NumberKind::Integer | NumberKind::NegativeInteger => parse_integer(raw),
NumberKind::Float => parse_float(raw),
NumberKind::SpecialFloat => parse_special_float(raw),
}
}
fn parse_integer(raw: &str) -> Result<Value> {
let parsed = parse_int_raw(raw)?;
if parsed.negative {
let val = i128::try_from(parsed.magnitude)
.map_err(|_| Error::integer_out_of_bounds(raw.to_string(), "i128"))?;
let val = -val;
fit_signed(val, raw)
} else {
fit_unsigned(parsed.magnitude, raw)
}
}
fn fit_signed(val: i128, #[allow(unused_variables)] raw: &str) -> Result<Value> {
#[cfg(feature = "integer128")]
{
if let Ok(v) = i64::try_from(val) {
return Ok(fit_signed_64(v));
} else {
return Ok(Value::Number(Number::I128(val)));
}
}
#[cfg(not(feature = "integer128"))]
{
i64::try_from(val)
.map(fit_signed_64)
.map_err(|_| Error::integer_out_of_bounds(raw.to_string(), "i64"))
}
}
fn fit_signed_64(val: i64) -> Value {
if let Ok(v) = i8::try_from(val) {
Value::Number(Number::I8(v))
} else if let Ok(v) = i16::try_from(val) {
Value::Number(Number::I16(v))
} else if let Ok(v) = i32::try_from(val) {
Value::Number(Number::I32(v))
} else {
Value::Number(Number::I64(val))
}
}
fn fit_unsigned(val: u128, #[allow(unused_variables)] raw: &str) -> Result<Value> {
#[cfg(feature = "integer128")]
{
if let Ok(v) = u64::try_from(val) {
return Ok(fit_unsigned_64(v));
} else {
return Ok(Value::Number(Number::U128(val)));
}
}
#[cfg(not(feature = "integer128"))]
{
u64::try_from(val)
.map(fit_unsigned_64)
.map_err(|_| Error::integer_out_of_bounds(raw.to_string(), "u64"))
}
}
fn fit_unsigned_64(val: u64) -> Value {
if let Ok(v) = u8::try_from(val) {
Value::Number(Number::U8(v))
} else if let Ok(v) = u16::try_from(val) {
Value::Number(Number::U16(v))
} else if let Ok(v) = u32::try_from(val) {
Value::Number(Number::U32(v))
} else {
Value::Number(Number::U64(val))
}
}
fn parse_float(raw: &str) -> Result<Value> {
let cleaned: Cow<'_, str> = if raw.contains('_') {
Cow::Owned(raw.chars().filter(|&c| c != '_').collect())
} else {
Cow::Borrowed(raw)
};
let val: f64 = cleaned.parse().map_err(|_| {
Error::new(ErrorKind::Expected {
expected: "valid floating-point number".into(),
context: None,
})
})?;
Ok(Value::Number(Number::F64(F64(val))))
}
fn parse_special_float(raw: &str) -> Result<Value> {
match raw {
"inf" => Ok(Value::Number(Number::F64(F64(f64::INFINITY)))),
"-inf" => Ok(Value::Number(Number::F64(F64(f64::NEG_INFINITY)))),
"NaN" => Ok(Value::Number(Number::F64(F64(f64::NAN)))),
_ => Err(Error::new(ErrorKind::Expected {
expected: "valid floating-point number".into(),
context: None,
})),
}
}
fn string_to_value(s: &StringExpr<'_>) -> Value {
Value::String(s.value.clone())
}
fn bytes_to_value(b: &BytesExpr<'_>) -> Value {
Value::Bytes(b.value.clone())
}
fn option_to_value(opt: &OptionExpr<'_>) -> Result<Value> {
match &opt.value {
Some(inner) => {
let val = expr_to_value(&inner.expr)?;
Ok(Value::Option(Some(Box::new(val))))
}
None => Ok(Value::Option(None)),
}
}
fn seq_to_value(seq: &SeqExpr<'_>) -> Result<Value> {
let items: Result<Vec<Value>> = seq
.items
.iter()
.map(|item| expr_to_value(&item.expr))
.collect();
Ok(Value::Seq(items?))
}
fn map_to_value(map: &MapExpr<'_>) -> Result<Value> {
let mut result = crate::value::Map::with_capacity(map.entries.len());
for entry in &map.entries {
let key = expr_to_value(&entry.key)?;
let value = expr_to_value(&entry.value)?;
result.insert(key, value);
}
Ok(Value::Map(result))
}
fn tuple_to_value(tuple: &TupleExpr<'_>) -> Result<Value> {
let elements: Result<Vec<Value>> = tuple
.elements
.iter()
.map(|elem| expr_to_value(&elem.expr))
.collect();
Ok(Value::Tuple(elements?))
}
fn anon_struct_to_value(s: &AnonStructExpr<'_>) -> Result<Value> {
let struct_fields: StructFields = s
.fields
.iter()
.map(|f| {
let name = f.name.name.to_string();
let value = expr_to_value(&f.value)?;
Ok((name, value))
})
.collect::<Result<_>>()?;
Ok(Value::Struct(struct_fields))
}
fn struct_to_value(s: &StructExpr<'_>) -> Result<Value> {
let name = s.name.name.to_string();
let content = match &s.body {
None => NamedContent::Unit,
Some(StructBody::Tuple(tuple)) => {
let elements = tuple_body_to_vec(tuple)?;
NamedContent::Tuple(elements)
}
Some(StructBody::Fields(fields)) => {
let struct_fields = fields_body_to_struct_fields(fields)?;
NamedContent::Struct(struct_fields)
}
};
Ok(Value::Named { name, content })
}
fn tuple_body_to_vec(tuple: &TupleBody<'_>) -> Result<Vec<Value>> {
tuple
.elements
.iter()
.map(|elem| expr_to_value(&elem.expr))
.collect()
}
fn fields_body_to_struct_fields(fields: &FieldsBody<'_>) -> Result<StructFields> {
let mut result = StructFields::new();
for field in &fields.fields {
let key = field.name.name.to_string();
let value = expr_to_value(&field.value)?;
result.push((key, value));
}
Ok(result)
}
fn option_into_value(opt: OptionExpr<'_>) -> Result<Value> {
match opt.value {
Some(inner) => {
let val = expr_into_value(inner.expr)?;
Ok(Value::Option(Some(Box::new(val))))
}
None => Ok(Value::Option(None)),
}
}
fn seq_into_value(seq: SeqExpr<'_>) -> Result<Value> {
let mut items = Vec::with_capacity(seq.items.len());
for item in seq.items {
items.push(expr_into_value(item.expr)?);
}
Ok(Value::Seq(items))
}
fn map_into_value(map: MapExpr<'_>) -> Result<Value> {
let mut result = crate::value::Map::with_capacity(map.entries.len());
for entry in map.entries {
let key = expr_into_value(entry.key)?;
let value = expr_into_value(entry.value)?;
result.insert(key, value);
}
Ok(Value::Map(result))
}
fn tuple_into_value(tuple: TupleExpr<'_>) -> Result<Value> {
let mut elements = Vec::with_capacity(tuple.elements.len());
for elem in tuple.elements {
elements.push(expr_into_value(elem.expr)?);
}
Ok(Value::Tuple(elements))
}
fn anon_struct_into_value(s: AnonStructExpr<'_>) -> Result<Value> {
let mut struct_fields = StructFields::with_capacity(s.fields.len());
for f in s.fields {
let name = f.name.name.into_owned();
let value = expr_into_value(f.value)?;
struct_fields.push((name, value));
}
Ok(Value::Struct(struct_fields))
}
fn struct_into_value(s: StructExpr<'_>) -> Result<Value> {
let name = s.name.name.into_owned();
let content = match s.body {
None => NamedContent::Unit,
Some(StructBody::Tuple(tuple)) => {
let elements = tuple_body_into_vec(tuple)?;
NamedContent::Tuple(elements)
}
Some(StructBody::Fields(fields)) => {
let struct_fields = fields_body_into_struct_fields(fields)?;
NamedContent::Struct(struct_fields)
}
};
Ok(Value::Named { name, content })
}
fn tuple_body_into_vec(tuple: TupleBody<'_>) -> Result<Vec<Value>> {
let mut result = Vec::with_capacity(tuple.elements.len());
for elem in tuple.elements {
result.push(expr_into_value(elem.expr)?);
}
Ok(result)
}
fn fields_body_into_struct_fields(fields: FieldsBody<'_>) -> Result<StructFields> {
let mut result = StructFields::with_capacity(fields.fields.len());
for field in fields.fields {
let key = field.name.name.into_owned();
let value = expr_into_value(field.value)?;
result.push((key, value));
}
Ok(result)
}
#[must_use]
#[allow(clippy::too_many_lines)]
pub fn value_to_expr(value: Value) -> Expr<'static> {
let span = Span::synthetic();
match value {
Value::Unit => Expr::Unit(UnitExpr { span }),
Value::Bool(b) => Expr::Bool(BoolExpr { span, value: b }),
Value::Char(c) => Expr::Char(CharExpr {
span,
raw: Cow::Owned(escape_char(c)),
value: c,
}),
Value::Number(n) => {
let (raw, kind) = format_number(&n);
Expr::Number(NumberExpr {
span,
raw: Cow::Owned(raw),
kind,
})
}
Value::String(s) => Expr::String(StringExpr {
span,
raw: Cow::Owned(escape_string(&s)),
value: s,
kind: StringKind::Regular,
}),
Value::Bytes(b) => Expr::Bytes(BytesExpr {
span,
raw: Cow::Owned(format_bytes(&b)),
value: b,
kind: BytesKind::Regular,
}),
Value::Option(opt) => Expr::Option(Box::new(match opt {
None => OptionExpr { span, value: None },
Some(inner) => {
let inner_expr = value_to_expr(*inner);
OptionExpr {
span,
value: Some(OptionValue {
open_paren: Span::synthetic(),
leading: Trivia::empty(),
expr: inner_expr,
trailing: Trivia::empty(),
close_paren: Span::synthetic(),
}),
}
}
})),
Value::Seq(items) => {
let seq_items: Vec<SeqItem<'static>> = items
.into_iter()
.map(|v| SeqItem {
leading: Trivia::empty(),
expr: value_to_expr(v),
trailing: Trivia::empty(),
comma: None,
})
.collect();
Expr::Seq(SeqExpr {
span,
open_bracket: Span::synthetic(),
leading: Trivia::empty(),
items: seq_items,
trailing: Trivia::empty(),
close_bracket: Span::synthetic(),
})
}
Value::Tuple(elements) => {
let tuple_elements: Vec<TupleElement<'static>> = elements
.into_iter()
.map(|v| TupleElement {
leading: Trivia::empty(),
expr: value_to_expr(v),
trailing: Trivia::empty(),
comma: None,
})
.collect();
Expr::Tuple(TupleExpr {
span,
open_paren: Span::synthetic(),
leading: Trivia::empty(),
elements: tuple_elements,
trailing: Trivia::empty(),
close_paren: Span::synthetic(),
})
}
Value::Map(map) => {
let entries: Vec<MapEntry<'static>> = map
.into_iter()
.map(|(k, v)| MapEntry {
leading: Trivia::empty(),
key: value_to_expr(k),
pre_colon: Trivia::empty(),
colon: Span::synthetic(),
post_colon: Trivia::empty(),
value: value_to_expr(v),
trailing: Trivia::empty(),
comma: None,
})
.collect();
Expr::Map(MapExpr {
span,
open_brace: Span::synthetic(),
leading: Trivia::empty(),
entries,
trailing: Trivia::empty(),
close_brace: Span::synthetic(),
})
}
Value::Struct(fields) => {
let struct_fields: Vec<StructField<'static>> = fields
.into_iter()
.map(|(name, v)| StructField {
leading: Trivia::empty(),
name: Ident {
span: Span::synthetic(),
name: Cow::Owned(name),
},
pre_colon: Trivia::empty(),
colon: Span::synthetic(),
post_colon: Trivia::empty(),
value: value_to_expr(v),
trailing: Trivia::empty(),
comma: None,
})
.collect();
Expr::AnonStruct(AnonStructExpr {
span,
open_paren: Span::synthetic(),
leading: Trivia::empty(),
fields: struct_fields,
trailing: Trivia::empty(),
close_paren: Span::synthetic(),
})
}
Value::Named { name, content } => {
let body = match content {
NamedContent::Unit => None,
NamedContent::Tuple(elements) => {
let tuple_elements: Vec<TupleElement<'static>> = elements
.into_iter()
.map(|v| TupleElement {
leading: Trivia::empty(),
expr: value_to_expr(v),
trailing: Trivia::empty(),
comma: None,
})
.collect();
Some(StructBody::Tuple(TupleBody {
open_paren: Span::synthetic(),
leading: Trivia::empty(),
elements: tuple_elements,
trailing: Trivia::empty(),
close_paren: Span::synthetic(),
}))
}
NamedContent::Struct(fields) => {
let struct_fields: Vec<StructField<'static>> = fields
.into_iter()
.map(|(field_name, v)| StructField {
leading: Trivia::empty(),
name: Ident {
span: Span::synthetic(),
name: Cow::Owned(field_name),
},
pre_colon: Trivia::empty(),
colon: Span::synthetic(),
post_colon: Trivia::empty(),
value: value_to_expr(v),
trailing: Trivia::empty(),
comma: None,
})
.collect();
Some(StructBody::Fields(FieldsBody {
open_brace: Span::synthetic(),
leading: Trivia::empty(),
fields: struct_fields,
trailing: Trivia::empty(),
close_brace: Span::synthetic(),
}))
}
};
Expr::Struct(StructExpr {
span,
name: Ident {
span: Span::synthetic(),
name: Cow::Owned(name),
},
pre_body: Trivia::empty(),
body,
})
}
}
}
#[must_use]
pub fn synthetic_bool(value: bool) -> Expr<'static> {
Expr::Bool(BoolExpr {
span: Span::synthetic(),
value,
})
}
#[must_use]
pub fn synthetic_char(value: char) -> Expr<'static> {
Expr::Char(CharExpr {
span: Span::synthetic(),
raw: Cow::Owned(escape_char(value)),
value,
})
}
#[must_use]
pub fn synthetic_string(value: String) -> Expr<'static> {
let raw = escape_string(&value);
Expr::String(StringExpr {
span: Span::synthetic(),
raw: Cow::Owned(raw),
value,
kind: StringKind::Regular,
})
}
#[must_use]
pub fn synthetic_unit() -> Expr<'static> {
Expr::Unit(UnitExpr {
span: Span::synthetic(),
})
}
#[must_use]
pub fn synthetic_integer<T: core::fmt::Display + PartialOrd + Default + Copy>(
value: T,
) -> Expr<'static> {
let kind = if value < T::default() {
NumberKind::NegativeInteger
} else {
NumberKind::Integer
};
Expr::Number(NumberExpr {
span: Span::synthetic(),
raw: Cow::Owned(alloc::format!("{value}")),
kind,
})
}
#[must_use]
pub fn synthetic_f32(value: f32) -> Expr<'static> {
let (raw, kind) = if value.is_nan() {
("NaN".into(), NumberKind::SpecialFloat)
} else if value.is_infinite() {
if value.is_sign_positive() {
("inf".into(), NumberKind::SpecialFloat)
} else {
("-inf".into(), NumberKind::SpecialFloat)
}
} else {
let s = alloc::format!("{value}");
if s.contains('.') || s.contains('e') || s.contains('E') {
(s, NumberKind::Float)
} else {
(alloc::format!("{value}.0"), NumberKind::Float)
}
};
Expr::Number(NumberExpr {
span: Span::synthetic(),
raw: Cow::Owned(raw),
kind,
})
}
#[must_use]
pub fn synthetic_f64(value: f64) -> Expr<'static> {
let (raw, kind) = if value.is_nan() {
("NaN".into(), NumberKind::SpecialFloat)
} else if value.is_infinite() {
if value.is_sign_positive() {
("inf".into(), NumberKind::SpecialFloat)
} else {
("-inf".into(), NumberKind::SpecialFloat)
}
} else {
let s = alloc::format!("{value}");
if s.contains('.') || s.contains('e') || s.contains('E') {
(s, NumberKind::Float)
} else {
(alloc::format!("{value}.0"), NumberKind::Float)
}
};
Expr::Number(NumberExpr {
span: Span::synthetic(),
raw: Cow::Owned(raw),
kind,
})
}
#[must_use]
pub fn synthetic_seq(items: Vec<Expr<'static>>) -> Expr<'static> {
let seq_items: Vec<SeqItem<'static>> = items
.into_iter()
.map(|expr| SeqItem {
leading: Trivia::empty(),
expr,
trailing: Trivia::empty(),
comma: None,
})
.collect();
Expr::Seq(SeqExpr {
span: Span::synthetic(),
open_bracket: Span::synthetic(),
leading: Trivia::empty(),
items: seq_items,
trailing: Trivia::empty(),
close_bracket: Span::synthetic(),
})
}
#[must_use]
pub fn synthetic_map(entries: Vec<(Expr<'static>, Expr<'static>)>) -> Expr<'static> {
let map_entries: Vec<MapEntry<'static>> = entries
.into_iter()
.map(|(key, value)| MapEntry {
leading: Trivia::empty(),
key,
pre_colon: Trivia::empty(),
colon: Span::synthetic(),
post_colon: Trivia::empty(),
value,
trailing: Trivia::empty(),
comma: None,
})
.collect();
Expr::Map(MapExpr {
span: Span::synthetic(),
open_brace: Span::synthetic(),
leading: Trivia::empty(),
entries: map_entries,
trailing: Trivia::empty(),
close_brace: Span::synthetic(),
})
}
#[must_use]
pub fn synthetic_option(inner: Option<Expr<'static>>) -> Expr<'static> {
Expr::Option(Box::new(match inner {
None => OptionExpr {
span: Span::synthetic(),
value: None,
},
Some(expr) => OptionExpr {
span: Span::synthetic(),
value: Some(OptionValue {
open_paren: Span::synthetic(),
leading: Trivia::empty(),
expr,
trailing: Trivia::empty(),
close_paren: Span::synthetic(),
}),
},
}))
}
#[must_use]
pub fn synthetic_tuple(elements: Vec<Expr<'static>>) -> Expr<'static> {
let tuple_elements: Vec<TupleElement<'static>> = elements
.into_iter()
.map(|expr| TupleElement {
leading: Trivia::empty(),
expr,
trailing: Trivia::empty(),
comma: None,
})
.collect();
Expr::Tuple(TupleExpr {
span: Span::synthetic(),
open_paren: Span::synthetic(),
leading: Trivia::empty(),
elements: tuple_elements,
trailing: Trivia::empty(),
close_paren: Span::synthetic(),
})
}
#[must_use]
pub fn synthetic_struct(
name: impl Into<Cow<'static, str>>,
fields: Vec<(Cow<'static, str>, Expr<'static>)>,
) -> Expr<'static> {
let struct_fields: Vec<StructField<'static>> = fields
.into_iter()
.map(|(field_name, value)| StructField {
leading: Trivia::empty(),
name: Ident {
span: Span::synthetic(),
name: field_name,
},
pre_colon: Trivia::empty(),
colon: Span::synthetic(),
post_colon: Trivia::empty(),
value,
trailing: Trivia::empty(),
comma: None,
})
.collect();
Expr::Struct(StructExpr {
span: Span::synthetic(),
name: Ident {
span: Span::synthetic(),
name: name.into(),
},
pre_body: Trivia::empty(),
body: Some(StructBody::Fields(FieldsBody {
open_brace: Span::synthetic(),
leading: Trivia::empty(),
fields: struct_fields,
trailing: Trivia::empty(),
close_brace: Span::synthetic(),
})),
})
}
#[must_use]
pub fn synthetic_named_tuple(
name: impl Into<Cow<'static, str>>,
elements: Vec<Expr<'static>>,
) -> Expr<'static> {
let tuple_elements: Vec<TupleElement<'static>> = elements
.into_iter()
.map(|expr| TupleElement {
leading: Trivia::empty(),
expr,
trailing: Trivia::empty(),
comma: None,
})
.collect();
Expr::Struct(StructExpr {
span: Span::synthetic(),
name: Ident {
span: Span::synthetic(),
name: name.into(),
},
pre_body: Trivia::empty(),
body: Some(StructBody::Tuple(TupleBody {
open_paren: Span::synthetic(),
leading: Trivia::empty(),
elements: tuple_elements,
trailing: Trivia::empty(),
close_paren: Span::synthetic(),
})),
})
}
#[must_use]
pub fn synthetic_named_unit(name: impl Into<Cow<'static, str>>) -> Expr<'static> {
Expr::Struct(StructExpr {
span: Span::synthetic(),
name: Ident {
span: Span::synthetic(),
name: name.into(),
},
pre_body: Trivia::empty(),
body: None,
})
}
fn format_signed<T: itoa::Integer + Ord + Default>(v: T) -> (String, NumberKind) {
let s = itoa::Buffer::new().format(v).to_owned();
let kind = if v < T::default() {
NumberKind::NegativeInteger
} else {
NumberKind::Integer
};
(s, kind)
}
fn format_float(v: f64) -> (String, NumberKind) {
if v.is_nan() {
("NaN".into(), NumberKind::SpecialFloat)
} else if v.is_infinite() {
let s = if v.is_sign_positive() { "inf" } else { "-inf" };
(s.into(), NumberKind::SpecialFloat)
} else {
let s = format!("{v}");
if s.contains('.') || s.contains('e') || s.contains('E') {
(s, NumberKind::Float)
} else {
(format!("{v}.0"), NumberKind::Float)
}
}
}
fn format_number(n: &Number) -> (String, NumberKind) {
match n {
Number::I8(v) => format_signed(*v),
Number::I16(v) => format_signed(*v),
Number::I32(v) => format_signed(*v),
Number::I64(v) => format_signed(*v),
#[cfg(feature = "integer128")]
Number::I128(v) => format_signed(*v),
Number::U8(v) => (
itoa::Buffer::new().format(*v).to_owned(),
NumberKind::Integer,
),
Number::U16(v) => (
itoa::Buffer::new().format(*v).to_owned(),
NumberKind::Integer,
),
Number::U32(v) => (
itoa::Buffer::new().format(*v).to_owned(),
NumberKind::Integer,
),
Number::U64(v) => (
itoa::Buffer::new().format(*v).to_owned(),
NumberKind::Integer,
),
#[cfg(feature = "integer128")]
Number::U128(v) => (
itoa::Buffer::new().format(*v).to_owned(),
NumberKind::Integer,
),
Number::F32(f) => format_float(f64::from(f.get())),
Number::F64(f) => format_float(f.get()),
_ => ("0".into(), NumberKind::Integer),
}
}
fn escape_char(c: char) -> String {
match c {
'\'' => "'\\''".into(),
'\\' => "'\\\\'".into(),
'\n' => "'\\n'".into(),
'\r' => "'\\r'".into(),
'\t' => "'\\t'".into(),
'\0' => "'\\0'".into(),
c if c.is_ascii_control() => format!("'\\x{:02x}'", c as u8),
c => format!("'{c}'"),
}
}
fn escape_string(s: &str) -> String {
let mut result = String::with_capacity(s.len() + 2);
result.push('"');
for c in s.chars() {
match c {
'"' => result.push_str("\\\""),
'\\' => result.push_str("\\\\"),
'\n' => result.push_str("\\n"),
'\r' => result.push_str("\\r"),
'\t' => result.push_str("\\t"),
'\0' => result.push_str("\\0"),
c if c.is_ascii_control() => {
let _ = write!(result, "\\x{:02x}", c as u8);
}
c => result.push(c),
}
}
result.push('"');
result
}
fn format_bytes(bytes: &[u8]) -> String {
let mut result = String::with_capacity(bytes.len() + 3);
result.push_str("b\"");
for &b in bytes {
match b {
b'"' => result.push_str("\\\""),
b'\\' => result.push_str("\\\\"),
b'\n' => result.push_str("\\n"),
b'\r' => result.push_str("\\r"),
b'\t' => result.push_str("\\t"),
b'\0' => result.push_str("\\0"),
b if b.is_ascii_graphic() || b == b' ' => result.push(b as char),
b => {
let _ = write!(result, "\\x{b:02x}");
}
}
}
result.push('"');
result
}
#[cfg(test)]
#[allow(clippy::panic)]
mod tests {
use alloc::vec;
use super::*;
use crate::ast::parse_document;
#[test]
fn convert_integer() {
let doc = parse_document("42").unwrap();
let value = to_value(&doc).unwrap().unwrap();
assert_eq!(value, Value::Number(Number::U8(42)));
}
#[test]
fn convert_negative_integer() {
let doc = parse_document("-42").unwrap();
let value = to_value(&doc).unwrap().unwrap();
assert_eq!(value, Value::Number(Number::I8(-42)));
}
#[test]
fn convert_hex() {
let doc = parse_document("0xFF").unwrap();
let value = to_value(&doc).unwrap().unwrap();
assert_eq!(value, Value::Number(Number::U8(255)));
}
#[test]
fn convert_float() {
let doc = parse_document("3.14").unwrap();
let value = to_value(&doc).unwrap().unwrap();
match value {
Value::Number(Number::F32(_) | Number::F64(_)) => {}
_ => panic!("expected float"),
}
}
#[test]
fn convert_string() {
let doc = parse_document(r#""hello""#).unwrap();
let value = to_value(&doc).unwrap().unwrap();
assert_eq!(value, Value::String(String::from("hello")));
}
#[test]
fn convert_bool() {
let doc = parse_document("true").unwrap();
let value = to_value(&doc).unwrap().unwrap();
assert_eq!(value, Value::Bool(true));
}
#[test]
fn convert_option_none() {
let doc = parse_document("None").unwrap();
let value = to_value(&doc).unwrap().unwrap();
assert_eq!(value, Value::Option(None));
}
#[test]
fn convert_option_some() {
let doc = parse_document("Some(42)").unwrap();
let value = to_value(&doc).unwrap().unwrap();
assert_eq!(
value,
Value::Option(Some(Box::new(Value::Number(Number::U8(42)))))
);
}
#[test]
fn convert_seq() {
let doc = parse_document("[1, 2, 3]").unwrap();
let value = to_value(&doc).unwrap().unwrap();
assert_eq!(
value,
Value::Seq(vec![
Value::Number(Number::U8(1)),
Value::Number(Number::U8(2)),
Value::Number(Number::U8(3)),
])
);
}
#[test]
fn convert_tuple() {
let doc = parse_document("(1, 2, 3)").unwrap();
let value = to_value(&doc).unwrap().unwrap();
assert_eq!(
value,
Value::Tuple(vec![
Value::Number(Number::U8(1)),
Value::Number(Number::U8(2)),
Value::Number(Number::U8(3)),
])
);
}
#[test]
fn convert_map() {
let doc = parse_document(r#"{"a": 1}"#).unwrap();
let value = to_value(&doc).unwrap().unwrap();
match value {
Value::Map(map) => {
assert_eq!(map.len(), 1);
assert_eq!(
map.get(&Value::String(String::from("a"))),
Some(&Value::Number(Number::U8(1)))
);
}
_ => panic!("expected map"),
}
}
#[test]
fn convert_named_unit() {
let doc = parse_document("Point").unwrap();
let value = to_value(&doc).unwrap().unwrap();
assert_eq!(
value,
Value::Named {
name: String::from("Point"),
content: NamedContent::Unit,
}
);
}
#[test]
fn convert_named_tuple() {
let doc = parse_document("Point(1, 2)").unwrap();
let value = to_value(&doc).unwrap().unwrap();
assert_eq!(
value,
Value::Named {
name: String::from("Point"),
content: NamedContent::Tuple(vec![
Value::Number(Number::U8(1)),
Value::Number(Number::U8(2)),
]),
}
);
}
#[test]
fn convert_named_struct() {
let doc = parse_document("Point(x: 1, y: 2)").unwrap();
let value = to_value(&doc).unwrap().unwrap();
match value {
Value::Named {
name,
content: NamedContent::Struct(fields),
} => {
assert_eq!(name, "Point");
assert_eq!(fields.len(), 2);
assert_eq!(fields[0], (String::from("x"), Value::Number(Number::U8(1))));
assert_eq!(fields[1], (String::from("y"), Value::Number(Number::U8(2))));
}
_ => panic!("expected named struct, got {value:?}"),
}
}
#[test]
fn convert_empty_document() {
let doc = parse_document("").unwrap();
assert!(to_value(&doc).is_none());
}
}