Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add help (and ?) commands to numbat REPL #222

Merged
merged 16 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions book/src/cli-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ There is a set of special commands that only work in interactive mode:
|---------|--------|
| `list`, `ll`, `ls` | List all constants, units, and dimensions |
| `clear` | Clear screen |
| `help`, `?` | View short help text |
| `quit`, `exit` | Quit the session |

### Key bindings
Expand Down
1 change: 1 addition & 0 deletions book/src/web-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ There is a set of special commands that only work in the web version:
| Command | Action |
|---------|--------|
| `list`, `ll`, `ls` | List all constants, units, and dimensions |
| `help`, `?` | View short help text |
| `reset` | Reset state (clear constants, functions, units, …) |
| `clear` | Clear screen |

Expand Down
2 changes: 1 addition & 1 deletion numbat-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "numbat-cli"
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 <mail@david-peter.de>"]
categories = ["command-line-utilities", "science", "mathematics", "compilers"]
keywords = ["language", "compiler", "physics", "units", "calculation"]
Expand Down
50 changes: 22 additions & 28 deletions numbat-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ 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;
use numbat::resolver::CodeSource;
use numbat::{Context, ExitStatus, InterpreterResult, NumbatError};
use numbat::{InterpreterSettings, NameResolutionError, Type};
use numbat::{Context, InterpreterResult, NumbatError};
use numbat::{InterpreterSettings, NameResolutionError};

use anyhow::{bail, Context as AnyhowContext, Result};
use clap::{Parser, ValueEnum};
Expand All @@ -29,7 +30,13 @@ use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::{fs, thread};

type ControlFlow = std::ops::ControlFlow<numbat::ExitStatus>;
#[derive(Debug, PartialEq, Eq)]
pub enum ExitStatus {
Success,
Error,
}

type ControlFlow = std::ops::ControlFlow<ExitStatus>;

const PROMPT: &str = ">>> ";

