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..8a0e193 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"
@@ -28,6 +34,7 @@ dependencies = [
"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 052610a..941f747 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,6 +16,7 @@ path = "src/main.rs"
[lib]
name = "daisycalc"
path = "src/lib.rs"
+crate-type = ["cdylib", "rlib"]
[profile.release]
opt-level = 3
@@ -32,10 +33,15 @@ cfg-if = "1.0.0"
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/formattedtext.rs b/src/formattedtext.rs
deleted file mode 100644
index ed7c6ef..0000000
--- a/src/formattedtext.rs
+++ /dev/null
@@ -1,201 +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 }
- })
-}
-
-
-
-
-impl FormattedText {
- pub fn newline(stdout: &mut RawTerminal) -> Result<(), std::io::Error> {
- write!(stdout, "\n")?;
- return Ok(());
- }
-
- 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 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 = Self::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
index 1e85639..ceed6cc 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -10,14 +10,96 @@ 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,
diff --git a/src/main.rs b/src/main.rs
index 24747aa..94903de 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,3 @@
-pub mod promptbuffer;
-
use std::io::stdout;
use std::io::stdin;
use std::env;
@@ -11,11 +9,10 @@ use termion::{
color::DetectColors
};
-use promptbuffer::PromptBuffer;
+use daisycalc::PromptBuffer;
use daisycalc::command;
use daisycalc::Context;
use daisycalc::FormattedText;
-
use daisycalc::do_string;
#[cfg(test)]
@@ -87,7 +84,8 @@ pub fn main() -> Result<(), std::io::Error> {
'outer: loop {
- pb.write_prompt(&mut context, &mut stdout)?;
+ let t = pb.write_prompt(&mut context);
+ t.write(&context, &mut stdout)?;
let stdin = stdin();
for c in stdin.keys() {
@@ -96,7 +94,10 @@ pub fn main() -> Result<(), std::io::Error> {
'\n' => {
// Print again without cursor, in case we pressed enter
// while inside a substitution
- pb.write_prompt_nocursor(&mut context, &mut stdout)?;
+ 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; }
@@ -140,7 +141,8 @@ pub fn main() -> Result<(), std::io::Error> {
};
};
- pb.write_prompt(&mut context, &mut stdout)?;
+ let t = pb.write_prompt(&mut context);
+ t.write(&context, &mut stdout)?;
}
}
diff --git a/src/promptbuffer.rs b/src/promptbuffer.rs
index 0b6bc6b..dc9d7d1 100644
--- a/src/promptbuffer.rs
+++ b/src/promptbuffer.rs
@@ -1,9 +1,7 @@
use std::collections::VecDeque;
-use std::io::Write;
-use termion::raw::RawTerminal;
-use daisycalc::FormattedText;
-use daisycalc::parser::substitute_cursor;
-use daisycalc::Context;
+use crate::FormattedText;
+use crate::parser::substitute_cursor;
+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}