Переглянути джерело

Add strings to the language, use markup for printing

David Peter 2 роки тому
батько
коміт
e8a709684b

+ 1 - 0
examples/hello_world.nbt

@@ -0,0 +1 @@
+print("hello world")

+ 2 - 1
numbat-cli/src/ansi_formatter.rs

@@ -11,7 +11,8 @@ impl Formatter for ANSIFormatter {
     ) -> String {
         (match format_type {
             FormatType::Whitespace => text.normal(),
-            FormatType::Text => text.dimmed(),
+            FormatType::Dimmed => text.dimmed(),
+            FormatType::Text => text.normal(),
             FormatType::Keyword => text.magenta(),
             FormatType::Value => text.yellow(),
             FormatType::Unit => text.cyan(),

+ 9 - 14
numbat-cli/src/main.rs

@@ -152,7 +152,7 @@ impl Cli {
             let ctx = self.context.clone();
             let mut no_print_settings = InterpreterSettings {
                 print_fn: Box::new(
-                    move |_: &str| { // ignore any print statements when loading this module asynchronously
+                    move |_: &m::Markup| { // ignore any print statements when loading this module asynchronously
                     },
                 ),
             };
@@ -342,11 +342,11 @@ impl Cli {
         execution_mode: ExecutionMode,
         pretty_print_mode: PrettyPrintMode,
     ) -> ControlFlow {
-        let to_be_printed: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
+        let to_be_printed: Arc<Mutex<Vec<m::Markup>>> = Arc::new(Mutex::new(vec![]));
         let to_be_printed_c = to_be_printed.clone();
         let mut settings = InterpreterSettings {
-            print_fn: Box::new(move |s: &str| {
-                to_be_printed_c.lock().unwrap().push(s.to_string());
+            print_fn: Box::new(move |s: &m::Markup| {
+                to_be_printed_c.lock().unwrap().push(s.clone());
             }),
         };
 
@@ -380,14 +380,9 @@ impl Cli {
 
                 let to_be_printed = to_be_printed.lock().unwrap();
                 for s in to_be_printed.iter() {
-                    print!(
-                        "{}{}",
-                        if execution_mode == ExecutionMode::Interactive {
-                            "  "
-                        } else {
-                            ""
-                        },
-                        s
+                    println!(
+                        "{}",
+                        ansi_format(s, execution_mode == ExecutionMode::Interactive)
                     );
                 }
                 if !to_be_printed.is_empty() && execution_mode == ExecutionMode::Interactive {
@@ -403,9 +398,9 @@ impl Cli {
                                 if type_ == Type::scalar() {
                                     m::empty()
                                 } else {
-                                    m::text("    [")
+                                    m::dimmed("    [")
                                         + e.get_type().to_readable_type(&registry)
-                                        + m::text("]")
+                                        + m::dimmed("]")
                                 }
                             } else {
                                 m::empty()

+ 3 - 1
numbat/src/ast.rs

@@ -66,8 +66,8 @@ pub enum Expression {
         span_op: Option<Span>, // not available for implicit multiplication and unicode exponents
     },
     FunctionCall(Span, Span, String, Vec<Expression>),
-
     Boolean(Span, bool),
+    String(Span, String),
     Condition(Span, Box<Expression>, Box<Expression>, Box<Expression>),
 }
 
@@ -99,6 +99,7 @@ impl Expression {
             Expression::Condition(span_if, _, _, then_expr) => {
                 span_if.extend(&then_expr.full_span())
             }
+            Expression::String(span, _) => *span,
         }
     }
 }
@@ -362,6 +363,7 @@ impl ReplaceSpans for Expression {
                 Box::new(then.replace_spans()),
                 Box::new(else_.replace_spans()),
             ),
+            Expression::String(_, string) => Expression::String(Span::dummy(), string.clone()),
         }
     }
 }

+ 12 - 4
numbat/src/bytecode_interpreter.rs

@@ -5,6 +5,7 @@ use crate::interpreter::{
     Interpreter, InterpreterResult, InterpreterSettings, Result, RuntimeError,
 };
 use crate::prefix::Prefix;
+use crate::pretty_print::PrettyPrint;
 use crate::typed_ast::{BinaryOperator, Expression, Statement, UnaryOperator};
 use crate::unit::Unit;
 use crate::unit_registry::UnitRegistry;
@@ -94,6 +95,10 @@ impl BytecodeInterpreter {
                 let index = self.vm.add_constant(Constant::Boolean(*val));
                 self.vm.add_op1(Op::LoadConstant, index);
             }
+            Expression::String(_, string) => {
+                let index = self.vm.add_constant(Constant::String(string.clone()));
+                self.vm.add_op1(Op::LoadConstant, index)
+            }
             Expression::Condition(_, condition, then_expr, else_expr) => {
                 self.compile_expression(condition)?;
 
@@ -132,6 +137,7 @@ impl BytecodeInterpreter {
             | Expression::UnaryOperator(..)
             | Expression::BinaryOperator(_, BinaryOperator::ConvertTo, _, _, _)
             | Expression::Boolean(..)
+            | Expression::String(..)
             | Expression::Condition(..) => {}
             Expression::BinaryOperator(..) => {
                 self.vm.add_op(Op::FullSimplify);
@@ -237,9 +243,11 @@ impl BytecodeInterpreter {
             Statement::ProcedureCall(ProcedureKind::Type, args) => {
                 assert_eq!(args.len(), 1);
                 let arg = &args[0];
-                let type_str = format!("{}", arg.get_type());
 
-                let idx = self.vm.add_string(type_str);
+                use crate::markup as m;
+                let idx = self.vm.add_string(
+                    m::dimmed("=") + m::whitespace(" ") + arg.get_type().pretty_print(),
+                );
                 self.vm.add_op1(Op::PrintString, idx);
             }
             Statement::ProcedureCall(kind, args) => {
@@ -264,11 +272,11 @@ impl BytecodeInterpreter {
             print_fn: &mut settings.print_fn,
         };
 
-        self.vm.disassemble(&mut ctx);
+        self.vm.disassemble();
 
         let result = self.vm.run(&mut ctx);
 
-        self.vm.debug(&mut ctx);
+        self.vm.debug();
 
         result
     }

+ 5 - 1
numbat/src/ffi.rs

@@ -4,6 +4,7 @@ use std::sync::OnceLock;
 
 use crate::currency::ExchangeRatesCache;
 use crate::interpreter::RuntimeError;
+use crate::pretty_print::PrettyPrint;
 use crate::value::Value;
 use crate::vm::ExecutionContext;
 use crate::{ast::ProcedureKind, quantity::Quantity};
@@ -297,7 +298,10 @@ pub(crate) fn functions() -> &'static HashMap<String, ForeignFunction> {
 fn print(ctx: &mut ExecutionContext, args: &[Value]) -> ControlFlow {
     assert!(args.len() == 1);
 
-    (ctx.print_fn)(&format!("{}\n", args[0]));
+    match &args[0] {
+        Value::String(string) => (ctx.print_fn)(&crate::markup::text(string)), // print string without quotes
+        arg => (ctx.print_fn)(&arg.pretty_print()),
+    }
 
     ControlFlow::Continue(())
 }

+ 3 - 2
numbat/src/interpreter.rs

@@ -1,4 +1,5 @@
 use crate::{
+    markup::Markup,
     quantity::{Quantity, QuantityError},
     typed_ast::Statement,
     unit_registry::{UnitRegistry, UnitRegistryError},
@@ -56,7 +57,7 @@ impl InterpreterResult {
 
 pub type Result<T> = std::result::Result<T, RuntimeError>;
 
-pub type PrintFunction = dyn FnMut(&str) -> () + Send;
+pub type PrintFunction = dyn FnMut(&Markup) -> () + Send;
 
 pub struct InterpreterSettings {
     pub print_fn: Box<PrintFunction>,
@@ -65,7 +66,7 @@ pub struct InterpreterSettings {
 impl Default for InterpreterSettings {
     fn default() -> Self {
         Self {
-            print_fn: Box::new(move |s: &str| {
+            print_fn: Box::new(move |s: &Markup| {
                 print!("{}", s);
             }),
         }

+ 9 - 0
numbat/src/markup.rs

@@ -3,6 +3,7 @@ use std::fmt::Display;
 #[derive(Debug, Clone, PartialEq)]
 pub enum FormatType {
     Whitespace,
+    Dimmed,
     Text,
     Keyword,
     Value,
@@ -73,6 +74,14 @@ pub fn whitespace(text: impl AsRef<str>) -> Markup {
     ))
 }
 
+pub fn dimmed(text: impl AsRef<str>) -> Markup {
+    Markup::from(FormattedString(
+        OutputType::Normal,
+        FormatType::Dimmed,
+        text.as_ref().to_string(),
+    ))
+}
+
 pub fn text(text: impl AsRef<str>) -> Markup {
     Markup::from(FormattedString(
         OutputType::Normal,

+ 5 - 0
numbat/src/parser.rs

@@ -1019,6 +1019,11 @@ impl<'a> Parser<'a> {
                     false
                 },
             ))
+        } else if let Some(token) = self.match_exact(TokenKind::String) {
+            Ok(Expression::String(
+                token.span,
+                token.lexeme.trim_matches('"').to_string(),
+            ))
         } else if self.match_exact(TokenKind::LeftParen).is_some() {
             let inner = self.expression()?;
 

+ 1 - 0
numbat/src/prefix_transformer.rs

@@ -80,6 +80,7 @@ impl Transformer {
                 Box::new(self.transform_expression(*then)),
                 Box::new(self.transform_expression(*else_)),
             ),
+            expr @ Expression::String(_, _) => expr,
         }
     }
 

+ 6 - 0
numbat/src/pretty_print.rs

@@ -9,3 +9,9 @@ impl PrettyPrint for bool {
         crate::markup::keyword(if *self { "true" } else { "false" })
     }
 }
+
+impl PrettyPrint for String {
+    fn pretty_print(&self) -> Markup {
+        crate::markup::text(format!("\"{self}\""))
+    }
+}

+ 3 - 0
numbat/src/tokenizer.rs

@@ -84,6 +84,8 @@ pub enum TokenKind {
     Then,
     Else,
 
+    Str,
+
     Long,
     Short,
     Both,
@@ -297,6 +299,7 @@ impl Tokenizer {
             m.insert("if", TokenKind::If);
             m.insert("then", TokenKind::Then);
             m.insert("else", TokenKind::Else);
+            m.insert("str", TokenKind::Str);
             m
         });
 

+ 6 - 0
numbat/src/typechecker.rs

@@ -343,6 +343,9 @@ fn evaluate_const_expr(expr: &typed_ast::Expression) -> Result<Exponent> {
         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"),
         ),
@@ -699,6 +702,9 @@ impl TypeChecker {
                 )
             }
             ast::Expression::Boolean(span, val) => typed_ast::Expression::Boolean(*span, *val),
+            ast::Expression::String(span, string) => {
+                typed_ast::Expression::String(*span, string.clone())
+            }
             ast::Expression::Condition(span, condition, then, else_) => {
                 let condition = self.check_expression(condition)?;
                 if condition.get_type() != Type::Boolean {

+ 11 - 3
numbat/src/typed_ast.rs

@@ -21,7 +21,7 @@ impl DType {
             [single] => m::type_identifier(single),
             ref multiple => Itertools::intersperse(
                 multiple.iter().map(|n| m::type_identifier(n)),
-                m::text(" or "),
+                m::dimmed(" or "),
             )
             .sum(),
         }
@@ -32,6 +32,7 @@ impl DType {
 pub enum Type {
     Dimension(DType),
     Boolean,
+    String,
 }
 
 impl std::fmt::Display for Type {
@@ -39,6 +40,7 @@ impl std::fmt::Display for Type {
         match self {
             Type::Dimension(d) => d.fmt(f),
             Type::Boolean => write!(f, "bool"),
+            Type::String => write!(f, "str"),
         }
     }
 }
@@ -48,6 +50,7 @@ impl PrettyPrint for Type {
         match self {
             Type::Dimension(d) => d.pretty_print(),
             Type::Boolean => m::keyword("bool"),
+            Type::String => m::keyword("str"),
         }
     }
 }
@@ -56,7 +59,7 @@ impl Type {
     pub fn to_readable_type(&self, registry: &DimensionRegistry) -> Markup {
         match self {
             Type::Dimension(d) => d.to_readable_type(registry),
-            Type::Boolean => self.pretty_print(),
+            _ => self.pretty_print(),
         }
     }
 
@@ -81,6 +84,7 @@ pub enum Expression {
     FunctionCall(Span, Span, String, Vec<Expression>, DType),
     Boolean(Span, bool),
     Condition(Span, Box<Expression>, Box<Expression>, Box<Expression>),
+    String(Span, String),
 }
 
 impl Expression {
@@ -102,6 +106,7 @@ impl Expression {
             Expression::Condition(span_if, _, _, then_expr) => {
                 span_if.extend(&then_expr.full_span())
             }
+            Expression::String(span, _) => *span,
         }
     }
 }
@@ -142,6 +147,7 @@ impl Expression {
             Expression::FunctionCall(_, _, _, _, type_) => Type::Dimension(type_.clone()),
             Expression::Boolean(_, _) => Type::Boolean,
             Expression::Condition(_, _, then, _) => then.get_type(),
+            Expression::String(_, _) => Type::String,
         }
     }
 }
@@ -335,7 +341,8 @@ fn with_parens(expr: &Expression) -> Markup {
         | Expression::Identifier(..)
         | Expression::UnitIdentifier(..)
         | Expression::FunctionCall(..)
-        | Expression::Boolean(..) => expr.pretty_print(),
+        | Expression::Boolean(..)
+        | Expression::String(..) => expr.pretty_print(),
         Expression::UnaryOperator { .. }
         | Expression::BinaryOperator { .. }
         | Expression::Condition(..) => m::operator("(") + expr.pretty_print() + m::operator(")"),
@@ -485,6 +492,7 @@ impl PrettyPrint for Expression {
                     + m::operator(")")
             }
             Boolean(_, val) => val.pretty_print(),
+            String(_, string) => string.pretty_print(),
             Condition(_, condition, then, else_) => {
                 m::keyword("if")
                     + m::space()

+ 3 - 0
numbat/src/value.rs

@@ -4,6 +4,7 @@ use crate::{pretty_print::PrettyPrint, quantity::Quantity};
 pub enum Value {
     Quantity(Quantity),
     Boolean(bool),
+    String(String),
 }
 
 impl Value {
@@ -29,6 +30,7 @@ impl std::fmt::Display for Value {
         match self {
             Value::Quantity(q) => write!(f, "{}", q),
             Value::Boolean(b) => write!(f, "{}", b),
+            Value::String(s) => write!(f, "\"{}\"", s),
         }
     }
 }
@@ -38,6 +40,7 @@ impl PrettyPrint for Value {
         match self {
             Value::Quantity(q) => q.pretty_print(),
             Value::Boolean(b) => b.pretty_print(),
+            Value::String(s) => s.pretty_print(),
         }
     }
 }

+ 42 - 60
numbat/src/vm.rs

@@ -3,6 +3,7 @@ use std::{collections::HashMap, fmt::Display};
 use crate::{
     ffi::{self, ArityRange, Callable, ForeignFunction},
     interpreter::{InterpreterResult, PrintFunction, Result, RuntimeError},
+    markup::Markup,
     math,
     name_resolution::LAST_RESULT_IDENTIFIERS,
     prefix::Prefix,
@@ -157,6 +158,7 @@ pub enum Constant {
     Scalar(f64),
     Unit(Unit),
     Boolean(bool),
+    String(String),
 }
 
 impl Constant {
@@ -165,6 +167,7 @@ impl Constant {
             Constant::Scalar(n) => Value::Quantity(Quantity::from_scalar(*n)),
             Constant::Unit(u) => Value::Quantity(Quantity::from_unit(u.clone())),
             Constant::Boolean(b) => Value::Boolean(*b),
+            Constant::String(s) => Value::String(s.clone()),
         }
     }
 }
@@ -175,6 +178,7 @@ impl Display for Constant {
             Constant::Scalar(n) => write!(f, "{}", n),
             Constant::Unit(unit) => write!(f, "{}", unit),
             Constant::Boolean(val) => write!(f, "{}", val),
+            Constant::String(val) => write!(f, "\"{}\"", val),
         }
     }
 }
@@ -221,8 +225,8 @@ pub struct Vm {
     /// Unit prefixes in use
     prefixes: Vec<Prefix>,
 
-    /// Strings that are already available at compile time
-    strings: Vec<String>,
+    /// Strings/text that is already available at compile time
+    strings: Vec<Markup>,
 
     /// The names of global variables or [Unit]s. The second
     /// entry is the canonical name for units.
@@ -374,25 +378,22 @@ impl Vm {
         Some(position as u16)
     }
 
-    pub fn disassemble(&self, ctx: &mut ExecutionContext) {
+    pub fn disassemble(&self) {
         if !self.debug {
             return;
         }
 
-        self.println(ctx, "");
-        self.println(ctx, ".CONSTANTS");
+        eprintln!("");
+        eprintln!(".CONSTANTS");
         for (idx, constant) in self.constants.iter().enumerate() {
-            self.println(ctx, format!("  {:04} {}", idx, constant));
+            eprintln!("  {:04} {}", idx, constant);
         }
-        self.println(ctx, ".IDENTIFIERS");
+        eprintln!(".IDENTIFIERS");
         for (idx, identifier) in self.global_identifiers.iter().enumerate() {
-            self.println(ctx, format!("  {:04} {}", idx, identifier.0));
+            eprintln!("  {:04} {}", idx, identifier.0);
         }
         for (idx, (function_name, bytecode)) in self.bytecode.iter().enumerate() {
-            self.println(
-                ctx,
-                format!(".CODE {idx} ({name})", idx = idx, name = function_name),
-            );
+            eprintln!(".CODE {idx} ({name})", idx = idx, name = function_name);
             let mut offset = 0;
             while offset < bytecode.len() {
                 let this_offset = offset;
@@ -414,34 +415,25 @@ impl Vm {
                     .collect::<Vec<String>>()
                     .join(" ");
 
-                self.print(
-                    ctx,
-                    format!(
-                        "  {:04} {:<13} {}",
-                        this_offset,
-                        op.to_string(),
-                        operands_str,
-                    ),
+                eprint!(
+                    "  {:04} {:<13} {}",
+                    this_offset,
+                    op.to_string(),
+                    operands_str,
                 );
 
                 if op == Op::LoadConstant {
-                    self.print(
-                        ctx,
-                        format!("     (value: {})", self.constants[operands[0] as usize]),
-                    );
+                    eprint!("     (value: {})", self.constants[operands[0] as usize]);
                 } else if op == Op::Call {
-                    self.print(
-                        ctx,
-                        format!(
-                            "   ({}, num_args={})",
-                            self.bytecode[operands[0] as usize].0, operands[1] as usize
-                        ),
+                    eprint!(
+                        "   ({}, num_args={})",
+                        self.bytecode[operands[0] as usize].0, operands[1] as usize
                     );
                 }
-                self.println(ctx, "");
+                eprintln!();
             }
         }
-        self.println(ctx, "");
+        eprintln!();
     }
 
     // The following functions are helpers for the actual execution of the code
@@ -515,7 +507,7 @@ impl Vm {
     fn run_without_cleanup(&mut self, ctx: &mut ExecutionContext) -> Result<InterpreterResult> {
         let mut result_last_statement = None;
         while !self.is_at_the_end() {
-            self.debug(ctx);
+            self.debug();
 
             let op = unsafe { std::mem::transmute::<u8, Op>(self.read_byte()) };
 
@@ -695,7 +687,7 @@ impl Vm {
                 Op::PrintString => {
                     let s_idx = self.read_u16() as usize;
                     let s = &self.strings[s_idx];
-                    self.println(ctx, s);
+                    self.print(ctx, s);
                 }
                 Op::FullSimplify => match self.pop() {
                     Value::Quantity(q) => {
@@ -741,44 +733,34 @@ impl Vm {
         }
     }
 
-    pub fn debug(&self, ctx: &mut ExecutionContext) {
+    pub fn debug(&self) {
         if !self.debug {
             return;
         }
 
         let frame = self.current_frame();
-        self.print(
-            ctx,
-            format!(
-                "FRAME = {}, IP = {}, ",
-                self.bytecode[frame.function_idx].0, frame.ip
-            ),
+        eprint!(
+            "FRAME = {}, IP = {}, ",
+            self.bytecode[frame.function_idx].0, frame.ip
         );
-        self.println(
-            ctx,
-            format!(
-                "Stack: [{}]",
-                self.stack
-                    .iter()
-                    .map(|x| x.to_string())
-                    .collect::<Vec<_>>()
-                    .join("] [")
-            ),
+        eprintln!(
+            "Stack: [{}]",
+            self.stack
+                .iter()
+                .map(|x| x.to_string())
+                .collect::<Vec<_>>()
+                .join("] [")
         );
     }
 
-    pub fn add_string(&mut self, s: String) -> u16 {
-        self.strings.push(s);
+    pub fn add_string(&mut self, m: Markup) -> u16 {
+        self.strings.push(m);
         assert!(self.strings.len() <= u16::MAX as usize);
         (self.strings.len() - 1) as u16 // TODO: this can overflow, see above
     }
 
-    fn print<S: AsRef<str>>(&self, ctx: &mut ExecutionContext, s: S) {
-        (ctx.print_fn)(s.as_ref());
-    }
-
-    fn println<S: AsRef<str>>(&self, ctx: &mut ExecutionContext, s: S) {
-        self.print(ctx, format!("{}\n", s.as_ref()));
+    fn print(&self, ctx: &mut ExecutionContext, m: &Markup) {
+        (ctx.print_fn)(&m);
     }
 }
 
@@ -793,7 +775,7 @@ fn vm_basic() {
     vm.add_op(Op::Add);
     vm.add_op(Op::Return);
 
-    let mut print_fn = |_: &str| {};
+    let mut print_fn = |_: &Markup| {};
     let mut ctx = ExecutionContext {
         print_fn: &mut print_fn,
     };