rink_core/output/
reply.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use super::fmt::{flat_join, join, Span, TokenFmt};
6use super::NumberParts;
7use crate::ast::{Expr, Precedence, UnaryOpType};
8use crate::output::Digits;
9use chrono::{DateTime, TimeZone};
10use serde_derive::Serialize;
11use std::collections::BTreeMap;
12use std::fmt::{Display, Formatter, Result as FmtResult};
13use std::iter::once;
14use std::rc::Rc;
15
16#[derive(Debug, Clone, Serialize)]
17#[serde(tag = "type")]
18#[serde(rename_all = "lowercase")]
19pub enum ExprParts {
20    Literal {
21        text: String,
22    },
23    Unit {
24        name: String,
25    },
26    Property {
27        property: String,
28        subject: Vec<ExprParts>,
29    },
30    Error {
31        message: String,
32    },
33}
34
35#[derive(Debug, Clone, Serialize)]
36pub struct ExprReply {
37    exprs: Vec<ExprParts>,
38    ast: Expr,
39}
40
41#[derive(Debug, Clone, Serialize)]
42#[serde(rename_all = "camelCase")]
43pub struct DefReply {
44    pub canon_name: String,
45    pub def: Option<String>,
46    pub def_expr: Option<ExprReply>,
47    pub value: Option<NumberParts>,
48    pub doc: Option<String>,
49}
50
51#[derive(Debug, Clone, Serialize)]
52pub struct ConversionReply {
53    pub value: NumberParts,
54}
55
56#[derive(Debug, Clone, Serialize)]
57pub struct FactorizeReply {
58    pub factorizations: Vec<Factorization>,
59}
60
61#[derive(Debug, Clone, Serialize)]
62#[serde(transparent)]
63pub struct Factorization {
64    pub units: BTreeMap<Rc<String>, usize>,
65}
66
67#[derive(Debug, Clone, Serialize)]
68pub struct UnitsInCategory {
69    pub category: Option<String>,
70    pub units: Vec<String>,
71}
72
73#[derive(Debug, Clone, Serialize)]
74pub struct UnitsForReply {
75    pub units: Vec<UnitsInCategory>,
76    /// Dimensions and quantity are set.
77    pub of: NumberParts,
78}
79
80#[derive(Debug, Clone, Serialize)]
81pub struct UnitListReply {
82    pub rest: NumberParts,
83    pub list: Vec<NumberParts>,
84}
85
86#[derive(Debug, Clone, Serialize)]
87pub struct DurationReply {
88    pub raw: NumberParts,
89    pub years: NumberParts,
90    pub months: NumberParts,
91    pub weeks: NumberParts,
92    pub days: NumberParts,
93    pub hours: NumberParts,
94    pub minutes: NumberParts,
95    pub seconds: NumberParts,
96}
97
98#[derive(Debug, Clone, Serialize)]
99pub struct SearchReply {
100    pub results: Vec<NumberParts>,
101}
102
103#[derive(Debug, Clone, Serialize)]
104pub struct PropertyReply {
105    pub name: String,
106    pub value: NumberParts,
107    pub doc: Option<String>,
108}
109
110#[derive(Debug, Clone, Serialize)]
111pub struct SubstanceReply {
112    pub name: String,
113    pub doc: Option<String>,
114    pub amount: NumberParts,
115    pub properties: Vec<PropertyReply>,
116}
117
118#[derive(Debug, Clone, Serialize)]
119pub struct DateReply {
120    pub year: i32,
121    pub month: i32,
122    pub day: i32,
123    pub hour: i32,
124    pub minute: i32,
125    pub second: i32,
126    pub nanosecond: i32,
127    /// chrono-humanize output, if enabled.
128    pub human: Option<String>,
129    pub string: String,
130    pub rfc3339: String,
131}
132
133#[derive(Debug, Clone, Serialize)]
134#[allow(clippy::large_enum_variant)]
135#[serde(rename_all = "camelCase")]
136#[serde(tag = "type")]
137pub enum QueryReply {
138    Number(NumberParts),
139    Date(DateReply),
140    Substance(SubstanceReply),
141    Duration(Box<DurationReply>),
142    Def(Box<DefReply>),
143    Conversion(Box<ConversionReply>),
144    Factorize(FactorizeReply),
145    UnitsFor(UnitsForReply),
146    UnitList(UnitListReply),
147    Search(SearchReply),
148}
149
150#[derive(Debug, Clone, Serialize)]
151pub struct ConformanceError {
152    pub left: NumberParts,
153    pub right: NumberParts,
154    pub suggestions: Vec<String>,
155}
156
157#[derive(Debug, Clone, Serialize)]
158pub struct NotFoundError {
159    pub got: String,
160    pub suggestion: Option<String>,
161}
162
163#[derive(Debug, Clone, Serialize)]
164#[serde(tag = "type")]
165#[serde(rename_all = "camelCase")]
166pub enum QueryError {
167    Conformance(Box<ConformanceError>),
168    NotFound(NotFoundError),
169    Generic { message: String },
170}
171
172impl QueryError {
173    pub fn generic(message: String) -> QueryError {
174        QueryError::Generic { message }
175    }
176}
177
178impl ExprReply {
179    pub fn from(expr: &Expr) -> ExprReply {
180        let mut parts = vec![];
181
182        fn recurse(expr: &Expr, parts: &mut Vec<ExprParts>, prec: Precedence) {
183            macro_rules! literal {
184                ($e:expr) => {{
185                    parts.push(ExprParts::Literal {
186                        text: $e.to_owned(),
187                    })
188                }};
189            }
190            match *expr {
191                Expr::Unit { ref name } => parts.push(ExprParts::Unit { name: name.clone() }),
192                Expr::Quote { ref string } => literal!(format!("'{}'", string)),
193                Expr::Const { ref value } => {
194                    let (_exact, val) = value.to_string(10, Digits::Default);
195                    literal!(val)
196                }
197                Expr::Date { .. } => literal!("NYI: date expr to expr parts"),
198                Expr::Mul { ref exprs } => {
199                    if prec < Precedence::Mul {
200                        literal!("(");
201                    }
202                    for expr in exprs.iter() {
203                        recurse(expr, parts, Precedence::Pow);
204                    }
205                    if prec < Precedence::Mul {
206                        literal!(")");
207                    }
208                }
209                Expr::Call { ref func, ref args } => {
210                    literal!(format!("{}(", func.name()));
211                    if let Some(first) = args.first() {
212                        recurse(first, parts, Precedence::Equals);
213                    }
214                    for arg in args.iter().skip(1) {
215                        literal!(",");
216                        recurse(arg, parts, Precedence::Equals);
217                    }
218                    literal!(")")
219                }
220                Expr::BinOp(ref binop) => {
221                    let op_prec = Precedence::from(binop.op);
222                    let succ = Precedence::next(binop.op);
223                    if prec < op_prec {
224                        literal!("(");
225                    }
226                    recurse(&binop.left, parts, succ);
227                    literal!(binop.op.symbol());
228                    recurse(&binop.right, parts, op_prec);
229                    if prec < op_prec {
230                        literal!(")");
231                    }
232                }
233                Expr::UnaryOp(ref unaryop) => match unaryop.op {
234                    UnaryOpType::Positive => {
235                        literal!("+");
236                        recurse(&unaryop.expr, parts, Precedence::Plus)
237                    }
238                    UnaryOpType::Negative => {
239                        literal!("-");
240                        recurse(&unaryop.expr, parts, Precedence::Plus)
241                    }
242                    UnaryOpType::Degree(ref suffix) => {
243                        if prec < Precedence::Mul {
244                            literal!("(");
245                        }
246                        recurse(&unaryop.expr, parts, Precedence::Mul);
247                        literal!(suffix.to_string());
248                        if prec < Precedence::Mul {
249                            literal!(")");
250                        }
251                    }
252                },
253                Expr::Of {
254                    ref property,
255                    ref expr,
256                } => {
257                    if prec < Precedence::Add {
258                        literal!("(");
259                    }
260                    let mut sub = vec![];
261                    recurse(expr, &mut sub, Precedence::Div);
262                    parts.push(ExprParts::Property {
263                        property: property.to_owned(),
264                        subject: sub,
265                    });
266                    if prec < Precedence::Add {
267                        literal!(")");
268                    }
269                }
270                Expr::Error { ref message } => parts.push(ExprParts::Error {
271                    message: message.to_owned(),
272                }),
273            }
274        }
275
276        recurse(expr, &mut parts, Precedence::Equals);
277
278        ExprReply {
279            exprs: parts,
280            ast: expr.clone(),
281        }
282    }
283}
284
285impl From<NotFoundError> for QueryError {
286    fn from(v: NotFoundError) -> Self {
287        QueryError::NotFound(v)
288    }
289}
290
291impl From<String> for QueryError {
292    fn from(message: String) -> Self {
293        QueryError::Generic { message }
294    }
295}
296
297impl Display for QueryReply {
298    fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
299        match *self {
300            QueryReply::Number(ref v) => write!(fmt, "{}", v),
301            QueryReply::Date(ref v) => write!(fmt, "{}", v),
302            QueryReply::Substance(ref v) => write!(fmt, "{}", v),
303            QueryReply::Duration(ref v) => write!(fmt, "{}", v),
304            QueryReply::Def(ref v) => write!(fmt, "{}", v),
305            QueryReply::Conversion(ref v) => write!(fmt, "{}", v),
306            QueryReply::Factorize(ref v) => write!(fmt, "{}", v),
307            QueryReply::UnitsFor(ref v) => write!(fmt, "{}", v),
308            QueryReply::UnitList(ref v) => write!(fmt, "{}", v),
309            QueryReply::Search(ref v) => write!(fmt, "{}", v),
310        }
311    }
312}
313
314impl Display for QueryError {
315    fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
316        match *self {
317            QueryError::Generic { ref message } => write!(fmt, "{}", message),
318            QueryError::Conformance(ref v) => write!(fmt, "{}", v),
319            QueryError::NotFound(ref v) => write!(fmt, "{}", v),
320        }
321    }
322}
323
324impl Display for NotFoundError {
325    fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
326        match self.suggestion.as_ref() {
327            Some(ref s) => write!(fmt, "No such unit {}, did you mean {}?", self.got, s),
328            None => write!(fmt, "No such unit {}", self.got),
329        }
330    }
331}
332
333impl Display for ConformanceError {
334    fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
335        writeln!(fmt, "Conformance error: {} != {}", self.left, self.right)?;
336        write!(fmt, "Suggestions: {}", self.suggestions.join(", "))
337    }
338}
339
340impl DateReply {
341    pub fn new<Tz>(ctx: &crate::loader::Context, date: DateTime<Tz>) -> DateReply
342    where
343        Tz: TimeZone,
344        Tz::Offset: Display,
345    {
346        use chrono::{Datelike, Timelike};
347        DateReply {
348            string: date.to_string(),
349            rfc3339: date.to_rfc3339(),
350            year: date.year(),
351            month: date.month() as i32,
352            day: date.day() as i32,
353            hour: date.hour() as i32,
354            minute: date.minute() as i32,
355            second: date.second() as i32,
356            nanosecond: date.nanosecond() as i32,
357            human: ctx.humanize(date),
358        }
359    }
360}
361
362impl Display for DateReply {
363    fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
364        write!(fmt, "{}", self.string)?;
365        if let Some(ref human) = self.human {
366            write!(fmt, " ({})", human)?;
367        }
368        Ok(())
369    }
370}
371
372impl Display for SubstanceReply {
373    fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
374        write!(
375            fmt,
376            "{}: {}{}",
377            self.name,
378            self.doc
379                .as_ref()
380                .map(|x| format!("{} ", x))
381                .unwrap_or_default(),
382            self.properties
383                .iter()
384                .map(|prop| format!(
385                    "{} = {}{}",
386                    prop.name,
387                    prop.value.format("n u"),
388                    prop.doc
389                        .as_ref()
390                        .map(|x| format!(" ({})", x))
391                        .unwrap_or_else(|| "".to_owned())
392                ))
393                .collect::<Vec<_>>()
394                .join("; ")
395        )
396    }
397}
398
399impl Display for DefReply {
400    fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
401        write!(fmt, "Definition: {}", self.canon_name)?;
402        if let Some(ref def) = self.def {
403            write!(fmt, " = {}", def)?;
404        }
405        if let Some(ref value) = self.value {
406            write!(fmt, " = {}", value.format("n u p"))?;
407        }
408        if let Some(ref doc) = self.doc {
409            write!(fmt, ". {}", doc)?;
410        }
411        Ok(())
412    }
413}
414
415impl Display for ConversionReply {
416    fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
417        write!(fmt, "{}", self.value)
418    }
419}
420
421impl Display for FactorizeReply {
422    fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
423        write!(
424            fmt,
425            "Factorizations: {}",
426            self.factorizations
427                .iter()
428                .map(|x| {
429                    x.units
430                        .iter()
431                        .map(|(u, p)| {
432                            if *p == 1 {
433                                u.to_string()
434                            } else {
435                                format!("{}^{}", u, p)
436                            }
437                        })
438                        .collect::<Vec<_>>()
439                        .join(" ")
440                })
441                .collect::<Vec<_>>()
442                .join(";  ")
443        )
444    }
445}
446
447impl Display for UnitsForReply {
448    fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
449        write!(
450            fmt,
451            "Units for {}: {}",
452            self.of.format("D w"),
453            self.units
454                .iter()
455                .map(|cat| {
456                    if let Some(ref category) = cat.category {
457                        format!("{}: {}", category, cat.units.join(", "))
458                    } else {
459                        cat.units.join(", ")
460                    }
461                })
462                .collect::<Vec<_>>()
463                .join("; ")
464        )
465    }
466}
467
468impl Display for DurationReply {
469    fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
470        let res = [
471            &self.years,
472            &self.months,
473            &self.weeks,
474            &self.days,
475            &self.hours,
476            &self.minutes,
477        ]
478        .iter()
479        .filter(|x| x.exact_value.as_ref().map(|x| &**x) != Some("0"))
480        .chain(once(&&self.seconds))
481        .map(|x| x.to_string())
482        .collect::<Vec<_>>()
483        .join(", ");
484        write!(fmt, "{}", res)?;
485        if let Some(q) = self.raw.quantity.as_ref() {
486            write!(fmt, " ({})", q)
487        } else {
488            Ok(())
489        }
490    }
491}
492
493impl Display for UnitListReply {
494    fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
495        write!(
496            fmt,
497            "{}",
498            self.list
499                .iter()
500                .map(|x| x.to_string())
501                .collect::<Vec<_>>()
502                .join(", ")
503        )?;
504        if let Some(q) = self.rest.quantity.as_ref() {
505            write!(fmt, " ({})", q)
506        } else {
507            Ok(())
508        }
509    }
510}
511
512impl Display for SearchReply {
513    fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
514        write!(
515            fmt,
516            "Search results: {}",
517            self.results
518                .iter()
519                .map(|x| x.format("u p"))
520                .collect::<Vec<_>>()
521                .join(", ")
522        )
523    }
524}
525
526impl<'a> TokenFmt<'a> for QueryReply {
527    fn to_spans(&'a self) -> Vec<Span<'a>> {
528        match self {
529            QueryReply::Number(reply) => reply.to_spans(),
530            QueryReply::Date(reply) => reply.to_spans(),
531            QueryReply::Substance(reply) => reply.to_spans(),
532            QueryReply::Duration(reply) => reply.to_spans(),
533            QueryReply::Def(reply) => reply.to_spans(),
534            QueryReply::Conversion(reply) => reply.to_spans(),
535            QueryReply::Factorize(reply) => reply.to_spans(),
536            QueryReply::UnitsFor(reply) => reply.to_spans(),
537            QueryReply::UnitList(reply) => reply.to_spans(),
538            QueryReply::Search(reply) => reply.to_spans(),
539        }
540    }
541}
542
543impl<'a> TokenFmt<'a> for DateReply {
544    fn to_spans(&'a self) -> Vec<Span<'a>> {
545        if let Some(ref human) = self.human {
546            vec![
547                Span::date_time(&self.string),
548                Span::plain(" ("),
549                Span::plain(human),
550                Span::plain(")"),
551            ]
552        } else {
553            vec![Span::date_time(&self.string)]
554        }
555    }
556}
557
558impl<'a> TokenFmt<'a> for SubstanceReply {
559    fn to_spans(&'a self) -> Vec<Span<'a>> {
560        let mut tokens = vec![Span::unit(&self.name), Span::plain(": ")];
561        if let Some(ref doc) = self.doc {
562            tokens.push(Span::doc_string(doc));
563            tokens.push(Span::plain(" "));
564        }
565        tokens.push(Span::list_begin(""));
566        tokens.extend(join(
567            self.properties.iter().map(|prop| Span::child(prop)),
568            Span::list_sep("; "),
569        ));
570        tokens
571    }
572}
573
574impl<'a> TokenFmt<'a> for PropertyReply {
575    fn to_spans(&'a self) -> Vec<Span<'a>> {
576        let mut tokens = vec![
577            Span::prop_name(&self.name),
578            Span::plain(" = "),
579            Span::child(&self.value),
580        ];
581        if let Some(ref doc) = self.doc {
582            tokens.push(Span::plain(". "));
583            tokens.push(Span::doc_string(doc));
584        }
585        tokens
586    }
587}
588
589impl<'a> TokenFmt<'a> for DurationReply {
590    fn to_spans(&'a self) -> Vec<Span<'a>> {
591        let parts = [
592            &self.years,
593            &self.months,
594            &self.weeks,
595            &self.days,
596            &self.hours,
597            &self.minutes,
598        ];
599
600        let res = join(
601            parts
602                .iter()
603                .copied()
604                .filter(|x| x.exact_value.as_ref().map(|x| &**x) != Some("0"))
605                .chain(once(&self.seconds))
606                .map(|x| Span::child(x)),
607            Span::plain(", "),
608        );
609
610        if let Some(ref q) = self.raw.quantity {
611            res.chain(once(Span::plain(" (")))
612                .chain(once(Span::quantity(q)))
613                .chain(once(Span::plain(")")))
614                .collect()
615        } else {
616            res.collect()
617        }
618    }
619}
620
621impl<'a> TokenFmt<'a> for DefReply {
622    fn to_spans(&'a self) -> Vec<Span<'a>> {
623        let mut tokens = vec![Span::plain("Definition: "), Span::unit(&self.canon_name)];
624        if let Some(ref def) = self.def {
625            tokens.push(Span::plain(" = "));
626            tokens.push(Span::plain(def));
627        }
628        if let Some(ref value) = self.value {
629            tokens.push(Span::plain(" = "));
630            tokens.extend(value.token_format("n u p").to_spans());
631        }
632        if let Some(ref doc) = self.doc {
633            tokens.push(Span::plain(". "));
634            tokens.push(Span::doc_string(doc));
635        }
636        tokens
637    }
638}
639
640impl<'a> TokenFmt<'a> for ConversionReply {
641    fn to_spans(&'a self) -> Vec<Span<'a>> {
642        self.value.to_spans()
643    }
644}
645
646impl<'a> TokenFmt<'a> for FactorizeReply {
647    fn to_spans(&'a self) -> Vec<Span<'a>> {
648        once(Span::list_begin("Factorizations: "))
649            .chain(join(
650                self.factorizations.iter().map(|fac| Span::child(fac)),
651                Span::list_sep("; "),
652            ))
653            .collect()
654    }
655}
656
657impl<'a> TokenFmt<'a> for Factorization {
658    fn to_spans(&'a self) -> Vec<Span<'a>> {
659        flat_join(
660            self.units.iter().map(|(unit, pow)| {
661                if *pow == 1 {
662                    vec![Span::unit(&**unit)]
663                } else {
664                    vec![Span::unit(&**unit), Span::pow(format!("^{}", pow))]
665                }
666            }),
667            Span::plain(" "),
668        )
669        .collect()
670    }
671}
672
673impl<'a> TokenFmt<'a> for UnitsForReply {
674    fn to_spans(&'a self) -> Vec<Span<'a>> {
675        let mut tokens = vec![Span::plain("Units for ")];
676        tokens.extend(self.of.token_format("D w").to_spans());
677        tokens.push(Span::list_begin(": "));
678        tokens.extend(join(
679            self.units.iter().map(|cat| Span::child(cat)),
680            Span::list_sep("; "),
681        ));
682
683        tokens
684    }
685}
686
687impl<'a> TokenFmt<'a> for UnitsInCategory {
688    fn to_spans(&'a self) -> Vec<Span<'a>> {
689        let mut tokens = vec![
690            if let Some(ref cat) = self.category {
691                Span::plain(cat)
692            } else {
693                Span::plain("Uncategorized")
694            },
695            Span::list_begin(": "),
696        ];
697        tokens.extend(join(
698            self.units.iter().map(Span::unit),
699            Span::list_sep(", "),
700        ));
701        tokens
702    }
703}
704
705impl<'a> TokenFmt<'a> for UnitListReply {
706    fn to_spans(&'a self) -> Vec<Span<'a>> {
707        let mut tokens = vec![Span::list_begin("")];
708        tokens.extend(join(
709            self.list.iter().map(|num| Span::child(num)),
710            Span::list_sep(", "),
711        ));
712        if let Some(ref quantity) = self.rest.quantity {
713            tokens.push(Span::plain(" ("));
714            tokens.push(Span::quantity(quantity));
715            tokens.push(Span::plain(")"));
716        }
717        tokens
718    }
719}
720
721impl<'a> TokenFmt<'a> for SearchReply {
722    fn to_spans(&'a self) -> Vec<Span<'a>> {
723        once(Span::list_begin("Search results: "))
724            .chain(flat_join(
725                self.results
726                    .iter()
727                    .map(|x| x.token_format("u p").to_spans()),
728                Span::list_sep(", "),
729            ))
730            .collect()
731    }
732}
733
734impl<'a> TokenFmt<'a> for QueryError {
735    fn to_spans(&'a self) -> Vec<Span<'a>> {
736        match self {
737            QueryError::Conformance(err) => err.to_spans(),
738            QueryError::NotFound(err) => err.to_spans(),
739            QueryError::Generic { message } => vec![Span::plain(message)],
740        }
741    }
742}
743
744impl<'a> TokenFmt<'a> for ConformanceError {
745    fn to_spans(&'a self) -> Vec<Span<'a>> {
746        let mut tokens = vec![
747            Span::error("Conformance error: "),
748            Span::child(&self.left),
749            Span::plain(" != "),
750            Span::child(&self.right),
751            Span::list_begin("\nSuggestions: "),
752        ];
753        tokens.extend(join(
754            self.suggestions.iter().map(Span::plain),
755            Span::list_sep(", "),
756        ));
757        tokens
758    }
759}
760
761impl<'a> TokenFmt<'a> for NotFoundError {
762    fn to_spans(&'a self) -> Vec<Span<'a>> {
763        let mut tokens = vec![Span::error("No such unit "), Span::user_input(&self.got)];
764        if let Some(ref suggestion) = self.suggestion {
765            tokens.push(Span::error(", did you mean "));
766            tokens.push(Span::unit(suggestion));
767            tokens.push(Span::error("?"));
768        }
769        tokens
770    }
771}