Browse Source

move help print to core numbat and give it its own context

- need to make printing silent for 'use prelude'
- need to add it to numbat-wasm

WIP: refactor help to return markup

this way it can be used with whatever formatter is available

functional version of markup-returning help

Standing Issues
- Cannot use `print` function in examples because it gets order messed
  up
- adds an extra indent in the next line that is printed after the help
  is printed

run cargo fmt
tomeichlersmith 2 years ago
parent
commit
884e8c6090
6 changed files with 170 additions and 44 deletions
  1. 8 43
      numbat-cli/src/main.rs
  2. 7 0
      numbat-wasm/src/lib.rs
  3. 2 0
      numbat-wasm/www/index.js
  4. 1 1
      numbat/Cargo.toml
  5. 151 0
      numbat/src/help.rs
  6. 1 0
      numbat/src/lib.rs

+ 8 - 43
numbat-cli/src/main.rs

@@ -8,6 +8,7 @@ use highlighter::NumbatHighlighter;
 
 use itertools::Itertools;
 use numbat::diagnostic::ErrorDiagnostic;
+use numbat::help::help_markup;
 use numbat::markup as m;
 use numbat::module_importer::{BuiltinModuleImporter, ChainedImporter, FileSystemImporter};
 use numbat::pretty_print::PrettyPrint;
@@ -130,45 +131,6 @@ impl Cli {
         }
     }
 