Expand Down Expand Up @@ -284,6 +291,14 @@ impl Cli {
"quit" | "exit" => {
return Ok(());
}
"help" | "?" => {
let help = help_markup();
print!("{}", ansi_format(&help, true));
// currently, the ansi formatter adds indents
// _after_ each newline and so we need to manually
// add an extra blank line to absorb this indent
println!();
}
_ => {
let result = self.parse_and_evaluate(
&line,
Expand Down Expand Up @@ -371,36 +386,15 @@ impl Cli {
println!();
}

let result_markup = interpreter_result.to_markup(statements.last(), &registry);
print!("{}", ansi_format(&result_markup, false));

match interpreter_result {
InterpreterResult::Value(value) => {
let type_ = statements.last().map_or(m::empty(), |s| {
if let numbat::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::whitespace(" ")
+ m::operator("=")
+ m::space()
+ value.pretty_print()
+ type_;
println!("{}", ansi_format(&q_markup, false));
InterpreterResult::Value(_) => {
println!();

ControlFlow::Continue(())
}
InterpreterResult::Continue => ControlFlow::Continue(()),
InterpreterResult::Exit(exit_status) => ControlFlow::Break(exit_status),
}
}
Err(NumbatError::ResolverError(e)) => {
Expand Down
11 changes: 11 additions & 0 deletions numbat-cli/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,14 @@ fn without_prelude() {
.success()
.stdout(predicates::str::contains("5.2"));
}

#[test]
fn help_text() {
numbat()
.write_stdin("help")
.assert()
.success()
.stdout(predicates::str::contains(
"Energy of red photons: 1.87855 eV",
));
}
51 changes: 12 additions & 39 deletions numbat-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ use numbat::module_importer::BuiltinModuleImporter;
use std::sync::{Arc, Mutex};
use wasm_bindgen::prelude::*;

use jquery_terminal_formatter::{jt_format, JqueryTerminalFormatter};
use jquery_terminal_formatter::JqueryTerminalFormatter;

use numbat::help::help_markup;
use numbat::markup::Formatter;
use numbat::pretty_print::PrettyPrint;
use numbat::resolver::CodeSource;
use numbat::{markup as m, NameResolutionError, NumbatError, Type};
use numbat::{Context, InterpreterResult, InterpreterSettings};
use numbat::{markup as m, NameResolutionError, NumbatError};
use numbat::{Context, InterpreterSettings};

use crate::jquery_terminal_formatter::JqueryTerminalWriter;

Expand Down Expand Up @@ -98,42 +99,8 @@ impl Numbat {
output.push_str("\n");
}

match result {
InterpreterResult::Value(value) => {
if !to_be_printed.is_empty() {
output.push_str("\n");
}

// TODO: the following statement is copied from numbat-cli. Move this to the numbat crate
// to avoid duplication.
let type_ = statements.last().map_or(m::empty(), |s| {
if let numbat::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 markup = m::whitespace(" ")
+ m::operator("=")
+ m::space()
+ value.pretty_print()
+ type_;
output.push_str(&fmt.format(&markup, true));
}
InterpreterResult::Continue => {}
InterpreterResult::Exit(_) => {
output.push_str(&jt_format(Some("error"), "Error!".into()))
}
}
let result_markup = result.to_markup(statements.last(), &registry);
output.push_str(&fmt.format(&result_markup, true));

InterpreterOutput {
output,
Expand All @@ -156,6 +123,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)
Expand Down
2 changes: 2 additions & 0 deletions numbat-wasm/www/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion numbat/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <mail@david-peter.de>"]
categories = ["science", "mathematics", "compilers"]
keywords = ["language", "compiler", "physics", "units", "calculation"]
Expand Down
3 changes: 1 addition & 2 deletions numbat/examples/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ fn main() {
let mut importer = FileSystemImporter::default();
importer.add_path("numbat/modules");
let mut ctx = Context::new(importer);
let result = ctx.interpret("use all", CodeSource::Internal).unwrap();
assert!(result.1.is_success());
let _result = ctx.interpret("use all", CodeSource::Internal).unwrap();

println!("| Dimension | Unit name | Identifier(s) |");
println!("| --- | --- | --- |");
Expand Down
3 changes: 1 addition & 2 deletions numbat/examples/unit_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ fn main() {
let mut importer = FileSystemImporter::default();
importer.add_path("numbat/modules");
let mut ctx = Context::new(importer);
let result = ctx.interpret("use all", CodeSource::Internal).unwrap();
assert!(result.1.is_success());
let _ = ctx.interpret("use all", CodeSource::Internal).unwrap();

println!("digraph G {{");
println!(" layout=fdp;");
Expand Down
64 changes: 64 additions & 0 deletions numbat/src/help.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/// 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::resolver::CodeSource;
use crate::Context;
use crate::InterpreterSettings;

use std::sync::{Arc, Mutex};

fn evaluate_example(context: &mut Context, input: &str) -> m::Markup {
let statement_output: Arc<Mutex<Vec<m::Markup>>> = Arc::new(Mutex::new(vec![]));
let statement_output_c = statement_output.clone();
let mut settings = InterpreterSettings {
print_fn: Box::new(move |s: &m::Markup| {
statement_output_c.lock().unwrap().push(s.clone());
}),
};

let (statements, interpreter_result) = context
.interpret_with_settings(&mut settings, input, CodeSource::Internal)
.expect("No error in 'help' examples");

let markup =
statement_output
.lock()
.unwrap()
.iter()
.fold(m::empty(), |accumulated_mk, single_line| {
accumulated_mk + m::nl() + m::whitespace(" ") + single_line.clone() + m::nl()
})
+ interpreter_result.to_markup(statements.last(), context.dimension_registry());

markup
}

pub fn help_markup() -> m::Markup {
let mut output = m::nl()
+ m::text("Numbat is a statically typed programming language for scientific computations")
+ m::nl()
+ m::text("with first class support for physical dimensions and units. Please refer to")
+ m::nl()
+ m::text("the full documentation online at ")
+ m::string("https://numbat.dev/doc/")
+ m::text(" or try one of these ")
+ m::nl()
+ m::text("examples:")
+ m::nl()
+ m::nl();

let examples = [
"8 km / (1 h + 25 min)",
"atan2(30 cm, 1 m) -> deg",
"let ω = 2 π c / 660 nm",
r#"print("Energy of red photons: {ℏ ω -> eV}")"#,
];
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 += evaluate_example(&mut example_context, example) + m::nl();
}
output
}
47 changes: 35 additions & 12 deletions numbat/src/interpreter.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use crate::{
dimension::DimensionRegistry,
markup::Markup,
pretty_print::PrettyPrint,
quantity::{Quantity, QuantityError},
typed_ast::Statement,
typed_ast::{Statement, Type},
unit_registry::{UnitRegistry, UnitRegistryError},
};

use crate::markup as m;

use thiserror::Error;

pub use crate::value::Value;
Expand Down Expand Up @@ -35,26 +39,45 @@ pub enum RuntimeError {
UserError(String),
}

#[derive(Debug, PartialEq, Eq)]
pub enum ExitStatus {
Success,
Error,
}

#[derive(Debug, PartialEq, Eq)]
#[must_use]
pub enum InterpreterResult {
Value(Value),
Continue,
Exit(ExitStatus),
}

impl InterpreterResult {
pub fn is_success(&self) -> bool {
pub fn to_markup(
&self,
evaluated_statement: Option<&Statement>,
registry: &DimensionRegistry,
) -> Markup {
match self {
Self::Value(_) => true,
Self::Continue => true,
Self::Exit(_) => false,
Self::Value(value) => {
let type_markup = if let Some(s) = evaluated_statement {
if let 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()
}
} else {
m::empty()
};
m::whitespace(" ")
+ m::operator("=")
+ m::space()
+ value.pretty_print()
+ type_markup
+ m::nl()
}
Self::Continue => m::empty(),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion numbat/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod diagnostic;
mod dimension;
mod ffi;
mod gamma;
pub mod help;
mod interpreter;
pub mod keywords;
pub mod markup;
Expand Down Expand Up @@ -51,7 +52,6 @@ use thiserror::Error;
use typechecker::{TypeCheckError, TypeChecker};

pub use diagnostic::Diagnostic;
pub use interpreter::ExitStatus;
pub use interpreter::InterpreterResult;
pub use interpreter::InterpreterSettings;
pub use interpreter::RuntimeError;
Expand Down
Loading