diff --git a/.gitignore b/.gitignore
index ee19741..5cdd539 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
/target
/src/target
-/pkg
-*.pkg.*
\ No newline at end of file
+/pkg
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 62b4b82..9cf2ae1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -14,6 +14,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+[[package]]
+name = "bumpalo"
+version = "3.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -22,12 +28,13 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "daisycalc"
-version = "1.1.0"
+version = "1.1.1"
dependencies = [
"cfg-if",
"num",
"termion",
"toml",
+ "wasm-bindgen",
]
[[package]]
@@ -52,6 +59,12 @@ version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
[[package]]
name = "memchr"
version = "2.5.0"
@@ -140,6 +153,30 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+dependencies = [
+ "proc-macro2",
+]
+
[[package]]
name = "redox_syscall"
version = "0.2.16"
@@ -173,6 +210,17 @@ dependencies = [
"serde",
]
+[[package]]
+name = "syn"
+version = "2.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
[[package]]
name = "termion"
version = "2.0.1"
@@ -219,6 +267,66 @@ dependencies = [
"winnow",
]
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.87"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.87"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.87"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.87"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.87"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
+
[[package]]
name = "winnow"
version = "0.4.6"
diff --git a/Cargo.toml b/Cargo.toml
index 327e09e..ab23e32 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "daisycalc"
-version = "1.1.0"
+version = "1.1.1"
edition = "2021"
build = "buildscript/main.rs"
license = "GPL-3.0-only"
@@ -13,6 +13,11 @@ readme = "README.md"
name = "daisy"
path = "src/main.rs"
+[lib]
+name = "daisycalc"
+path = "src/lib.rs"
+crate-type = ["cdylib", "rlib"]
+
[profile.release]
opt-level = 3
debug = 0
@@ -25,11 +30,18 @@ panic = "abort"
[dependencies]
cfg-if = "1.0.0"
-
-[target.'cfg(target_family = "unix")'.dependencies]
-termion = "2.0.1"
num = "0.4.1"
#astro-float = "0.7.1"
+[package.metadata.wasm-pack.profile.release]
+wasm-opt = true
+
+[target.'cfg(target_family = "unix")'.dependencies]
+termion = "2.0.1"
+
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+wasm-bindgen = "0.2"
+
+
[build-dependencies]
toml = "0.7.4"
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..1aacafc
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,14 @@
+FROM ubuntu AS node
+RUN apt-get update
+RUN apt-get install cargo npm -y
+COPY ./site ./site
+RUN cd /site && npm install
+
+FROM ubuntu
+RUN apt-get update
+RUN apt-get install nginx -y
+COPY --from=node /site /var/www/html
+COPY ./pkg /var/www/html/pkg
+COPY default /etc/nginx/sites-enabled/default
+EXPOSE 80
+CMD ["nginx", "-g", "daemon off;"]
\ No newline at end of file
diff --git a/README.md b/README.md
index fe53747..9d7c662 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,11 @@
-![](./misc/banner.png)
+![](./site/misc/readme-banner.png)
A high-precision scientific calculator with support for units, derivatives, and more.
Many features are missing, this is still under development.
+**Web demo: [here](daisy.betalupi.com)**
+
# 📦 Installation
- **Cargo:** `cargo install daisycalc`
- **Arch:** `yay -S daisy`
diff --git a/TODO.md b/TODO.md
index 8a7ebba..ef9106d 100644
--- a/TODO.md
+++ b/TODO.md
@@ -8,6 +8,7 @@
- git tag -a v1.0.0 -m "Version 1.0.0" on merge commit
- cargo publish
- Update packages
+ - Build wasm & push changes
## Pre-release
@@ -24,7 +25,6 @@
- Better tests (assignment, many expressions in one context)
- Optional config file
- Optional history file
- - Compile to WASM, publish a webapp
- evaluate straight from command line
- Package for debian, nix
diff --git a/default b/default
new file mode 100644
index 0000000..ec6d36d
--- /dev/null
+++ b/default
@@ -0,0 +1,31 @@
+types {
+ application/wasm wasm;
+ application/x-font-ttf ttc;
+ application/x-font-otf otf;
+ application/font-woff2 woff2;
+ font/ttf ttf;
+}
+
+
+server {
+ listen 80 default_server;
+ listen [::]:80 default_server;
+
+ root /var/www/html;
+
+ # Add index.php to the list if you are using PHP
+ index index.html index.htm index.nginx-debian.html;
+
+ server_name _;
+
+ location / {
+ # First attempt to serve request as file, then
+ # as directory, then fall back to displaying a 404.
+ try_files $uri $uri/ =404;
+ }
+
+ location ~* .(js|css|ttf|ttc|otf|eot|woff|woff2)$ {
+ add_header access-control-allow-origin "*";
+ expires max;
+ }
+}
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..e52a66d
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,18 @@
+version: "3"
+
+# You'll need to edit this file if you want to use it.
+networks:
+ reverse_proxy:
+ external: true
+
+services:
+ daisy:
+ build: ./.
+ container_name: daisy
+ restart: unless-stopped
+
+ networks:
+ - reverse_proxy
+
+ #ports:
+ # - "80:80"
\ No newline at end of file
diff --git a/misc/daisy.svg b/misc/daisy.svg
deleted file mode 100644
index 4fb6cc0..0000000
--- a/misc/daisy.svg
+++ /dev/null
@@ -1,240 +0,0 @@
-
-
-
-
diff --git a/site/.gitignore b/site/.gitignore
new file mode 100644
index 0000000..6bd0e57
--- /dev/null
+++ b/site/.gitignore
@@ -0,0 +1,3 @@
+/node_modules
+/package-lock.json
+/pkg
\ No newline at end of file
diff --git a/site/index.html b/site/index.html
new file mode 100644
index 0000000..e10b735
--- /dev/null
+++ b/site/index.html
@@ -0,0 +1,152 @@
+
+
+
+
+ Daisy Web
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/site/package.json b/site/package.json
new file mode 100644
index 0000000..b1c1b1d
--- /dev/null
+++ b/site/package.json
@@ -0,0 +1,5 @@
+{
+ "dependencies": {
+ "xterm": "^5.3.0"
+ }
+}
diff --git a/site/resources/Fantasque.ttf b/site/resources/Fantasque.ttf
new file mode 100755
index 0000000..6cb7375
Binary files /dev/null and b/site/resources/Fantasque.ttf differ
diff --git a/site/resources/banner.svg b/site/resources/banner.svg
new file mode 100644
index 0000000..efa19b7
--- /dev/null
+++ b/site/resources/banner.svg
@@ -0,0 +1,137 @@
+
+
+
+
diff --git a/site/resources/daisy-dark.svg b/site/resources/daisy-dark.svg
new file mode 100644
index 0000000..2eb030b
--- /dev/null
+++ b/site/resources/daisy-dark.svg
@@ -0,0 +1,137 @@
+
+
+
+
diff --git a/site/resources/daisy-icon-dark.svg b/site/resources/daisy-icon-dark.svg
new file mode 100644
index 0000000..cae6a08
--- /dev/null
+++ b/site/resources/daisy-icon-dark.svg
@@ -0,0 +1,133 @@
+
+
+
+
diff --git a/site/resources/daisy-icon-light.svg b/site/resources/daisy-icon-light.svg
new file mode 100644
index 0000000..9c73081
--- /dev/null
+++ b/site/resources/daisy-icon-light.svg
@@ -0,0 +1,133 @@
+
+
+
+
diff --git a/site/resources/daisy-light.svg b/site/resources/daisy-light.svg
new file mode 100644
index 0000000..a4610fb
--- /dev/null
+++ b/site/resources/daisy-light.svg
@@ -0,0 +1,137 @@
+
+
+
+
diff --git a/misc/banner.png b/site/resources/readme-banner.png
similarity index 100%
rename from misc/banner.png
rename to site/resources/readme-banner.png
diff --git a/src/command/mod.rs b/src/command/mod.rs
index ebc774c..39a0096 100644
--- a/src/command/mod.rs
+++ b/src/command/mod.rs
@@ -16,6 +16,7 @@ pub fn is_command(
| "vars"
| "consts" | "constants"
| "del" | "delete"
+ | "flags"
=> true,
_ => false
}
@@ -66,6 +67,7 @@ pub fn do_command(
"\n",
"╞═══════════════ [t]Commands[n] ═══════════════╡\n",
" [c]help[n] Show this help\n",
+ " [c]flags[n] Show command-line options\n",
" [c]clear[n] Clear the terminal\n",
" [c]quit[n] Exit daisy\n",
//" [c]units[n] List available units\n",
@@ -81,6 +83,27 @@ pub fn do_command(
return t;
},
+ "flags" => {
+ return FormattedText::new(
+ concat!(
+ "\n",
+ "A list of command-line arguments is below\n",
+ "\n",
+ "╞════ [t]Flag[n] ════╪════════════════ [t]Function[n] ════════════════╡\n",
+ " [c]--help[n] Show help\n",
+ " [c]--version[n] Show version\n",
+ " [c]--info[n] Show system information\n",
+ " [c]--256color[n] Use full color support (default)\n",
+ " [c]--8color[n] Use reduced colors (ANSI, no styling)\n",
+ " [c]--nocolor[n] Do not use colors and styling\n",
+ " [c]--nosub[n] Disable inline substitution\n",
+ " [c]--nosuper[n] Disable superscript powers\n",
+ " [c]--nooneover[n] Disable \"one-over\" fractions as -1 power\n",
+ "\n\n"
+ ).to_string()
+ );
+ },
+
"clear" => {
return FormattedText::new("[clear]".to_string());
},
diff --git a/src/entrypoint/mod.rs b/src/entrypoint/mod.rs
deleted file mode 100644
index 54ff10f..0000000
--- a/src/entrypoint/mod.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-// Select main script for target system
-cfg_if::cfg_if! {
- if #[cfg(target_family = "unix")] {
- mod unix;
- pub use unix::main as main_e;
- } else {
- pub fn main_e() -> Result<(), std::io::Error> {
- unimplemented!("Not yet implemented.");
- }
- }
-}
\ No newline at end of file
diff --git a/src/entrypoint/unix/mod.rs b/src/entrypoint/unix/mod.rs
deleted file mode 100644
index bca160f..0000000
--- a/src/entrypoint/unix/mod.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-mod unix;
-mod promptbuffer;
-pub use self::unix::main;
\ No newline at end of file
diff --git a/src/entrypoint/unix/unix.rs b/src/entrypoint/unix/unix.rs
deleted file mode 100644
index 104a60a..0000000
--- a/src/entrypoint/unix/unix.rs
+++ /dev/null
@@ -1,115 +0,0 @@
-use std::io::stdout;
-use std::io::stdin;
-use std::env;
-
-use termion::{
- event::Key,
- input::TermRead,
- raw::IntoRawMode,
- color::DetectColors
-};
-
-use super::promptbuffer::PromptBuffer;
-use crate::command;
-use crate::context::Context;
-use crate::FormattedText;
-
-#[inline(always)]
-pub fn main() -> Result<(), std::io::Error> {
- let mut stdout = stdout().into_raw_mode().unwrap();
- let mut pb: PromptBuffer = PromptBuffer::new(64);
- let mut context = Context::new();
-
- // Set color compatibilty
- let term_colors = stdout.available_colors().unwrap_or(0);
- if term_colors >= 256 {
- context.config.term_color_type = 2;
- } else if term_colors >= 8 {
- context.config.term_color_type = 1;
- } else {
- context.config.term_color_type = 0;
- }
-
- context.config.check();
-
-
-
- // Handle command-line arguments
- let args: Vec = env::args().collect();
- if args.iter().any(|s| s == "--help") {
- let t = command::do_command(&mut context, &String::from("help"));
- t.write(&context, &mut stdout)?;
- return Ok(());
- } else if args.iter().any(|s| s == "--version") {
- let t = FormattedText::new(format!(
- "Daisy v{}\n", env!("CARGO_PKG_VERSION")
- ));
- t.write(&context, &mut stdout)?;
- return Ok(());
- } else if args.iter().any(|s| s == "--debug") {
- let t = FormattedText::new(format!(
- concat!(
- "Daisy v{}\n",
- "Your terminal supports {} colors.\n"
- ),
- env!("CARGO_PKG_VERSION"),
- term_colors
- ));
- t.write(&context, &mut stdout)?;
- return Ok(());
- }
-
- 'outer: loop {
-
- pb.write_prompt(&mut context, &mut stdout)?;
-
- let stdin = stdin();
- for c in stdin.keys() {
- if let Key::Char(q) = c.as_ref().unwrap() {
- match q {
- '\n' => {
- // Print again without cursor, in case we pressed enter
- // while inside a substitution
- pb.write_prompt_nocursor(&mut context, &mut stdout)?;
- let in_str = pb.enter();
- FormattedText::newline(&mut stdout)?;
- if in_str == "" { break; }
-
- if in_str.trim() == "quit" {
- break 'outer;
- } else {
- let r = crate::do_string(&mut context, &in_str);
-
- match r {
- Ok(t) | Err(t) => {
- t.write(&context, &mut stdout).unwrap();
- }
- }
- }
-
- break;
- },
- _ => { pb.add_char(*q); }
- };
- } else {
- match c.unwrap() {
- Key::Backspace => { pb.backspace(); },
- Key::Delete => { pb.delete(); },
- Key::Left => { pb.cursor_left(); },
- Key::Right => { pb.cursor_right(); },
- Key::Up => { pb.hist_up(); },
- Key::Down => { pb.hist_down(); },
-
- Key::Ctrl('d') |
- Key::Ctrl('c') => { break 'outer; },
- _ => {}
- };
- };
-
- pb.write_prompt(&mut context, &mut stdout)?;
- }
- }
-
- FormattedText::newline(&mut stdout)?;
- return Ok(());
-}
\ No newline at end of file
diff --git a/src/formattedtext.rs b/src/formattedtext.rs
deleted file mode 100644
index dfcfc55..0000000
--- a/src/formattedtext.rs
+++ /dev/null
@@ -1,199 +0,0 @@
-use std::io::Write;
-use termion::raw::RawTerminal;
-use termion::color;
-use termion::style;
-use termion::clear;
-use termion::cursor;
-use std::ops::Add;
-use crate::context::Context;
-
-
-#[derive(Debug)]
-#[derive(Clone)]
-pub struct FormattedText {
- text: String
-}
-
-impl ToString for FormattedText {
- fn to_string(&self) -> String { return self.text.clone(); }
-}
-
-
-fn format_map_none(c: char) -> Option {
- Some(match c {
- 'n'|'i'|'t'|'a'|
- 'e'|'c'|'s'|'r'|
- 'p'
- => { "".to_string() },
- _ => { return None }
- })
-}
-
-
-fn format_map_ansi(c: char) -> Option {
- Some(match c {
- 'n' => { // Normal text
- format!("{}{}", color::Fg(color::Reset), color::Bg(color::Reset))
- },
- 'i' => { // Normal italic text
- format!("{}{}", color::Fg(color::Reset), color::Bg(color::Reset))
- },
- 't' => { // Title text (should be cyan)
- format!("{}{}", color::Fg(color::AnsiValue(6)), color::Bg(color::Reset))
- },
- 'a' => { // Colored text (should be pink)
- format!("{}{}", color::Fg(color::AnsiValue(5)), color::Bg(color::Reset))
- },
- 'e' => { // Error titles (should be red)
- format!("{}{}", color::Fg(color::AnsiValue(1)), color::Bg(color::Reset))
- },
- 'c' => { // Console text (inverted black on white)
- format!("{}{}", color::Fg(color::AnsiValue(0)), color::Bg(color::AnsiValue(7)))
- },
- 'p' => { // Input prompt (how ==> is styled) (should be blue)
- format!("{}{}", color::Fg(color::AnsiValue(4)), color::Bg(color::Reset))
- },
- 's' => { // Repeat prompt (how => is styled) (should be pink)
- format!("{}{}", color::Fg(color::AnsiValue(5)), color::Bg(color::Reset))
- },
- 'r' => { // Result prompt (how = is styled) (should be green)
- format!("{}{}", color::Fg(color::AnsiValue(2)), color::Bg(color::Reset))
- },
-
- _ => { return None }
- })
-}
-
-
-// style::reset also resets color.
-// Make sure color comes AFTER style reset.
-fn format_map_full(c: char) -> Option {
- Some(match c {
- 'n' => { // Normal text
- format!("{}{}", style::Reset, color::Fg(color::Reset))
- },
- 'i' => { // Normal italic text
- format!("{}{}", color::Fg(color::Reset), style::Italic)
- },
- 't' => { // Title text
- format!("{}{}", color::Fg(color::Magenta), style::Bold)
- },
- 'a' => { // Colored text
- format!("{}{}", style::Reset, color::Fg(color::Magenta))
- },
- 'e' => { // Error titles
- format!("{}{}", color::Fg(color::Red), style::Bold)
- },
- 'c' => { // Console text
- format!("{}{}", color::Fg(color::LightBlack), style::Italic)
- },
- 'p' => { // Input prompt (how ==> is styled)
- format!("{}{}", color::Fg(color::Blue), style::Bold)
- },
- 's' => { // Repeat prompt (how => is styled)
- format!("{}{}", color::Fg(color::Magenta), style::Bold)
- },
- 'r' => { // Result prompt (how = is styled)
- format!("{}{}", color::Fg(color::Green), style::Bold)
- },
-
-
- _ => { return None }
- })
-}
-
-pub fn format_map(c: char, context: &Context) -> Option {
- match context.config.term_color_type {
- 0 => format_map_none(c),
- 1 => format_map_ansi(c),
- 2 => format_map_full(c),
- _ => unreachable!("Invalid term_color_type")
- }
-}
-
-
-impl FormattedText {
- pub fn newline(stdout: &mut RawTerminal) -> Result<(), std::io::Error> {
- write!(stdout, "\n")?;
- return Ok(());
- }
-}
-
-
-impl FormattedText {
- pub fn new(s: String) -> FormattedText {
- return FormattedText {
- text: s
- }
- }
-
- pub fn push(&mut self, s: &str) {
- self.text.push_str(s);
- }
-
-
- pub fn write(&self, context: &Context, stdout: &mut RawTerminal) -> Result<(), std::io::Error> {
-
- if self.text == "[clear]" {
- write!(
- stdout,
- "{}{}",
- clear::All,
- cursor::Goto(1, 1)
- )?;
- return Ok(());
- }
-
-
- let mut s = String::new();
- let mut chars = self.text.chars();
-
- while let Some(c) = chars.next() {
- match c {
- '[' => {
- let a = chars.next().unwrap();
-
- // Handle double [[ as escaped [
- if a == '[' { s.push('['); }
-
- let b = chars.next().unwrap();
-
- match (a, b) {
- (c, ']') => { // Normal text
-
- let q = format_map(c, context);
-
- if q.is_some() {
- s.push_str(&q.unwrap());
- } else {
- s.push('[');
- s.push(a);
- s.push(b);
- }
- },
-
- _ => {
- s.push('[');
- s.push(a);
- s.push(b);
- }
- }
- },
- '\n' => { s.push_str("\r\n") },
- _ => s.push(c)
- }
- }
-
- write!(stdout, "\r{}", s)?;
- return Ok(());
- }
-}
-
-
-impl Add for FormattedText {
- type Output = Self;
-
- fn add(self, other: Self) -> Self::Output {
- return FormattedText::new(format!("{}{}", self.text, other.text));
- }
-}
\ No newline at end of file
diff --git a/src/formattedtext/formattedtext.rs b/src/formattedtext/formattedtext.rs
new file mode 100644
index 0000000..53188f3
--- /dev/null
+++ b/src/formattedtext/formattedtext.rs
@@ -0,0 +1,40 @@
+use std::ops::Add;
+use std::ops::AddAssign;
+
+
+#[derive(Debug)]
+#[derive(Clone)]
+pub struct FormattedText {
+ pub(super) text: String
+}
+
+impl ToString for FormattedText {
+ fn to_string(&self) -> String { return self.text.clone(); }
+}
+
+impl FormattedText {
+ pub fn new(s: String) -> FormattedText {
+ return FormattedText {
+ text: s
+ }
+ }
+
+ pub fn push(&mut self, s: &str) {
+ self.text.push_str(s);
+ }
+}
+
+
+impl Add for FormattedText {
+ type Output = Self;
+
+ fn add(self, other: Self) -> Self::Output {
+ return FormattedText::new(format!("{}{}", self.text, other.text));
+ }
+}
+
+impl AddAssign for FormattedText where {
+ fn add_assign(&mut self, other: Self) {
+ self.text.push_str(&other.text);
+ }
+}
\ No newline at end of file
diff --git a/src/formattedtext/mod.rs b/src/formattedtext/mod.rs
new file mode 100644
index 0000000..d30b4f7
--- /dev/null
+++ b/src/formattedtext/mod.rs
@@ -0,0 +1,12 @@
+mod formattedtext;
+pub use formattedtext::FormattedText;
+
+
+// Select write implementation by target system
+cfg_if::cfg_if! {
+ if #[cfg(target_family = "unix")] {
+ mod unix_backend;
+ } else if #[cfg(target_arch = "wasm32")] {
+ mod wasm_backend;
+ }
+}
\ No newline at end of file
diff --git a/src/formattedtext/unix_backend.rs b/src/formattedtext/unix_backend.rs
new file mode 100644
index 0000000..789e14a
--- /dev/null
+++ b/src/formattedtext/unix_backend.rs
@@ -0,0 +1,179 @@
+use super::FormattedText;
+use std::io::Write;
+use crate::context::Context;
+
+use termion::raw::RawTerminal;
+use termion::color;
+use termion::style;
+use termion::clear;
+use termion::cursor;
+
+fn format_map_ansi(s: &str) -> Option {
+ Some(match s {
+ "n" => { // Normal text
+ format!("{}{}", color::Fg(color::Reset), color::Bg(color::Reset))
+ },
+ "i" => { // Normal italic text
+ format!("{}{}", color::Fg(color::Reset), color::Bg(color::Reset))
+ },
+ "t" => { // Title text (should be cyan)
+ format!("{}{}", color::Fg(color::AnsiValue(6)), color::Bg(color::Reset))
+ },
+ "a" => { // Colored text (should be pink)
+ format!("{}{}", color::Fg(color::AnsiValue(5)), color::Bg(color::Reset))
+ },
+ "e" => { // Error titles (should be red)
+ format!("{}{}", color::Fg(color::AnsiValue(1)), color::Bg(color::Reset))
+ },
+ "c" => { // Console text (inverted black on white)
+ format!("{}{}", color::Fg(color::AnsiValue(0)), color::Bg(color::AnsiValue(7)))
+ },
+ "p" => { // Input prompt (how ==> is styled) (should be blue)
+ format!("{}{}", color::Fg(color::AnsiValue(4)), color::Bg(color::Reset))
+ },
+ "s" => { // Repeat prompt (how => is styled) (should be pink)
+ format!("{}{}", color::Fg(color::AnsiValue(5)), color::Bg(color::Reset))
+ },
+ "r" => { // Result prompt (how = is styled) (should be green)
+ format!("{}{}", color::Fg(color::AnsiValue(2)), color::Bg(color::Reset))
+ },
+
+ _ => { return None }
+ })
+}
+
+
+
+fn format_map_none(s: &str) -> Option {
+ Some(match s {
+ "n"|"i"|"t"|"a"|
+ "e"|"c"|"s"|"r"|
+ "p"
+ => { "".to_string() },
+ _ => { return None }
+ })
+}
+
+// style::reset also resets color.
+// Make sure color comes AFTER style reset.
+fn format_map_full(s: &str) -> Option {
+ Some(match s {
+ "n" => { // Normal text
+ format!("{}{}", style::Reset, color::Fg(color::Reset))
+ },
+ "i" => { // Normal italic text
+ format!("{}{}", color::Fg(color::Reset), style::Italic)
+ },
+ "t" => { // Title text
+ format!("{}{}", color::Fg(color::Magenta), style::Bold)
+ },
+ "a" => { // Colored text
+ format!("{}{}", style::Reset, color::Fg(color::Magenta))
+ },
+ "e" => { // Error titles
+ format!("{}{}", color::Fg(color::Red), style::Bold)
+ },
+ "c" => { // Console text
+ format!("{}{}", color::Fg(color::LightBlack), style::Italic)
+ },
+ "p" => { // Input prompt (how ==> is styled)
+ format!("{}{}", color::Fg(color::Blue), style::Bold)
+ },
+ "s" => { // Repeat prompt (how => is styled)
+ format!("{}{}", color::Fg(color::Magenta), style::Bold)
+ },
+ "r" => { // Result prompt (how = is styled)
+ format!("{}{}", color::Fg(color::Green), style::Bold)
+ },
+
+
+ _ => { return None }
+ })
+}
+
+impl FormattedText {
+ pub fn newline(stdout: &mut RawTerminal) -> Result<(), std::io::Error> {
+ write!(stdout, "\n")?;
+ return Ok(());
+ }
+
+ pub fn format_map(s: &str, context: &Context) -> Option {
+ match context.config.term_color_type {
+ 0 => format_map_none(s),
+ 1 => format_map_ansi(s),
+ 2 => format_map_full(s),
+ _ => unreachable!("Invalid term_color_type")
+ }
+ }
+
+ pub fn write(&self, context: &Context, stdout: &mut RawTerminal) -> Result<(), std::io::Error> {
+
+ let mut word = String::new();
+ let mut reading = false; // are we reading a word?
+ let mut chars = self.text.chars();
+ let mut out = String::new();
+
+ while let Some(c) = chars.next() {
+
+ match c {
+ '[' => {
+ if reading {
+ // Discard old word, start reading again.
+ out.push_str(&word);
+ word.clear();
+ }
+
+ // Start reading a new word
+ reading = true;
+ word.push(c);
+ },
+
+ ']' => {
+ if !reading {
+ out.push(c);
+ } else {
+ word.push(c);
+
+
+ let f = Self::format_map(&word[1..word.len()-1], context);
+
+ if f.is_some() {
+ out.push_str(&f.unwrap());
+ } else if word == "[clear]" {
+ out.push_str(&format!(
+ "{}{}",
+ clear::All,
+ cursor::Goto(1, 1)
+ ));
+ } else if word.starts_with("[cursorright") {
+ let n: u16 = word[12..word.len()-1].parse().unwrap();
+ out.push_str(&format!(
+ "{}",
+ cursor::Right(n),
+ ));
+ } else {
+ out.push_str(&word);
+ }
+
+ reading = false;
+ word.clear();
+ }
+ },
+
+ '\n' => {
+ if reading { word.push_str("\r\n"); }
+ else { out.push_str("\r\n"); }
+ },
+
+ _ => {
+ if reading { word.push(c); }
+ else { out.push(c); }
+ }
+ }
+ }
+
+ write!(stdout, "\r{}", out)?;
+ stdout.flush()?;
+ return Ok(());
+ }
+}
\ No newline at end of file
diff --git a/src/formattedtext/wasm_backend.rs b/src/formattedtext/wasm_backend.rs
new file mode 100644
index 0000000..61ac800
--- /dev/null
+++ b/src/formattedtext/wasm_backend.rs
@@ -0,0 +1,80 @@
+use super::FormattedText;
+
+fn format_map(s: &str) -> Option {
+ Some(match s {
+ "n" => {"\x1B[0m"},
+ "i" => {"\x1B[3m"},
+ "t" => {"\x1B[1;35m"},
+ "a" => {"\x1B[0;35m"},
+ "e" => {"\x1B[1;31m"},
+ "c" => {"\x1B[3;90m"},
+ "p" => {"\x1B[1;34m"},
+ "s" => {"\x1B[1;35m"},
+ "r" => {"\x1B[1;32m"},
+ _ => { return None }
+ }.to_string())
+}
+
+
+impl FormattedText {
+ pub fn write(&self) -> String {
+
+ let mut word = String::new();
+ let mut reading = false; // are we reading a word?
+ let mut chars = self.text.chars();
+ let mut out = String::new();
+
+ while let Some(c) = chars.next() {
+
+ match c {
+ '[' => {
+ if reading {
+ // Discard old word, start reading again.
+ out.push_str(&word);
+ word.clear();
+ }
+
+ // Start reading a new word
+ reading = true;
+ word.push(c);
+ },
+
+ ']' => {
+ if !reading {
+ out.push(c);
+ } else {
+ word.push(c);
+
+ let f = format_map(&word[1..word.len()-1]);
+
+ if f.is_some() {
+ out.push_str(&f.unwrap());
+ } else if word == "[clear]" {
+ out.push_str(&format!("\x1B[2J\x1B[H"));
+ } else if word.starts_with("[cursorright") {
+ let n: u16 = word[12..word.len()-1].parse().unwrap();
+ out.push_str(&format!("\x1B[{n}C"));
+ } else {
+ out.push_str(&word);
+ }
+
+ reading = false;
+ word.clear();
+ }
+ },
+
+ '\n' => {
+ if reading { word.push_str("\r\n"); }
+ else { out.push_str("\r\n"); }
+ },
+
+ _ => {
+ if reading { word.push(c); }
+ else { out.push(c); }
+ }
+ }
+ }
+
+ return out;
+ }
+}
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..ceed6cc
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,365 @@
+pub mod parser;
+pub mod command;
+pub mod quantity;
+
+use crate::parser::substitute;
+use crate::parser::LineLocation;
+
+
+mod context;
+mod formattedtext;
+mod errors;
+mod evaluate;
+mod promptbuffer;
+
+pub use crate::formattedtext::FormattedText;
+pub use crate::context::Context;
+pub use crate::errors::DaisyError;
+pub use crate::evaluate::evaluate;
+pub use crate::promptbuffer::PromptBuffer;
+
+
+
+cfg_if::cfg_if! {
+ if #[cfg(target_arch = "wasm32")] {
+ use wasm_bindgen::prelude::*;
+
+ #[derive(Debug)]
+ pub struct State {
+ pub context: Context,
+ pub promptbuffer: PromptBuffer
+ }
+
+ #[wasm_bindgen]
+ pub extern fn daisy_init() -> *mut State {
+ Box::into_raw(Box::new(State {
+ context: Context::new(),
+ promptbuffer: PromptBuffer::new(64)
+ }))
+ }
+
+ #[wasm_bindgen]
+ pub extern fn daisy_free(state: *mut State) {
+ unsafe { drop(Box::from_raw(state)) };
+ }
+
+ #[wasm_bindgen]
+ pub fn daisy_prompt(state: *mut State) -> String {
+ let t = unsafe { (*state).promptbuffer.write_prompt(&mut (*state).context) };
+ return t.write();
+ }
+
+
+ #[wasm_bindgen]
+ pub fn daisy_char(state: *mut State, s: String) -> String {
+ let mut out = FormattedText::new("".to_string());
+
+ match &s[..] {
+ "\r" => {
+ // Print again without cursor, in case we pressed enter
+ // while inside a substitution
+ let t = unsafe { (*state).promptbuffer.write_prompt_nocursor(&mut (*state).context) };
+ out += t;
+
+
+ let in_str = unsafe { (*state).promptbuffer.enter() };
+ out += FormattedText::new("\n".to_string());
+ if in_str == "" {
+ return format!("\r\n{}", daisy_prompt(state));
+ }
+
+ if in_str.trim() == "quit" {
+ return "[quit]".to_string();
+ } else {
+ let r = crate::do_string( unsafe { &mut (*state).context }, &in_str);
+
+ match r {
+ Ok(t) | Err(t) => {
+ out += t;
+ }
+ }
+ }
+ },
+
+ "\x7F" => { unsafe { (*state).promptbuffer.backspace(); } },
+ "\x1B[3~" => { unsafe { (*state).promptbuffer.delete(); } },
+ "\x1B[D" => { unsafe { (*state).promptbuffer.cursor_left(); } },
+ "\x1B[C" => { unsafe { (*state).promptbuffer.cursor_right(); } },
+ "\x1B[A" => { unsafe { (*state).promptbuffer.hist_up(); } },
+ "\x1B[B" => { unsafe { (*state).promptbuffer.hist_down(); } },
+
+ //'\x04' | '\x03'
+ //=> { break 'outer; },
+
+ _ => { unsafe { (*state).promptbuffer.add_char(s.chars().next().unwrap()); } },
+ };
+
+ let t = unsafe { (*state).promptbuffer.write_prompt(&mut (*state).context) };
+ return (out + t).write();
+ }
+ }
+}
+
+#[inline(always)]
+pub fn do_string(
+ context: &mut Context,
+ s: &String
+) -> Result {
+
+ let r: (LineLocation, DaisyError);
+ if command::is_command(s) {
+ return Ok(command::do_command(context, s));
+ } else if s.contains("=") {
+ let x = do_assignment(context, s);
+ match x {
+ Ok(t) => { return Ok(t) },
+ Err(t) => { r = t }
+ };
+ } else {
+ let x = do_expression(context, s);
+ match x {
+ Ok((t, e)) => { context.push_hist(e); return Ok(t) },
+ Err(t) => { r = t }
+ };
+ }
+
+ let (l, e) = r;
+ let mut t = FormattedText::new("".to_string());
+ if l.zero() {
+ t.push(&format!(
+ "\n {}\n\n",
+ e.text().to_string(),
+ ));
+ } else {
+ t.push(&format!(
+ concat!(
+ "{}[e]{}[n]\n",
+ " {}\n\n"
+ ),
+ " ".repeat(l.pos + 4),
+ "^".repeat(l.len),
+ e.text().to_string(),
+ ));
+ }
+
+ return Err(t);
+}
+
+// Handle a simple evaluation string.
+// Returns a FormattedText with output that should be printed.
+#[inline(always)]
+fn do_expression(
+ context: &mut Context,
+ s: &String
+) -> Result<(FormattedText, parser::Expression), (LineLocation, DaisyError)> {
+
+ let mut output = FormattedText::new("".to_string());
+
+ let g = parser::parse(context, &s)?;
+ let g_evaluated = evaluate::evaluate(context, &g)?;
+
+ // Display parsed string
+ output.push(&format!(
+ " [s]=>[n] {}\n\n",
+ g.display(context)
+ ));
+
+ // Display result
+ output.push(&format!(
+ " [r]=[n] {}\n\n",
+ g_evaluated.display_outer(context),
+ ));
+
+ return Ok((output, g_evaluated));
+}
+
+
+// Handle a variable or function definition string.
+// Returns a FormattedText with output that should be printed.
+#[inline(always)]
+fn do_assignment(
+ context: &mut Context,
+ s: &String
+) -> Result {
+
+ let mut output = FormattedText::new("".to_string());
+
+ let parts = s.split("=").collect::>();
+ if parts.len() != 2 {
+ return Err((
+ LineLocation::new_zero(),
+ DaisyError::Syntax
+ ));
+ }
+
+ // Index of first non-whitespace character in left
+ // (relative to whole prompt)
+ let starting_left = parts[0]
+ .char_indices()
+ .find(|(_, ch)| !(ch.is_whitespace() && *ch != '\n'))
+ .map(|(i, _)| i)
+ .unwrap_or_else(|| parts[0].len());
+
+ // Index of first non-whitespace character in right
+ // (relative to whole prompt)
+ // +1 accounts for equals sign
+ let starting_right = parts[0].chars().count() + 1 +
+ parts[1]
+ .char_indices()
+ .find(|(_, ch)| !(ch.is_whitespace() && *ch != '\n'))
+ .map(|(i, _)| i)
+ .unwrap_or_else(|| parts[0].len());
+
+
+ let left = substitute(context, &parts[0].trim().to_string());
+ let right = substitute(context, &parts[1].trim().to_string());
+ let is_function = left.contains("(");
+
+ // The order of methods below is a bit odd.
+ // This is intentional, since we want to check a definition's
+ // variable name before even attempting to parse its content.
+ if is_function {
+ let mut mode = 0;
+ let mut name = String::new();
+ let mut args = String::new();
+ for c in left.chars() {
+ match mode {
+
+ // Mode 0: reading function name
+ 0 => {
+ if c == '(' {
+ mode = 1; continue;
+ } else { name.push(c); }
+ },
+
+ // Mode 1: reading arguments
+ 1 => {
+ if c == ')' {
+ mode = 2; continue;
+ } else { args.push(c); }
+ },
+
+ // Mode 2: we should be done by now.
+ // That close paren should've been the last character.
+ 2 => {
+ return Err((
+ LineLocation{ pos: starting_left, len: left.chars().count() },
+ DaisyError::Syntax
+ ));
+ },
+
+ _ => unreachable!()
+ }
+ }
+
+
+ let args = args
+ .split(",").collect::>()
+ .iter().map(|x| x.trim().to_string()).collect::>();
+
+ if name.len() == 0 {
+ return Err((
+ LineLocation{ pos: starting_left, len: left.chars().count() },
+ DaisyError::Syntax
+ ));
+ };
+
+ if !context.valid_function(&name) {
+ return Err((
+ LineLocation{ pos: starting_left, len: left.chars().count() },
+ DaisyError::BadFunction
+ ));
+ };
+
+ if args.iter().find(|x| &x[..] == "").is_some() {
+ return Err((
+ LineLocation{ pos: starting_left, len: left.chars().count() },
+ DaisyError::Syntax
+ ));
+ };
+
+ for a in &args {
+ if !context.valid_varible(a) {
+ return Err((
+ LineLocation{ pos: starting_left, len: left.chars().count() },
+ DaisyError::BadVariable
+ ));
+ }
+ }
+
+ // Parse right hand side
+ let g = parser::parse(context, &right);
+ let Ok(g) = g else {
+ let Err((l, e)) = g else { unreachable!() };
+ return Err((
+ LineLocation{ pos: l.pos + starting_right, len: l.len},
+ e
+ ));
+ };
+
+ // Display parsed string
+ output.push(&format!(
+ " [s]=>[n] {left} = {}\n\n",
+ g.display(context)
+ ));
+
+ // Evaluate expression with shadow variables
+ for a in &args { context.add_shadow(a.to_string(), None);}
+ let g_evaluated = evaluate::evaluate(context, &g);
+ context.clear_shadow();
+ let Ok(_g_evaluated) = g_evaluated else {
+ let Err((l, e)) = g_evaluated else { unreachable!() };
+ return Err((
+ LineLocation{ pos: l.pos + starting_right, len: l.len},
+ e
+ ));
+ };
+
+ // We could push g_evaluated instead, but an un-evaluated string
+ // makes the 'vars' command prettier.
+ //
+ // We still need to evaluate g above, though, to make sure it works.
+ context.push_function(name, args, g).unwrap();
+ } else {
+
+ if !context.valid_varible(&left) {
+ return Err((
+ LineLocation{ pos: starting_left, len: left.chars().count() },
+ DaisyError::BadVariable
+ ));
+ }
+
+ // Parse right hand side
+ let g = parser::parse(context, &right);
+ let Ok(g) = g else {
+ let Err((l, e)) = g else { unreachable!() };
+ return Err((
+ LineLocation{ pos: l.pos + starting_right, len: l.len},
+ e
+ ));
+ };
+
+ // Display parsed string
+ output.push(&format!(
+ " [t]=>[n] {left} = {}\n\n",
+ g.display(context)
+ ));
+
+ // Evaluate expression
+ let g_evaluated = evaluate::evaluate(context, &g);
+ let Ok(g_evaluated) = g_evaluated else {
+ let Err((l, e)) = g_evaluated else { unreachable!() };
+ return Err((
+ LineLocation{ pos: l.pos + starting_right, len: l.len},
+ e
+ ));
+ };
+
+ context.push_variable(left.to_string(), g_evaluated).unwrap();
+ }
+
+ return Ok(output);
+
+}
+
+
diff --git a/src/main.rs b/src/main.rs
index 0841f07..94903de 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,289 +1,151 @@
-pub mod parser;
-pub mod command;
-pub mod quantity;
-pub mod evaluate;
-pub mod context;
-pub mod errors;
-pub mod formattedtext;
+use std::io::stdout;
+use std::io::stdin;
+use std::env;
-use crate::parser::substitute;
-use crate::errors::DaisyError;
-use crate::formattedtext::FormattedText;
-use crate::context::Context;
-use crate::parser::LineLocation;
+use termion::{
+ event::Key,
+ input::TermRead,
+ raw::IntoRawMode,
+ color::DetectColors
+};
-
-// Run main script for target system
-mod entrypoint;
-use crate::entrypoint::main_e;
+use daisycalc::PromptBuffer;
+use daisycalc::command;
+use daisycalc::Context;
+use daisycalc::FormattedText;
+use daisycalc::do_string;
#[cfg(test)]
mod tests;
-fn main() -> Result<(), std::io::Error> {
- return main_e();
-}
+
#[inline(always)]
-pub fn do_string(
- context: &mut Context,
- s: &String
-) -> Result {
+pub fn main() -> Result<(), std::io::Error> {
+ let mut stdout = stdout().into_raw_mode().unwrap();
+ let mut pb: PromptBuffer = PromptBuffer::new(64);
+ let mut context = Context::new();
- let r: (LineLocation, DaisyError);
- if command::is_command(s) {
- return Ok(command::do_command(context, s));
- } else if s.contains("=") {
- let x = do_assignment(context, s);
- match x {
- Ok(t) => { return Ok(t) },
- Err(t) => { r = t }
- };
+ // Detect color compatibilty
+ // Currently unused, this is slow.
+ /*
+ let term_colors = stdout.available_colors().unwrap_or(0);
+ if term_colors >= 256 {
+ context.config.term_color_type = 2;
+ } else if term_colors >= 8 {
+ context.config.term_color_type = 1;
} else {
- let x = do_expression(context, s);
- match x {
- Ok((t, e)) => { context.push_hist(e); return Ok(t) },
- Err(t) => { r = t }
- };
+ context.config.term_color_type = 0;
}
+ */
- let (l, e) = r;
- let mut t = FormattedText::new("".to_string());
- if l.zero() {
- t.push(&format!(
- "\n {}\n\n",
- e.text().to_string(),
+
+ // Handle command-line arguments
+ let args: Vec = env::args().collect();
+ if args.iter().any(|s| s == "--help") {
+ let t = command::do_command(&mut context, &String::from("help"));
+ t.write(&context, &mut stdout)?;
+ let t = command::do_command(&mut context, &String::from("flags"));
+ t.write(&context, &mut stdout)?;
+ return Ok(());
+ } else if args.iter().any(|s| s == "--version") {
+ let t = FormattedText::new(format!(
+ "Daisy v{}\n", env!("CARGO_PKG_VERSION")
));
- } else {
- t.push(&format!(
+ t.write(&context, &mut stdout)?;
+ return Ok(());
+ } else if args.iter().any(|s| s == "--info") {
+ let t = FormattedText::new(format!(
concat!(
- "{}[e]{}[n]\n",
- " {}\n\n"
+ "Daisy v{}\n",
+ "Your terminal supports {} colors.\n"
),
- " ".repeat(l.pos + 4),
- "^".repeat(l.len),
- e.text().to_string(),
+ env!("CARGO_PKG_VERSION"),
+ stdout.available_colors().unwrap_or(0)
));
+ t.write(&context, &mut stdout)?;
+ return Ok(());
+ } else if args.iter().any(|s| s == "--256color") {
+ context.config.term_color_type = 2;
+ } else if args.iter().any(|s| s == "--8color") {
+ context.config.term_color_type = 1;
+ } else if args.iter().any(|s| s == "--0color") {
+ context.config.term_color_type = 0;
+ } else if args.iter().any(|s| s == "--nosub") {
+ context.config.enable_substituion = false;
+ } else if args.iter().any(|s| s == "--nosuper") {
+ context.config.enable_super_powers = false;
+ } else if args.iter().any(|s| s == "--nooneover") {
+ context.config.enable_one_over_power = false;
}
- return Err(t);
-}
-
-// Handle a simple evaluation string.
-// Returns a FormattedText with output that should be printed.
-#[inline(always)]
-fn do_expression(
- context: &mut Context,
- s: &String
-) -> Result<(FormattedText, parser::Expression), (LineLocation, DaisyError)> {
-
- let mut output = FormattedText::new("".to_string());
-
- let g = parser::parse(context, &s)?;
- let g_evaluated = evaluate::evaluate(context, &g)?;
-
- // Display parsed string
- output.push(&format!(
- " [s]=>[n] {}\n\n",
- g.display(context)
- ));
-
- // Display result
- output.push(&format!(
- " [r]=[n] {}\n\n",
- g_evaluated.display_outer(context),
- ));
-
- return Ok((output, g_evaluated));
-}
+ context.config.check();
-// Handle a variable or function definition string.
-// Returns a FormattedText with output that should be printed.
-#[inline(always)]
-fn do_assignment(
- context: &mut Context,
- s: &String
-) -> Result {
+ 'outer: loop {
- let mut output = FormattedText::new("".to_string());
+ let t = pb.write_prompt(&mut context);
+ t.write(&context, &mut stdout)?;
- let parts = s.split("=").collect::>();
- if parts.len() != 2 {
- return Err((
- LineLocation::new_zero(),
- DaisyError::Syntax
- ));
+ let stdin = stdin();
+ for c in stdin.keys() {
+ if let Key::Char(q) = c.as_ref().unwrap() {
+ match q {
+ '\n' => {
+ // Print again without cursor, in case we pressed enter
+ // while inside a substitution
+ let t = pb.write_prompt_nocursor(&mut context);
+ t.write(&context, &mut stdout)?;
+
+
+ let in_str = pb.enter();
+ FormattedText::newline(&mut stdout)?;
+ if in_str == "" { break; }
+
+ if in_str.trim() == "quit" {
+ break 'outer;
+ } else {
+ let r = crate::do_string(&mut context, &in_str);
+
+ match r {
+ Ok(t) | Err(t) => {
+ t.write(&context, &mut stdout).unwrap();
+ }
+ }
+ }
+
+ break;
+ },
+
+ // Only process sane characters
+ 'a'..='z' | 'A'..='Z' | '0'..='9'
+ |'!'|'@'|'#'|'$'|'%'|'^'|'&'|'*'|'('|')'
+ |'?'|'~'|','|'.'|'['|']'
+ |'<'|'>'|'/'|'_'|'-'|':'|'|'|'='|'+'|';'
+ => { pb.add_char(*q); },
+
+ _ => {}
+ };
+ } else {
+ match c.unwrap() {
+ Key::Backspace => { pb.backspace(); },
+ Key::Delete => { pb.delete(); },
+ Key::Left => { pb.cursor_left(); },
+ Key::Right => { pb.cursor_right(); },
+ Key::Up => { pb.hist_up(); },
+ Key::Down => { pb.hist_down(); },
+
+ Key::Ctrl('d') |
+ Key::Ctrl('c') => { break 'outer; },
+ _ => {}
+ };
+ };
+
+ let t = pb.write_prompt(&mut context);
+ t.write(&context, &mut stdout)?;
+ }
}
- // Index of first non-whitespace character in left
- // (relative to whole prompt)
- let starting_left = parts[0]
- .char_indices()
- .find(|(_, ch)| !(ch.is_whitespace() && *ch != '\n'))
- .map(|(i, _)| i)
- .unwrap_or_else(|| parts[0].len());
-
- // Index of first non-whitespace character in right
- // (relative to whole prompt)
- // +1 accounts for equals sign
- let starting_right = parts[0].chars().count() + 1 +
- parts[1]
- .char_indices()
- .find(|(_, ch)| !(ch.is_whitespace() && *ch != '\n'))
- .map(|(i, _)| i)
- .unwrap_or_else(|| parts[0].len());
-
-
- let left = substitute(context, &parts[0].trim().to_string());
- let right = substitute(context, &parts[1].trim().to_string());
- let is_function = left.contains("(");
-
- // The order of methods below is a bit odd.
- // This is intentional, since we want to check a definition's
- // variable name before even attempting to parse its content.
- if is_function {
- let mut mode = 0;
- let mut name = String::new();
- let mut args = String::new();
- for c in left.chars() {
- match mode {
-
- // Mode 0: reading function name
- 0 => {
- if c == '(' {
- mode = 1; continue;
- } else { name.push(c); }
- },
-
- // Mode 1: reading arguments
- 1 => {
- if c == ')' {
- mode = 2; continue;
- } else { args.push(c); }
- },
-
- // Mode 2: we should be done by now.
- // That close paren should've been the last character.
- 2 => {
- return Err((
- LineLocation{ pos: starting_left, len: left.chars().count() },
- DaisyError::Syntax
- ));
- },
-
- _ => unreachable!()
- }
- }
-
-
- let args = args
- .split(",").collect::>()
- .iter().map(|x| x.trim().to_string()).collect::>();
-
- if name.len() == 0 {
- return Err((
- LineLocation{ pos: starting_left, len: left.chars().count() },
- DaisyError::Syntax
- ));
- };
-
- if !context.valid_function(&name) {
- return Err((
- LineLocation{ pos: starting_left, len: left.chars().count() },
- DaisyError::BadFunction
- ));
- };
-
- if args.iter().find(|x| &x[..] == "").is_some() {
- return Err((
- LineLocation{ pos: starting_left, len: left.chars().count() },
- DaisyError::Syntax
- ));
- };
-
- for a in &args {
- if !context.valid_varible(a) {
- return Err((
- LineLocation{ pos: starting_left, len: left.chars().count() },
- DaisyError::BadVariable
- ));
- }
- }
-
- // Parse right hand side
- let g = parser::parse(context, &right);
- let Ok(g) = g else {
- let Err((l, e)) = g else { unreachable!() };
- return Err((
- LineLocation{ pos: l.pos + starting_right, len: l.len},
- e
- ));
- };
-
- // Display parsed string
- output.push(&format!(
- " [s]=>[n] {left} = {}\n\n",
- g.display(context)
- ));
-
- // Evaluate expression with shadow variables
- for a in &args { context.add_shadow(a.to_string(), None);}
- let g_evaluated = evaluate::evaluate(context, &g);
- context.clear_shadow();
- let Ok(_g_evaluated) = g_evaluated else {
- let Err((l, e)) = g_evaluated else { unreachable!() };
- return Err((
- LineLocation{ pos: l.pos + starting_right, len: l.len},
- e
- ));
- };
-
- // We could push g_evaluated instead, but an un-evaluated string
- // makes the 'vars' command prettier.
- //
- // We still need to evaluate g above, though, to make sure it works.
- context.push_function(name, args, g).unwrap();
- } else {
-
- if !context.valid_varible(&left) {
- return Err((
- LineLocation{ pos: starting_left, len: left.chars().count() },
- DaisyError::BadVariable
- ));
- }
-
- // Parse right hand side
- let g = parser::parse(context, &right);
- let Ok(g) = g else {
- let Err((l, e)) = g else { unreachable!() };
- return Err((
- LineLocation{ pos: l.pos + starting_right, len: l.len},
- e
- ));
- };
-
- // Display parsed string
- output.push(&format!(
- " [t]=>[n] {left} = {}\n\n",
- g.display(context)
- ));
-
- // Evaluate expression
- let g_evaluated = evaluate::evaluate(context, &g);
- let Ok(g_evaluated) = g_evaluated else {
- let Err((l, e)) = g_evaluated else { unreachable!() };
- return Err((
- LineLocation{ pos: l.pos + starting_right, len: l.len},
- e
- ));
- };
-
- context.push_variable(left.to_string(), g_evaluated).unwrap();
- }
-
- return Ok(output);
-
-}
-
-
+ FormattedText::newline(&mut stdout)?;
+ return Ok(());
+}
\ No newline at end of file
diff --git a/src/entrypoint/unix/promptbuffer.rs b/src/promptbuffer.rs
similarity index 80%
rename from src/entrypoint/unix/promptbuffer.rs
rename to src/promptbuffer.rs
index 619f759..dc9d7d1 100644
--- a/src/entrypoint/unix/promptbuffer.rs
+++ b/src/promptbuffer.rs
@@ -1,9 +1,7 @@
use std::collections::VecDeque;
-use std::io::Write;
-use termion::raw::RawTerminal;
-use crate::formattedtext;
+use crate::FormattedText;
use crate::parser::substitute_cursor;
-use crate::context::Context;
+use crate::Context;
const PROMPT_STR: &str = "==> ";
@@ -18,12 +16,49 @@ pub struct PromptBuffer {
// 1 means "on last item of history"
hist_cursor: usize,
- buffer: String,
+ pub buffer: String,
buffer_changed: bool,
cursor: usize,
last_print_len: usize
}
+impl PromptBuffer {
+ // Same as write_primpt, but pretends there is no cursor
+ pub fn write_prompt_nocursor(&mut self, context: &Context) -> FormattedText {
+ let tmp = self.cursor;
+ self.cursor = 0;
+ let r = self.write_prompt(context);
+ self.cursor = tmp;
+ return r;
+ }
+
+ pub fn write_prompt(&mut self, context: &Context) -> FormattedText {
+ let l = self.buffer.chars().count();
+ let i = if l == 0 {0} else {l - self.cursor};
+
+ // Draw prettyprinted expression
+ let (display_c, s) = substitute_cursor(context, &self.get_contents(), i);
+
+ let mut tx = FormattedText::new("".to_string());
+
+ tx.push(&format!("\r[p]{PROMPT_STR}[n]{s}"));
+
+
+ // If this string is shorter, clear the remaining old one.
+ if s.chars().count() < self.last_print_len {
+ tx.push(&" ".repeat(self.last_print_len - s.chars().count()));
+ }
+
+ let q = (display_c + PROMPT_STR.chars().count()) as u16;
+ tx.push(&format!("\r[cursorright{q}]"));
+
+ self.last_print_len = s.chars().count();
+
+ return tx;
+ }
+}
+
+
impl PromptBuffer {
pub fn new(maxlen: usize) -> PromptBuffer {
return PromptBuffer {
@@ -37,48 +72,6 @@ impl PromptBuffer {
};
}
- // Same as write_primpt, but pretends there is no cursor
- pub fn write_prompt_nocursor(&mut self, context: &Context, stdout: &mut RawTerminal) -> Result<(), std::io::Error> {
- let tmp = self.cursor;
- self.cursor = 0;
- let r = self.write_prompt(context, stdout);
- self.cursor = tmp;
- return r;
- }
-
- pub fn write_prompt(&mut self, context: &Context, stdout: &mut RawTerminal) -> Result<(), std::io::Error> {
- let l = self.buffer.chars().count();
- let i = if l == 0 {0} else {l - self.cursor};
-
- // Draw prettyprinted expression
- let (display_c, s) = substitute_cursor(context, &self.get_contents(), i);
-
- write!(
- stdout, "\r{}{PROMPT_STR}{}{}",
- formattedtext::format_map('p', context).unwrap(),
- formattedtext::format_map('n', context).unwrap(),
- s
- )?;
-
- // If this string is shorter, clear the remaining old one.
- if s.chars().count() < self.last_print_len {
- write!(
- stdout, "{}",
- " ".repeat(self.last_print_len - s.chars().count()),
- )?;
- }
-
- write!(
- stdout, "\r{}",
- termion::cursor::Right((display_c + PROMPT_STR.chars().count()) as u16)
- )?;
-
- stdout.flush()?;
- self.last_print_len = s.chars().count();
-
- return Ok(());
- }
-
// Prompt methods
pub fn get_contents(&self) -> &String {&self.buffer}
diff --git a/src/tests.rs b/src/tests.rs
index d54f10f..e765a57 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -1,7 +1,7 @@
// Many of these have been borrowed from insect.
-use crate::parser;
-use crate::evaluate::evaluate;
-use crate::context::Context;
+use daisycalc::parser;
+use daisycalc::evaluate;
+use daisycalc::Context;
fn eval_to_str(s: &str) -> Result {
let g = match parser::parse_no_context(&String::from(s)) {