-    fn help(&mut self) -> Result<()> {
-        let output = m::nl()
-            + m::keyword("numbat")
-            + m::space()
-            + m::text(env!("CARGO_PKG_DESCRIPTION"))
-            + m::nl()
-            + m::text("You can start by trying one of the examples:")
-            + m::nl();
-
-        println!("{}", ansi_format(&output, false));
-
-        let examples = vec![
-            "8 km / (1 h + 25 min)",
-            "atan2(30 cm, 1 m) -> deg",
-            r#"print("Energy of red photons: {ℏ 2 π c / 660 cm -> eV}")"#,
-        ];
-        for example in examples.iter() {
-            println!("{}{}", PROMPT, example);
-            let eg_result = self.parse_and_evaluate(
-                example,
-                CodeSource::Internal,
-                ExecutionMode::Normal,
-                self.args.pretty_print,
-            );
-            if eg_result.is_break() {
-                bail!("Interpreter failed during help examples")
-            }
-        }
-
-        let output = m::nl() // extra whitespace after last example
-            +m::text("Full documentation:")
-            +m::space()
-            +m::keyword("https://numbat.dev/doc/")
-            +m::nl();
-        println!("{}", ansi_format(&output, false));
-
-        Ok(())
-    }
-
     fn run(&mut self) -> Result<()> {
         let load_prelude = !self.args.no_prelude;
         let load_init = !(self.args.no_prelude || self.args.no_init);
@@ -324,10 +286,13 @@ impl Cli {
                                 return Ok(());
                             }
                             "help" | "?" => {
-                                // purposefully ignoring result of help
-                                // because if the examples are failing I am assuming
-                                // the user is a developer intentionally changing numbat
-                                let _ = self.help();
+                                let pretty_print = match self.args.pretty_print {
+                                    PrettyPrintMode::Always => true,
+                                    PrettyPrintMode::Never => false,
+                                    PrettyPrintMode::Auto => true,
+                                };
+                                let help = help_markup(pretty_print);
+                                print!("{}", ansi_format(&help, true));
                             }
                             _ => {
                                 let result = self.parse_and_evaluate(

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

@@ -13,6 +13,7 @@ use numbat::pretty_print::PrettyPrint;
 use numbat::resolver::CodeSource;
 use numbat::{markup as m, NameResolutionError, NumbatError, Type};
 use numbat::{Context, InterpreterResult, InterpreterSettings};
+use numbat::help::help_markup();
 
 use crate::jquery_terminal_formatter::JqueryTerminalWriter;
 
@@ -156,6 +157,12 @@ impl Numbat {
         fmt.format(&markup, false).into()
     }
 
+    pub fn help(&self) -> JsValue {
+        let markup = help_markup();
+        let fmt = JqueryTerminalFormatter {};
+        fmt.format(&markup, true).into()
+    }
+
     pub fn get_completions_for(&self, input: &str) -> Vec<JsValue> {
         self.ctx
             .get_completions_for(input)

+ 2 - 0
numbat-wasm/www/index.js

@@ -53,6 +53,8 @@ function interpret(input) {
     this.clear();
   } else if (input_trimmed == "list" || input_trimmed == "ll" || input_trimmed == "ls") {
     output = numbat.print_environment();
+  } else if (input_trimmed == "help" || input_trimmed == "?") {
+    output = numbat.help();
   } else {
     var result = numbat.interpret(input);
     output = result.output;

+ 1 - 1
numbat/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "numbat"
-description = " A statically typed programming language for scientific computations with first class support for physical dimensions and units"
+description = "A statically typed programming language for scientific computations with first class support for physical dimensions and units."
 authors = ["David Peter <[email protected]>"]
 categories = ["science", "mathematics", "compilers"]
 keywords = ["language", "compiler", "physics", "units", "calculation"]

+ 151 - 0
numbat/src/help.rs

@@ -0,0 +1,151 @@
+/**
+ * Print a help, linking the documentation and live-running some examples
+ * in an isolated context
+ */
+use crate::markup as m;
+use crate::module_importer::BuiltinModuleImporter;
+use crate::pretty_print::PrettyPrint;
+use crate::resolver::CodeSource;
+use crate::{Context, InterpreterResult, NumbatError};
+use crate::{InterpreterSettings, NameResolutionError, Type};
+
+use std::sync::{Arc, Mutex};
+
+/**
+ * evaluate an example code in the input context
+ *
+ * Current limitations
+ * - We need the interpreted statement to get the pretty-printed statement,
+ *   but that only comes after interpretation. Thus we cannot show an example
+ *   where the statement itself prints (i.e. the `print` function) because it
+ *   will be printed _before_ the pretty-printed statement.
+ */
+fn evaluate_example(
+    context: &mut Context,
+    input: &str,
+    to_be_printed: Arc<Mutex<Vec<m::Markup>>>,
+    pretty_print: bool,
+) -> () {
+    let to_be_printed_c = to_be_printed.clone();
+    let mut settings = InterpreterSettings {
+        print_fn: Box::new(move |s: &m::Markup| {
+            to_be_printed_c
+                .lock()
+                .unwrap()
+                .push(m::nl() + m::whitespace("  ") + s.clone() + m::nl());
+        }),
+    };
+
+    let (result, registry) = {
+        let registry = context.dimension_registry().clone(); // TODO: get rid of this clone
+        (
+            context.interpret_with_settings(&mut settings, input, CodeSource::Internal),
+            registry,
+        )
+    };
+
+    match result {
+        Ok((statements, interpreter_result)) => {
+            if pretty_print {
+                let statements_markup =
+                    statements
+                        .iter()
+                        .fold(m::empty(), |accumulated_mk, statement| {
+                            accumulated_mk
+                                + m::nl()
+                                + m::whitespace("  ")
+                                + statement.pretty_print()
+                                + m::nl()
+                        });
+                to_be_printed.lock().unwrap().push(statements_markup);
+            }
+
+            match interpreter_result {
+                InterpreterResult::Value(value) => {
+                    let type_ = statements.last().map_or(m::empty(), |s| {
+                        if let crate::Statement::Expression(e) = s {
+                            let type_ = e.get_type();
+
+                            if type_ == Type::scalar() {
+                                m::empty()
+                            } else {
+                                m::dimmed("    [")
+                                    + e.get_type().to_readable_type(&registry)
+                                    + m::dimmed("]")
+                            }
+                        } else {
+                            m::empty()
+                        }
+                    });
+
+                    let q_markup = m::nl()
+                        + m::whitespace("    ")
+                        + m::operator("=")
+                        + m::space()
+                        + value.pretty_print()
+                        + type_
+                        + m::nl();
+                    to_be_printed.lock().unwrap().push(q_markup);
+                }
+                InterpreterResult::Continue => (),
+                InterpreterResult::Exit(_exit_status) => {
+                    println!("Interpretation Error.");
+                }
+            }
+        }
+        Err(NumbatError::ResolverError(e)) => {
+            context.print_diagnostic(e.clone());
+        }
+        Err(NumbatError::NameResolutionError(
+            e @ (NameResolutionError::IdentifierClash { .. }
+            | NameResolutionError::ReservedIdentifier(_)),
+        )) => {
+            context.print_diagnostic(e);
+        }
+        Err(NumbatError::TypeCheckError(e)) => {
+            context.print_diagnostic(e);
+        }
+        Err(NumbatError::RuntimeError(e)) => {
+            context.print_diagnostic(e);
+        }
+    }
+}
+
+pub fn help_markup(pretty_print: bool) -> m::Markup {
+    let output = Arc::new(Mutex::new(vec![
+        m::nl()
+            + m::keyword("numbat")
+            + m::space()
+            + m::text(env!("CARGO_PKG_DESCRIPTION"))
+            + m::nl()
+            + m::text("You can start by trying one of the examples:")
+            + m::nl(),
+    ]));
+
+    let examples = vec![
+        "8 km / (1 h + 25 min)",
+        "atan2(30 cm, 1 m) -> deg",
+        "let ω = 2 π c / 660 cm",
+        "# Energy of red photons",
+        "ℏ ω -> eV",
+    ];
+    let mut example_context = Context::new(BuiltinModuleImporter::default());
+    evaluate_example(&mut example_context, "use prelude", output.clone(), false);
+    for example in examples.iter() {
+        output
+            .lock()
+            .unwrap()
+            .push(m::text(">>> ") + m::text(example) + m::nl());
+        evaluate_example(&mut example_context, example, output.clone(), pretty_print);
+        output.lock().unwrap().push(m::nl());
+    }
+    output.lock().unwrap().push(
+        m::text("Full documentation:")
+            + m::space()
+            + m::keyword("https://numbat.dev/doc/")
+            + m::nl()
+            + m::nl(),
+    );
+    let markup = output.lock().unwrap().to_vec().into_iter().sum();
+    markup
+}

+ 1 - 0
numbat/src/lib.rs

@@ -7,6 +7,7 @@ pub mod diagnostic;
 mod dimension;
 mod ffi;
 mod gamma;
+pub mod help;
 mod interpreter;
 pub mod keywords;
 pub mod markup;