Browse Source

Merge branch 'master' into example_decorator

Bzero 1 year ago
parent
commit
1cf1bb9863

+ 5 - 1
book/src/SUMMARY.md

@@ -21,7 +21,6 @@
     - [XKCD 687](./example-xkcd_687.md)
     - [XKCD 2585](./example-xkcd_2585.md)
     - [XKCD 2812](./example-xkcd_2812.md)
-- [IDE / editor integration](./editor-integration.md)
 
 # Numbat language reference
 
@@ -69,6 +68,11 @@
 
 - [Type system](./type-system.md)
 
+# Other topics
+
+- [IDE / editor integration](./editor-integration.md)
+- [Comparison with other tools](./comparison.md)
+
 # Support
 
 - [Contact us](./contact-us.md)

+ 50 - 0
book/src/comparison.md

@@ -0,0 +1,50 @@
+# Comparison with other tools
+
+The following table provides a comparison of Numbat with other scientific calculators and programming languages. This comparison
+is certainly *not* objective, as we only list criteria that we consider important. If you think that a tool or language is missing
+or misrepresented, please [let us know](https://github.com/sharkdp/numbat/issues).
+
+|                                        | Numbat          | [Qalculate](https://qalculate.github.io/) | [Kalker](https://github.com/PaddiM8/kalker) | [GNU Units](https://www.gnu.org/software/units/) | [Frink](https://frinklang.org/) | [Wolfram Alpha](https://www.wolframalpha.com/) |
+|----------------------------------------|-----------------|-----------|--------|-----------|-------|---------------|
+| FOSS License                           | MIT, Apache-2.0 | GPL-2.0   | MIT    | GPL-3.0   | ❌     | ❌             |
+| **Interfaces**                         |                 |           |        |           |       |               |
+| Command-line                           | ✓               | ✓         | ✓    | ✓         | ✓     | ✓             |
+| Web version                            | ✓               | ❌        | ✓     | ❌         | ❌     | ✓             |
+| Graphical                              | ❌              | ✓         | ❌    | ❌         | (✓)   | ✓             |
+| **Units**                              |                 |           |        |           |       |               |
+| Comprehensive list of units            | ✓               | ✓         | ❌    | ✓         | ✓     | ✓             |
+| Custom units                           | ✓               | ✓         | ❌    | ✓         | ✓     | ❌             |
+| Physical dimensions                    | ✓               | ❌        | ❌    | ❌         | ❌     | ❌             |
+| Currency conversions                   | ✓               | ✓         | ❌    | ❌         | ✓     | ✓             |
+| Date and time calculations             | ✓               | ✓         | ❌    | ❌         | ✓     | ✓             |
+| **Language features**                  |                 |           |        |           |       |               |
+| Custom functions                       | ✓               | ✓        | ✓     | ❌         | ✓     | ❌             |
+| Real programming language              | ✓               | ❌        | ❌     | ❌         | ✓     | ?             |
+| Strongly typed                         | ✓               | ❌        | ❌     | ❌         | ❌     | ❌             |
+| **Calculator features**                |                 |           |        |           |       |               |
+| Symbolic calculations                  | ❌               | (✓)        | ❌    | ❌         | (✓)     | ✓             |
+| Hex/Oct/Bin mode                       | ✓               | ✓         | ✓     | ✓         | ✓     | ✓             |
+| Complex numbers                        | ❌ ([#180](https://github.com/sharkdp/numbat/issues/180))  | ✓        | ✓     | ❌         | ✓     | ✓             |
+| Vectors, Matrices                      | ❌               | ✓        | ✓      | ❌         | ✓     | ✓             |
+
+## Detailed comparison
+
+- [Qalculate](https://qalculate.github.io/) is a fantastic calculator with a strong support for units and conversions.
+  If you don't need the full power of a programming language, Qalculate is probably more feature-complete than Numbat.
+- [Frink](https://frinklang.org/) is a special-purpose programming language with a focus on scientific calculations
+  and units of measurement. The language is probably more powerful than Numbat, but lacks a static type system. It's also
+  a imperative/OOP language, while Numbat is a functional/declarative language. Frink is not open-source.
+- [GNU Units](https://www.gnu.org/software/units/) is probably the most comprehensive tool in terms of pre-defined units.
+  Numbat makes it very easy to define [custom units](./unit-definitions.md). If you think that a unit should be part
+  of the standard library, please [let us know](https://github.com/sharkdp/numbat/issues).
+- [Wolfram Alpha](https://www.wolframalpha.com/) is a very powerful tool, but it's focused on single-line queries instead
+  of longer computations. The query language lacks a strict syntax (which some might consider a feature). The tool is not
+  open source and sometimes has limitations with respect to the number/size of queries you can make.
+
+## Other interesting tools / languages
+
+- [F#](https://fsharp.org/) is the only programming language that we know of that comes close in terms of having an
+  expressive type system that is based on units of measure. In fact, Numbats type system is heavily inspired by F#,
+  except that it uses physical dimensions instead of physical units on the type level. Both languages have feature
+  full [type inference](./function-definitions.md#type-inference). F# is not listed above, as it's not really suitable
+  as a scientific calculator.

+ 1 - 1
book/src/list-units.md

@@ -130,7 +130,7 @@ and — where sensible — units allow for [binary prefixes](https://en.wikipedi
 | `Money` | [Czech koruna](https://en.wikipedia.org/wiki/Czech_koruna) | `czech_koruna`, `czech_korunas`, `CZK`, `czk`, `Kč` |
 | `Money` | [Danish krone](https://en.wikipedia.org/wiki/Danish_krone) | `danish_krone`, `danish_kroner`, `DKK`, `dkk` |
 | `Money` | [US dollar](https://en.wikipedia.org/wiki/United_States_dollar) | `$`, `dollar`, `dollars`, `USD`, `usd` |
-| `Money` | [Euro](https://en.wikipedia.org/wiki/Euro) | `EUR`, `euro`, `euros`, `€` |
+| `Money` | [Euro](https://en.wikipedia.org/wiki/Euro) | `EUR`, `eur`, `euro`, `euros`, `€` |
 | `Money` | [Hong Kong dollar](https://en.wikipedia.org/wiki/Hong_Kong_dollar) | `HK$`, `hk$`, `HKD`, `hkd`, `hong_kong_dollar`, `hong_kong_dollars` |
 | `Money` | [Hungarian forint](https://en.wikipedia.org/wiki/Hungarian_forint) | `Ft`, `HUF`, `huf`, `hungarian_forint`, `hungarian_forints` |
 | `Money` | [Icelandic króna](https://en.wikipedia.org/wiki/Icelandic_króna) | `icelandic_krona`, `icelandic_kronur`, `icelandic_króna`, `icelandic_krónur`, `ISK`, `isk` |

+ 1 - 1
numbat-cli/src/completer.rs

@@ -131,7 +131,7 @@ impl Completer for NumbatCompleter {
             candidates
                 .map(|w| Pair {
                     display: w.to_string(),
-                    replacement: w.to_string(),
+                    replacement: w,
                 })
                 .collect(),
         ))

+ 10 - 4
numbat-cli/src/highlighter.rs

@@ -40,17 +40,23 @@ impl Highlighter for NumbatHighlighter {
         if ctx.variable_names().any(|n| n == candidate)
             || ctx.function_names().any(|n| format!("{n}(") == candidate)
         {
-            Cow::Owned(ansi_format(&markup::identifier(candidate), false))
+            Cow::Owned(ansi_format(
+                &markup::identifier(candidate.to_string()),
+                false,
+            ))
         } else if ctx
             .unit_names()
             .iter()
             .any(|un| un.iter().any(|n| n == candidate))
         {
-            Cow::Owned(ansi_format(&markup::unit(candidate), false))
+            Cow::Owned(ansi_format(&markup::unit(candidate.to_string()), false))
         } else if ctx.dimension_names().iter().any(|n| n == candidate) {
-            Cow::Owned(ansi_format(&markup::type_identifier(candidate), false))
+            Cow::Owned(ansi_format(
+                &markup::type_identifier(candidate.to_string()),
+                false,
+            ))
         } else if KEYWORDS.iter().any(|k| k == &candidate) {
-            Cow::Owned(ansi_format(&markup::keyword(candidate), false))
+            Cow::Owned(ansi_format(&markup::keyword(candidate.to_string()), false))
         } else {
             Cow::Borrowed(candidate)
         }

+ 3 - 3
numbat-cli/src/main.rs

@@ -418,7 +418,7 @@ impl Cli {
                                                 let m = m::text(
                                                     "successfully saved session history to",
                                                 ) + m::space()
-                                                    + m::string(dst);
+                                                    + m::string(dst.to_string());
                                                 println!("{}", ansi_format(&m, interactive));
                                             }
                                             Err(err) => {
@@ -511,7 +511,7 @@ impl Cli {
             Err(_) => Err(()),
         };
 
-        let control_flow = match interpretation_result {
+        let control_flow = match interpretation_result.map_err(|b| *b) {
             Ok((statements, interpreter_result)) => {
                 if interactive || pretty_print {
                     println!();
@@ -550,7 +550,7 @@ impl Cli {
                 ControlFlow::Continue(())
             }
             Err(NumbatError::ResolverError(e)) => {
-                self.print_diagnostic(e.clone());
+                self.print_diagnostic(e);
                 execution_mode.exit_status_in_case_of_error()
             }
             Err(NumbatError::NameResolutionError(

+ 1 - 0
numbat-wasm/src/lib.rs

@@ -103,6 +103,7 @@ impl Numbat {
         match self
             .ctx
             .interpret_with_settings(&mut settings, code, CodeSource::Text)
+            .map_err(|b| *b)
         {
             Ok((statements, result)) => {
                 // Pretty print

+ 1 - 0
numbat/modules/math/constants.nbt

@@ -9,6 +9,7 @@ let π = 3.14159265358979323846264338327950288
 
 @name("Tau")
 @url("https://en.wikipedia.org/wiki/Turn_(angle)#Tau_proposals")
+@aliases(tau)
 let τ = 2 π
 
 @name("Euler's number")

+ 1 - 1
numbat/modules/units/currency.nbt

@@ -3,7 +3,7 @@ dimension Money
 
 @name("Euro")
 @url("https://en.wikipedia.org/wiki/Euro")
-@aliases(euros, EUR, €: short)
+@aliases(euros, EUR, eur, €: short)
 unit euro: Money
 
 # See currencies.nbt for non-Euro currencies

+ 84 - 76
numbat/src/ast.rs

@@ -36,67 +36,77 @@ impl PrettyPrint for BinaryOperator {
     fn pretty_print(&self) -> Markup {
         use BinaryOperator::*;
 
+        let operator = m::operator(match self {
+            Add => "+",
+            Sub => "-",
+            Mul => "×",
+            Div => "/",
+            Power => "^",
+            ConvertTo => "➞",
+            LessThan => "<",
+            GreaterThan => ">",
+            LessOrEqual => "≤",
+            GreaterOrEqual => "≥",
+            Equal => "==",
+            NotEqual => "≠",
+            LogicalAnd => "&&",
+            LogicalOr => "||",
+        });
+
         match self {
-            Add => m::space() + m::operator("+") + m::space(),
-            Sub => m::space() + m::operator("-") + m::space(),
-            Mul => m::space() + m::operator("×") + m::space(),
-            Div => m::space() + m::operator("/") + m::space(),
-            Power => m::operator("^"),
-            ConvertTo => m::space() + m::operator("➞") + m::space(),
-            LessThan => m::space() + m::operator("<") + m::space(),
-            GreaterThan => m::space() + m::operator(">") + m::space(),
-            LessOrEqual => m::space() + m::operator("≤") + m::space(),
-            GreaterOrEqual => m::space() + m::operator("≥") + m::space(),
-            Equal => m::space() + m::operator("==") + m::space(),
-            NotEqual => m::space() + m::operator("≠") + m::space(),
-            LogicalAnd => m::space() + m::operator("&&") + m::space(),
-            LogicalOr => m::space() + m::operator("||") + m::space(),
+            Power => operator,
+            _ => m::space() + operator + m::space(),
         }
     }
 }
 
 #[derive(Debug, Clone, PartialEq)]
-pub enum StringPart {
+pub enum StringPart<'a> {
     Fixed(String),
     Interpolation {
         span: Span,
-        expr: Box<Expression>,
-        format_specifiers: Option<String>,
+        expr: Box<Expression<'a>>,
+        format_specifiers: Option<&'a str>,
     },
 }
 
 #[derive(Debug, Clone, PartialEq)]
-pub enum Expression {
+pub enum Expression<'a> {
     Scalar(Span, Number),
-    Identifier(Span, String),
-    UnitIdentifier(Span, Prefix, String, String),
+    Identifier(Span, &'a str),
+    UnitIdentifier(Span, Prefix, String, String), // can't easily be made &'a str
     TypedHole(Span),
     UnaryOperator {
         op: UnaryOperator,
-        expr: Box<Expression>,
+        expr: Box<Expression<'a>>,
         span_op: Span,
     },
     BinaryOperator {
         op: BinaryOperator,
-        lhs: Box<Expression>,
-        rhs: Box<Expression>,
+        lhs: Box<Expression<'a>>,
+        rhs: Box<Expression<'a>>,
         span_op: Option<Span>, // not available for implicit multiplication and unicode exponents
     },
-    FunctionCall(Span, Span, Box<Expression>, Vec<Expression>),
+    FunctionCall(Span, Span, Box<Expression<'a>>, Vec<Expression<'a>>),
     Boolean(Span, bool),
-    String(Span, Vec<StringPart>),
-    Condition(Span, Box<Expression>, Box<Expression>, Box<Expression>),
+    String(Span, Vec<StringPart<'a>>),
+    Condition(
+        Span,
+        Box<Expression<'a>>,
+        Box<Expression<'a>>,
+        Box<Expression<'a>>,
+    ),
     InstantiateStruct {
         full_span: Span,
         ident_span: Span,
-        name: String,
-        fields: Vec<(Span, String, Expression)>,
+        name: &'a str,
+        fields: Vec<(Span, &'a str, Expression<'a>)>,
     },
-    AccessField(Span, Span, Box<Expression>, String),
-    List(Span, Vec<Expression>),
+    AccessField(Span, Span, Box<Expression<'a>>, &'a str),
+    List(Span, Vec<Expression<'a>>),
 }
 
-impl Expression {
+impl Expression<'_> {
     pub fn full_span(&self) -> Span {
         match self {
             Expression::Scalar(span, _) => *span,
@@ -217,9 +227,9 @@ macro_rules! struct_ {
         crate::ast::Expression::InstantiateStruct {
             full_span: Span::dummy(),
             ident_span: Span::dummy(),
-            name: stringify!($name).to_owned(),
+            name: stringify!($name),
             fields: vec![
-                $((Span::dummy(), stringify!($field).to_owned(), $val)),*
+                $((Span::dummy(), stringify!($field), $val)),*
             ]
         }
     }};
@@ -360,7 +370,7 @@ impl PrettyPrint for TypeExpression {
     fn pretty_print(&self) -> Markup {
         match self {
             TypeExpression::Unity(_) => m::type_identifier("1"),
-            TypeExpression::TypeIdentifier(_, ident) => m::type_identifier(ident),
+            TypeExpression::TypeIdentifier(_, ident) => m::type_identifier(ident.clone()),
             TypeExpression::Multiply(_, lhs, rhs) => {
                 lhs.pretty_print() + m::space() + m::operator("×") + m::space() + rhs.pretty_print()
             }
@@ -394,48 +404,48 @@ pub enum TypeParameterBound {
 }
 
 #[derive(Debug, Clone, PartialEq)]
-pub struct DefineVariable {
+pub struct DefineVariable<'a> {
     pub identifier_span: Span,
-    pub identifier: String,
-    pub expr: Expression,
+    pub identifier: &'a str,
+    pub expr: Expression<'a>,
     pub type_annotation: Option<TypeAnnotation>,
-    pub decorators: Vec<Decorator>,
+    pub decorators: Vec<Decorator<'a>>,
 }
 
 #[derive(Debug, Clone, PartialEq)]
-pub enum Statement {
-    Expression(Expression),
-    DefineVariable(DefineVariable),
+pub enum Statement<'a> {
+    Expression(Expression<'a>),
+    DefineVariable(DefineVariable<'a>),
     DefineFunction {
         function_name_span: Span,
-        function_name: String,
-        type_parameters: Vec<(Span, String, Option<TypeParameterBound>)>,
+        function_name: &'a str,
+        type_parameters: Vec<(Span, &'a str, Option<TypeParameterBound>)>,
         /// Parameters, optionally with type annotations.
-        parameters: Vec<(Span, String, Option<TypeAnnotation>)>,
+        parameters: Vec<(Span, &'a str, Option<TypeAnnotation>)>,
         /// Function body. If it is absent, the function is implemented via FFI
-        body: Option<Expression>,
+        body: Option<Expression<'a>>,
         /// Local variables
-        local_variables: Vec<DefineVariable>,
+        local_variables: Vec<DefineVariable<'a>>,
         /// Optional annotated return type
         return_type_annotation: Option<TypeAnnotation>,
-        decorators: Vec<Decorator>,
+        decorators: Vec<Decorator<'a>>,
     },
-    DefineDimension(Span, String, Vec<TypeExpression>),
-    DefineBaseUnit(Span, String, Option<TypeExpression>, Vec<Decorator>),
+    DefineDimension(Span, &'a str, Vec<TypeExpression>),
+    DefineBaseUnit(Span, &'a str, Option<TypeExpression>, Vec<Decorator<'a>>),
     DefineDerivedUnit {
         identifier_span: Span,
-        identifier: String,
-        expr: Expression,
+        identifier: &'a str,
+        expr: Expression<'a>,
         type_annotation_span: Option<Span>,
         type_annotation: Option<TypeAnnotation>,
-        decorators: Vec<Decorator>,
+        decorators: Vec<Decorator<'a>>,
     },
-    ProcedureCall(Span, ProcedureKind, Vec<Expression>),
+    ProcedureCall(Span, ProcedureKind, Vec<Expression<'a>>),
     ModuleImport(Span, ModulePath),
     DefineStruct {
         struct_name_span: Span,
-        struct_name: String,
-        fields: Vec<(Span, String, TypeAnnotation)>,
+        struct_name: &'a str,
+        fields: Vec<(Span, &'a str, TypeAnnotation)>,
     },
 }
 
@@ -491,7 +501,7 @@ impl ReplaceSpans for TypeExpression {
 }
 
 #[cfg(test)]
-impl ReplaceSpans for StringPart {
+impl ReplaceSpans for StringPart<'_> {
     fn replace_spans(&self) -> Self {
         match self {
             f @ StringPart::Fixed(_) => f.clone(),
@@ -502,18 +512,18 @@ impl ReplaceSpans for StringPart {
             } => StringPart::Interpolation {
                 span: Span::dummy(),
                 expr: Box::new(expr.replace_spans()),
-                format_specifiers: format_specifiers.clone(),
+                format_specifiers: *format_specifiers,
             },
         }
     }
 }
 
 #[cfg(test)]
-impl ReplaceSpans for Expression {
+impl ReplaceSpans for Expression<'_> {
     fn replace_spans(&self) -> Self {
         match self {
             Expression::Scalar(_, name) => Expression::Scalar(Span::dummy(), *name),
-            Expression::Identifier(_, name) => Expression::Identifier(Span::dummy(), name.clone()),
+            Expression::Identifier(_, name) => Expression::Identifier(Span::dummy(), name),
             Expression::UnitIdentifier(_, prefix, name, full_name) => {
                 Expression::UnitIdentifier(Span::dummy(), *prefix, name.clone(), full_name.clone())
             }
@@ -557,17 +567,17 @@ impl ReplaceSpans for Expression {
             Expression::InstantiateStruct { name, fields, .. } => Expression::InstantiateStruct {
                 full_span: Span::dummy(),
                 ident_span: Span::dummy(),
-                name: name.clone(),
+                name,
                 fields: fields
                     .iter()
-                    .map(|(_, n, v)| (Span::dummy(), n.clone(), v.replace_spans()))
+                    .map(|(_, n, v)| (Span::dummy(), *n, v.replace_spans()))
                     .collect(),
             },
             Expression::AccessField(_, _, expr, attr) => Expression::AccessField(
                 Span::dummy(),
                 Span::dummy(),
                 Box::new(expr.replace_spans()),
-                attr.clone(),
+                attr,
             ),
             Expression::List(_, elements) => Expression::List(
                 Span::dummy(),
@@ -579,11 +589,11 @@ impl ReplaceSpans for Expression {
 }
 
 #[cfg(test)]
-impl ReplaceSpans for DefineVariable {
+impl ReplaceSpans for DefineVariable<'_> {
     fn replace_spans(&self) -> Self {
         Self {
             identifier_span: Span::dummy(),
-            identifier: self.identifier.clone(),
+            identifier: self.identifier,
             expr: self.expr.replace_spans(),
             type_annotation: self.type_annotation.as_ref().map(|t| t.replace_spans()),
             decorators: self.decorators.clone(),
@@ -592,7 +602,7 @@ impl ReplaceSpans for DefineVariable {
 }
 
 #[cfg(test)]
-impl ReplaceSpans for Statement {
+impl ReplaceSpans for Statement<'_> {
     fn replace_spans(&self) -> Self {
         match self {
             Statement::Expression(expr) => Statement::Expression(expr.replace_spans()),
@@ -610,17 +620,17 @@ impl ReplaceSpans for Statement {
                 decorators,
             } => Statement::DefineFunction {
                 function_name_span: Span::dummy(),
-                function_name: function_name.clone(),
+                function_name,
                 type_parameters: type_parameters
                     .iter()
-                    .map(|(_, name, bound)| (Span::dummy(), name.clone(), bound.clone()))
+                    .map(|(_, name, bound)| (Span::dummy(), *name, bound.clone()))
                     .collect(),
                 parameters: parameters
                     .iter()
                     .map(|(_, name, type_)| {
                         (
                             Span::dummy(),
-                            name.clone(),
+                            *name,
                             type_.as_ref().map(|t| t.replace_spans()),
                         )
                     })
@@ -635,12 +645,12 @@ impl ReplaceSpans for Statement {
             },
             Statement::DefineDimension(_, name, dexprs) => Statement::DefineDimension(
                 Span::dummy(),
-                name.clone(),
+                name,
                 dexprs.iter().map(|t| t.replace_spans()).collect(),
             ),
             Statement::DefineBaseUnit(_, name, type_, decorators) => Statement::DefineBaseUnit(
                 Span::dummy(),
-                name.clone(),
+                name,
                 type_.as_ref().map(|t| t.replace_spans()),
                 decorators.clone(),
             ),
@@ -653,7 +663,7 @@ impl ReplaceSpans for Statement {
                 decorators,
             } => Statement::DefineDerivedUnit {
                 identifier_span: Span::dummy(),
-                identifier: identifier.clone(),
+                identifier,
                 expr: expr.replace_spans(),
                 type_annotation_span: type_annotation_span.map(|_| Span::dummy()),
                 type_annotation: type_annotation.as_ref().map(|t| t.replace_spans()),
@@ -673,12 +683,10 @@ impl ReplaceSpans for Statement {
                 ..
             } => Statement::DefineStruct {
                 struct_name_span: Span::dummy(),
-                struct_name: struct_name.clone(),
+                struct_name,
                 fields: fields
                     .iter()
-                    .map(|(_span, name, type_)| {
-                        (Span::dummy(), name.clone(), type_.replace_spans())
-                    })
+                    .map(|(_span, name, type_)| (Span::dummy(), *name, type_.replace_spans()))
                     .collect(),
             },
         }
@@ -686,7 +694,7 @@ impl ReplaceSpans for Statement {
 }
 
 #[cfg(test)]
-impl ReplaceSpans for Vec<Statement> {
+impl ReplaceSpans for Vec<Statement<'_>> {
     fn replace_spans(&self) -> Self {
         self.iter().map(|s| s.replace_spans()).collect()
     }

+ 22 - 27
numbat/src/bytecode_interpreter.rs

@@ -69,15 +69,15 @@ impl BytecodeInterpreter {
                     .rposition(|l| &l.identifier == identifier)
                 {
                     self.vm.add_op1(Op::GetUpvalue, upvalue_position as u16);
-                } else if LAST_RESULT_IDENTIFIERS.contains(&identifier.as_str()) {
+                } else if LAST_RESULT_IDENTIFIERS.contains(identifier) {
                     self.vm.add_op(Op::GetLastResult);
-                } else if let Some(is_foreign) = self.functions.get(identifier) {
+                } else if let Some(is_foreign) = self.functions.get(*identifier) {
                     let index = self
                         .vm
                         .add_constant(Constant::FunctionReference(if *is_foreign {
-                            FunctionReference::Foreign(identifier.clone())
+                            FunctionReference::Foreign(identifier.to_string())
                         } else {
-                            FunctionReference::Normal(identifier.clone())
+                            FunctionReference::Normal(identifier.to_string())
                         }));
                     self.vm.add_op1(Op::LoadConstant, index);
                 } else {
@@ -178,7 +178,7 @@ impl BytecodeInterpreter {
 
                 let sorted_exprs = exprs
                     .iter()
-                    .sorted_by_key(|(n, _)| struct_info.fields.get_index_of(n).unwrap());
+                    .sorted_by_key(|(n, _)| struct_info.fields.get_index_of(*n).unwrap());
 
                 for (_, expr) in sorted_exprs.rev() {
                     self.compile_expression(expr)?;
@@ -198,7 +198,7 @@ impl BytecodeInterpreter {
                     );
                 };
 
-                let idx = struct_info.fields.get_index_of(attr).unwrap();
+                let idx = struct_info.fields.get_index_of(*attr).unwrap();
 
                 self.vm.add_op1(Op::AccessStructField, idx as u16);
             }
@@ -221,7 +221,7 @@ impl BytecodeInterpreter {
                 for part in string_parts {
                     match part {
                         StringPart::Fixed(s) => {
-                            let index = self.vm.add_constant(Constant::String(s.clone()));
+                            let index = self.vm.add_constant(Constant::String(s.to_string()));
                             self.vm.add_op1(Op::LoadConstant, index)
                         }
                         StringPart::Interpolation {
@@ -231,7 +231,7 @@ impl BytecodeInterpreter {
                         } => {
                             self.compile_expression(expr)?;
                             let index = self.vm.add_constant(Constant::FormatSpecifiers(
-                                format_specifiers.clone(),
+                                format_specifiers.map(|s| s.to_string()),
                             ));
                             self.vm.add_op1(Op::LoadConstant, index)
                         }
@@ -283,12 +283,11 @@ impl BytecodeInterpreter {
 
         // For variables, we ignore the prefix info and only use the names
         let aliases = crate::decorator::name_and_aliases(identifier, decorators)
-            .map(|(name, _)| name)
-            .cloned()
+            .map(|(name, _)| name.to_owned())
             .collect::<Vec<_>>();
         let metadata = LocalMetadata {
-            name: crate::decorator::name(decorators),
-            url: crate::decorator::url(decorators),
+            name: crate::decorator::name(decorators).map(ToOwned::to_owned),
+            url: crate::decorator::url(decorators).map(ToOwned::to_owned),
             description: crate::decorator::description(decorators),
             aliases: aliases.clone(),
         };
@@ -336,7 +335,7 @@ impl BytecodeInterpreter {
                 let current_depth = self.current_depth();
                 for parameter in parameters {
                     self.locals[current_depth].push(Local {
-                        identifier: parameter.1.clone(),
+                        identifier: parameter.1.to_string(),
                         depth: current_depth,
                         metadata: LocalMetadata::default(),
                     });
@@ -353,7 +352,7 @@ impl BytecodeInterpreter {
 
                 self.vm.end_function();
 
-                self.functions.insert(name.clone(), false);
+                self.functions.insert(name.to_string(), false);
             }
             Statement::DefineFunction(
                 name,
@@ -372,7 +371,7 @@ impl BytecodeInterpreter {
                 self.vm
                     .add_foreign_function(name, parameters.len()..=parameters.len());
 
-                self.functions.insert(name.clone(), true);
+                self.functions.insert(name.to_string(), true);
             }
             Statement::DefineDimension(_name, _dexprs) => {
                 // Declaring a dimension is like introducing a new type. The information
@@ -380,7 +379,7 @@ impl BytecodeInterpreter {
             }
             Statement::DefineBaseUnit(unit_name, decorators, annotation, type_) => {
                 let aliases = decorator::name_and_aliases(unit_name, decorators)
-                    .map(|(name, ap)| (name.clone(), ap))
+                    .map(|(name, ap)| (name.to_owned(), ap))
                     .collect();
 
                 self.vm
@@ -394,11 +393,11 @@ impl BytecodeInterpreter {
                                 .map(|a| a.pretty_print())
                                 .unwrap_or(type_.to_readable_type(dimension_registry, false)),
                             aliases,
-                            name: decorator::name(decorators),
+                            name: decorator::name(decorators).map(ToOwned::to_owned),
                             canonical_name: decorator::get_canonical_unit_name(
                                 unit_name, decorators,
                             ),
-                            url: decorator::url(decorators),
+                            url: decorator::url(decorators).map(ToOwned::to_owned),
                             description: decorator::description(decorators),
                             binary_prefixes: decorators.contains(&Decorator::BinaryPrefixes),
                             metric_prefixes: decorators.contains(&Decorator::MetricPrefixes),
@@ -408,7 +407,7 @@ impl BytecodeInterpreter {
 
                 let constant_idx = self.vm.add_constant(Constant::Unit(Unit::new_base(
                     unit_name,
-                    crate::decorator::get_canonical_unit_name(unit_name.as_str(), &decorators[..]),
+                    crate::decorator::get_canonical_unit_name(unit_name, &decorators[..]),
                 )));
                 for (name, _) in decorator::name_and_aliases(unit_name, decorators) {
                     self.unit_name_to_constant_index
@@ -424,7 +423,7 @@ impl BytecodeInterpreter {
                 _readable_type,
             ) => {
                 let aliases = decorator::name_and_aliases(unit_name, decorators)
-                    .map(|(name, ap)| (name.clone(), ap))
+                    .map(|(name, ap)| (name.to_owned(), ap))
                     .collect();
 
                 let constant_idx = self.vm.add_constant(Constant::Unit(Unit::new_base(
@@ -437,11 +436,7 @@ impl BytecodeInterpreter {
                 let unit_information_idx = self.vm.add_unit_information(
                     unit_name,
                     Some(
-                        &crate::decorator::get_canonical_unit_name(
-                            unit_name.as_str(),
-                            &decorators[..],
-                        )
-                        .name,
+                        &crate::decorator::get_canonical_unit_name(unit_name, &decorators[..]).name,
                     ),
                     UnitMetadata {
                         type_: type_.to_concrete_type(), // We guarantee that derived-unit definitions do not contain generics, so no TGen(..)s can escape
@@ -450,9 +445,9 @@ impl BytecodeInterpreter {
                             .map(|a| a.pretty_print())
                             .unwrap_or(type_.to_readable_type(dimension_registry, false)),
                         aliases,
-                        name: decorator::name(decorators),
+                        name: decorator::name(decorators).map(ToOwned::to_owned),
                         canonical_name: decorator::get_canonical_unit_name(unit_name, decorators),
-                        url: decorator::url(decorators),
+                        url: decorator::url(decorators).map(ToOwned::to_owned),
                         description: decorator::description(decorators),
                         binary_prefixes: decorators.contains(&Decorator::BinaryPrefixes),
                         metric_prefixes: decorators.contains(&Decorator::MetricPrefixes),

+ 4 - 3
numbat/src/column_formatter.rs

@@ -37,8 +37,9 @@ impl ColumnFormatter {
 
             if min_num_columns < 1 {
                 for entry in entries {
-                    result += Markup::from(FormattedString(OutputType::Normal, format, entry))
-                        + m::whitespace(" ".repeat(self.padding));
+                    result +=
+                        Markup::from(FormattedString(OutputType::Normal, format, entry.into()))
+                            + m::whitespace(" ".repeat(self.padding));
                 }
                 return result;
             }
@@ -81,7 +82,7 @@ impl ColumnFormatter {
                             result += Markup::from(FormattedString(
                                 OutputType::Normal,
                                 format,
-                                (*entry).into(),
+                                entry.to_string().into(),
                             ));
                             result += m::whitespace(" ".repeat(whitespace_length));
                         } else {

+ 58 - 30
numbat/src/decorator.rs

@@ -1,47 +1,75 @@
-use crate::{prefix_parser::AcceptsPrefix, unit::CanonicalName};
+use crate::{prefix_parser::AcceptsPrefix, span::Span, unit::CanonicalName};
 
 #[derive(Debug, Clone, PartialEq, Eq)]
-pub enum Decorator {
+pub enum Decorator<'a> {
     MetricPrefixes,
     BinaryPrefixes,
-    Aliases(Vec<(String, Option<AcceptsPrefix>)>),
+    Aliases(Vec<(&'a str, Option<AcceptsPrefix>, Span)>),
     Url(String),
     Name(String),
     Description(String),
     Example(String, Option<String>),
 }
 
-pub fn name_and_aliases<'a>(
-    name: &'a String,
+/// Get an iterator of data computed from a name and/or its alias's `AcceptsPrefix` and
+/// `Span`. If `name` itself is in the list of aliases, then it (or more precisely, the
+/// data computed from it) will be placed at the front of the iterator
+///
+/// `f` says how to turn a triple of data associated with `name` or an alias, `(name,
+/// accepts_prefix, Option<span>)`, into a `T`. The generality is really just here to
+/// decide whether to yield `(&'a String, AcceptsPrefix)` or a `(&'a String,
+/// AcceptsPrefix, Span)`.
+fn name_and_aliases_inner<'a, T: 'a>(
+    name: &'a str,
     decorators: &'a [Decorator],
-) -> Box<dyn Iterator<Item = (&'a String, AcceptsPrefix)> + 'a> {
-    let aliases = {
-        let mut aliases_vec = vec![];
-        for decorator in decorators {
-            if let Decorator::Aliases(aliases) = decorator {
-                aliases_vec = aliases
-                    .iter()
-                    .map(|(name, accepts_prefix)| {
-                        (name, accepts_prefix.unwrap_or(AcceptsPrefix::only_long()))
-                    })
-                    .collect();
+    f: impl 'a + Fn(&'a str, AcceptsPrefix, Option<Span>) -> T,
+) -> impl 'a + Iterator<Item = T> {
+    // contains all the aliases of `name`, starting with `name` itself
+    let mut aliases_vec = vec![f(name, AcceptsPrefix::only_long(), None)];
+
+    for decorator in decorators {
+        if let Decorator::Aliases(aliases) = decorator {
+            for (n, ap, span) in aliases {
+                let ap = ap.unwrap_or(AcceptsPrefix::only_long());
+                if *n == name {
+                    // use the AcceptsPrefix from the alias, but the span from `name`
+                    // itself; this way we always report a conflicting `name` first
+                    // before reporting any of its aliases. in effect we swallow aliases
+                    // equal to `name` itself (but keep their metadata)
+                    aliases_vec[0] = f(n, ap, None);
+                } else {
+                    aliases_vec.push(f(n, ap, Some(*span)));
+                }
             }
         }
-        aliases_vec
-    };
-
-    if !aliases.iter().any(|(n, _)| n == &name) {
-        let name_iter = std::iter::once((name, AcceptsPrefix::only_long()));
-        Box::new(name_iter.chain(aliases))
-    } else {
-        Box::new(aliases.into_iter())
     }
+
+    aliases_vec.into_iter()
+}
+
+/// Returns iterator of `(name_or_alias, accepts_prefix)` for the given name
+pub fn name_and_aliases<'a>(
+    name: &'a str,
+    decorators: &'a [Decorator],
+) -> impl 'a + Iterator<Item = (&'a str, AcceptsPrefix)> {
+    name_and_aliases_inner(name, decorators, |n, accepts_prefix, _| (n, accepts_prefix))
+}
+
+/// Returns iterator of `(name_or_alias, accepts_prefix, span)` for the given name
+pub fn name_and_aliases_spans<'a>(
+    name: &'a str,
+    name_span: Span,
+    decorators: &'a [Decorator],
+) -> impl 'a + Iterator<Item = (&'a str, AcceptsPrefix, Span)> {
+    name_and_aliases_inner(name, decorators, move |n, accepts_prefix, span| {
+        (n, accepts_prefix, span.unwrap_or(name_span))
+    })
 }
 
 pub fn get_canonical_unit_name(unit_name: &str, decorators: &[Decorator]) -> CanonicalName {
     for decorator in decorators {
         if let Decorator::Aliases(aliases) = decorator {
-            for (alias, accepts_prefix) in aliases {
+            for (alias, accepts_prefix, _) in aliases {
                 match accepts_prefix {
                     &Some(ap) if ap.short => {
                         return CanonicalName::new(alias, ap);
@@ -57,19 +85,19 @@ pub fn get_canonical_unit_name(unit_name: &str, decorators: &[Decorator]) -> Can
     }
 }
 
-pub fn name(decorators: &[Decorator]) -> Option<String> {
+pub fn name<'a>(decorators: &'a [Decorator<'a>]) -> Option<&'a str> {
     for decorator in decorators {
         if let Decorator::Name(name) = decorator {
-            return Some(name.clone());
+            return Some(name);
         }
     }
     None
 }
 
-pub fn url(decorators: &[Decorator]) -> Option<String> {
+pub fn url<'a>(decorators: &'a [Decorator<'a>]) -> Option<&'a str> {
     for decorator in decorators {
         if let Decorator::Url(url) = decorator {
-            return Some(url.clone());
+            return Some(url);
         }
     }
     None
@@ -103,7 +131,7 @@ pub fn examples(decorators: &[Decorator]) -> Vec<(String, Option<String>)> {
 pub fn contains_aliases_with_prefixes(decorates: &[Decorator]) -> bool {
     for decorator in decorates {
         if let Decorator::Aliases(aliases) = decorator {
-            if aliases.iter().any(|(_, prefixes)| prefixes.is_some()) {
+            if aliases.iter().any(|(_, prefixes, _)| prefixes.is_some()) {
                 return true;
             }
         }

+ 1 - 1
numbat/src/dimension.rs

@@ -24,7 +24,7 @@ impl DimensionRegistry {
                     .any(|(_, n, _)| n == name)
                 {
                     Ok(BaseRepresentation::from_factor(BaseRepresentationFactor(
-                        name.clone(),
+                        name.to_string(),
                         Exponent::from_integer(1),
                     )))
                 } else {

+ 3 - 3
numbat/src/ffi/plot.rs

@@ -99,9 +99,9 @@ pub fn show(args: Args) -> Result<Value> {
     // Dynamic dispatch hack since we don't have bounded polymorphism.
     // And no real support for generics in the FFI.
     let Value::StructInstance(info, _) = args.front().unwrap() else {
-        return Err(RuntimeError::UserError(format!(
-            "Unsupported argument to 'show'.",
-        )));
+        return Err(RuntimeError::UserError(
+            "Unsupported argument to 'show'.".into(),
+        ));
     };
 
     let plot = if info.name == "LinePlot" {

+ 1 - 1
numbat/src/help.rs

@@ -62,7 +62,7 @@ pub fn help_markup() -> m::Markup {
     let mut example_context = Context::new(BuiltinModuleImporter::default());
     let _use_prelude_output = evaluate_example(&mut example_context, "use prelude");
     for example in examples.iter() {
-        output += m::text(">>> ") + m::text(example) + m::nl();
+        output += m::text(">>> ") + m::text(*example) + m::nl();
         output += evaluate_example(&mut example_context, example) + m::nl();
     }
     output

+ 3 - 3
numbat/src/interpreter/mod.rs

@@ -135,7 +135,7 @@ impl InterpreterResult {
     }
 }
 
-pub type Result<T> = std::result::Result<T, RuntimeError>;
+pub type Result<T> = std::result::Result<T, Box<RuntimeError>>;
 
 pub type PrintFunction = dyn FnMut(&Markup) + Send;
 
@@ -210,7 +210,7 @@ mod tests {
             .expect("No name resolution errors for inputs in this test suite");
         let mut typechecker = crate::typechecker::TypeChecker::default();
         let statements_typechecked = typechecker
-            .check(statements_transformed)
+            .check(&statements_transformed)
             .expect("No type check errors for inputs in this test suite");
         BytecodeInterpreter::new().interpret_statements(
             &mut InterpreterSettings::default(),
@@ -237,7 +237,7 @@ mod tests {
     #[track_caller]
     fn assert_runtime_error(input: &str, err_expected: RuntimeError) {
         if let Err(err_actual) = get_interpreter_result(input) {
-            assert_eq!(err_actual, err_expected);
+            assert_eq!(*err_actual, err_expected);
         } else {
             panic!();
         }

+ 81 - 71
numbat/src/lib.rs

@@ -47,6 +47,8 @@ mod unit_registry;
 pub mod value;
 mod vm;
 
+use std::borrow::Cow;
+
 use bytecode_interpreter::BytecodeInterpreter;
 use column_formatter::ColumnFormatter;
 use currency::ExchangeRatesCache;
@@ -94,7 +96,7 @@ pub enum NumbatError {
     RuntimeError(RuntimeError),
 }
 
-type Result<T> = std::result::Result<T, NumbatError>;
+type Result<T> = std::result::Result<T, Box<NumbatError>>;
 
 #[derive(Clone)]
 pub struct Context {
@@ -202,15 +204,6 @@ impl Context {
     }
 
     pub fn print_environment(&self) -> Markup {
-        let mut functions: Vec<_> = self.function_names().collect();
-        functions.sort();
-        let mut dimensions = Vec::from(self.dimension_names());
-        dimensions.sort();
-        let mut units = Vec::from(self.unit_names());
-        units.sort();
-        let mut variables: Vec<_> = self.variable_names().collect();
-        variables.sort();
-
         let mut output = m::empty();
 
         output += m::emphasized("List of functions:") + m::nl();
@@ -255,11 +248,11 @@ impl Context {
     /// Gets completions for the given word_part
     ///
     /// If `add_paren` is true, then an opening paren will be added to the end of function names
-    pub fn get_completions_for<'a>(
+    pub fn get_completions_for(
         &self,
-        word_part: &'a str,
+        word_part: &str,
         add_paren: bool,
-    ) -> impl Iterator<Item = String> + 'a {
+    ) -> impl Iterator<Item = String> {
         const COMMON_METRIC_PREFIXES: &[&str] = &[
             "pico", "nano", "micro", "milli", "centi", "kilo", "mega", "giga", "tera",
         ];
@@ -272,53 +265,60 @@ impl Context {
             })
             .collect();
 
-        let mut words: Vec<_> = KEYWORDS.iter().map(|k| k.to_string()).collect();
+        let mut words = Vec::new();
+
+        let mut add_if_valid = |word: Cow<'_, str>| {
+            if word.starts_with(word_part) {
+                words.push(word.into_owned());
+            }
+        };
+
+        for kw in KEYWORDS {
+            add_if_valid((*kw).into());
+        }
 
         for (patterns, _) in UNICODE_INPUT {
             for pattern in *patterns {
-                words.push(pattern.to_string());
+                add_if_valid((*pattern).into());
             }
         }
 
-        {
-            for variable in self.variable_names() {
-                words.push(variable.clone());
-            }
+        for variable in self.variable_names() {
+            add_if_valid(variable.into());
+        }
 
-            for function in self.function_names() {
-                if add_paren {
-                    words.push(format!("{function}("));
-                } else {
-                    words.push(function.to_string());
-                }
+        for mut function in self.function_names() {
+            if add_paren {
+                function.push('(');
             }
+            add_if_valid(function.into());
+        }
 
-            for dimension in self.dimension_names() {
-                words.push(dimension.clone());
-            }
+        for dimension in self.dimension_names() {
+            add_if_valid(dimension.into());
+        }
 
-            for (_, (_, meta)) in self.unit_representations() {
-                for (unit, accepts_prefix) in meta.aliases {
-                    words.push(unit.clone());
-
-                    // Add some of the common long prefixes for units that accept them.
-                    // We do not add all possible prefixes here in order to keep the
-                    // number of completions to a reasonable size. Also, we do not add
-                    // short prefixes for units that accept them, as that leads to lots
-                    // and lots of 2-3 character words.
-                    if accepts_prefix.long && meta.metric_prefixes {
-                        for prefix in &metric_prefixes {
-                            words.push(format!("{prefix}{unit}"));
-                        }
+        for (_, (_, meta)) in self.unit_representations() {
+            for (unit, accepts_prefix) in meta.aliases {
+                // Add some of the common long prefixes for units that accept them.
+                // We do not add all possible prefixes here in order to keep the
+                // number of completions to a reasonable size. Also, we do not add
+                // short prefixes for units that accept them, as that leads to lots
+                // and lots of 2-3 character words.
+                if accepts_prefix.long && meta.metric_prefixes {
+                    for prefix in &metric_prefixes {
+                        add_if_valid(format!("{prefix}{unit}").into());
                     }
                 }
+
+                add_if_valid(unit.into());
             }
         }
 
         words.sort();
         words.dedup();
 
-        words.into_iter().filter(move |w| w.starts_with(word_part))
+        words.into_iter()
     }
 
     pub fn print_info_for_keyword(&mut self, keyword: &str) -> Markup {
@@ -338,7 +338,8 @@ impl Context {
                 .ok()
                 .map(|(_, md)| md)
             {
-                let mut help = m::text("Unit: ") + m::unit(md.name.as_deref().unwrap_or(keyword));
+                let mut help =
+                    m::text("Unit: ") + m::unit(md.name.unwrap_or_else(|| keyword.to_string()));
                 if let Some(url) = &md.url {
                     help += m::text(" (") + m::string(url_encode(url)) + m::text(")");
                 }
@@ -359,12 +360,13 @@ impl Context {
                     let desc = "Description: ";
                     let mut lines = description.lines();
                     help += m::text(desc)
-                        + m::text(lines.by_ref().next().unwrap_or("").trim())
+                        + m::text(lines.by_ref().next().unwrap_or("").trim().to_string())
                         + m::nl();
 
                     for line in lines {
-                        help +=
-                            m::whitespace(" ".repeat(desc.len())) + m::text(line.trim()) + m::nl();
+                        help += m::whitespace(" ".repeat(desc.len()))
+                            + m::text(line.trim().to_string())
+                            + m::nl();
                     }
                 }
 
@@ -387,17 +389,17 @@ impl Context {
                     if !prefix.is_none() {
                         help += m::nl()
                             + m::value("1 ")
-                            + m::unit(keyword)
+                            + m::unit(keyword.to_string())
                             + m::text(" = ")
                             + m::value(prefix.factor().pretty_print())
                             + m::space()
-                            + m::unit(&full_name);
+                            + m::unit(full_name.to_string());
                     }
 
                     if let Some(BaseUnitAndFactor(prod, num)) = x {
                         help += m::nl()
                             + m::value("1 ")
-                            + m::unit(&full_name)
+                            + m::unit(full_name.to_string())
                             + m::text(" = ")
                             + m::value(num.pretty_print())
                             + m::space()
@@ -409,7 +411,8 @@ impl Context {
                                 Some(m::FormatType::Unit),
                             );
                     } else {
-                        help += m::nl() + m::unit(&full_name) + m::text(" is a base unit");
+                        help +=
+                            m::nl() + m::unit(full_name.to_string()) + m::text(" is a base unit");
                     }
                 };
 
@@ -422,9 +425,9 @@ impl Context {
         if let Some(l) = self.interpreter.lookup_global(keyword) {
             let mut help = m::text("Variable: ");
             if let Some(name) = &l.metadata.name {
-                help += m::text(name);
+                help += m::text(name.clone());
             } else {
-                help += m::identifier(keyword);
+                help += m::identifier(keyword.to_string());
             }
             if let Some(url) = &l.metadata.url {
                 help += m::text(" (") + m::string(url_encode(url)) + m::text(")");
@@ -434,11 +437,14 @@ impl Context {
             if let Some(description) = &l.metadata.description {
                 let desc = "Description: ";
                 let mut lines = description.lines();
-                help +=
-                    m::text(desc) + m::text(lines.by_ref().next().unwrap_or("").trim()) + m::nl();
+                help += m::text(desc)
+                    + m::text(lines.by_ref().next().unwrap_or("").trim().to_string())
+                    + m::nl();
 
                 for line in lines {
-                    help += m::whitespace(" ".repeat(desc.len())) + m::text(line.trim()) + m::nl();
+                    help += m::whitespace(" ".repeat(desc.len()))
+                        + m::text(line.trim().to_string())
+                        + m::nl();
                 }
             }
 
@@ -467,9 +473,9 @@ impl Context {
 
             let mut help = m::text("Function:    ");
             if let Some(name) = &metadata.name {
-                help += m::text(name);
+                help += m::text(name.to_string());
             } else {
-                help += m::identifier(keyword);
+                help += m::identifier(keyword.to_string());
             }
             if let Some(url) = &metadata.url {
                 help += m::text(" (") + m::string(url_encode(url)) + m::text(")");
@@ -484,11 +490,14 @@ impl Context {
             if let Some(description) = &metadata.description {
                 let desc = "Description: ";
                 let mut lines = description.lines();
-                help +=
-                    m::text(desc) + m::text(lines.by_ref().next().unwrap_or("").trim()) + m::nl();
+                help += m::text(desc)
+                    + m::text(lines.by_ref().next().unwrap_or("").trim().to_string())
+                    + m::nl();
 
                 for line in lines {
-                    help += m::whitespace(" ".repeat(desc.len())) + m::text(line.trim()) + m::nl();
+                    help += m::whitespace(" ".repeat(desc.len()))
+                        + m::text(line.trim().to_string())
+                        + m::nl();
                 }
             }
 
@@ -541,20 +550,20 @@ impl Context {
         &mut self.resolver
     }
 
-    pub fn interpret(
+    pub fn interpret<'a>(
         &mut self,
-        code: &str,
+        code: &'a str,
         code_source: CodeSource,
-    ) -> Result<(Vec<typed_ast::Statement>, InterpreterResult)> {
+    ) -> Result<(Vec<typed_ast::Statement<'a>>, InterpreterResult)> {
         self.interpret_with_settings(&mut InterpreterSettings::default(), code, code_source)
     }
 
-    pub fn interpret_with_settings(
+    pub fn interpret_with_settings<'a>(
         &mut self,
         settings: &mut InterpreterSettings,
-        code: &str,
+        code: &'a str,
         code_source: CodeSource,
-    ) -> Result<(Vec<typed_ast::Statement>, InterpreterResult)> {
+    ) -> Result<(Vec<typed_ast::Statement<'a>>, InterpreterResult)> {
         let statements = self
             .resolver
             .resolve(code, code_source.clone())
@@ -586,8 +595,8 @@ impl Context {
 
         let result = self
             .typechecker
-            .check(transformed_statements)
-            .map_err(NumbatError::TypeCheckError);
+            .check(&transformed_statements)
+            .map_err(|err| NumbatError::TypeCheckError(*err));
 
         if result.is_err() {
             // Reset the state of the prefix transformer to what we had before. This is necessary
@@ -637,6 +646,7 @@ impl Context {
                         "renminbi",
                         "元",
                         "EUR",
+                        "eur",
                         "euro",
                         "euros",
                         "€",
@@ -773,9 +783,9 @@ impl Context {
                             let erc = ExchangeRatesCache::fetch();
 
                             if erc.is_none() {
-                                return Err(NumbatError::RuntimeError(
+                                return Err(Box::new(NumbatError::RuntimeError(
                                     RuntimeError::CouldNotLoadExchangeRates,
-                                ));
+                                )));
                             }
                         }
 
@@ -824,7 +834,7 @@ impl Context {
             self.interpreter = interpreter_old;
         }
 
-        let result = result.map_err(NumbatError::RuntimeError)?;
+        let result = result.map_err(|err| NumbatError::RuntimeError(*err))?;
 
         Ok((typed_statements, result))
     }

+ 31 - 32
numbat/src/markup.rs

@@ -1,4 +1,4 @@
-use std::fmt::Display;
+use std::{borrow::Cow, fmt::Display};
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum FormatType {
@@ -23,7 +23,7 @@ pub enum OutputType {
 }
 
 #[derive(Debug, Clone, PartialEq)]
-pub struct FormattedString(pub OutputType, pub FormatType, pub String);
+pub struct FormattedString(pub OutputType, pub FormatType, pub Cow<'static, str>);
 
 #[derive(Debug, Clone, Default, PartialEq)]
 pub struct Markup(pub Vec<FormattedString>);
@@ -43,10 +43,9 @@ impl Display for Markup {
 impl std::ops::Add for Markup {
     type Output = Markup;
 
-    fn add(self, rhs: Self) -> Self::Output {
-        let mut res = self.0;
-        res.extend_from_slice(&rhs.0);
-        Markup(res)
+    fn add(mut self, rhs: Self) -> Self::Output {
+        self.0.extend(rhs.0);
+        self
     }
 }
 
@@ -66,7 +65,7 @@ pub fn space() -> Markup {
     Markup::from(FormattedString(
         OutputType::Normal,
         FormatType::Whitespace,
-        " ".to_string(),
+        " ".into(),
     ))
 }
 
@@ -74,99 +73,99 @@ pub fn empty() -> Markup {
     Markup::default()
 }
 
-pub fn whitespace(text: impl AsRef<str>) -> Markup {
+pub fn whitespace(text: impl Into<Cow<'static, str>>) -> Markup {
     Markup::from(FormattedString(
         OutputType::Normal,
         FormatType::Whitespace,
-        text.as_ref().to_string(),
+        text.into(),
     ))
 }
 
-pub fn emphasized(text: impl AsRef<str>) -> Markup {
+pub fn emphasized(text: impl Into<Cow<'static, str>>) -> Markup {
     Markup::from(FormattedString(
         OutputType::Normal,
         FormatType::Emphasized,
-        text.as_ref().to_string(),
+        text.into(),
     ))
 }
 
-pub fn dimmed(text: impl AsRef<str>) -> Markup {
+pub fn dimmed(text: impl Into<Cow<'static, str>>) -> Markup {
     Markup::from(FormattedString(
         OutputType::Normal,
         FormatType::Dimmed,
-        text.as_ref().to_string(),
+        text.into(),
     ))
 }
 
-pub fn text(text: impl AsRef<str>) -> Markup {
+pub fn text(text: impl Into<Cow<'static, str>>) -> Markup {
     Markup::from(FormattedString(
         OutputType::Normal,
         FormatType::Text,
-        text.as_ref().to_string(),
+        text.into(),
     ))
 }
 
-pub fn string(text: impl AsRef<str>) -> Markup {
+pub fn string(text: impl Into<Cow<'static, str>>) -> Markup {
     Markup::from(FormattedString(
         OutputType::Normal,
         FormatType::String,
-        text.as_ref().to_string(),
+        text.into(),
     ))
 }
 
-pub fn keyword(text: impl AsRef<str>) -> Markup {
+pub fn keyword(text: impl Into<Cow<'static, str>>) -> Markup {
     Markup::from(FormattedString(
         OutputType::Normal,
         FormatType::Keyword,
-        text.as_ref().to_string(),
+        text.into(),
     ))
 }
 
-pub fn value(text: impl AsRef<str>) -> Markup {
+pub fn value(text: impl Into<Cow<'static, str>>) -> Markup {
     Markup::from(FormattedString(
         OutputType::Normal,
         FormatType::Value,
-        text.as_ref().to_string(),
+        text.into(),
     ))
 }
 
-pub fn unit(text: impl AsRef<str>) -> Markup {
+pub fn unit(text: impl Into<Cow<'static, str>>) -> Markup {
     Markup::from(FormattedString(
         OutputType::Normal,
         FormatType::Unit,
-        text.as_ref().to_string(),
+        text.into(),
     ))
 }
 
-pub fn identifier(text: impl AsRef<str>) -> Markup {
+pub fn identifier(text: impl Into<Cow<'static, str>>) -> Markup {
     Markup::from(FormattedString(
         OutputType::Normal,
         FormatType::Identifier,
-        text.as_ref().to_string(),
+        text.into(),
     ))
 }
 
-pub fn type_identifier(text: impl AsRef<str>) -> Markup {
+pub fn type_identifier(text: impl Into<Cow<'static, str>>) -> Markup {
     Markup::from(FormattedString(
         OutputType::Normal,
         FormatType::TypeIdentifier,
-        text.as_ref().to_string(),
+        text.into(),
     ))
 }
 
-pub fn operator(text: impl AsRef<str>) -> Markup {
+pub fn operator(text: impl Into<Cow<'static, str>>) -> Markup {
     Markup::from(FormattedString(
         OutputType::Normal,
         FormatType::Operator,
-        text.as_ref().to_string(),
+        text.into(),
     ))
 }
 
-pub fn decorator(text: impl AsRef<str>) -> Markup {
+pub fn decorator(text: impl Into<Cow<'static, str>>) -> Markup {
     Markup::from(FormattedString(
         OutputType::Normal,
         FormatType::Decorator,
-        text.as_ref().to_string(),
+        text.into(),
     ))
 }
 
@@ -206,7 +205,7 @@ pub struct PlainTextFormatter;
 
 impl Formatter for PlainTextFormatter {
     fn format_part(&self, FormattedString(_, _, text): &FormattedString) -> String {
-        text.clone()
+        text.to_string()
     }
 }
 

+ 156 - 133
numbat/src/parser.rs

@@ -258,7 +258,7 @@ impl ParseError {
 }
 
 type Result<T, E = ParseError> = std::result::Result<T, E>;
-type ParseResult = Result<Vec<Statement>, (Vec<Statement>, Vec<ParseError>)>;
+type ParseResult<'a> = Result<Vec<Statement<'a>>, (Vec<Statement<'a>>, Vec<ParseError>)>;
 
 static PROCEDURES: &[TokenKind] = &[
     TokenKind::ProcedurePrint,
@@ -267,12 +267,12 @@ static PROCEDURES: &[TokenKind] = &[
     TokenKind::ProcedureType,
 ];
 
-struct Parser {
+struct Parser<'a> {
     current: usize,
-    decorator_stack: Vec<Decorator>,
+    decorator_stack: Vec<Decorator<'a>>,
 }
 
-impl Parser {
+impl<'a> Parser<'a> {
     pub(crate) fn new() -> Self {
         Parser {
             current: 0,
@@ -280,7 +280,7 @@ impl Parser {
         }
     }
 
-    fn skip_empty_lines(&mut self, tokens: &[Token]) {
+    fn skip_empty_lines<'b>(&mut self, tokens: &'b [Token<'a>]) {
         while self.match_exact(tokens, TokenKind::Newline).is_some() {}
     }
 
@@ -289,7 +289,7 @@ impl Parser {
     /// will try to recover from the error and parse as many statements as possible
     /// while stacking all the errors in a `Vec`. At the end, it returns the complete
     /// list of statements parsed + the list of errors accumulated.
-    fn parse(&mut self, tokens: &[Token]) -> ParseResult {
+    fn parse(&mut self, tokens: &[Token<'a>]) -> ParseResult<'a> {
         let mut statements = vec![];
         let mut errors = vec![];
 
@@ -348,7 +348,7 @@ impl Parser {
         }
     }
 
-    fn accepts_prefix(&mut self, tokens: &[Token]) -> Result<Option<AcceptsPrefix>> {
+    fn accepts_prefix(&mut self, tokens: &[Token<'a>]) -> Result<Option<AcceptsPrefix>> {
         if self.match_exact(tokens, TokenKind::Colon).is_some() {
             if self.match_exact(tokens, TokenKind::Long).is_some() {
                 Ok(Some(AcceptsPrefix::only_long()))
@@ -371,16 +371,18 @@ impl Parser {
 
     fn list_of_aliases(
         &mut self,
-        tokens: &[Token],
-    ) -> Result<Vec<(String, Option<AcceptsPrefix>)>> {
+        tokens: &[Token<'a>],
+    ) -> Result<Vec<(&'a str, Option<AcceptsPrefix>, Span)>> {
         if self.match_exact(tokens, TokenKind::RightParen).is_some() {
             return Ok(vec![]);
         }
 
-        let mut identifiers: Vec<(String, Option<AcceptsPrefix>)> =
-            vec![(self.identifier(tokens)?, self.accepts_prefix(tokens)?)];
+        let span = self.peek(tokens).span;
+        let mut identifiers = vec![(self.identifier(tokens)?, self.accepts_prefix(tokens)?, span)];
+
         while self.match_exact(tokens, TokenKind::Comma).is_some() {
-            identifiers.push((self.identifier(tokens)?, self.accepts_prefix(tokens)?));
+            let span = self.peek(tokens).span;
+            identifiers.push((self.identifier(tokens)?, self.accepts_prefix(tokens)?, span));
         }
 
         if self.match_exact(tokens, TokenKind::RightParen).is_none() {
@@ -393,7 +395,7 @@ impl Parser {
         Ok(identifiers)
     }
 
-    fn statement(&mut self, tokens: &[Token]) -> Result<Statement> {
+    fn statement(&mut self, tokens: &[Token<'a>]) -> Result<Statement<'a>> {
         if !(self.peek(tokens).kind == TokenKind::At
             || self.peek(tokens).kind == TokenKind::Unit
             || self.peek(tokens).kind == TokenKind::Let
@@ -430,9 +432,9 @@ impl Parser {
 
     fn parse_variable(
         &mut self,
-        tokens: &[Token],
+        tokens: &[Token<'a>],
         flush_decorators: bool,
-    ) -> Result<DefineVariable> {
+    ) -> Result<DefineVariable<'a>> {
         if let Some(identifier) = self.match_exact(tokens, TokenKind::Identifier) {
             let identifier_span = self.last(tokens).unwrap().span;
 
@@ -472,7 +474,7 @@ impl Parser {
 
                 Ok(DefineVariable {
                     identifier_span,
-                    identifier: identifier.lexeme.to_owned(),
+                    identifier: identifier.lexeme,
                     expr,
                     type_annotation,
                     decorators,
@@ -486,7 +488,7 @@ impl Parser {
         }
     }
 
-    fn parse_function_declaration(&mut self, tokens: &[Token]) -> Result<Statement> {
+    fn parse_function_declaration(&mut self, tokens: &[Token<'a>]) -> Result<Statement<'a>> {
         if let Some(fn_name) = self.match_exact(tokens, TokenKind::Identifier) {
             let function_name_span = self.last(tokens).unwrap().span;
             let mut type_parameters = vec![];
@@ -520,7 +522,7 @@ impl Parser {
                         };
 
                         let span = self.last(tokens).unwrap().span;
-                        type_parameters.push((span, type_parameter_name.lexeme.to_string(), bound));
+                        type_parameters.push((span, type_parameter_name.lexeme, bound));
 
                         if self.match_exact(tokens, TokenKind::Comma).is_none()
                             && self.peek(tokens).kind != TokenKind::GreaterThan
@@ -559,7 +561,7 @@ impl Parser {
                         None
                     };
 
-                    parameters.push((span, param_name.lexeme.to_string(), param_type_dexpr));
+                    parameters.push((span, param_name.lexeme, param_type_dexpr));
 
                     parameter_span = parameter_span.extend(&self.last(tokens).unwrap().span);
 
@@ -645,7 +647,7 @@ impl Parser {
 
             Ok(Statement::DefineFunction {
                 function_name_span,
-                function_name: fn_name.lexeme.to_owned(),
+                function_name: fn_name.lexeme,
                 type_parameters,
                 parameters,
                 body,
@@ -661,7 +663,7 @@ impl Parser {
         }
     }
 
-    fn parse_dimension_declaration(&mut self, tokens: &[Token]) -> Result<Statement> {
+    fn parse_dimension_declaration(&mut self, tokens: &[Token<'a>]) -> Result<Statement<'a>> {
         if let Some(identifier) = self.match_exact(tokens, TokenKind::Identifier) {
             if identifier.lexeme.starts_with("__") {
                 return Err(ParseError::new(
@@ -681,13 +683,13 @@ impl Parser {
 
                 Ok(Statement::DefineDimension(
                     identifier.span,
-                    identifier.lexeme.to_owned(),
+                    identifier.lexeme,
                     dexprs,
                 ))
             } else {
                 Ok(Statement::DefineDimension(
                     identifier.span,
-                    identifier.lexeme.to_owned(),
+                    identifier.lexeme,
                     vec![],
                 ))
             }
@@ -699,7 +701,7 @@ impl Parser {
         }
     }
 
-    fn parse_decorators(&mut self, tokens: &[Token]) -> Result<Statement> {
+    fn parse_decorators(&mut self, tokens: &[Token<'a>]) -> Result<Statement<'a>> {
         if let Some(decorator) = self.match_exact(tokens, TokenKind::Identifier) {
             let decorator = match decorator.lexeme {
                 "metric_prefixes" => Decorator::MetricPrefixes,
@@ -816,7 +818,7 @@ impl Parser {
         }
     }
 
-    fn parse_unit_declaration(&mut self, tokens: &[Token]) -> Result<Statement> {
+    fn parse_unit_declaration(&mut self, tokens: &[Token<'a>]) -> Result<Statement<'a>> {
         if let Some(identifier) = self.match_exact(tokens, TokenKind::Identifier) {
             let identifier_span = self.last(tokens).unwrap().span;
             let (type_annotation_span, dexpr) =
@@ -827,7 +829,7 @@ impl Parser {
                     (None, None)
                 };
 
-            let unit_name = identifier.lexeme.to_owned();
+            let unit_name = identifier.lexeme;
 
             if decorator::contains_examples(&self.decorator_stack) {
                 return Err(ParseError {
@@ -878,7 +880,7 @@ impl Parser {
         }
     }
 
-    fn parse_use(&mut self, tokens: &[Token]) -> Result<Statement> {
+    fn parse_use(&mut self, tokens: &[Token<'a>]) -> Result<Statement<'a>> {
         let mut span = self.peek(tokens).span;
 
         if let Some(identifier) = self.match_exact(tokens, TokenKind::Identifier) {
@@ -905,7 +907,7 @@ impl Parser {
         }
     }
 
-    fn parse_struct(&mut self, tokens: &[Token]) -> Result<Statement> {
+    fn parse_struct(&mut self, tokens: &[Token<'a>]) -> Result<Statement<'a>> {
         let name = self.identifier(tokens)?;
         let name_span = self.last(tokens).unwrap().span;
 
@@ -954,7 +956,7 @@ impl Parser {
                 });
             }
 
-            fields.push((field_name.span, field_name.lexeme.to_owned(), attr_type));
+            fields.push((field_name.span, field_name.lexeme, attr_type));
         }
 
         Ok(Statement::DefineStruct {
@@ -964,7 +966,7 @@ impl Parser {
         })
     }
 
-    fn parse_procedure(&mut self, tokens: &[Token]) -> Result<Statement> {
+    fn parse_procedure(&mut self, tokens: &[Token<'a>]) -> Result<Statement<'a>> {
         let span = self.last(tokens).unwrap().span;
         let procedure_kind = match self.last(tokens).unwrap().kind {
             TokenKind::ProcedurePrint => ProcedureKind::Print,
@@ -994,11 +996,11 @@ impl Parser {
     /// - arg `next` specifiy the next parser to call between each symbols
     fn parse_binop(
         &mut self,
-        tokens: &[Token],
+        tokens: &[Token<'a>],
         op_symbol: &[TokenKind],
         op: impl Fn(TokenKind) -> BinaryOperator,
-        next_parser: impl Fn(&mut Self) -> Result<Expression>,
-    ) -> Result<Expression> {
+        next_parser: impl Fn(&mut Self) -> Result<Expression<'a>>,
+    ) -> Result<Expression<'a>> {
         let mut expr = next_parser(self)?;
         while let Some(matched) = self.match_any(tokens, op_symbol) {
             let span_op = Some(self.last(tokens).unwrap().span);
@@ -1014,13 +1016,13 @@ impl Parser {
         Ok(expr)
     }
 
-    pub fn expression(&mut self, tokens: &[Token]) -> Result<Expression> {
+    pub fn expression(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         self.postfix_apply(tokens)
     }
 
-    fn identifier(&mut self, tokens: &[Token]) -> Result<String> {
+    fn identifier(&mut self, tokens: &[Token<'a>]) -> Result<&'a str> {
         if let Some(identifier) = self.match_exact(tokens, TokenKind::Identifier) {
-            Ok(identifier.lexeme.to_owned())
+            Ok(identifier.lexeme)
         } else {
             Err(ParseError::new(
                 ParseErrorKind::ExpectedIdentifier,
@@ -1029,7 +1031,7 @@ impl Parser {
         }
     }
 
-    pub fn postfix_apply(&mut self, tokens: &[Token]) -> Result<Expression> {
+    pub fn postfix_apply(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         let mut expr = self.condition(tokens)?;
         let mut full_span = expr.full_span();
         while self.match_exact(tokens, TokenKind::PostfixApply).is_some() {
@@ -1062,7 +1064,7 @@ impl Parser {
         Ok(expr)
     }
 
-    fn condition(&mut self, tokens: &[Token]) -> Result<Expression> {
+    fn condition(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         if self.match_exact(tokens, TokenKind::If).is_some() {
             let span_if = self.last(tokens).unwrap().span;
             let condition_expr = self.conversion(tokens)?;
@@ -1104,7 +1106,7 @@ impl Parser {
         }
     }
 
-    fn conversion(&mut self, tokens: &[Token]) -> Result<Expression> {
+    fn conversion(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         self.parse_binop(
             tokens,
             &[TokenKind::Arrow, TokenKind::To],
@@ -1113,7 +1115,7 @@ impl Parser {
         )
     }
 
-    fn logical_or(&mut self, tokens: &[Token]) -> Result<Expression> {
+    fn logical_or(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         self.parse_binop(
             tokens,
             &[TokenKind::LogicalOr],
@@ -1122,7 +1124,7 @@ impl Parser {
         )
     }
 
-    fn logical_and(&mut self, tokens: &[Token]) -> Result<Expression> {
+    fn logical_and(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         self.parse_binop(
             tokens,
             &[TokenKind::LogicalAnd],
@@ -1131,7 +1133,7 @@ impl Parser {
         )
     }
 
-    fn logical_neg(&mut self, tokens: &[Token]) -> Result<Expression> {
+    fn logical_neg(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         if self
             .match_exact(tokens, TokenKind::ExclamationMark)
             .is_some()
@@ -1149,7 +1151,7 @@ impl Parser {
         }
     }
 
-    fn comparison(&mut self, tokens: &[Token]) -> Result<Expression> {
+    fn comparison(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         self.parse_binop(
             tokens,
             &[
@@ -1173,7 +1175,7 @@ impl Parser {
         )
     }
 
-    fn term(&mut self, tokens: &[Token]) -> Result<Expression> {
+    fn term(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         self.parse_binop(
             tokens,
             &[TokenKind::Plus, TokenKind::Minus],
@@ -1186,7 +1188,7 @@ impl Parser {
         )
     }
 
-    fn factor(&mut self, tokens: &[Token]) -> Result<Expression> {
+    fn factor(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         self.parse_binop(
             tokens,
             &[TokenKind::Multiply, TokenKind::Divide],
@@ -1199,7 +1201,7 @@ impl Parser {
         )
     }
 
-    fn per_factor(&mut self, tokens: &[Token]) -> Result<Expression> {
+    fn per_factor(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         self.parse_binop(
             tokens,
             &[TokenKind::Per],
@@ -1208,7 +1210,7 @@ impl Parser {
         )
     }
 
-    fn unary(&mut self, tokens: &[Token]) -> Result<Expression> {
+    fn unary(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         if self.match_exact(tokens, TokenKind::Minus).is_some() {
             let span = self.last(tokens).unwrap().span;
             let rhs = self.unary(tokens)?;
@@ -1227,7 +1229,7 @@ impl Parser {
         }
     }
 
-    fn ifactor(&mut self, tokens: &[Token]) -> Result<Expression> {
+    fn ifactor(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         let mut expr = self.power(tokens)?;
 
         while self.next_token_could_start_power_expression(tokens) {
@@ -1243,7 +1245,7 @@ impl Parser {
         Ok(expr)
     }
 
-    fn power(&mut self, tokens: &[Token]) -> Result<Expression> {
+    fn power(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         let mut expr = self.factorial(tokens)?;
 
         if self.match_exact(tokens, TokenKind::Power).is_some() {
@@ -1278,7 +1280,7 @@ impl Parser {
         Ok(expr)
     }
 
-    fn factorial(&mut self, tokens: &[Token]) -> Result<Expression> {
+    fn factorial(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         let mut expr = self.unicode_power(tokens)?;
 
         while self
@@ -1323,7 +1325,7 @@ impl Parser {
         }
     }
 
-    fn unicode_power(&mut self, tokens: &[Token]) -> Result<Expression> {
+    fn unicode_power(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         let mut expr = self.call(tokens)?;
 
         if let Some(exponent) = self.match_exact(tokens, TokenKind::UnicodeExponent) {
@@ -1343,7 +1345,7 @@ impl Parser {
         Ok(expr)
     }
 
-    fn call(&mut self, tokens: &[Token]) -> Result<Expression> {
+    fn call(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         let mut expr = self.primary(tokens)?;
 
         loop {
@@ -1367,13 +1369,13 @@ impl Parser {
         }
     }
 
-    fn arguments(&mut self, tokens: &[Token]) -> Result<Vec<Expression>> {
+    fn arguments(&mut self, tokens: &[Token<'a>]) -> Result<Vec<Expression<'a>>> {
         self.skip_empty_lines(tokens);
         if self.match_exact(tokens, TokenKind::RightParen).is_some() {
             return Ok(vec![]);
         }
 
-        let mut args: Vec<Expression> = vec![self.expression(tokens)?];
+        let mut args = vec![self.expression(tokens)?];
         loop {
             self.skip_empty_lines(tokens);
 
@@ -1404,7 +1406,7 @@ impl Parser {
         Ok(args)
     }
 
-    fn primary(&mut self, tokens: &[Token]) -> Result<Expression> {
+    fn primary(&mut self, tokens: &[Token<'a>]) -> Result<Expression<'a>> {
         // This function needs to be kept in sync with `next_token_could_start_power_expression` below.
 
         let overflow_error = |span| {
@@ -1525,7 +1527,7 @@ impl Parser {
                         });
                     }
 
-                    fields.push((field_name.span, field_name.lexeme.to_owned(), expr));
+                    fields.push((field_name.span, field_name.lexeme, expr));
                 }
 
                 let full_span = span.extend(&self.last(tokens).unwrap().span);
@@ -1533,12 +1535,12 @@ impl Parser {
                 return Ok(Expression::InstantiateStruct {
                     full_span,
                     ident_span: span,
-                    name: identifier.lexeme.to_owned(),
+                    name: identifier.lexeme,
                     fields,
                 });
             }
 
-            Ok(Expression::Identifier(span, identifier.lexeme.to_owned()))
+            Ok(Expression::Identifier(span, identifier.lexeme))
         } else if let Some(inner) = self.match_any(tokens, &[TokenKind::True, TokenKind::False]) {
             Ok(Expression::Boolean(
                 inner.span,
@@ -1636,9 +1638,9 @@ impl Parser {
 
     fn interpolation(
         &mut self,
-        tokens: &[Token],
-        parts: &mut Vec<StringPart>,
-        token: &Token,
+        tokens: &[Token<'a>],
+        parts: &mut Vec<StringPart<'a>>,
+        token: &Token<'a>,
     ) -> Result<()> {
         parts.push(StringPart::Fixed(strip_and_escape(token.lexeme)));
 
@@ -1646,7 +1648,7 @@ impl Parser {
 
         let format_specifiers = self
             .match_exact(tokens, TokenKind::StringInterpolationSpecifiers)
-            .map(|token| token.lexeme.to_owned());
+            .map(|token| token.lexeme);
 
         parts.push(StringPart::Interpolation {
             span: expr.full_span(),
@@ -1671,7 +1673,7 @@ impl Parser {
         )
     }
 
-    fn type_annotation(&mut self, tokens: &[Token]) -> Result<TypeAnnotation> {
+    fn type_annotation(&mut self, tokens: &[Token<'a>]) -> Result<TypeAnnotation> {
         if let Some(token) = self.match_exact(tokens, TokenKind::Bool) {
             Ok(TypeAnnotation::Bool(token.span))
         } else if let Some(token) = self.match_exact(tokens, TokenKind::String) {
@@ -1756,11 +1758,11 @@ impl Parser {
         }
     }
 
-    fn dimension_expression(&mut self, tokens: &[Token]) -> Result<TypeExpression> {
+    fn dimension_expression(&mut self, tokens: &[Token<'a>]) -> Result<TypeExpression> {
         self.dimension_factor(tokens)
     }
 
-    fn dimension_factor(&mut self, tokens: &[Token]) -> Result<TypeExpression> {
+    fn dimension_factor(&mut self, tokens: &[Token<'a>]) -> Result<TypeExpression> {
         let mut expr = self.dimension_power(tokens)?;
         while let Some(operator_token) =
             self.match_any(tokens, &[TokenKind::Multiply, TokenKind::Divide])
@@ -1777,7 +1779,7 @@ impl Parser {
         Ok(expr)
     }
 
-    fn dimension_power(&mut self, tokens: &[Token]) -> Result<TypeExpression> {
+    fn dimension_power(&mut self, tokens: &[Token<'a>]) -> Result<TypeExpression> {
         let expr = self.dimension_primary(tokens)?;
 
         if self.match_exact(tokens, TokenKind::Power).is_some() {
@@ -1805,7 +1807,7 @@ impl Parser {
         }
     }
 
-    fn dimension_exponent(&mut self, tokens: &[Token]) -> Result<(Span, Exponent)> {
+    fn dimension_exponent(&mut self, tokens: &[Token<'a>]) -> Result<(Span, Exponent)> {
         if let Some(token) = self.match_exact(tokens, TokenKind::Number) {
             let span = self.last(tokens).unwrap().span;
             let num_str = token.lexeme.replace('_', "");
@@ -1867,7 +1869,7 @@ impl Parser {
         }
     }
 
-    fn dimension_primary(&mut self, tokens: &[Token]) -> Result<TypeExpression> {
+    fn dimension_primary(&mut self, tokens: &[Token<'a>]) -> Result<TypeExpression> {
         let e = Err(ParseError::new(
             ParseErrorKind::ExpectedDimensionPrimary,
             self.peek(tokens).span,
@@ -1905,11 +1907,11 @@ impl Parser {
         }
     }
 
-    fn match_exact<'a>(
+    fn match_exact<'b>(
         &mut self,
-        tokens: &'a [Token<'a>],
+        tokens: &'b [Token<'a>],
         token_kind: TokenKind,
-    ) -> Option<&'a Token<'a>> {
+    ) -> Option<&'b Token<'a>> {
         let token = self.peek(tokens);
         if token.kind == token_kind {
             self.advance(tokens);
@@ -1932,22 +1934,22 @@ impl Parser {
 
     /// Same as 'match_exact', but skips empty lines before matching. Note that this
     /// does *not* skip empty lines in case there is no match.
-    fn match_exact_beyond_linebreaks<'a>(
+    fn match_exact_beyond_linebreaks<'b>(
         &mut self,
-        tokens: &'a [Token<'a>],
+        tokens: &'b [Token<'a>],
         token_kind: TokenKind,
-    ) -> Option<&'a Token<'a>> {
+    ) -> Option<&'b Token<'a>> {
         if self.look_ahead_beyond_linebreak(tokens, token_kind) {
             self.skip_empty_lines(tokens);
         }
         self.match_exact(tokens, token_kind)
     }
 
-    fn match_any<'a>(
+    fn match_any<'b>(
         &mut self,
-        tokens: &'a [Token<'a>],
+        tokens: &'b [Token<'a>],
         kinds: &[TokenKind],
-    ) -> Option<&'a Token<'a>> {
+    ) -> Option<&'b Token<'a>> {
         for kind in kinds {
             if let result @ Some(..) = self.match_exact(tokens, *kind) {
                 return result;
@@ -1962,11 +1964,11 @@ impl Parser {
         }
     }
 
-    fn peek<'a>(&self, tokens: &'a [Token<'a>]) -> &'a Token<'a> {
+    fn peek<'b>(&self, tokens: &'b [Token<'a>]) -> &'b Token<'a> {
         &tokens[self.current]
     }
 
-    fn last<'a>(&self, tokens: &'a [Token<'a>]) -> Option<&'a Token<'a>> {
+    fn last<'b>(&self, tokens: &'b [Token<'a>]) -> Option<&'b Token<'a>> {
         if self.current == 0 {
             None
         } else {
@@ -2024,7 +2026,7 @@ fn strip_and_escape(s: &str) -> String {
 /// will try to recover from the error and parse as many statements as possible
 /// while stacking all the errors in a `Vec`. At the end, it returns the complete
 /// list of statements parsed + the list of errors accumulated.
-pub fn parse(input: &str, code_source_id: usize) -> ParseResult {
+pub fn parse(input: &str, code_source_id: usize) -> ParseResult<'_> {
     use crate::tokenizer::tokenize;
 
     let tokens = tokenize(input, code_source_id)
@@ -2032,8 +2034,10 @@ pub fn parse(input: &str, code_source_id: usize) -> ParseResult {
             ParseError::new(ParseErrorKind::TokenizerError(kind), span)
         })
         .map_err(|e| (Vec::new(), vec![e]))?;
+    let tokens = &tokens;
+
     let mut parser = Parser::new();
-    parser.parse(&tokens)
+    parser.parse(tokens)
 }
 
 #[cfg(test)]
@@ -2053,9 +2057,12 @@ mod tests {
     use std::fmt::Write;
 
     use super::*;
-    use crate::ast::{
-        binop, boolean, conditional, factorial, identifier, list, logical_neg, negate, scalar,
-        struct_, ReplaceSpans,
+    use crate::{
+        ast::{
+            binop, boolean, conditional, factorial, identifier, list, logical_neg, negate, scalar,
+            struct_, ReplaceSpans,
+        },
+        span::ByteIndex,
     };
 
     #[track_caller]
@@ -2472,7 +2479,7 @@ mod tests {
             &["let foo = 1", "let foo=1"],
             Statement::DefineVariable(DefineVariable {
                 identifier_span: Span::dummy(),
-                identifier: "foo".into(),
+                identifier: "foo",
                 expr: scalar!(1.0),
                 type_annotation: None,
                 decorators: Vec::new(),
@@ -2483,7 +2490,7 @@ mod tests {
             &["let x: Length = 1 * meter"],
             Statement::DefineVariable(DefineVariable {
                 identifier_span: Span::dummy(),
-                identifier: "x".into(),
+                identifier: "x",
                 expr: binop!(scalar!(1.0), Mul, identifier!("meter")),
                 type_annotation: Some(TypeAnnotation::TypeExpression(
                     TypeExpression::TypeIdentifier(Span::dummy(), "Length".into()),
@@ -2497,14 +2504,33 @@ mod tests {
             &["@name(\"myvar\") @aliases(foo, bar) let x: Length = 1 * meter"],
             Statement::DefineVariable(DefineVariable {
                 identifier_span: Span::dummy(),
-                identifier: "x".into(),
+                identifier: "x",
                 expr: binop!(scalar!(1.0), Mul, identifier!("meter")),
                 type_annotation: Some(TypeAnnotation::TypeExpression(
                     TypeExpression::TypeIdentifier(Span::dummy(), "Length".into()),
                 )),
                 decorators: vec![
                     decorator::Decorator::Name("myvar".into()),
-                    decorator::Decorator::Aliases(vec![("foo".into(), None), ("bar".into(), None)]),
+                    decorator::Decorator::Aliases(vec![
+                        (
+                            "foo",
+                            None,
+                            Span {
+                                start: ByteIndex(24),
+                                end: ByteIndex(27),
+                                code_source_id: 0,
+                            },
+                        ),
+                        (
+                            "bar",
+                            None,
+                            Span {
+                                start: ByteIndex(29),
+                                end: ByteIndex(32),
+                                code_source_id: 0,
+                            },
+                        ),
+                    ]),
                 ],
             }),
         );
@@ -2536,7 +2562,7 @@ mod tests {
     fn dimension_definition() {
         parse_as(
             &["dimension px"],
-            Statement::DefineDimension(Span::dummy(), "px".into(), vec![]),
+            Statement::DefineDimension(Span::dummy(), "px", vec![]),
         );
 
         parse_as(
@@ -2547,7 +2573,7 @@ mod tests {
             ],
             Statement::DefineDimension(
                 Span::dummy(),
-                "Area".into(),
+                "Area",
                 vec![TypeExpression::Multiply(
                     Span::dummy(),
                     Box::new(TypeExpression::TypeIdentifier(
@@ -2566,7 +2592,7 @@ mod tests {
             &["dimension Velocity = Length / Time"],
             Statement::DefineDimension(
                 Span::dummy(),
-                "Velocity".into(),
+                "Velocity",
                 vec![TypeExpression::Divide(
                     Span::dummy(),
                     Box::new(TypeExpression::TypeIdentifier(
@@ -2582,7 +2608,7 @@ mod tests {
             &["dimension Area = Length^2"],
             Statement::DefineDimension(
                 Span::dummy(),
-                "Area".into(),
+                "Area",
                 vec![TypeExpression::Power(
                     Some(Span::dummy()),
                     Box::new(TypeExpression::TypeIdentifier(
@@ -2599,7 +2625,7 @@ mod tests {
             &["dimension Energy = Mass * Length^2 / Time^2"],
             Statement::DefineDimension(
                 Span::dummy(),
-                "Energy".into(),
+                "Energy",
                 vec![TypeExpression::Divide(
                     Span::dummy(),
                     Box::new(TypeExpression::Multiply(
@@ -2629,7 +2655,7 @@ mod tests {
             &["dimension X = Length^(12345/67890)"],
             Statement::DefineDimension(
                 Span::dummy(),
-                "X".into(),
+                "X",
                 vec![TypeExpression::Power(
                     Some(Span::dummy()),
                     Box::new(TypeExpression::TypeIdentifier(
@@ -2655,7 +2681,7 @@ mod tests {
             &["fn foo() = 1", "fn foo() =\n  1"],
             Statement::DefineFunction {
                 function_name_span: Span::dummy(),
-                function_name: "foo".into(),
+                function_name: "foo",
                 type_parameters: vec![],
                 parameters: vec![],
                 body: Some(scalar!(1.0)),
@@ -2669,7 +2695,7 @@ mod tests {
             &["fn foo() -> Scalar = 1"],
             Statement::DefineFunction {
                 function_name_span: Span::dummy(),
-                function_name: "foo".into(),
+                function_name: "foo",
                 type_parameters: vec![],
                 parameters: vec![],
                 body: Some(scalar!(1.0)),
@@ -2685,9 +2711,9 @@ mod tests {
             &["fn foo(x) = 1"],
             Statement::DefineFunction {
                 function_name_span: Span::dummy(),
-                function_name: "foo".into(),
+                function_name: "foo",
                 type_parameters: vec![],
-                parameters: vec![(Span::dummy(), "x".into(), None)],
+                parameters: vec![(Span::dummy(), "x", None)],
                 body: Some(scalar!(1.0)),
                 local_variables: vec![],
                 return_type_annotation: None,
@@ -2699,9 +2725,9 @@ mod tests {
             &["fn foo(x,) = 1"],
             Statement::DefineFunction {
                 function_name_span: Span::dummy(),
-                function_name: "foo".into(),
+                function_name: "foo",
                 type_parameters: vec![],
-                parameters: vec![(Span::dummy(), "x".into(), None)],
+                parameters: vec![(Span::dummy(), "x", None)],
                 body: Some(scalar!(1.0)),
                 local_variables: vec![],
                 return_type_annotation: None,
@@ -2716,12 +2742,9 @@ mod tests {
             ) = 1"],
             Statement::DefineFunction {
                 function_name_span: Span::dummy(),
-                function_name: "foo".into(),
+                function_name: "foo",
                 type_parameters: vec![],
-                parameters: vec![
-                    (Span::dummy(), "x".into(), None),
-                    (Span::dummy(), "y".into(), None),
-                ],
+                parameters: vec![(Span::dummy(), "x", None), (Span::dummy(), "y", None)],
                 body: Some(scalar!(1.0)),
                 local_variables: vec![],
                 return_type_annotation: None,
@@ -2733,12 +2756,12 @@ mod tests {
             &["fn foo(x, y, z) = 1"],
             Statement::DefineFunction {
                 function_name_span: Span::dummy(),
-                function_name: "foo".into(),
+                function_name: "foo",
                 type_parameters: vec![],
                 parameters: vec![
-                    (Span::dummy(), "x".into(), None),
-                    (Span::dummy(), "y".into(), None),
-                    (Span::dummy(), "z".into(), None),
+                    (Span::dummy(), "x", None),
+                    (Span::dummy(), "y", None),
+                    (Span::dummy(), "z", None),
                 ],
                 body: Some(scalar!(1.0)),
                 local_variables: vec![],
@@ -2751,26 +2774,26 @@ mod tests {
             &["fn foo(x: Length, y: Time, z: Length^3 · Time^2) -> Scalar = 1"],
             Statement::DefineFunction {
                 function_name_span: Span::dummy(),
-                function_name: "foo".into(),
+                function_name: "foo",
                 type_parameters: vec![],
                 parameters: vec![
                     (
                         Span::dummy(),
-                        "x".into(),
+                        "x",
                         Some(TypeAnnotation::TypeExpression(
                             TypeExpression::TypeIdentifier(Span::dummy(), "Length".into()),
                         )),
                     ),
                     (
                         Span::dummy(),
-                        "y".into(),
+                        "y",
                         Some(TypeAnnotation::TypeExpression(
                             TypeExpression::TypeIdentifier(Span::dummy(), "Time".into()),
                         )),
                     ),
                     (
                         Span::dummy(),
-                        "z".into(),
+                        "z",
                         Some(TypeAnnotation::TypeExpression(TypeExpression::Multiply(
                             Span::dummy(),
                             Box::new(TypeExpression::Power(
@@ -2807,11 +2830,11 @@ mod tests {
             &["fn foo<X>(x: X) = 1"],
             Statement::DefineFunction {
                 function_name_span: Span::dummy(),
-                function_name: "foo".into(),
-                type_parameters: vec![(Span::dummy(), "X".into(), None)],
+                function_name: "foo",
+                type_parameters: vec![(Span::dummy(), "X", None)],
                 parameters: vec![(
                     Span::dummy(),
-                    "x".into(),
+                    "x",
                     Some(TypeAnnotation::TypeExpression(
                         TypeExpression::TypeIdentifier(Span::dummy(), "X".into()),
                     )),
@@ -2827,11 +2850,11 @@ mod tests {
             &["fn foo<X: Dim>(x: X) = 1"],
             Statement::DefineFunction {
                 function_name_span: Span::dummy(),
-                function_name: "foo".into(),
-                type_parameters: vec![(Span::dummy(), "X".into(), Some(TypeParameterBound::Dim))],
+                function_name: "foo",
+                type_parameters: vec![(Span::dummy(), "X", Some(TypeParameterBound::Dim))],
                 parameters: vec![(
                     Span::dummy(),
-                    "x".into(),
+                    "x",
                     Some(TypeAnnotation::TypeExpression(
                         TypeExpression::TypeIdentifier(Span::dummy(), "X".into()),
                     )),
@@ -2847,9 +2870,9 @@ mod tests {
             &["@name(\"Some function\") @description(\"This is a description of some_function.\") fn some_function(x) = 1"],
             Statement::DefineFunction {
                 function_name_span: Span::dummy(),
-                function_name: "some_function".into(),
+                function_name: "some_function",
                 type_parameters: vec![],
-                parameters: vec![(Span::dummy(), "x".into(), None)],
+                parameters: vec![(Span::dummy(), "x", None)],
                 body: Some(scalar!(1.0)),
                 local_variables: vec![],
                 return_type_annotation: None,
@@ -2884,13 +2907,13 @@ mod tests {
             &["fn double_kef(x) = y where y = x * 2"],
             Statement::DefineFunction {
                 function_name_span: Span::dummy(),
-                function_name: "double_kef".into(),
+                function_name: "double_kef",
                 type_parameters: vec![],
-                parameters: vec![(Span::dummy(), "x".into(), None)],
+                parameters: vec![(Span::dummy(), "x", None)],
                 body: Some(identifier!("y")),
                 local_variables: vec![DefineVariable {
                     identifier_span: Span::dummy(),
-                    identifier: String::from("y"),
+                    identifier: "y",
                     expr: binop!(identifier!("x"), Mul, scalar!(2.0)),
                     type_annotation: None,
                     decorators: vec![],
@@ -2906,21 +2929,21 @@ mod tests {
                    and z = y + x"],
             Statement::DefineFunction {
                 function_name_span: Span::dummy(),
-                function_name: "kefirausaure".into(),
+                function_name: "kefirausaure",
                 type_parameters: vec![],
-                parameters: vec![(Span::dummy(), "x".into(), None)],
+                parameters: vec![(Span::dummy(), "x", None)],
                 body: Some(binop!(identifier!("z"), Add, identifier!("y"))),
                 local_variables: vec![
                     DefineVariable {
                         identifier_span: Span::dummy(),
-                        identifier: String::from("y"),
+                        identifier: "y",
                         expr: binop!(identifier!("x"), Add, identifier!("x")),
                         type_annotation: None,
                         decorators: vec![],
                     },
                     DefineVariable {
                         identifier_span: Span::dummy(),
-                        identifier: String::from("z"),
+                        identifier: "z",
                         expr: binop!(identifier!("y"), Add, identifier!("x")),
                         type_annotation: None,
                         decorators: vec![],
@@ -3366,7 +3389,7 @@ mod tests {
                     StringPart::Interpolation {
                         span: Span::dummy(),
                         expr: Box::new(binop!(scalar!(1.0), Add, scalar!(2.0))),
-                        format_specifiers: Some(":0.2".to_string()),
+                        format_specifiers: Some(":0.2"),
                     },
                 ],
             ),
@@ -3389,11 +3412,11 @@ mod tests {
             &["struct Foo { foo: Scalar, bar: Scalar }"],
             Statement::DefineStruct {
                 struct_name_span: Span::dummy(),
-                struct_name: "Foo".to_owned(),
+                struct_name: "Foo",
                 fields: vec![
                     (
                         Span::dummy(),
-                        "foo".to_owned(),
+                        "foo",
                         TypeAnnotation::TypeExpression(TypeExpression::TypeIdentifier(
                             Span::dummy(),
                             "Scalar".to_owned(),
@@ -3401,7 +3424,7 @@ mod tests {
                     ),
                     (
                         Span::dummy(),
-                        "bar".to_owned(),
+                        "bar",
                         TypeAnnotation::TypeExpression(TypeExpression::TypeIdentifier(
                             Span::dummy(),
                             "Scalar".to_owned(),
@@ -3430,7 +3453,7 @@ mod tests {
                     foo: scalar!(1.0),
                     bar: scalar!(2.0)
                 }),
-                "foo".to_owned(),
+                "foo",
             ),
         );
     }

+ 51 - 29
numbat/src/prefix_parser.rs

@@ -8,8 +8,8 @@ use crate::{name_resolution::NameResolutionError, prefix::Prefix};
 static PREFIXES: OnceLock<Vec<(&'static str, &'static [&'static str], Prefix)>> = OnceLock::new();
 
 #[derive(Debug, Clone, PartialEq)]
-pub enum PrefixParserResult {
-    Identifier(String),
+pub enum PrefixParserResult<'a> {
+    Identifier(&'a str),
     /// Span, prefix, unit name in source (e.g. 'm'), full unit name (e.g. 'meter')
     UnitIdentifier(Span, Prefix, String, String),
 }
@@ -52,6 +52,25 @@ impl AcceptsPrefix {
     }
 }
 
+/// The spans associated with an alias passed to `@aliases`
+#[derive(Debug, Clone, Copy)]
+pub(crate) struct AliasSpanInfo {
+    /// The span of the name to which the alias refers
+    pub(crate) name_span: Span,
+    /// The span of the alias itself (in an `@aliases` decorator)
+    pub(crate) alias_span: Span,
+}
+
+impl AliasSpanInfo {
+    #[cfg(test)]
+    fn dummy() -> Self {
+        Self {
+            name_span: Span::dummy(),
+            alias_span: Span::dummy(),
+        }
+    }
+}
+
 #[derive(Debug, Clone)]
 struct UnitInfo {
     definition_span: Span,
@@ -152,23 +171,23 @@ impl PrefixParser {
     fn ensure_name_is_available(
         &self,
         name: &str,
-        conflict_span: Span,
+        definition_span: Span,
         clash_with_other_identifiers: bool,
     ) -> Result<()> {
         if self.reserved_identifiers.contains(&name) {
-            return Err(NameResolutionError::ReservedIdentifier(conflict_span));
+            return Err(NameResolutionError::ReservedIdentifier(definition_span));
         }
 
         if clash_with_other_identifiers {
             if let Some(original_span) = self.other_identifiers.get(name) {
-                return Err(self.identifier_clash_error(name, conflict_span, *original_span));
+                return Err(self.identifier_clash_error(name, definition_span, *original_span));
             }
         }
 
         match self.parse(name) {
             PrefixParserResult::Identifier(_) => Ok(()),
             PrefixParserResult::UnitIdentifier(original_span, _, _, _) => {
-                Err(self.identifier_clash_error(name, conflict_span, original_span))
+                Err(self.identifier_clash_error(name, definition_span, original_span))
             }
         }
     }
@@ -180,9 +199,12 @@ impl PrefixParser {
         metric: bool,
         binary: bool,
         full_name: &str,
-        definition_span: Span,
+        AliasSpanInfo {
+            name_span,
+            alias_span,
+        }: AliasSpanInfo,
     ) -> Result<()> {
-        self.ensure_name_is_available(unit_name, definition_span, true)?;
+        self.ensure_name_is_available(unit_name, alias_span, true)?;
 
         for (prefix_long, prefixes_short, prefix) in Self::prefixes() {
             if !(prefix.is_metric() && metric || prefix.is_binary() && binary) {
@@ -192,7 +214,7 @@ impl PrefixParser {
             if accepts_prefix.long {
                 self.ensure_name_is_available(
                     &format!("{prefix_long}{unit_name}"),
-                    definition_span,
+                    alias_span,
                     true,
                 )?;
             }
@@ -200,7 +222,7 @@ impl PrefixParser {
                 for prefix_short in *prefixes_short {
                     self.ensure_name_is_available(
                         &format!("{prefix_short}{unit_name}"),
-                        definition_span,
+                        alias_span,
                         true,
                     )?;
                 }
@@ -208,7 +230,7 @@ impl PrefixParser {
         }
 
         let unit_info = UnitInfo {
-            definition_span,
+            definition_span: name_span,
             accepts_prefix,
             metric_prefixes: metric,
             binary_prefixes: binary,
@@ -228,12 +250,12 @@ impl PrefixParser {
         Ok(())
     }
 
-    pub fn parse(&self, input: &str) -> PrefixParserResult {
+    pub fn parse<'a>(&self, input: &'a str) -> PrefixParserResult<'a> {
         if let Some(info) = self.units.get(input) {
             return PrefixParserResult::UnitIdentifier(
                 info.definition_span,
                 Prefix::none(),
-                input.into(),
+                input.to_string(),
                 info.full_name.clone(),
             );
         }
@@ -255,7 +277,7 @@ impl PrefixParser {
                     return PrefixParserResult::UnitIdentifier(
                         info.definition_span,
                         *prefix,
-                        unit_name.to_string(),
+                        unit_name.clone(),
                         info.full_name.clone(),
                     );
                 }
@@ -269,14 +291,14 @@ impl PrefixParser {
                     return PrefixParserResult::UnitIdentifier(
                         info.definition_span,
                         *prefix,
-                        unit_name.to_string(),
+                        unit_name.clone(),
                         info.full_name.clone(),
                     );
                 }
             }
         }
 
-        PrefixParserResult::Identifier(input.into())
+        PrefixParserResult::Identifier(input)
     }
 }
 
@@ -294,7 +316,7 @@ mod tests {
                 true,
                 false,
                 "meter",
-                Span::dummy(),
+                AliasSpanInfo::dummy(),
             )
             .unwrap();
         prefix_parser
@@ -304,7 +326,7 @@ mod tests {
                 true,
                 false,
                 "meter",
-                Span::dummy(),
+                AliasSpanInfo::dummy(),
             )
             .unwrap();
 
@@ -315,7 +337,7 @@ mod tests {
                 true,
                 true,
                 "byte",
-                Span::dummy(),
+                AliasSpanInfo::dummy(),
             )
             .unwrap();
         prefix_parser
@@ -325,7 +347,7 @@ mod tests {
                 true,
                 true,
                 "byte",
-                Span::dummy(),
+                AliasSpanInfo::dummy(),
             )
             .unwrap();
 
@@ -336,7 +358,7 @@ mod tests {
                 false,
                 false,
                 "me",
-                Span::dummy(),
+                AliasSpanInfo::dummy(),
             )
             .unwrap();
 
@@ -516,38 +538,38 @@ mod tests {
 
         assert_eq!(
             prefix_parser.parse("kilom"),
-            PrefixParserResult::Identifier("kilom".into())
+            PrefixParserResult::Identifier("kilom")
         );
         assert_eq!(
             prefix_parser.parse("kilome"),
-            PrefixParserResult::Identifier("kilome".into())
+            PrefixParserResult::Identifier("kilome")
         );
         assert_eq!(
             prefix_parser.parse("kme"),
-            PrefixParserResult::Identifier("kme".into())
+            PrefixParserResult::Identifier("kme")
         );
 
         assert_eq!(
             prefix_parser.parse("kilomete"),
-            PrefixParserResult::Identifier("kilomete".into())
+            PrefixParserResult::Identifier("kilomete")
         );
         assert_eq!(
             prefix_parser.parse("kilometerr"),
-            PrefixParserResult::Identifier("kilometerr".into())
+            PrefixParserResult::Identifier("kilometerr")
         );
 
         assert_eq!(
             prefix_parser.parse("foometer"),
-            PrefixParserResult::Identifier("foometer".into())
+            PrefixParserResult::Identifier("foometer")
         );
 
         assert_eq!(
             prefix_parser.parse("kibimeter"),
-            PrefixParserResult::Identifier("kibimeter".into())
+            PrefixParserResult::Identifier("kibimeter")
         );
         assert_eq!(
             prefix_parser.parse("Kim"),
-            PrefixParserResult::Identifier("Kim".into())
+            PrefixParserResult::Identifier("Kim")
         );
     }
 }

+ 28 - 22
numbat/src/prefix_transformer.rs

@@ -2,7 +2,7 @@ use crate::{
     ast::{DefineVariable, Expression, Statement, StringPart},
     decorator::{self, Decorator},
     name_resolution::NameResolutionError,
-    prefix_parser::{PrefixParser, PrefixParserResult},
+    prefix_parser::{AliasSpanInfo, PrefixParser, PrefixParserResult},
     span::Span,
 };
 
@@ -29,7 +29,7 @@ impl Transformer {
         }
     }
 
-    fn transform_expression(&self, expression: Expression) -> Expression {
+    fn transform_expression<'a>(&self, expression: Expression<'a>) -> Expression<'a> {
         match expression {
             expr @ Expression::Scalar(..) => expr,
             Expression::Identifier(span, identifier) => {
@@ -38,7 +38,7 @@ impl Transformer {
                     prefix,
                     unit_name,
                     full_name,
-                ) = self.prefix_parser.parse(&identifier)
+                ) = self.prefix_parser.parse(identifier)
                 {
                     Expression::UnitIdentifier(span, prefix, unit_name, full_name)
                 } else {
@@ -134,21 +134,27 @@ impl Transformer {
 
     pub(crate) fn register_name_and_aliases(
         &mut self,
-        name: &String,
+        name: &str,
+        name_span: Span,
         decorators: &[Decorator],
-        conflict_span: Span,
     ) -> Result<()> {
         let mut unit_names = vec![];
         let metric_prefixes = Self::has_decorator(decorators, Decorator::MetricPrefixes);
         let binary_prefixes = Self::has_decorator(decorators, Decorator::BinaryPrefixes);
-        for (alias, accepts_prefix) in decorator::name_and_aliases(name, decorators) {
+
+        for (alias, accepts_prefix, alias_span) in
+            decorator::name_and_aliases_spans(name, name_span, decorators)
+        {
             self.prefix_parser.add_unit(
                 alias,
                 accepts_prefix,
                 metric_prefixes,
                 binary_prefixes,
                 name,
-                conflict_span,
+                AliasSpanInfo {
+                    name_span,
+                    alias_span,
+                },
             )?;
             unit_names.push(alias.to_string());
         }
@@ -159,10 +165,10 @@ impl Transformer {
         Ok(())
     }
 
-    fn transform_define_variable(
+    fn transform_define_variable<'a>(
         &mut self,
-        define_variable: DefineVariable,
-    ) -> Result<DefineVariable> {
+        define_variable: DefineVariable<'a>,
+    ) -> Result<DefineVariable<'a>> {
         let DefineVariable {
             identifier_span,
             identifier,
@@ -171,11 +177,11 @@ impl Transformer {
             decorators,
         } = define_variable;
 
-        for (name, _) in decorator::name_and_aliases(&identifier, &decorators) {
-            self.variable_names.push(name.clone());
+        for (name, _) in decorator::name_and_aliases(identifier, &decorators) {
+            self.variable_names.push(name.to_owned());
         }
         self.prefix_parser
-            .add_other_identifier(&identifier, identifier_span)?;
+            .add_other_identifier(identifier, identifier_span)?;
         Ok(DefineVariable {
             identifier_span,
             identifier,
@@ -185,11 +191,11 @@ impl Transformer {
         })
     }
 
-    fn transform_statement(&mut self, statement: Statement) -> Result<Statement> {
+    fn transform_statement<'a>(&mut self, statement: Statement<'a>) -> Result<Statement<'a>> {
         Ok(match statement {
             Statement::Expression(expr) => Statement::Expression(self.transform_expression(expr)),
             Statement::DefineBaseUnit(span, name, dexpr, decorators) => {
-                self.register_name_and_aliases(&name, &decorators, span)?;
+                self.register_name_and_aliases(name, span, &decorators)?;
                 Statement::DefineBaseUnit(span, name, dexpr, decorators)
             }
             Statement::DefineDerivedUnit {
@@ -200,7 +206,7 @@ impl Transformer {
                 type_annotation,
                 decorators,
             } => {
-                self.register_name_and_aliases(&identifier, &decorators, identifier_span)?;
+                self.register_name_and_aliases(identifier, identifier_span, &decorators)?;
                 Statement::DefineDerivedUnit {
                     identifier_span,
                     identifier,
@@ -223,9 +229,9 @@ impl Transformer {
                 return_type_annotation,
                 decorators,
             } => {
-                self.function_names.push(function_name.clone());
+                self.function_names.push(function_name.to_owned());
                 self.prefix_parser
-                    .add_other_identifier(&function_name, function_name_span)?;
+                    .add_other_identifier(function_name, function_name_span)?;
 
                 // We create a clone of the full transformer for the purpose
                 // of checking/transforming the function body. The reason for this
@@ -266,7 +272,7 @@ impl Transformer {
                 fields,
             },
             Statement::DefineDimension(name_span, name, dexprs) => {
-                self.dimension_names.push(name.clone());
+                self.dimension_names.push(name.to_owned());
                 Statement::DefineDimension(name_span, name, dexprs)
             }
             Statement::ProcedureCall(span, procedure, args) => Statement::ProcedureCall(
@@ -280,10 +286,10 @@ impl Transformer {
         })
     }
 
-    pub fn transform(
+    pub fn transform<'a>(
         &mut self,
-        statements: impl IntoIterator<Item = Statement>,
-    ) -> Result<Vec<Statement>> {
+        statements: impl IntoIterator<Item = Statement<'a>>,
+    ) -> Result<Vec<Statement<'a>>> {
         statements
             .into_iter()
             .map(|statement| self.transform_statement(statement))

+ 1 - 1
numbat/src/product.rs

@@ -54,7 +54,7 @@ impl<Factor: Power + Clone + Canonicalize + Ord + Display, const CANONICALIZE: b
                     + m::Markup::from(m::FormattedString(
                         m::OutputType::Normal,
                         format_type,
-                        factor.to_string(),
+                        factor.to_string().into(),
                     ))
                     + if i == num_factors - 1 {
                         m::empty()

+ 17 - 12
numbat/src/resolver.rs

@@ -96,11 +96,11 @@ impl Resolver {
         self.codesources.get(&id).cloned().unwrap()
     }
 
-    fn parse(&self, code: &str, code_source_id: usize) -> Result<Vec<Statement>> {
+    fn parse<'a>(&self, code: &'a str, code_source_id: usize) -> Result<Vec<Statement<'a>>> {
         parse(code, code_source_id).map_err(|e| ResolverError::ParseErrors(e.1))
     }
 
-    fn inlining_pass(&mut self, program: &[Statement]) -> Result<Vec<Statement>> {
+    fn inlining_pass<'a>(&mut self, program: &[Statement<'a>]) -> Result<Vec<Statement<'a>>> {
         let mut new_program = vec![];
 
         for statement in program {
@@ -108,13 +108,14 @@ impl Resolver {
                 Statement::ModuleImport(span, module_path) => {
                     if !self.imported_modules.contains(module_path) {
                         if let Some((code, filesystem_path)) = self.importer.import(module_path) {
+                            let code: &'static str = Box::leak(code.into_boxed_str());
                             self.imported_modules.push(module_path.clone());
                             let code_source_id = self.add_code_source(
                                 CodeSource::Module(module_path.clone(), filesystem_path),
-                                &code,
+                                code,
                             );
 
-                            let imported_program = self.parse(&code, code_source_id)?;
+                            let imported_program = self.parse(code, code_source_id)?;
                             let inlined_program = self.inlining_pass(&imported_program)?;
                             for statement in inlined_program {
                                 new_program.push(statement);
@@ -131,7 +132,11 @@ impl Resolver {
         Ok(new_program)
     }
 
-    pub fn resolve(&mut self, code: &str, code_source: CodeSource) -> Result<Vec<Statement>> {
+    pub fn resolve<'a>(
+        &mut self,
+        code: &'a str,
+        code_source: CodeSource,
+    ) -> Result<Vec<Statement<'a>>> {
         let code_source_id = self.add_code_source(code_source, code);
         let statements = self.parse(code, code_source_id)?;
 
@@ -194,12 +199,12 @@ mod tests {
             &[
                 Statement::DefineVariable(DefineVariable {
                     identifier_span: Span::dummy(),
-                    identifier: "a".into(),
+                    identifier: "a",
                     expr: Expression::Scalar(Span::dummy(), Number::from_f64(1.0)),
                     type_annotation: None,
                     decorators: Vec::new(),
                 }),
-                Statement::Expression(Expression::Identifier(Span::dummy(), "a".into()))
+                Statement::Expression(Expression::Identifier(Span::dummy(), "a"))
             ]
         );
     }
@@ -224,12 +229,12 @@ mod tests {
             &[
                 Statement::DefineVariable(DefineVariable {
                     identifier_span: Span::dummy(),
-                    identifier: "a".into(),
+                    identifier: "a",
                     expr: Expression::Scalar(Span::dummy(), Number::from_f64(1.0)),
                     type_annotation: None,
                     decorators: Vec::new(),
                 }),
-                Statement::Expression(Expression::Identifier(Span::dummy(), "a".into()))
+                Statement::Expression(Expression::Identifier(Span::dummy(), "a"))
             ]
         );
     }
@@ -253,15 +258,15 @@ mod tests {
             &[
                 Statement::DefineVariable(DefineVariable {
                     identifier_span: Span::dummy(),
-                    identifier: "y".into(),
+                    identifier: "y",
                     expr: Expression::Scalar(Span::dummy(), Number::from_f64(1.0)),
                     type_annotation: None,
                     decorators: Vec::new(),
                 }),
                 Statement::DefineVariable(DefineVariable {
                     identifier_span: Span::dummy(),
-                    identifier: "x".into(),
-                    expr: Expression::Identifier(Span::dummy(), "y".into()),
+                    identifier: "x",
+                    expr: Expression::Identifier(Span::dummy(), "y"),
                     type_annotation: None,
                     decorators: Vec::new(),
                 }),

+ 22 - 14
numbat/src/tokenizer.rs

@@ -22,10 +22,7 @@ pub enum TokenizerErrorKind {
     ExpectedDigit { character: Option<char> },
 
     #[error("Expected base-{base} digit")]
-    ExpectedDigitInBase {
-        base: usize,
-        character: Option<char>,
-    },
+    ExpectedDigitInBase { base: u8, character: Option<char> },
 
     #[error("Unterminated string")]
     UnterminatedString,
@@ -125,7 +122,7 @@ pub enum TokenKind {
 
     // Variable-length tokens
     Number,
-    IntegerWithBase(usize),
+    IntegerWithBase(u8),
     Identifier,
 
     // A normal string without interpolation: `"hello world"`
@@ -378,6 +375,18 @@ impl Tokenizer {
     }
 
     fn scan_single_token<'a>(&mut self, input: &'a str) -> Result<Option<Token<'a>>> {
+        fn is_ascii_hex_digit(c: char) -> bool {
+            c.is_ascii_hexdigit()
+        }
+
+        fn is_ascii_octal_digit(c: char) -> bool {
+            ('0'..='7').contains(&c)
+        }
+
+        fn is_ascii_binary_digit(c: char) -> bool {
+            c == '0' || c == '1'
+        }
+
         static KEYWORDS: OnceLock<HashMap<&'static str, TokenKind>> = OnceLock::new();
         let keywords = KEYWORDS.get_or_init(|| {
             let mut m = HashMap::new();
@@ -463,18 +472,17 @@ impl Tokenizer {
                 .map(|c| c == 'x' || c == 'o' || c == 'b')
                 .unwrap_or(false) =>
             {
-                let (base, is_digit_in_base): (_, Box<dyn Fn(char) -> bool>) =
-                    match self.peek(input).unwrap() {
-                        'x' => (16, Box::new(|c| c.is_ascii_hexdigit())),
-                        'o' => (8, Box::new(|c| ('0'..='7').contains(&c))),
-                        'b' => (2, Box::new(|c| c == '0' || c == '1')),
-                        _ => unreachable!(),
-                    };
+                let (base, is_digit_in_base) = match self.peek(input).unwrap() {
+                    'x' => (16, is_ascii_hex_digit as fn(char) -> bool),
+                    'o' => (8, is_ascii_octal_digit as _),
+                    'b' => (2, is_ascii_binary_digit as _),
+                    _ => unreachable!(),
+                };
 
                 self.advance(input); // skip over the x/o/b
 
-                // If the first character is not a digits, that's an error.
-                if !self.peek(input).map(&is_digit_in_base).unwrap_or(false) {
+                // If the first character is not a digit, that's an error.
+                if !self.peek(input).map(is_digit_in_base).unwrap_or(false) {
                     return tokenizer_error(
                         self.current,
                         TokenizerErrorKind::ExpectedDigitInBase {

+ 4 - 4
numbat/src/traversal.rs

@@ -11,7 +11,7 @@ impl ForAllTypeSchemes for StructInfo {
     }
 }
 
-impl ForAllTypeSchemes for Expression {
+impl ForAllTypeSchemes for Expression<'_> {
     fn for_all_type_schemes(&mut self, f: &mut dyn FnMut(&mut TypeScheme)) {
         match self {
             Expression::Scalar(_, _, type_) => f(type_),
@@ -75,7 +75,7 @@ impl ForAllTypeSchemes for Expression {
     }
 }
 
-impl ForAllTypeSchemes for Statement {
+impl ForAllTypeSchemes for Statement<'_> {
     fn for_all_type_schemes(&mut self, f: &mut dyn FnMut(&mut TypeScheme)) {
         match self {
             Statement::Expression(expr) => expr.for_all_type_schemes(f),
@@ -115,7 +115,7 @@ pub trait ForAllExpressions {
     fn for_all_expressions(&self, f: &mut dyn FnMut(&Expression));
 }
 
-impl ForAllExpressions for Statement {
+impl ForAllExpressions for Statement<'_> {
     fn for_all_expressions(&self, f: &mut dyn FnMut(&Expression)) {
         match self {
             Statement::Expression(expr) => expr.for_all_expressions(f),
@@ -143,7 +143,7 @@ impl ForAllExpressions for Statement {
     }
 }
 
-impl ForAllExpressions for Expression {
+impl ForAllExpressions for Expression<'_> {
     fn for_all_expressions(&self, f: &mut dyn FnMut(&Expression)) {
         f(self);
         match self {

+ 36 - 61
numbat/src/typechecker/const_evaluation.rs

@@ -13,24 +13,21 @@ fn to_rational_exponent(exponent_f64: f64) -> Option<Exponent> {
 /// support type checking of expressions like `(2 * meter)^(2*3 - 4)` where we
 /// need to know not just the *type* but also the *value* of the exponent.
 pub fn evaluate_const_expr(expr: &typed_ast::Expression) -> Result<Exponent> {
-    match expr {
+    let name = match expr {
         typed_ast::Expression::Scalar(span, n, _type) => {
-            Ok(to_rational_exponent(n.to_f64())
+            return Ok(to_rational_exponent(n.to_f64())
                 .ok_or(TypeCheckError::NonRationalExponent(*span))?)
         }
         typed_ast::Expression::UnaryOperator(_, ast::UnaryOperator::Negate, ref expr, _) => {
-            Ok(-evaluate_const_expr(expr)?)
+            return Ok(-evaluate_const_expr(expr)?)
         }
-        e @ typed_ast::Expression::UnaryOperator(_, ast::UnaryOperator::Factorial, _, _) => Err(
-            TypeCheckError::UnsupportedConstEvalExpression(e.full_span(), "factorial"),
-        ),
-        e @ typed_ast::Expression::UnaryOperator(_, ast::UnaryOperator::LogicalNeg, _, _) => Err(
-            TypeCheckError::UnsupportedConstEvalExpression(e.full_span(), "logical"),
-        ),
+        typed_ast::Expression::UnaryOperator(_, ast::UnaryOperator::Factorial, _, _) => "factorial",
+        typed_ast::Expression::UnaryOperator(_, ast::UnaryOperator::LogicalNeg, _, _) => "logical",
+
         e @ typed_ast::Expression::BinaryOperator(_span_op, op, lhs_expr, rhs_expr, _) => {
             let lhs = evaluate_const_expr(lhs_expr)?;
             let rhs = evaluate_const_expr(rhs_expr)?;
-            match op {
+            return match op {
                 typed_ast::BinaryOperator::Add => Ok(lhs
                     .checked_add(&rhs)
                     .ok_or_else(|| TypeCheckError::OverflowInConstExpr(expr.full_span()))?),
@@ -42,8 +39,8 @@ pub fn evaluate_const_expr(expr: &typed_ast::Expression) -> Result<Exponent> {
                     .ok_or_else(|| TypeCheckError::OverflowInConstExpr(expr.full_span()))?),
                 typed_ast::BinaryOperator::Div => {
                     if rhs == Rational::zero() {
-                        Err(TypeCheckError::DivisionByZeroInConstEvalExpression(
-                            e.full_span(),
+                        Err(Box::new(
+                            TypeCheckError::DivisionByZeroInConstEvalExpression(e.full_span()),
                         ))
                     } else {
                         Ok(lhs
@@ -61,69 +58,47 @@ pub fn evaluate_const_expr(expr: &typed_ast::Expression) -> Result<Exponent> {
                         )
                         .ok_or_else(|| TypeCheckError::OverflowInConstExpr(expr.full_span()))?)
                     } else {
-                        Err(TypeCheckError::UnsupportedConstEvalExpression(
+                        Err(Box::new(TypeCheckError::UnsupportedConstEvalExpression(
                             e.full_span(),
                             "exponentiation with non-integer exponent",
-                        ))
+                        )))
                     }
                 }
-                typed_ast::BinaryOperator::ConvertTo => Err(
+                typed_ast::BinaryOperator::ConvertTo => Err(Box::new(
                     TypeCheckError::UnsupportedConstEvalExpression(e.full_span(), "conversion"),
-                ),
+                )),
                 typed_ast::BinaryOperator::LessThan
                 | typed_ast::BinaryOperator::GreaterThan
                 | typed_ast::BinaryOperator::LessOrEqual
                 | typed_ast::BinaryOperator::GreaterOrEqual
                 | typed_ast::BinaryOperator::Equal
-                | typed_ast::BinaryOperator::NotEqual => Err(
+                | typed_ast::BinaryOperator::NotEqual => Err(Box::new(
                     TypeCheckError::UnsupportedConstEvalExpression(e.full_span(), "comparison"),
-                ),
+                )),
                 typed_ast::BinaryOperator::LogicalAnd | typed_ast::BinaryOperator::LogicalOr => {
-                    Err(TypeCheckError::UnsupportedConstEvalExpression(
+                    Err(Box::new(TypeCheckError::UnsupportedConstEvalExpression(
                         e.full_span(),
                         "logical",
-                    ))
+                    )))
                 }
-            }
+            };
         }
-        e @ typed_ast::Expression::Identifier(..) => Err(
-            TypeCheckError::UnsupportedConstEvalExpression(e.full_span(), "variable"),
-        ),
-        e @ typed_ast::Expression::UnitIdentifier(..) => Err(
-            TypeCheckError::UnsupportedConstEvalExpression(e.full_span(), "unit identifier"),
-        ),
-        e @ typed_ast::Expression::FunctionCall(_, _, _, _, _) => Err(
-            TypeCheckError::UnsupportedConstEvalExpression(e.full_span(), "function call"),
-        ),
-        e @ &typed_ast::Expression::CallableCall(_, _, _, _) => Err(
-            TypeCheckError::UnsupportedConstEvalExpression(e.full_span(), "function call"),
-        ),
-        e @ typed_ast::Expression::Boolean(_, _) => Err(
-            TypeCheckError::UnsupportedConstEvalExpression(e.full_span(), "Boolean value"),
-        ),
-        e @ typed_ast::Expression::String(_, _) => Err(
-            TypeCheckError::UnsupportedConstEvalExpression(e.full_span(), "String"),
-        ),
-        e @ typed_ast::Expression::Condition(..) => Err(
-            TypeCheckError::UnsupportedConstEvalExpression(e.full_span(), "Conditional"),
-        ),
-        e @ typed_ast::Expression::BinaryOperatorForDate(..) => {
-            Err(TypeCheckError::UnsupportedConstEvalExpression(
-                e.full_span(),
-                "binary operator for datetimes",
-            ))
-        }
-        e @ typed_ast::Expression::InstantiateStruct(_, _, _) => Err(
-            TypeCheckError::UnsupportedConstEvalExpression(e.full_span(), "instantiate struct"),
-        ),
-        e @ typed_ast::Expression::AccessField(_, _, _, _, _, _) => Err(
-            TypeCheckError::UnsupportedConstEvalExpression(e.full_span(), "access field of struct"),
-        ),
-        e @ typed_ast::Expression::List(_, _, _) => Err(
-            TypeCheckError::UnsupportedConstEvalExpression(e.full_span(), "lists"),
-        ),
-        e @ typed_ast::Expression::TypedHole(_, _) => Err(
-            TypeCheckError::UnsupportedConstEvalExpression(e.full_span(), "typed hole"),
-        ),
-    }
+        typed_ast::Expression::Identifier(..) => "variable",
+        typed_ast::Expression::UnitIdentifier(..) => "unit identifier",
+        typed_ast::Expression::FunctionCall(_, _, _, _, _) => "function call",
+        typed_ast::Expression::CallableCall(_, _, _, _) => "function call",
+        typed_ast::Expression::Boolean(_, _) => "Boolean value",
+        typed_ast::Expression::String(_, _) => "String",
+        typed_ast::Expression::Condition(..) => "Conditional",
+        typed_ast::Expression::BinaryOperatorForDate(..) => "binary operator for datetimes",
+        typed_ast::Expression::InstantiateStruct(_, _, _) => "instantiate struct",
+        typed_ast::Expression::AccessField(_, _, _, _, _, _) => "access field of struct",
+        typed_ast::Expression::List(_, _, _) => "lists",
+        typed_ast::Expression::TypedHole(_, _) => "typed hole",
+    };
+
+    Err(Box::new(TypeCheckError::UnsupportedConstEvalExpression(
+        expr.full_span(),
+        name,
+    )))
 }

+ 2 - 3
numbat/src/typechecker/environment.rs

@@ -28,8 +28,7 @@ impl FunctionSignature {
         let (fn_type, type_parameters) = self.fn_type.instantiate_for_printing(Some(
             self.type_parameters
                 .iter()
-                .map(|(_, name, _)| name.clone())
-                .collect(),
+                .map(|(_, name, _)| name.as_str()),
         ));
 
         let Type::Fn(ref parameter_types, ref return_type) = fn_type.inner else {
@@ -45,7 +44,7 @@ impl FunctionSignature {
                         Some(annotation) => annotation.pretty_print(),
                         None => type_.to_readable_type(registry),
                     };
-                    (name.clone(), readable_type)
+                    (name.as_str(), readable_type)
                 });
 
         let readable_return_type = match &self.return_type_annotation {

+ 1 - 1
numbat/src/typechecker/error.rs

@@ -156,4 +156,4 @@ pub enum TypeCheckError {
     MultipleTypedHoles(Span),
 }
 
-pub type Result<T> = std::result::Result<T, TypeCheckError>;
+pub type Result<T> = std::result::Result<T, Box<TypeCheckError>>;

File diff suppressed because it is too large
+ 231 - 171
numbat/src/typechecker/mod.rs


+ 2 - 2
numbat/src/typechecker/substitutions.rs

@@ -148,7 +148,7 @@ impl ApplySubstitution for StructInfo {
     }
 }
 
-impl ApplySubstitution for Expression {
+impl ApplySubstitution for Expression<'_> {
     fn apply(&mut self, s: &Substitution) -> Result<(), SubstitutionError> {
         match self {
             Expression::Scalar(_, _, type_) => type_.apply(s),
@@ -210,7 +210,7 @@ impl ApplySubstitution for Expression {
     }
 }
 
-impl ApplySubstitution for Statement {
+impl ApplySubstitution for Statement<'_> {
     fn apply(&mut self, s: &Substitution) -> Result<(), SubstitutionError> {
         match self {
             Statement::Expression(e) => e.apply(s),

+ 11 - 6
numbat/src/typechecker/tests/mod.rs

@@ -49,13 +49,18 @@ fn type_c() -> DType {
     DType::base_dimension("A").multiply(&DType::base_dimension("B"))
 }
 
-fn run_typecheck(input: &str) -> Result<typed_ast::Statement> {
-    let code = &format!("{TEST_PRELUDE}\n{input}");
-    let statements = parse(code, 0).expect("No parse errors for inputs in this test suite");
-    let transformed_statements = Transformer::new().transform(statements)?;
+fn run_typecheck(input: &str) -> Result<typed_ast::Statement<'_>> {
+    let statements = parse(TEST_PRELUDE, 0)
+        .expect("No parse errors for inputs in this test suite")
+        .into_iter()
+        .chain(parse(input, 0).expect("No parse errors for inputs in this test suite"));
+
+    let transformed_statements = Transformer::new()
+        .transform(statements)
+        .map_err(|err| Box::new(err.into()))?;
 
     TypeChecker::default()
-        .check(transformed_statements)
+        .check(&transformed_statements)
         .map(|mut statements_checked| statements_checked.pop().unwrap())
 }
 
@@ -78,7 +83,7 @@ fn get_inferred_fn_type(input: &str) -> TypeScheme {
 #[track_caller]
 fn get_typecheck_error(input: &str) -> TypeCheckError {
     if let Err(err) = dbg!(run_typecheck(input)) {
-        err
+        *err
     } else {
         panic!("Input was expected to yield a type check error");
     }

+ 7 - 8
numbat/src/typechecker/type_scheme.rs

@@ -40,9 +40,9 @@ impl TypeScheme {
         }
     }
 
-    pub fn instantiate_for_printing(
+    pub fn instantiate_for_printing<'a, I: Iterator<Item = &'a str> + ExactSizeIterator>(
         &self,
-        type_parameters: Option<Vec<String>>,
+        type_parameters: Option<I>,
     ) -> (QualifiedType, Vec<TypeVariable>) {
         match self {
             TypeScheme::Concrete(t) => {
@@ -53,9 +53,7 @@ impl TypeScheme {
             TypeScheme::Quantified(n_gen, _) => {
                 // TODO: is this a good idea? we don't take care of name clashes here
                 let type_parameters = match type_parameters {
-                    Some(tp) if tp.len() == *n_gen => {
-                        tp.iter().map(|s| TypeVariable::new(s.clone())).collect()
-                    }
+                    Some(tp) if tp.len() == *n_gen => tp.map(TypeVariable::new).collect(),
                     _ => {
                         if *n_gen <= 26 {
                             (0..*n_gen)
@@ -111,7 +109,8 @@ impl TypeScheme {
         registry: &crate::dimension::DimensionRegistry,
         with_quantifiers: bool,
     ) -> crate::markup::Markup {
-        let (instantiated_type, type_parameters) = self.instantiate_for_printing(None);
+        let (instantiated_type, type_parameters) =
+            self.instantiate_for_printing::<<Vec<&str> as IntoIterator>::IntoIter>(None);
 
         let mut markup = m::empty();
 
@@ -124,7 +123,7 @@ impl TypeScheme {
 
             for type_parameter in &type_parameters {
                 markup += m::space();
-                markup += m::type_identifier(type_parameter.unsafe_name());
+                markup += m::type_identifier(type_parameter.unsafe_name().to_string());
 
                 if instantiated_type.bounds.is_dtype_bound(type_parameter) {
                     markup += m::operator(":");
@@ -220,7 +219,7 @@ impl PrettyPrint for TypeScheme {
                 for type_parameter in &type_parameters {
                     markup += m::keyword("forall");
                     markup += m::space();
-                    markup += m::type_identifier(type_parameter.unsafe_name());
+                    markup += m::type_identifier(type_parameter.unsafe_name().to_string());
 
                     if instantiated_type.bounds.is_dtype_bound(type_parameter) {
                         markup += m::operator(":");

+ 121 - 104
numbat/src/typed_ast.rs

@@ -36,14 +36,16 @@ impl DTypeFactor {
     }
 }
 
+type DtypeFactorPower = (DTypeFactor, Exponent);
+
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct DType {
     // Always in canonical form
-    pub factors: Vec<(DTypeFactor, Exponent)>, // TODO make this private
+    pub factors: Vec<DtypeFactorPower>, // TODO make this private
 }
 
 impl DType {
-    pub fn from_factors(factors: &[(DTypeFactor, Exponent)]) -> DType {
+    pub fn from_factors(factors: &[DtypeFactorPower]) -> DType {
         let mut dtype = DType {
             factors: factors.into(),
         };
@@ -74,11 +76,12 @@ impl DType {
         names.extend(registry.get_derived_entry_names_for(&base_representation));
         match &names[..] {
             [] => self.pretty_print(),
-            [single] => m::type_identifier(single),
-            multiple => {
-                Itertools::intersperse(multiple.iter().map(m::type_identifier), m::dimmed(" or "))
-                    .sum()
-            }
+            [single] => m::type_identifier(single.to_string()),
+            multiple => Itertools::intersperse(
+                multiple.iter().cloned().map(m::type_identifier),
+                m::dimmed(" or "),
+            )
+            .sum(),
         }
     }
 
@@ -201,9 +204,7 @@ impl DType {
             .contains(name)
     }
 
-    pub fn split_first_factor(
-        &self,
-    ) -> Option<(&(DTypeFactor, Exponent), &[(DTypeFactor, Exponent)])> {
+    pub fn split_first_factor(&self) -> Option<(&DtypeFactorPower, &[DtypeFactorPower])> {
         self.factors.split_first()
     }
 
@@ -325,11 +326,11 @@ impl std::fmt::Display for Type {
 impl PrettyPrint for Type {
     fn pretty_print(&self) -> Markup {
         match self {
-            Type::TVar(TypeVariable::Named(name)) => m::type_identifier(name),
+            Type::TVar(TypeVariable::Named(name)) => m::type_identifier(name.clone()),
             Type::TVar(TypeVariable::Quantified(_)) => {
                 unreachable!("Quantified types should not be printed")
             }
-            Type::TPar(name) => m::type_identifier(name),
+            Type::TPar(name) => m::type_identifier(name.clone()),
             Type::Dimension(d) => d.pretty_print(),
             Type::Boolean => m::type_identifier("Bool"),
             Type::String => m::type_identifier("String"),
@@ -349,7 +350,7 @@ impl PrettyPrint for Type {
                     + return_type.pretty_print()
                     + m::operator("]")
             }
-            Type::Struct(info) => m::type_identifier(&info.name),
+            Type::Struct(info) => m::type_identifier(info.name.clone()),
             Type::List(element_type) => {
                 m::type_identifier("List")
                     + m::operator("<")
@@ -451,16 +452,16 @@ impl Type {
 }
 
 #[derive(Debug, Clone, PartialEq)]
-pub enum StringPart {
+pub enum StringPart<'a> {
     Fixed(String),
     Interpolation {
         span: Span,
-        expr: Box<Expression>,
-        format_specifiers: Option<String>,
+        expr: Box<Expression<'a>>,
+        format_specifiers: Option<&'a str>,
     },
 }
 
-impl PrettyPrint for StringPart {
+impl PrettyPrint for StringPart<'_> {
     fn pretty_print(&self) -> Markup {
         match self {
             StringPart::Fixed(s) => m::string(escape_numbat_string(s)),
@@ -472,7 +473,7 @@ impl PrettyPrint for StringPart {
                 let mut markup = m::operator("{") + expr.pretty_print();
 
                 if let Some(format_specifiers) = format_specifiers {
-                    markup += m::text(format_specifiers);
+                    markup += m::text(format_specifiers.to_string());
                 }
 
                 markup += m::operator("}");
@@ -483,23 +484,23 @@ impl PrettyPrint for StringPart {
     }
 }
 
-impl PrettyPrint for &Vec<StringPart> {
+impl PrettyPrint for &Vec<StringPart<'_>> {
     fn pretty_print(&self) -> Markup {
         m::operator("\"") + self.iter().map(|p| p.pretty_print()).sum() + m::operator("\"")
     }
 }
 
 #[derive(Debug, Clone, PartialEq)]
-pub enum Expression {
+pub enum Expression<'a> {
     Scalar(Span, Number, TypeScheme),
-    Identifier(Span, String, TypeScheme),
+    Identifier(Span, &'a str, TypeScheme),
     UnitIdentifier(Span, Prefix, String, String, TypeScheme),
-    UnaryOperator(Span, UnaryOperator, Box<Expression>, TypeScheme),
+    UnaryOperator(Span, UnaryOperator, Box<Expression<'a>>, TypeScheme),
     BinaryOperator(
         Option<Span>,
         BinaryOperator,
-        Box<Expression>,
-        Box<Expression>,
+        Box<Expression<'a>>,
+        Box<Expression<'a>>,
         TypeScheme,
     ),
     /// A special binary operator that has a DateTime as one (or both) of the operands
@@ -507,32 +508,37 @@ pub enum Expression {
         Option<Span>,
         BinaryOperator,
         /// LHS must evaluate to a DateTime
-        Box<Expression>,
+        Box<Expression<'a>>,
         /// RHS can evaluate to a DateTime or a quantity of type Time
-        Box<Expression>,
+        Box<Expression<'a>>,
         TypeScheme,
     ),
     // A 'proper' function call
-    FunctionCall(Span, Span, String, Vec<Expression>, TypeScheme),
+    FunctionCall(Span, Span, &'a str, Vec<Expression<'a>>, TypeScheme),
     // A call via a function object
-    CallableCall(Span, Box<Expression>, Vec<Expression>, TypeScheme),
+    CallableCall(Span, Box<Expression<'a>>, Vec<Expression<'a>>, TypeScheme),
     Boolean(Span, bool),
-    Condition(Span, Box<Expression>, Box<Expression>, Box<Expression>),
-    String(Span, Vec<StringPart>),
-    InstantiateStruct(Span, Vec<(String, Expression)>, StructInfo),
+    Condition(
+        Span,
+        Box<Expression<'a>>,
+        Box<Expression<'a>>,
+        Box<Expression<'a>>,
+    ),
+    String(Span, Vec<StringPart<'a>>),
+    InstantiateStruct(Span, Vec<(&'a str, Expression<'a>)>, StructInfo),
     AccessField(
         Span,
         Span,
-        Box<Expression>,
-        String,     // field name
+        Box<Expression<'a>>,
+        &'a str,    // field name
         TypeScheme, // struct type
         TypeScheme, // resulting field type
     ),
-    List(Span, Vec<Expression>, TypeScheme),
+    List(Span, Vec<Expression<'a>>, TypeScheme),
     TypedHole(Span, TypeScheme),
 }
 
-impl Expression {
+impl Expression<'_> {
     pub fn full_span(&self) -> Span {
         match self {
             Expression::Scalar(span, ..) => *span,
@@ -569,51 +575,56 @@ impl Expression {
 }
 
 #[derive(Debug, Clone, PartialEq)]
-pub struct DefineVariable(
-    pub String,
-    pub Vec<Decorator>,
-    pub Expression,
+pub struct DefineVariable<'a>(
+    pub &'a str,
+    pub Vec<Decorator<'a>>,
+    pub Expression<'a>,
     pub Option<TypeAnnotation>,
     pub TypeScheme,
     pub Markup,
 );
 
 #[derive(Debug, Clone, PartialEq)]
-pub enum Statement {
-    Expression(Expression),
-    DefineVariable(DefineVariable),
+pub enum Statement<'a> {
+    Expression(Expression<'a>),
+    DefineVariable(DefineVariable<'a>),
     DefineFunction(
-        String,
-        Vec<Decorator>,                            // decorators
-        Vec<(String, Option<TypeParameterBound>)>, // type parameters
+        &'a str,
+        Vec<Decorator<'a>>,                         // decorators
+        Vec<(&'a str, Option<TypeParameterBound>)>, // type parameters
         Vec<(
             // parameters:
             Span,                   // span of the parameter
-            String,                 // parameter name
+            &'a str,                // parameter name
             Option<TypeAnnotation>, // parameter type annotation
             Markup,                 // readable parameter type
         )>,
-        Option<Expression>,     // function body
-        Vec<DefineVariable>,    // local variables
-        TypeScheme,             // function type
-        Option<TypeAnnotation>, // return type annotation
-        Markup,                 // readable return type
+        Option<Expression<'a>>,  // function body
+        Vec<DefineVariable<'a>>, // local variables
+        TypeScheme,              // function type
+        Option<TypeAnnotation>,  // return type annotation
+        Markup,                  // readable return type
+    ),
+    DefineDimension(&'a str, Vec<TypeExpression>),
+    DefineBaseUnit(
+        &'a str,
+        Vec<Decorator<'a>>,
+        Option<TypeAnnotation>,
+        TypeScheme,
     ),
-    DefineDimension(String, Vec<TypeExpression>),
-    DefineBaseUnit(String, Vec<Decorator>, Option<TypeAnnotation>, TypeScheme),
     DefineDerivedUnit(
-        String,
-        Expression,
-        Vec<Decorator>,
+        &'a str,
+        Expression<'a>,
+        Vec<Decorator<'a>>,
         Option<TypeAnnotation>,
         TypeScheme,
         Markup,
     ),
-    ProcedureCall(crate::ast::ProcedureKind, Vec<Expression>),
+    ProcedureCall(crate::ast::ProcedureKind, Vec<Expression<'a>>),
     DefineStruct(StructInfo),
 }
 
-impl Statement {
+impl Statement<'_> {
     pub fn as_expression(&self) -> Option<&Expression> {
         if let Self::Expression(v) = self {
             Some(v)
@@ -663,9 +674,8 @@ impl Statement {
                 return_type_annotation,
                 readable_return_type,
             ) => {
-                let (fn_type, _) = fn_type.instantiate_for_printing(Some(
-                    type_parameters.iter().map(|(n, _)| n.clone()).collect(),
-                ));
+                let (fn_type, _) =
+                    fn_type.instantiate_for_printing(Some(type_parameters.iter().map(|(n, _)| *n)));
 
                 for DefineVariable(_, _, _, type_annotation, type_, readable_type) in
                     local_variables
@@ -722,7 +732,9 @@ impl Statement {
         exponents
     }
 
-    pub(crate) fn find_typed_hole(&self) -> Result<Option<(Span, TypeScheme)>, TypeCheckError> {
+    pub(crate) fn find_typed_hole(
+        &self,
+    ) -> Result<Option<(Span, TypeScheme)>, Box<TypeCheckError>> {
         let mut hole = None;
         let mut found_multiple_holes = false;
         self.for_all_expressions(&mut |expr| {
@@ -735,14 +747,16 @@ impl Statement {
         });
 
         if found_multiple_holes {
-            Err(TypeCheckError::MultipleTypedHoles(hole.unwrap().0))
+            Err(Box::new(TypeCheckError::MultipleTypedHoles(
+                hole.unwrap().0,
+            )))
         } else {
             Ok(hole)
         }
     }
 }
 
-impl Expression {
+impl Expression<'_> {
     pub fn get_type(&self) -> Type {
         match self {
             Expression::Scalar(_, _, type_) => type_.unsafe_as_concrete(),
@@ -837,8 +851,8 @@ fn decorator_markup(decorators: &Vec<Decorator>) -> Markup {
                     m::decorator("@aliases")
                         + m::operator("(")
                         + Itertools::intersperse(
-                            names.iter().map(|(name, accepts_prefix)| {
-                                m::unit(name) + accepts_prefix_markup(accepts_prefix)
+                            names.iter().map(|(name, accepts_prefix, _)| {
+                                m::unit(name.to_string()) + accepts_prefix_markup(accepts_prefix)
                             }),
                             m::operator(", "),
                         )
@@ -846,23 +860,29 @@ fn decorator_markup(decorators: &Vec<Decorator>) -> Markup {
                         + m::operator(")")
                 }
                 Decorator::Url(url) => {
-                    m::decorator("@url") + m::operator("(") + m::string(url) + m::operator(")")
+                    m::decorator("@url")
+                        + m::operator("(")
+                        + m::string(url.clone())
+                        + m::operator(")")
                 }
                 Decorator::Name(name) => {
-                    m::decorator("@name") + m::operator("(") + m::string(name) + m::operator(")")
+                    m::decorator("@name")
+                        + m::operator("(")
+                        + m::string(name.clone())
+                        + m::operator(")")
                 }
                 Decorator::Description(description) => {
                     m::decorator("@description")
                         + m::operator("(")
-                        + m::string(description)
+                        + m::string(description.clone())
                         + m::operator(")")
                 }
                 Decorator::Example(example_code, example_description) => {
                     m::decorator("@example")
                         + m::operator("(")
-                        + m::string(example_code)
+                        + m::string(example_code.clone())
                         + if let Some(example_description) = example_description {
-                            m::operator(", ") + m::string(example_description)
+                            m::operator(", ") + m::string(example_description.clone())
                         } else {
                             m::empty()
                         }
@@ -874,14 +894,14 @@ fn decorator_markup(decorators: &Vec<Decorator>) -> Markup {
     markup_decorators
 }
 
-pub fn pretty_print_function_signature(
+pub fn pretty_print_function_signature<'a>(
     function_name: &str,
     fn_type: &QualifiedType,
     type_parameters: &[TypeVariable],
     parameters: impl Iterator<
         Item = (
-            String, // parameter name
-            Markup, // readable parameter type
+            &'a str, // parameter name
+            Markup,  // readable parameter type
         ),
     >,
     readable_return_type: &Markup,
@@ -892,7 +912,7 @@ pub fn pretty_print_function_signature(
         m::operator("<")
             + Itertools::intersperse(
                 type_parameters.iter().map(|tv| {
-                    m::type_identifier(tv.unsafe_name())
+                    m::type_identifier(tv.unsafe_name().to_string())
                         + if fn_type.bounds.is_dtype_bound(tv) {
                             m::operator(":") + m::space() + m::type_identifier("Dim")
                         } else {
@@ -907,7 +927,7 @@ pub fn pretty_print_function_signature(
 
     let markup_parameters = Itertools::intersperse(
         parameters.map(|(name, parameter_type)| {
-            m::identifier(name) + m::operator(":") + m::space() + parameter_type.clone()
+            m::identifier(name.to_string()) + m::operator(":") + m::space() + parameter_type
         }),
         m::operator(", "),
     )
@@ -918,7 +938,7 @@ pub fn pretty_print_function_signature(
 
     m::keyword("fn")
         + m::space()
-        + m::identifier(function_name)
+        + m::identifier(function_name.to_string())
         + markup_type_parameters
         + m::operator("(")
         + markup_parameters
@@ -926,7 +946,7 @@ pub fn pretty_print_function_signature(
         + markup_return_type
 }
 
-impl PrettyPrint for Statement {
+impl PrettyPrint for Statement<'_> {
     fn pretty_print(&self) -> Markup {
         match self {
             Statement::DefineVariable(DefineVariable(
@@ -939,7 +959,7 @@ impl PrettyPrint for Statement {
             )) => {
                 m::keyword("let")
                     + m::space()
-                    + m::identifier(identifier)
+                    + m::identifier(identifier.to_string())
                     + m::operator(":")
                     + m::space()
                     + readable_type.clone()
@@ -959,9 +979,8 @@ impl PrettyPrint for Statement {
                 _return_type_annotation,
                 readable_return_type,
             ) => {
-                let (fn_type, type_parameters) = fn_type.instantiate_for_printing(Some(
-                    type_parameters.iter().map(|(n, _)| n.clone()).collect(),
-                ));
+                let (fn_type, type_parameters) =
+                    fn_type.instantiate_for_printing(Some(type_parameters.iter().map(|(n, _)| *n)));
 
                 let mut pretty_local_variables = None;
                 let mut first = true;
@@ -986,7 +1005,7 @@ impl PrettyPrint for Statement {
                         plv += m::nl()
                             + introducer_keyword
                             + m::space()
-                            + m::identifier(identifier)
+                            + m::identifier(identifier.to_string())
                             + m::operator(":")
                             + m::space()
                             + readable_type.clone()
@@ -1004,7 +1023,7 @@ impl PrettyPrint for Statement {
                     &type_parameters,
                     parameters
                         .iter()
-                        .map(|(_, name, _, type_)| (name.clone(), type_.clone())),
+                        .map(|(_, name, _, type_)| (*name, type_.clone())),
                     readable_return_type,
                 ) + body
                     .as_ref()
@@ -1014,12 +1033,12 @@ impl PrettyPrint for Statement {
             }
             Statement::Expression(expr) => expr.pretty_print(),
             Statement::DefineDimension(identifier, dexprs) if dexprs.is_empty() => {
-                m::keyword("dimension") + m::space() + m::type_identifier(identifier)
+                m::keyword("dimension") + m::space() + m::type_identifier(identifier.to_string())
             }
             Statement::DefineDimension(identifier, dexprs) => {
                 m::keyword("dimension")
                     + m::space()
-                    + m::type_identifier(identifier)
+                    + m::type_identifier(identifier.to_string())
                     + m::space()
                     + m::operator("=")
                     + m::space()
@@ -1033,7 +1052,7 @@ impl PrettyPrint for Statement {
                 decorator_markup(decorators)
                     + m::keyword("unit")
                     + m::space()
-                    + m::unit(identifier)
+                    + m::unit(identifier.to_string())
                     + m::operator(":")
                     + m::space()
                     + annotation
@@ -1052,7 +1071,7 @@ impl PrettyPrint for Statement {
                 decorator_markup(decorators)
                     + m::keyword("unit")
                     + m::space()
-                    + m::unit(identifier)
+                    + m::unit(identifier.to_string())
                     + m::operator(":")
                     + m::space()
                     + readable_type.clone()
@@ -1089,7 +1108,7 @@ impl PrettyPrint for Statement {
                         m::space()
                             + Itertools::intersperse(
                                 fields.iter().map(|(n, (_, t))| {
-                                    m::identifier(n)
+                                    m::identifier(n.clone())
                                         + m::operator(":")
                                         + m::space()
                                         + t.pretty_print()
@@ -1160,7 +1179,7 @@ fn pretty_print_binop(op: &BinaryOperator, lhs: &Expression, rhs: &Expression) -
             }
             (Expression::Scalar(_, s, _), Expression::Identifier(_, name, _type)) => {
                 // Fuse multiplication of a scalar and identifier
-                pretty_scalar(*s) + m::space() + m::identifier(name)
+                pretty_scalar(*s) + m::space() + m::identifier(name.to_string())
             }
             _ => {
                 let add_parens_if_needed = |expr: &Expression| {
@@ -1244,13 +1263,13 @@ fn pretty_print_binop(op: &BinaryOperator, lhs: &Expression, rhs: &Expression) -
     }
 }
 
-impl PrettyPrint for Expression {
+impl PrettyPrint for Expression<'_> {
     fn pretty_print(&self) -> Markup {
         use Expression::*;
 
         match self {
             Scalar(_, n, _) => pretty_scalar(*n),
-            Identifier(_, name, _type) => m::identifier(name),
+            Identifier(_, name, _type) => m::identifier(name.to_string()),
             UnitIdentifier(_, prefix, _name, full_name, _type) => {
                 m::unit(format!("{}{}", prefix.as_string_long(), full_name))
             }
@@ -1266,7 +1285,7 @@ impl PrettyPrint for Expression {
             BinaryOperator(_, op, lhs, rhs, _type) => pretty_print_binop(op, lhs, rhs),
             BinaryOperatorForDate(_, op, lhs, rhs, _type) => pretty_print_binop(op, lhs, rhs),
             FunctionCall(_, _, name, args, _type) => {
-                m::identifier(name)
+                m::identifier(name.to_string())
                     + m::operator("(")
                     + itertools::Itertools::intersperse(
                         args.iter().map(|e| e.pretty_print()),
@@ -1310,7 +1329,7 @@ impl PrettyPrint for Expression {
                         m::space()
                             + itertools::Itertools::intersperse(
                                 exprs.iter().map(|(n, e)| {
-                                    m::identifier(n)
+                                    m::identifier(n.to_string())
                                         + m::operator(":")
                                         + m::space()
                                         + e.pretty_print()
@@ -1323,7 +1342,7 @@ impl PrettyPrint for Expression {
                     + m::operator("}")
             }
             AccessField(_, _, expr, attr, _, _) => {
-                expr.pretty_print() + m::operator(".") + m::identifier(attr)
+                expr.pretty_print() + m::operator(".") + m::identifier(attr.to_string())
             }
             List(_, elements, _) => {
                 m::operator("[")
@@ -1348,8 +1367,7 @@ mod tests {
 
     fn parse(code: &str) -> Statement {
         let statements = crate::parser::parse(
-            &format!(
-                "dimension Scalar = 1
+            "dimension Scalar = 1
                  dimension Length
                  dimension Time
                  dimension Mass
@@ -1387,7 +1405,7 @@ mod tests {
                  @metric_prefixes
                  unit points
 
-                 struct Foo {{foo: Length, bar: Time}}
+                 struct Foo {foo: Length, bar: Time}
 
                  let a = 1
                  let b = 1
@@ -1402,19 +1420,18 @@ mod tests {
                  let länge = 1
                  let x_2 = 1
                  let µ = 1
-                 let _prefixed = 1
-
-                 {code}"
-            ),
+                 let _prefixed = 1",
             0,
         )
-        .unwrap();
+        .unwrap()
+        .into_iter()
+        .chain(crate::parser::parse(code, 0).unwrap());
 
         let mut transformer = Transformer::new();
         let transformed_statements = transformer.transform(statements).unwrap().replace_spans();
 
         crate::typechecker::TypeChecker::default()
-            .check(transformed_statements)
+            .check(&transformed_statements)
             .unwrap()
             .last()
             .unwrap()

+ 2 - 2
numbat/src/unicode_input.rs

@@ -54,8 +54,8 @@ pub const UNICODE_INPUT: &[(&[&str], &str)] = &[
     (&["beta"], "β"),
     (&["gamma"], "γ"),
     (&["delta"], "δ"),
-    (&["epsilon"], "ε"),
-    (&["varepsilon"], "ϵ"),
+    (&["epsilon"], "ϵ"),
+    (&["varepsilon"], "ε"),
     (&["zeta"], "ζ"),
     (&["eta"], "η"),
     (&["theta"], "θ"),

+ 2 - 2
numbat/src/value.rs

@@ -156,7 +156,7 @@ impl PrettyPrint for Value {
             Value::String(s) => s.pretty_print(),
             Value::DateTime(dt) => crate::markup::string(crate::datetime::to_string(dt)),
             Value::FunctionReference(r) => crate::markup::string(r.to_string()),
-            Value::FormatSpecifiers(Some(s)) => crate::markup::string(s),
+            Value::FormatSpecifiers(Some(s)) => crate::markup::string(s.clone()),
             Value::FormatSpecifiers(None) => crate::markup::empty(),
             Value::StructInstance(struct_info, values) => {
                 crate::markup::type_identifier(struct_info.name.clone())
@@ -168,7 +168,7 @@ impl PrettyPrint for Value {
                         crate::markup::space()
                             + itertools::Itertools::intersperse(
                                 struct_info.fields.keys().zip(values).map(|(name, val)| {
-                                    crate::markup::identifier(name)
+                                    crate::markup::identifier(name.clone())
                                         + crate::markup::operator(":")
                                         + crate::markup::space()
                                         + val.pretty_print()

+ 3 - 3
numbat/src/vm.rs

@@ -810,9 +810,9 @@ impl Vm {
                         .to_f64();
 
                     if lhs < 0. {
-                        return Err(RuntimeError::FactorialOfNegativeNumber);
+                        return Err(Box::new(RuntimeError::FactorialOfNegativeNumber));
                     } else if lhs.fract() != 0. {
-                        return Err(RuntimeError::FactorialOfNonInteger);
+                        return Err(Box::new(RuntimeError::FactorialOfNonInteger));
                     }
 
                     self.push_quantity(Quantity::from_scalar(math::factorial(lhs)));
@@ -862,7 +862,7 @@ impl Vm {
                             match result {
                                 std::ops::ControlFlow::Continue(()) => {}
                                 std::ops::ControlFlow::Break(runtime_error) => {
-                                    return Err(runtime_error);
+                                    return Err(Box::new(runtime_error));
                                 }
                             }
                         }

+ 1 - 1
numbat/tests/common.rs

@@ -18,7 +18,7 @@ pub fn get_test_context_without_prelude() -> Context {
 }
 
 pub fn get_test_context() -> Context {
-    static CONTEXT: Lazy<Result<Context, NumbatError>> = Lazy::new(|| {
+    static CONTEXT: Lazy<Result<Context, Box<NumbatError>>> = Lazy::new(|| {
         let mut context = get_test_context_without_prelude();
 
         let _ = context.interpret("use prelude", CodeSource::Internal)?;

+ 1 - 1
numbat/tests/interpreter.rs

@@ -44,7 +44,7 @@ fn fail(code: &str) -> NumbatError {
     let mut ctx = get_test_context();
     let ret = ctx.interpret(code, CodeSource::Internal);
     match ret {
-        Err(e) => e,
+        Err(e) => *e,
         Ok((_stmts, ret)) => {
             if let InterpreterResult::Value(val) = ret {
                 let fmt = PlainTextFormatter {};

+ 12 - 4
numbat/tests/prelude_and_examples.rs

@@ -30,7 +30,9 @@ fn assert_runs_without_prelude(code: &str) {
 
 fn assert_parse_error(code: &str) {
     assert!(matches!(
-        get_test_context().interpret(code, CodeSource::Internal),
+        get_test_context()
+            .interpret(code, CodeSource::Internal)
+            .map_err(|b| *b),
         Err(NumbatError::ResolverError(
             ResolverError::ParseErrors { .. }
         ))
@@ -39,21 +41,27 @@ fn assert_parse_error(code: &str) {
 
 fn assert_name_resolution_error(code: &str) {
     assert!(matches!(
-        get_test_context().interpret(code, CodeSource::Internal),
+        get_test_context()
+            .interpret(code, CodeSource::Internal)
+            .map_err(|b| *b),
         Err(NumbatError::NameResolutionError(_))
     ));
 }
 
 fn assert_typecheck_error(code: &str) {
     assert!(matches!(
-        get_test_context().interpret(code, CodeSource::Internal),
+        get_test_context()
+            .interpret(code, CodeSource::Internal)
+            .map_err(|b| *b),
         Err(NumbatError::TypeCheckError(_))
     ));
 }
 
 fn assert_runtime_error(code: &str) {
     assert!(matches!(
-        get_test_context().interpret(code, CodeSource::Internal),
+        get_test_context()
+            .interpret(code, CodeSource::Internal)
+            .map_err(|b| *b),
         Err(NumbatError::RuntimeError(_))
     ));
 }

Some files were not shown because too many files changed in this diff