Skip to content

Commit

Permalink
Implement string formatting specs
Browse files Browse the repository at this point in the history
  • Loading branch information
TheBB committed Nov 30, 2023
1 parent 535a7c2 commit 11445bb
Show file tree
Hide file tree
Showing 7 changed files with 1,075 additions and 72 deletions.
137 changes: 120 additions & 17 deletions gold/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::sync::Arc;
use serde::{Deserialize, Serialize};

use crate::error::{BindingType, Span, Syntax};
use crate::object::{StringFormatSpec, IntegerFormatSpec, FloatFormatSpec};

use super::error::{Error, Tagged, Action};
use super::object::{Object, Key};
Expand Down Expand Up @@ -244,70 +245,172 @@ impl Validatable for Binding {
// StringElement
// ----------------------------------------------------------------

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum AlignSpec {
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum StringAlignSpec {
Left,
Right,
AfterSign,
Center,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum AlignSpec {
AfterSign,
String(StringAlignSpec),
}

impl Default for AlignSpec {
fn default() -> Self {
Self::left()
}
}

impl AlignSpec {
pub fn left() -> Self {
Self::String(StringAlignSpec::Left)
}

pub fn right() -> Self {
Self::String(StringAlignSpec::Right)
}

pub fn center() -> Self {
Self::String(StringAlignSpec::Center)
}
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum SignSpec {
Plus,
Minus,
Space,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
impl Default for SignSpec {
fn default() -> Self {
Self::Minus
}
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum GroupingSpec {
Comma,
Underscore,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum UppercaseSpec {
Upper,
Lower,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum FormatType {
String,

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum IntegerFormatType {
Binary,
Character,
Decimal,
Octal,
Hex(UppercaseSpec),
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum FloatFormatType {
Sci(UppercaseSpec),
Fixed(UppercaseSpec),
General(UppercaseSpec),
Fixed,
General,
Percentage,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum FormatType {
String,
Integer(IntegerFormatType),
Float(FloatFormatType),
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct FormatSpec {
pub fill: char,
pub align: Option<AlignSpec>,
pub sign: Option<SignSpec>,
pub alternate: bool,
pub zero: bool,
pub width: Option<u32>,
pub width: Option<usize>,
pub grouping: Option<GroupingSpec>,
pub precision: Option<u32>,
pub precision: Option<usize>,
pub fmt_type: Option<FormatType>,
}

impl FormatSpec {
pub(crate) fn string_spec(&self) -> Option<StringFormatSpec> {
if self.sign.is_some() ||
self.alternate ||
self.grouping.is_some() ||
self.precision.is_some() ||
self.fmt_type.is_some_and(|x| x != FormatType::String)
{
return None;
}

let align = match self.align {
Some(AlignSpec::AfterSign) => { return None; },
Some(AlignSpec::String(x)) => x,
None => StringAlignSpec::Left,
};

Some(StringFormatSpec {
fill: self.fill,
width: self.width,
align,
})
}

pub(crate) fn integer_spec(&self) -> Option<IntegerFormatSpec> {
if self.precision.is_some() {
return None;
}

let fmt_type = match self.fmt_type {
None => IntegerFormatType::Decimal,
Some(FormatType::Integer(x)) => x,
_ => { return None; },
};

Some(IntegerFormatSpec {
fill: self.fill,
align: self.align.unwrap_or_else(AlignSpec::right),
sign: self.sign.unwrap_or_default(),
alternate: self.alternate,
width: self.width,
grouping: self.grouping,
fmt_type,
})
}

pub(crate) fn float_spec(&self) -> Option<FloatFormatSpec> {
let fmt_type = match self.fmt_type {
Some(FormatType::Float(x)) => x,
None => if self.precision.is_some() { FloatFormatType::Fixed } else { FloatFormatType::General },
_ => { return None; },
};

Some(FloatFormatSpec {
fill: self.fill,
align: self.align.unwrap_or_else(AlignSpec::right),
sign: self.sign.unwrap_or_default(),
width: self.width,
grouping: self.grouping,
precision: self.precision.unwrap_or(6),
fmt_type,
})
}
}

impl Default for FormatSpec{
fn default() -> Self {
Self {
fill: ' ',
align: None,
sign: None,
alternate: false,
zero: false,
width: None,
grouping: None,
precision: None,
Expand Down
6 changes: 5 additions & 1 deletion gold/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,9 @@ pub enum TypeMismatch {
/// Attempted to string interpolate an exotic type.
Interpolate(Type),

/// Attempted to interpolate a type with the wrong format spec.
InterpolateSpec(Type),

/// Two types were incompatible with a binary operator.
BinOp(Type, Type, BinOp),

Expand Down Expand Up @@ -854,7 +857,8 @@ impl Display for Reason {
Self::TypeMismatch(TypeMismatch::Call(x)) => f.write_fmt(format_args!("unsuitable type for function call: {}", x)),
Self::TypeMismatch(TypeMismatch::ExpectedPosArg { index, allowed, received }) => fmt_expected_arg(f, index + 1, allowed, received),
Self::TypeMismatch(TypeMismatch::ExpectedKwArg { name, allowed, received }) => fmt_expected_arg(f, name, allowed, received),
Self::TypeMismatch(TypeMismatch::Interpolate(x)) => f.write_fmt(format_args!("unsuitable type for string interpolation: {}", x)),
Self::TypeMismatch(TypeMismatch::Interpolate(x)) => f.write_fmt(format_args!("unsuitable type for interpolation: {}", x)),
Self::TypeMismatch(TypeMismatch::InterpolateSpec(x)) => f.write_fmt(format_args!("unsuitable type for format spec: {}", x)),
Self::TypeMismatch(TypeMismatch::Iterate(x)) => f.write_fmt(format_args!("non-iterable type: {}", x)),
Self::TypeMismatch(TypeMismatch::Json(x)) => f.write_fmt(format_args!("unsuitable type for JSON-like conversion: {}", x)),
Self::TypeMismatch(TypeMismatch::MapKey(x)) => f.write_fmt(format_args!("unsuitable type for map key: {}", x)),
Expand Down
4 changes: 2 additions & 2 deletions gold/src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,9 +484,9 @@ impl<'a> Namespace<'a> {
for element in elements {
match element {
StringElement::Raw(val) => rval += val.as_ref(),
StringElement::Interpolate(expr, _) => {
StringElement::Interpolate(expr, spec) => {
let val = self.eval(expr)?;
let text = val.format().map_err(expr.tag_error(Action::Format))?;
let text = val.format(spec.clone().unwrap_or_default()).map_err(expr.tag_error(Action::Format))?;
rval += &text;
}
}
Expand Down
Loading

0 comments on commit 11445bb

Please sign in to comment.