mirror of
				https://github.com/rm-dr/daisy
				synced 2025-10-30 14:04:47 -07:00 
			
		
		
		
	Compare commits
	
		
			35 Commits
		
	
	
		
			v1.0.1
			...
			599c9742d2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 599c9742d2 | |||
| 9f9cc5d084 | |||
| c8ebec59ae | |||
| fde1220a96 | |||
| 2f1f8a0801 | |||
| d906c474c5 | |||
| 315be575ee | |||
| 31c368f7d8 | |||
| 8a026fc2ea | |||
| 8497c125ef | |||
| 682205f5e1 | |||
| e1ba2a9c1f | |||
| b9cfe719a6 | |||
| 47abd9d18e | |||
| 4353548900 | |||
| 84ebf89d6f | |||
| 2391606ae1 | |||
| 38c982bb00 | |||
| 80b1c76c59 | |||
| 11b5cd877a | |||
| 3a08cfb2d3 | |||
| b136353d36 | |||
| a125e867c4 | |||
| c477302c88 | |||
| edc859dc01 | |||
| 77c357c2f3 | |||
| 3ae5383eed | |||
| 4055c08217 | |||
| 6969d17cce | |||
| 6e3609d85e | |||
| f9ec4d82fe | |||
| 86b5356d4f | |||
| cc81c3979c | |||
| b846a7c144 | |||
| 09996801d8 | 
							
								
								
									
										168
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										168
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -8,12 +8,6 @@ version = "1.1.0" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "az" |  | ||||||
| version = "1.2.1" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" |  | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "bitflags" | name = "bitflags" | ||||||
| version = "1.3.2" | version = "1.3.2" | ||||||
| @ -28,24 +22,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "daisycalc" | name = "daisycalc" | ||||||
| version = "1.0.1" | version = "1.1.0" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "cfg-if", |  "cfg-if", | ||||||
|  "rug", |  "num", | ||||||
|  "termion", |  "termion", | ||||||
|  "toml", |  "toml", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "gmp-mpfr-sys" |  | ||||||
| version = "1.5.2" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "5b560063e2ffa8ce9c2ef9bf487f2944a97deca5b8de0b5bcd0ae6437ef8b75f" |  | ||||||
| dependencies = [ |  | ||||||
|  "libc", |  | ||||||
|  "windows-sys", |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "hashbrown" | name = "hashbrown" | ||||||
| version = "0.12.3" | version = "0.12.3" | ||||||
| @ -64,9 +48,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "libc" | name = "libc" | ||||||
| version = "0.2.140" | version = "0.2.147" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "memchr" | name = "memchr" | ||||||
| @ -74,6 +58,82 @@ version = "2.5.0" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "num" | ||||||
|  | version = "0.4.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" | ||||||
|  | dependencies = [ | ||||||
|  |  "num-bigint", | ||||||
|  |  "num-complex", | ||||||
|  |  "num-integer", | ||||||
|  |  "num-iter", | ||||||
|  |  "num-rational", | ||||||
|  |  "num-traits", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "num-bigint" | ||||||
|  | version = "0.4.4" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" | ||||||
|  | dependencies = [ | ||||||
|  |  "autocfg", | ||||||
|  |  "num-integer", | ||||||
|  |  "num-traits", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "num-complex" | ||||||
|  | version = "0.4.4" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" | ||||||
|  | dependencies = [ | ||||||
|  |  "num-traits", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "num-integer" | ||||||
|  | version = "0.1.45" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" | ||||||
|  | dependencies = [ | ||||||
|  |  "autocfg", | ||||||
|  |  "num-traits", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "num-iter" | ||||||
|  | version = "0.1.43" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" | ||||||
|  | dependencies = [ | ||||||
|  |  "autocfg", | ||||||
|  |  "num-integer", | ||||||
|  |  "num-traits", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "num-rational" | ||||||
|  | version = "0.4.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" | ||||||
|  | dependencies = [ | ||||||
|  |  "autocfg", | ||||||
|  |  "num-bigint", | ||||||
|  |  "num-integer", | ||||||
|  |  "num-traits", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "num-traits" | ||||||
|  | version = "0.2.16" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" | ||||||
|  | dependencies = [ | ||||||
|  |  "autocfg", | ||||||
|  | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "numtoa" | name = "numtoa" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| @ -98,17 +158,6 @@ dependencies = [ | |||||||
|  "redox_syscall", |  "redox_syscall", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "rug" |  | ||||||
| version = "1.19.2" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "555e8b44763d034526db899c88cd56ccc4486cd38b444c8aa0e79d4e70ae5a34" |  | ||||||
| dependencies = [ |  | ||||||
|  "az", |  | ||||||
|  "gmp-mpfr-sys", |  | ||||||
|  "libc", |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "serde" | name = "serde" | ||||||
| version = "1.0.164" | version = "1.0.164" | ||||||
| @ -170,63 +219,6 @@ dependencies = [ | |||||||
|  "winnow", |  "winnow", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "windows-sys" |  | ||||||
| version = "0.42.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" |  | ||||||
| dependencies = [ |  | ||||||
|  "windows_aarch64_gnullvm", |  | ||||||
|  "windows_aarch64_msvc", |  | ||||||
|  "windows_i686_gnu", |  | ||||||
|  "windows_i686_msvc", |  | ||||||
|  "windows_x86_64_gnu", |  | ||||||
|  "windows_x86_64_gnullvm", |  | ||||||
|  "windows_x86_64_msvc", |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "windows_aarch64_gnullvm" |  | ||||||
| version = "0.42.2" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "windows_aarch64_msvc" |  | ||||||
| version = "0.42.2" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "windows_i686_gnu" |  | ||||||
| version = "0.42.2" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "windows_i686_msvc" |  | ||||||
| version = "0.42.2" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "windows_x86_64_gnu" |  | ||||||
| version = "0.42.2" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "windows_x86_64_gnullvm" |  | ||||||
| version = "0.42.2" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" |  | ||||||
|  |  | ||||||
| [[package]] |  | ||||||
| name = "windows_x86_64_msvc" |  | ||||||
| version = "0.42.2" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" |  | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "winnow" | name = "winnow" | ||||||
| version = "0.4.6" | version = "0.4.6" | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								Cargo.toml
									
									
									
									
									
								
							| @ -1,6 +1,6 @@ | |||||||
| [package] | [package] | ||||||
| name = "daisycalc" | name = "daisycalc" | ||||||
| version = "1.0.1" | version = "1.1.0" | ||||||
| edition = "2021" | edition = "2021" | ||||||
| build = "buildscript/main.rs" | build = "buildscript/main.rs" | ||||||
| license = "GPL-3.0-only" | license = "GPL-3.0-only" | ||||||
| @ -13,6 +13,10 @@ readme = "README.md" | |||||||
| name = "daisy" | name = "daisy" | ||||||
| path = "src/main.rs" | path = "src/main.rs" | ||||||
|  |  | ||||||
|  | [lib] | ||||||
|  | name = "daisycalc" | ||||||
|  | path = "src/lib.rs" | ||||||
|  |  | ||||||
| [profile.release] | [profile.release] | ||||||
| opt-level = 3 | opt-level = 3 | ||||||
| debug = 0 | debug = 0 | ||||||
| @ -25,10 +29,13 @@ panic = "abort" | |||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| cfg-if = "1.0.0" | cfg-if = "1.0.0" | ||||||
|  | num = "0.4.1" | ||||||
|  | #astro-float = "0.7.1" | ||||||
|  |  | ||||||
|  |  | ||||||
| [target.'cfg(target_family = "unix")'.dependencies] | [target.'cfg(target_family = "unix")'.dependencies] | ||||||
| termion = "2.0.1" | termion = "2.0.1" | ||||||
| rug = "1.19.2" |  | ||||||
|  |  | ||||||
| [build-dependencies] | [build-dependencies] | ||||||
| toml = "0.7.4" | toml = "0.7.4" | ||||||
							
								
								
									
										31
									
								
								TODO.md
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								TODO.md
									
									
									
									
									
								
							| @ -1,46 +1,40 @@ | |||||||
| ## Version Bump checklist | ## Version Bump checklist | ||||||
|  |  - TODO: build and publish script | ||||||
|  - update Cargo.toml |  - update Cargo.toml | ||||||
|  - run cargo test |  - run cargo test | ||||||
|  - commit |  - commit | ||||||
|  - git tag -a v1.0.0 -m "Version 1.0.0" |  - push | ||||||
|  - git push |  - merge | ||||||
|  - git push origin v1.0.0 |  - git tag -a v1.0.0 -m "Version 1.0.0" on merge commit | ||||||
|  - cargo publish |  - cargo publish | ||||||
|  - Update packages |  - Update packages | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Pre-release | ## Pre-release | ||||||
|  - Fix linelocation (consistent, what does an operator's linelocation mean?) |  | ||||||
|  - Tuple operations |  - Tuple operations | ||||||
|  - we don't need vectors as arguments to operators |  - we don't need vectors as arguments to operators | ||||||
|  - Assignment tests |  - Fix linelocation when evaluating functions | ||||||
|  |  | ||||||
| ## Parser | ## Parser | ||||||
|  - Better error when `sin = 2` |  | ||||||
|  - Should functions be operators? |  - Should functions be operators? | ||||||
|  - Binary, hex, octal numbers |  - Binary, hex, octal numbers | ||||||
|  |  | ||||||
|  |  | ||||||
| ## General | ## General | ||||||
|  |  - Better tests (assignment, many expressions in one context) | ||||||
|  - Optional config file |  - Optional config file | ||||||
|  - Optional history file |  - Optional history file | ||||||
|  - daisyrc file |  | ||||||
|  - Compile to WASM, publish a webapp |  - Compile to WASM, publish a webapp | ||||||
|  - evaluate straight from command line |  - evaluate straight from command line | ||||||
|  - Auto-push to crates.io |  - Package for debian, nix | ||||||
|  - Package for debian |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## Internals | ## Internals | ||||||
|  - Non-recursive treeify |  - Non-recursive treeify | ||||||
|  - Faster factorial function. Maybe use gamma instead? |  - Faster factorial function. Maybe use gamma instead? | ||||||
|  - Arbitrary precision float (rug doesn't offer arbitrary exponents) |  - Arbitrary precision float (rug doesn't offer arbitrary exponents) | ||||||
|  - Remove rug dependency (too big, incompatible) |  | ||||||
|  |  | ||||||
| ## Math Features | ## Math Features | ||||||
|  - Dice |  | ||||||
|  - Mean, Median, Min |  - Mean, Median, Min | ||||||
|  - Arbitrary base logarithm |  - Arbitrary base logarithm | ||||||
|  - Derivatives |  - Derivatives | ||||||
| @ -48,21 +42,16 @@ | |||||||
|  - Complex numbers |  - Complex numbers | ||||||
|  - acot/acoth functions |  - acot/acoth functions | ||||||
|  - Sums and products with functional arguments |  - Sums and products with functional arguments | ||||||
|  |  - Add functions: gcd, inverse mod, dice | ||||||
|  |  | ||||||
| ## Prompt | ## Prompt | ||||||
|  |  - Fix terminal color detection | ||||||
|  - Live syntax/output (like firefox js terminal) |  - Live syntax/output (like firefox js terminal) | ||||||
|  - Syntax highlight input and output |  - Syntax highlighting | ||||||
|  - fish-style tab completion |  - fish-style tab completion | ||||||
|  - Numbered expressions, history recall |  - Numbered expressions, history recall | ||||||
|  - Color configuration |  | ||||||
|  - Enable/disable unit sets (defaults?) |  - Enable/disable unit sets (defaults?) | ||||||
|  - Consistent unit ordering |  - Consistent unit ordering | ||||||
|  - Better linelocation |  | ||||||
|    - we shouldn't need to re-print user input on evaluation errors, red arrows should adjust themselves to the prettyprinted string |  | ||||||
|  - Backend-independent colorful printing |  | ||||||
|    - Better colors in error texts |  | ||||||
|  - Better substitution. Consistent: when ascii, when unicode? |  | ||||||
|  - Command to list substitutions |  | ||||||
|  |  | ||||||
| ## Units | ## Units | ||||||
|  - long prefixes (megatonne, etc) |  - long prefixes (megatonne, etc) | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								shell.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								shell.nix
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | let | ||||||
|  |  pkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/f155f0cf4ea43c4e3c8918d2d327d44777b6cad4.tar.gz") {}; | ||||||
|  | in pkgs.mkShell { | ||||||
|  |   buildInputs = with pkgs; [ | ||||||
|  |     cargo | ||||||
|  |     rustc | ||||||
|  |     rustfmt | ||||||
|  |     m4 | ||||||
|  |    ]; | ||||||
|  | } | ||||||
|  |  | ||||||
| @ -16,6 +16,7 @@ pub fn is_command( | |||||||
| 		| "vars" | 		| "vars" | ||||||
| 		| "consts" | "constants" | 		| "consts" | "constants" | ||||||
| 		| "del" | "delete" | 		| "del" | "delete" | ||||||
|  | 		| "flags" | ||||||
| 		=> true, | 		=> true, | ||||||
| 		_ => false | 		_ => false | ||||||
| 	} | 	} | ||||||
| @ -81,6 +82,27 @@ pub fn do_command( | |||||||
| 			return t; | 			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\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" => { | 		"clear" => { | ||||||
| 			return FormattedText::new("[clear]".to_string()); | 			return FormattedText::new("[clear]".to_string()); | ||||||
| 		}, | 		}, | ||||||
| @ -224,7 +246,7 @@ pub fn do_command( | |||||||
| 			if args.len() != 2 { | 			if args.len() != 2 { | ||||||
| 				return FormattedText::new( | 				return FormattedText::new( | ||||||
| 					format!( | 					format!( | ||||||
| 						"[c]{first}[n] [t]takes exactly two arguments.[n]\n\n", | 						"[c]{first}[n] [t]takes exactly one argument.[n]\n\n", | ||||||
| 					) | 					) | ||||||
| 				); | 				); | ||||||
| 			} | 			} | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ pub struct Config { | |||||||
| 	// with prettier unicode alternatives? | 	// with prettier unicode alternatives? | ||||||
| 	// | 	// | ||||||
| 	// Automatically disabled if enable_unicode is off. | 	// Automatically disabled if enable_unicode is off. | ||||||
| 	//pub enable_substituion: bool, | 	pub enable_substituion: bool, | ||||||
|  |  | ||||||
| 	// Should we print simple powers | 	// Should we print simple powers | ||||||
| 	// as unicode superscript chars? | 	// as unicode superscript chars? | ||||||
| @ -38,7 +38,7 @@ impl Config { | |||||||
| 	pub fn new() -> Config { | 	pub fn new() -> Config { | ||||||
| 		Config{ | 		Config{ | ||||||
| 			term_color_type: 2, | 			term_color_type: 2, | ||||||
| 			//enable_substituion: true, | 			enable_substituion: true, | ||||||
| 			//enable_unicode: true, | 			//enable_unicode: true, | ||||||
| 			enable_super_powers: true, | 			enable_super_powers: true, | ||||||
| 			enable_one_over_power: true | 			enable_one_over_power: true | ||||||
| @ -61,7 +61,7 @@ impl Config { | |||||||
|  |  | ||||||
|  |  | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| #[derive(Clone)] | //#[derive(Clone)] | ||||||
| pub struct Context { | pub struct Context { | ||||||
| 	pub config: Config, | 	pub config: Config, | ||||||
|  |  | ||||||
| @ -81,7 +81,7 @@ impl Context { | |||||||
| 			history: Vec::new(), | 			history: Vec::new(), | ||||||
| 			variables: HashMap::new(), | 			variables: HashMap::new(), | ||||||
| 			functions: HashMap::new(), | 			functions: HashMap::new(), | ||||||
| 			shadow: HashMap::new(), | 			shadow: HashMap::new() | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @ -126,6 +126,7 @@ impl Context { | |||||||
| 		} else { panic!() } | 		} else { panic!() } | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Can we define a new variable with this name? | ||||||
| 	pub fn valid_varible(&self, s: &str) -> bool { | 	pub fn valid_varible(&self, s: &str) -> bool { | ||||||
| 		if { | 		if { | ||||||
| 			Function::from_string(s).is_some() || | 			Function::from_string(s).is_some() || | ||||||
| @ -145,10 +146,17 @@ impl Context { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Can we get a value fro mthis variable name? | ||||||
| 	pub fn is_varible(&self, s: &str) -> bool { | 	pub fn is_varible(&self, s: &str) -> bool { | ||||||
| 		return { | 		return { | ||||||
|  | 			( | ||||||
|  | 				s == "ans" && | ||||||
|  | 				self.history.len() != 0 | ||||||
|  | 			) || | ||||||
|  | 			( | ||||||
| 				self.valid_varible(s) && | 				self.valid_varible(s) && | ||||||
| 				(self.variables.contains_key(s) || self.shadow.contains_key(s)) | 				(self.variables.contains_key(s) || self.shadow.contains_key(s)) | ||||||
|  | 			) | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | |||||||
| @ -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."); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @ -1,3 +0,0 @@ | |||||||
| mod unix; |  | ||||||
| mod promptbuffer; |  | ||||||
| pub use self::unix::main; |  | ||||||
| @ -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<String> = 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(()); |  | ||||||
| } |  | ||||||
| @ -63,7 +63,7 @@ pub fn evaluate( | |||||||
|  |  | ||||||
| 					context.get_variable(&s) | 					context.get_variable(&s) | ||||||
| 				}, | 				}, | ||||||
| 				Expression::Operator(_, Operator::Function(_), _) => { Some(eval_function(g)?) }, | 				Expression::Operator(_, Operator::Function(_), _) => { eval_function(g)? }, | ||||||
| 				Expression::Operator(_, _, _) => { eval_operator(context, g)? }, | 				Expression::Operator(_, _, _) => { eval_operator(context, g)? }, | ||||||
| 			}; | 			}; | ||||||
|  |  | ||||||
|  | |||||||
| @ -26,7 +26,7 @@ fn to_radians(q: Quantity) -> Result<Quantity, ()> { | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| pub fn eval_function(g: &Expression) -> Result<Expression, (LineLocation, DaisyError)> { | pub fn eval_function(g: &Expression) -> Result<Option<Expression>, (LineLocation, DaisyError)> { | ||||||
|  |  | ||||||
| 	let Expression::Operator(loc, Operator::Function(f), args) = g else {unreachable!()}; | 	let Expression::Operator(loc, Operator::Function(f), args) = g else {unreachable!()}; | ||||||
|  |  | ||||||
| @ -41,113 +41,112 @@ pub fn eval_function(g: &Expression) -> Result<Expression, (LineLocation, DaisyE | |||||||
| 		)) | 		)) | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	let Expression::Quantity(l, q) = a else {panic!()}; | 	let Expression::Quantity(l, q) = a else { return Ok(None); }; | ||||||
|  |  | ||||||
|  |  | ||||||
| 	match f { | 	match f { | ||||||
| 		Function::NoUnit => { return Ok(Expression::Quantity(*loc + *l, q.without_unit())); } | 		Function::NoUnit => { return Ok(Some(Expression::Quantity(*loc + *l, q.without_unit()))); } | ||||||
| 		Function::ToBase => { return Ok(Expression::Quantity(*loc + *l, q.convert_to_base())); } | 		Function::ToBase => { return Ok(Some(Expression::Quantity(*loc + *l, q.convert_to_base()))); } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 		Function::Abs => { | 		Function::Abs => { | ||||||
| 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.abs())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.abs()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Floor => { | 		Function::Floor => { | ||||||
| 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.floor())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.floor()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Ceil => { | 		Function::Ceil => { | ||||||
| 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.ceil())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.ceil()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Round => { | 		Function::Round => { | ||||||
| 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.round())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.round()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::NaturalLog => { | 		Function::NaturalLog => { | ||||||
| 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.ln())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.ln()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::TenLog => { | 		Function::TenLog => { | ||||||
| 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.log10())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.log10()))); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 		Function::Sin => { | 		Function::Sin => { | ||||||
| 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.sin())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.sin()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Cos => { | 		Function::Cos => { | ||||||
| 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.cos())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.cos()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Tan => { | 		Function::Tan => { | ||||||
| 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.tan())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.tan()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Csc => { | 		Function::Csc => { | ||||||
| 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.csc())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.csc()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Sec => { | 		Function::Sec => { | ||||||
| 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.sec())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.sec()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Cot => { | 		Function::Cot => { | ||||||
| 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.cot())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.cot()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Sinh => { | 		Function::Sinh => { | ||||||
| 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.sinh())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.sinh()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Cosh => { | 		Function::Cosh => { | ||||||
| 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.cosh())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.cosh()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Tanh => { | 		Function::Tanh => { | ||||||
| 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.tanh())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.tanh()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Csch => { | 		Function::Csch => { | ||||||
| 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.csch())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.csch()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Sech => { | 		Function::Sech => { | ||||||
| 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.sech())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.sech()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Coth => { | 		Function::Coth => { | ||||||
| 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | 			let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.coth())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.coth()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Asin => { | 		Function::Asin => { | ||||||
| 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.asin())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.asin()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Acos => { | 		Function::Acos => { | ||||||
| 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.acos())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.acos()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Atan => { | 		Function::Atan => { | ||||||
| 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.atan())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.atan()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Asinh => { | 		Function::Asinh => { | ||||||
| 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.asinh())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.asinh()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Acosh => { | 		Function::Acosh => { | ||||||
| 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.acosh())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.acosh()))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::Atanh => { | 		Function::Atanh => { | ||||||
| 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, q.atanh())); | 			return Ok(Some(Expression::Quantity(*loc + *l, q.atanh()))); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -160,7 +159,7 @@ pub fn eval_function(g: &Expression) -> Result<Expression, (LineLocation, DaisyE | |||||||
| 			let mut r = q.without_unit(); | 			let mut r = q.without_unit(); | ||||||
| 			r += Quantity::new_rational(-273.15f64).unwrap(); | 			r += Quantity::new_rational(-273.15f64).unwrap(); | ||||||
|  |  | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, r)); | 			return Ok(Some(Expression::Quantity(*loc + *l, r))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::ToFahrenheit => { | 		Function::ToFahrenheit => { | ||||||
| 			let mut k = Quantity::new_rational(1f64).unwrap(); | 			let mut k = Quantity::new_rational(1f64).unwrap(); | ||||||
| @ -172,7 +171,7 @@ pub fn eval_function(g: &Expression) -> Result<Expression, (LineLocation, DaisyE | |||||||
| 			r += Quantity::new_rational(-459.67).unwrap(); | 			r += Quantity::new_rational(-459.67).unwrap(); | ||||||
|  |  | ||||||
|  |  | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, r)); | 			return Ok(Some(Expression::Quantity(*loc + *l, r))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::FromCelsius => { | 		Function::FromCelsius => { | ||||||
| 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | ||||||
| @ -181,7 +180,7 @@ pub fn eval_function(g: &Expression) -> Result<Expression, (LineLocation, DaisyE | |||||||
| 			r += q.clone(); | 			r += q.clone(); | ||||||
| 			r.insert_unit(FreeUnit::from_whole(WholeUnit::Kelvin), Scalar::new_rational(1f64).unwrap()); | 			r.insert_unit(FreeUnit::from_whole(WholeUnit::Kelvin), Scalar::new_rational(1f64).unwrap()); | ||||||
|  |  | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, r)); | 			return Ok(Some(Expression::Quantity(*loc + *l, r))); | ||||||
| 		}, | 		}, | ||||||
| 		Function::FromFahrenheit => { | 		Function::FromFahrenheit => { | ||||||
| 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | 			if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} | ||||||
| @ -191,7 +190,7 @@ pub fn eval_function(g: &Expression) -> Result<Expression, (LineLocation, DaisyE | |||||||
| 			r *= Quantity::new_rational_from_frac(5i64, 9i64).unwrap(); | 			r *= Quantity::new_rational_from_frac(5i64, 9i64).unwrap(); | ||||||
| 			r.insert_unit(FreeUnit::from_whole(WholeUnit::Kelvin), Scalar::new_rational(1f64).unwrap()); | 			r.insert_unit(FreeUnit::from_whole(WholeUnit::Kelvin), Scalar::new_rational(1f64).unwrap()); | ||||||
|  |  | ||||||
| 			return Ok(Expression::Quantity(*loc + *l, r)); | 			return Ok(Some(Expression::Quantity(*loc + *l, r))); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @ -75,12 +75,26 @@ pub fn eval_operator(context: &mut Context, g: &Expression) -> Result<Option<Exp | |||||||
| 			if let Expression::Quantity(la, a) = a { | 			if let Expression::Quantity(la, a) = a { | ||||||
| 				if let Expression::Quantity(lb, b) = b { | 				if let Expression::Quantity(lb, b) = b { | ||||||
| 					if !a.unit.compatible_with(&b.unit) { | 					if !a.unit.compatible_with(&b.unit) { | ||||||
|  | 						let a = a.convert_to_base().unit; | ||||||
|  | 						let b = b.convert_to_base().unit; | ||||||
|  |  | ||||||
|  | 						let a_s: String; | ||||||
|  | 						let b_s: String; | ||||||
|  | 						if a.unitless() { | ||||||
|  | 							a_s = String::from("scalar"); | ||||||
|  | 						} else { | ||||||
|  | 							a_s = a.display(context); | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						if b.unitless() { | ||||||
|  | 							b_s = String::from("scalar"); | ||||||
|  | 						} else { | ||||||
|  | 							b_s = b.display(context); | ||||||
|  | 						} | ||||||
|  |  | ||||||
| 						return Err(( | 						return Err(( | ||||||
| 							*la + *lb + *op_loc, | 							*la + *lb + *op_loc, | ||||||
| 							DaisyError::IncompatibleUnits( | 							DaisyError::IncompatibleUnits(a_s, b_s) | ||||||
| 								a.convert_to_base().unit.display(context), |  | ||||||
| 								b.convert_to_base().unit.display(context) |  | ||||||
| 							) |  | ||||||
| 						)); | 						)); | ||||||
| 					} | 					} | ||||||
| 					return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, a.clone() + b.clone()))); | 					return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, a.clone() + b.clone()))); | ||||||
| @ -98,12 +112,24 @@ pub fn eval_operator(context: &mut Context, g: &Expression) -> Result<Option<Exp | |||||||
| 			if let Expression::Quantity(la, a) = a { | 			if let Expression::Quantity(la, a) = a { | ||||||
| 				if let Expression::Quantity(lb, b) = b { | 				if let Expression::Quantity(lb, b) = b { | ||||||
| 					if !a.unit.compatible_with(&b.unit) { | 					if !a.unit.compatible_with(&b.unit) { | ||||||
|  |  | ||||||
|  | 						let a_s: String; | ||||||
|  | 						let b_s: String; | ||||||
|  | 						if a.unitless() { | ||||||
|  | 							a_s = String::from("scalar"); | ||||||
|  | 						} else { | ||||||
|  | 							a_s = a.display(context); | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						if b.unitless() { | ||||||
|  | 							b_s = String::from("scalar"); | ||||||
|  | 						} else { | ||||||
|  | 							b_s = b.display(context); | ||||||
|  | 						} | ||||||
|  |  | ||||||
| 						return Err(( | 						return Err(( | ||||||
| 							*la + *lb + *op_loc, | 							*la + *lb + *op_loc, | ||||||
| 							DaisyError::IncompatibleUnits( | 							DaisyError::IncompatibleUnits(a_s, b_s) | ||||||
| 								a.convert_to_base().unit.display(context), |  | ||||||
| 								b.convert_to_base().unit.display(context) |  | ||||||
| 							) |  | ||||||
| 						)); | 						)); | ||||||
| 					} | 					} | ||||||
| 					return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, a.clone() - b.clone()))); | 					return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, a.clone() - b.clone()))); | ||||||
| @ -138,7 +164,8 @@ pub fn eval_operator(context: &mut Context, g: &Expression) -> Result<Option<Exp | |||||||
|  |  | ||||||
| 			if let Expression::Quantity(la, a) = a { | 			if let Expression::Quantity(la, a) = a { | ||||||
| 				if let Expression::Quantity(lb, b) = b { | 				if let Expression::Quantity(lb, b) = b { | ||||||
| 					return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, a.clone() * b.clone()))); | 					let o = a.clone() * b.clone(); | ||||||
|  | 					return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, o))); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| @ -162,6 +189,11 @@ pub fn eval_operator(context: &mut Context, g: &Expression) -> Result<Option<Exp | |||||||
| 					if va.fract() != Quantity::new_rational(0f64).unwrap() { return Err((*la + *lb + *op_loc, DaisyError::BadMath)); } | 					if va.fract() != Quantity::new_rational(0f64).unwrap() { return Err((*la + *lb + *op_loc, DaisyError::BadMath)); } | ||||||
| 					if vb.fract() != Quantity::new_rational(0f64).unwrap() { return Err((*la + *lb + *op_loc, DaisyError::BadMath)); } | 					if vb.fract() != Quantity::new_rational(0f64).unwrap() { return Err((*la + *lb + *op_loc, DaisyError::BadMath)); } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 					let o = va.clone() % vb.clone(); | ||||||
|  | 					if o.is_nan() {return Err((*la + *lb + *op_loc, DaisyError::BadMath));} | ||||||
|  |  | ||||||
|  |  | ||||||
| 					return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, va.clone() % vb.clone()))); | 					return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, va.clone() % vb.clone()))); | ||||||
| 				} else { return Ok(None); } | 				} else { return Ok(None); } | ||||||
| 			} else { return Ok(None); } | 			} else { return Ok(None); } | ||||||
| @ -176,12 +208,26 @@ pub fn eval_operator(context: &mut Context, g: &Expression) -> Result<Option<Exp | |||||||
| 				if let Expression::Quantity(lb, vb) = b { | 				if let Expression::Quantity(lb, vb) = b { | ||||||
| 					let n = va.clone().convert_to(vb.clone()); | 					let n = va.clone().convert_to(vb.clone()); | ||||||
| 					if n.is_none() { | 					if n.is_none() { | ||||||
|  | 						let va = va.convert_to_base().unit; | ||||||
|  | 						let vb = vb.convert_to_base().unit; | ||||||
|  |  | ||||||
|  | 						let a_s: String; | ||||||
|  | 						let b_s: String; | ||||||
|  | 						if va.unitless() { | ||||||
|  | 							a_s = String::from("scalar"); | ||||||
|  | 						} else { | ||||||
|  | 							a_s = a.display(context); | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						if vb.unitless() { | ||||||
|  | 							b_s = String::from("scalar"); | ||||||
|  | 						} else { | ||||||
|  | 							b_s = b.display(context); | ||||||
|  | 						} | ||||||
|  |  | ||||||
| 						return Err(( | 						return Err(( | ||||||
| 							*la + *lb + *op_loc, | 							*la + *lb + *op_loc, | ||||||
| 							DaisyError::IncompatibleUnits( | 							DaisyError::IncompatibleUnits(a_s, b_s) | ||||||
| 								va.convert_to_base().unit.display(context), |  | ||||||
| 								vb.convert_to_base().unit.display(context) |  | ||||||
| 							) |  | ||||||
| 						)); | 						)); | ||||||
| 					} | 					} | ||||||
| 					return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, n.unwrap()))); | 					return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, n.unwrap()))); | ||||||
|  | |||||||
| @ -22,7 +22,8 @@ impl ToString for FormattedText { | |||||||
| fn format_map_none(c: char) -> Option<String> { | fn format_map_none(c: char) -> Option<String> { | ||||||
| 	Some(match c { | 	Some(match c { | ||||||
| 		'n'|'i'|'t'|'a'| | 		'n'|'i'|'t'|'a'| | ||||||
| 		'e'|'c'|'s'|'r' | 		'e'|'c'|'s'|'r'| | ||||||
|  | 		'p' | ||||||
| 		=> { "".to_string() }, | 		=> { "".to_string() }, | ||||||
| 		_ => { return None } | 		_ => { return None } | ||||||
| 	}) | 	}) | ||||||
| @ -37,34 +38,39 @@ fn format_map_ansi(c: char) -> Option<String> { | |||||||
| 		'i' => { // Normal italic text | 		'i' => { // Normal italic text | ||||||
| 			format!("{}{}", color::Fg(color::Reset), color::Bg(color::Reset)) | 			format!("{}{}", color::Fg(color::Reset), color::Bg(color::Reset)) | ||||||
| 		}, | 		}, | ||||||
| 		't' => { // Title text | 		't' => { // Title text (should be cyan) | ||||||
| 			format!("{}{}", color::Fg(color::AnsiValue(6)), color::Bg(color::Reset)) | 			format!("{}{}", color::Fg(color::AnsiValue(6)), color::Bg(color::Reset)) | ||||||
| 		}, | 		}, | ||||||
| 		'a' => { // Colored text | 		'a' => { // Colored text (should be pink) | ||||||
| 			format!("{}{}", color::Fg(color::AnsiValue(5)), color::Bg(color::Reset)) | 			format!("{}{}", color::Fg(color::AnsiValue(5)), color::Bg(color::Reset)) | ||||||
| 		}, | 		}, | ||||||
| 		'e' => { // Error titles | 		'e' => { // Error titles (should be red) | ||||||
| 			format!("{}{}", color::Fg(color::AnsiValue(1)), color::Bg(color::Reset)) | 			format!("{}{}", color::Fg(color::AnsiValue(1)), color::Bg(color::Reset)) | ||||||
| 		}, | 		}, | ||||||
| 		'c' => { // Console text | 		'c' => { // Console text (inverted black on white) | ||||||
| 			format!("{}{}", color::Fg(color::AnsiValue(0)), color::Bg(color::AnsiValue(7))) | 			format!("{}{}", color::Fg(color::AnsiValue(0)), color::Bg(color::AnsiValue(7))) | ||||||
| 		}, | 		}, | ||||||
| 		's' => { // Repeat prompt (how => is styled) | 		'p' => { // Input prompt (how ==> is styled) (should be blue) | ||||||
| 			format!("{}{}", color::Fg(color::AnsiValue(2)), color::Bg(color::Reset)) |  | ||||||
| 		}, |  | ||||||
| 		'r' => { // Result prompt (how = is styled) |  | ||||||
| 			format!("{}{}", color::Fg(color::AnsiValue(4)), color::Bg(color::Reset)) | 			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 } | 		_ => { return None } | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | // style::reset also resets color. | ||||||
|  | // Make sure color comes AFTER style reset. | ||||||
| fn format_map_full(c: char) -> Option<String> { | fn format_map_full(c: char) -> Option<String> { | ||||||
| 	Some(match c { | 	Some(match c { | ||||||
| 		'n' => { // Normal text | 		'n' => { // Normal text | ||||||
| 			format!("{}{}", color::Fg(color::Reset), style::Reset) | 			format!("{}{}", style::Reset, color::Fg(color::Reset)) | ||||||
| 		}, | 		}, | ||||||
| 		'i' => { // Normal italic text | 		'i' => { // Normal italic text | ||||||
| 			format!("{}{}", color::Fg(color::Reset), style::Italic) | 			format!("{}{}", color::Fg(color::Reset), style::Italic) | ||||||
| @ -73,7 +79,7 @@ fn format_map_full(c: char) -> Option<String> { | |||||||
| 			format!("{}{}", color::Fg(color::Magenta), style::Bold) | 			format!("{}{}", color::Fg(color::Magenta), style::Bold) | ||||||
| 		}, | 		}, | ||||||
| 		'a' => { // Colored text | 		'a' => { // Colored text | ||||||
| 			format!("{}{}", color::Fg(color::Magenta), style::Reset) | 			format!("{}{}", style::Reset, color::Fg(color::Magenta)) | ||||||
| 		}, | 		}, | ||||||
| 		'e' => { // Error titles | 		'e' => { // Error titles | ||||||
| 			format!("{}{}", color::Fg(color::Red), style::Bold) | 			format!("{}{}", color::Fg(color::Red), style::Bold) | ||||||
| @ -81,6 +87,9 @@ fn format_map_full(c: char) -> Option<String> { | |||||||
| 		'c' => { // Console text | 		'c' => { // Console text | ||||||
| 			format!("{}{}", color::Fg(color::LightBlack), style::Italic) | 			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) | 		's' => { // Repeat prompt (how => is styled) | ||||||
| 			format!("{}{}", color::Fg(color::Magenta), style::Bold) | 			format!("{}{}", color::Fg(color::Magenta), style::Bold) | ||||||
| 		}, | 		}, | ||||||
| @ -88,19 +97,28 @@ fn format_map_full(c: char) -> Option<String> { | |||||||
| 			format!("{}{}", color::Fg(color::Green), style::Bold) | 			format!("{}{}", color::Fg(color::Green), style::Bold) | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  |  | ||||||
| 		_ => { return None } | 		_ => { return None } | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| impl FormattedText { | impl FormattedText { | ||||||
| 	pub fn newline(stdout: &mut RawTerminal<std::io::Stdout>) -> Result<(), std::io::Error> { | 	pub fn newline(stdout: &mut RawTerminal<std::io::Stdout>) -> Result<(), std::io::Error> { | ||||||
| 		write!( | 		write!(stdout, "\n")?; | ||||||
| 			stdout, |  | ||||||
| 			"\r\n", |  | ||||||
| 		)?; |  | ||||||
| 		return Ok(()); | 		return Ok(()); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	pub fn format_map(c: char, context: &Context) -> Option<String> { | ||||||
|  | 		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") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -145,12 +163,7 @@ impl FormattedText { | |||||||
| 					match (a, b) { | 					match (a, b) { | ||||||
| 						(c, ']') => { // Normal text | 						(c, ']') => { // Normal text | ||||||
|  |  | ||||||
| 							let q = match context.config.term_color_type { | 							let q = Self::format_map(c, context); | ||||||
| 								0 => format_map_none(c), |  | ||||||
| 								1 => format_map_ansi(c), |  | ||||||
| 								2 => format_map_full(c), |  | ||||||
| 								_ => unreachable!("Invalid term_color_type") |  | ||||||
| 							}; |  | ||||||
|  |  | ||||||
| 							if q.is_some() { | 							if q.is_some() { | ||||||
| 								s.push_str(&q.unwrap()); | 								s.push_str(&q.unwrap()); | ||||||
| @ -173,7 +186,7 @@ impl FormattedText { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		write!(stdout, "{}", s)?; | 		write!(stdout, "\r{}", s)?; | ||||||
| 		return Ok(()); | 		return Ok(()); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										283
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,283 @@ | |||||||
|  | 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; | ||||||
|  |  | ||||||
|  | pub use crate::formattedtext::FormattedText; | ||||||
|  | pub use crate::context::Context; | ||||||
|  | pub use crate::errors::DaisyError; | ||||||
|  | pub use crate::evaluate::evaluate; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #[inline(always)] | ||||||
|  | pub fn do_string( | ||||||
|  | 	context: &mut Context, | ||||||
|  | 	s: &String | ||||||
|  | ) -> Result<FormattedText, FormattedText> { | ||||||
|  |  | ||||||
|  | 	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<FormattedText, (LineLocation, DaisyError)> { | ||||||
|  |  | ||||||
|  | 	let mut output = FormattedText::new("".to_string()); | ||||||
|  |  | ||||||
|  | 	let parts = s.split("=").collect::<Vec<&str>>(); | ||||||
|  | 	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::<Vec<&str>>() | ||||||
|  | 			.iter().map(|x| x.trim().to_string()).collect::<Vec<String>>(); | ||||||
|  |  | ||||||
|  | 		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); | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										372
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										372
									
								
								src/main.rs
									
									
									
									
									
								
							| @ -1,289 +1,149 @@ | |||||||
| pub mod parser; | pub mod promptbuffer; | ||||||
| pub mod command; |  | ||||||
| pub mod quantity; |  | ||||||
| pub mod evaluate; |  | ||||||
| pub mod context; |  | ||||||
| pub mod errors; |  | ||||||
| pub mod formattedtext; |  | ||||||
|  |  | ||||||
| use crate::parser::substitute; | use std::io::stdout; | ||||||
| use crate::errors::DaisyError; | use std::io::stdin; | ||||||
| use crate::formattedtext::FormattedText; | use std::env; | ||||||
| use crate::context::Context; |  | ||||||
| use crate::parser::LineLocation; |  | ||||||
|  |  | ||||||
|  | use termion::{ | ||||||
|  | 	event::Key, | ||||||
|  | 	input::TermRead, | ||||||
|  | 	raw::IntoRawMode, | ||||||
|  | 	color::DetectColors | ||||||
|  | }; | ||||||
|  |  | ||||||
| // Run main script for target system | use promptbuffer::PromptBuffer; | ||||||
| mod entrypoint; | use daisycalc::command; | ||||||
| use crate::entrypoint::main_e; | use daisycalc::Context; | ||||||
|  | use daisycalc::FormattedText; | ||||||
|  |  | ||||||
|  | use daisycalc::do_string; | ||||||
|  |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests; | mod tests; | ||||||
|  |  | ||||||
| fn main() -> Result<(), std::io::Error> { |  | ||||||
| 	return main_e(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[inline(always)] | #[inline(always)] | ||||||
| pub fn do_string( | pub fn main() -> Result<(), std::io::Error> { | ||||||
| 	context: &mut Context, | 	let mut stdout = stdout().into_raw_mode().unwrap(); | ||||||
| 	s: &String | 	let mut pb: PromptBuffer = PromptBuffer::new(64); | ||||||
| ) -> Result<FormattedText, FormattedText> { | 	let mut context = Context::new(); | ||||||
|  |  | ||||||
| 	let r: (LineLocation, DaisyError); | 	// Detect color compatibilty | ||||||
| 	if command::is_command(s) { | 	// Currently unused, this is slow. | ||||||
| 		return Ok(command::do_command(context, s)); | 	/* | ||||||
| 	} else if s.contains("=") { | 	let term_colors = stdout.available_colors().unwrap_or(0); | ||||||
| 		let x = do_assignment(context, s); | 	if term_colors >= 256 { | ||||||
| 		match x { | 		context.config.term_color_type = 2; | ||||||
| 			Ok(t) => { return Ok(t) }, | 	} else if term_colors >= 8 { | ||||||
| 			Err(t) => { r = t } | 		context.config.term_color_type = 1; | ||||||
| 		}; |  | ||||||
| 	} else { | 	} else { | ||||||
| 		let x = do_expression(context, s); | 		context.config.term_color_type = 0; | ||||||
| 		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()); | 	// Handle command-line arguments | ||||||
| 	if l.zero() { | 	let args: Vec<String> = env::args().collect(); | ||||||
| 		t.push(&format!( | 	if args.iter().any(|s| s == "--help") { | ||||||
| 			"\n  {}\n\n", | 		let t = command::do_command(&mut context, &String::from("help")); | ||||||
| 			e.text().to_string(), | 		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.write(&context, &mut stdout)?; | ||||||
| 		t.push(&format!( | 		return Ok(()); | ||||||
|  | 	} else if args.iter().any(|s| s == "--info") { | ||||||
|  | 		let t = FormattedText::new(format!( | ||||||
| 			concat!( | 			concat!( | ||||||
| 				"{}[e]{}[n]\n", | 				"Daisy v{}\n", | ||||||
| 				"  {}\n\n" | 				"Your terminal supports {} colors.\n" | ||||||
| 			), | 			), | ||||||
| 			" ".repeat(l.pos + 4), | 			env!("CARGO_PKG_VERSION"), | ||||||
| 			"^".repeat(l.len), | 			stdout.available_colors().unwrap_or(0) | ||||||
| 			e.text().to_string(), |  | ||||||
| 		)); | 		)); | ||||||
|  | 		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); | 	context.config.check(); | ||||||
| } |  | ||||||
|  |  | ||||||
| // 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. | 	'outer: loop { | ||||||
| // Returns a FormattedText with output that should be printed. |  | ||||||
| #[inline(always)] |  | ||||||
| fn do_assignment( |  | ||||||
| 	context: &mut Context, |  | ||||||
| 	s: &String |  | ||||||
| ) -> Result<FormattedText, (LineLocation, DaisyError)> { |  | ||||||
|  |  | ||||||
| 	let mut output = FormattedText::new("".to_string()); | 		pb.write_prompt(&mut context, &mut stdout)?; | ||||||
|  |  | ||||||
| 	let parts = s.split("=").collect::<Vec<&str>>(); | 		let stdin = stdin(); | ||||||
| 	if parts.len() != 2 { | 		for c in stdin.keys() { | ||||||
| 		return Err(( | 			if let Key::Char(q) = c.as_ref().unwrap() { | ||||||
| 			LineLocation::new_zero(), | 				match q { | ||||||
| 			DaisyError::Syntax | 					'\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; } | ||||||
|  |  | ||||||
| 	// Index of first non-whitespace character in left | 						if in_str.trim() == "quit" { | ||||||
| 	// (relative to whole prompt) | 							break 'outer; | ||||||
| 	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::<Vec<&str>>() |  | ||||||
| 			.iter().map(|x| x.trim().to_string()).collect::<Vec<String>>(); |  | ||||||
|  |  | ||||||
| 		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 { | 						} else { | ||||||
|  | 							let r = crate::do_string(&mut context, &in_str); | ||||||
|  |  | ||||||
| 		if !context.valid_varible(&left) { | 							match r { | ||||||
| 			return Err(( | 								Ok(t) | Err(t) => { | ||||||
| 				LineLocation{ pos: starting_left, len: left.chars().count() }, | 									t.write(&context, &mut stdout).unwrap(); | ||||||
| 				DaisyError::BadVariable | 								} | ||||||
| 			)); | 							} | ||||||
| 						} | 						} | ||||||
|  |  | ||||||
| 		// Parse right hand side | 						break; | ||||||
| 		let g = parser::parse(context, &right); | 					}, | ||||||
| 		let Ok(g) = g else { |  | ||||||
| 			let Err((l, e)) = g else { unreachable!() }; | 					// Only process sane characters | ||||||
| 			return Err(( | 					'a'..='z' | 'A'..='Z' | '0'..='9' | ||||||
| 				LineLocation{ pos: l.pos + starting_right, len: l.len}, | 					|'!'|'@'|'#'|'$'|'%'|'^'|'&'|'*'|'('|')' | ||||||
| 				e | 					|'?'|'~'|','|'.'|'['|']' | ||||||
| 			)); | 					|'<'|'>'|'/'|'_'|'-'|':'|'|'|'='|'+'|';' | ||||||
|  | 					=> { 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; }, | ||||||
|  | 					_ => {} | ||||||
|  | 				}; | ||||||
| 			}; | 			}; | ||||||
|  |  | ||||||
| 		// Display parsed string | 			pb.write_prompt(&mut context, &mut stdout)?; | ||||||
| 		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(()); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -11,6 +11,23 @@ use super::super::LineLocation; | |||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub enum Expression { | pub enum Expression { | ||||||
|  | 	// Meaning of `LineLocation`: | ||||||
|  | 	// | ||||||
|  | 	// For Variables, Constants, Quantities, Tuples: | ||||||
|  | 	// If this expression was parsed, LineLocation is what part of the prompt was parsed to get this expression | ||||||
|  | 	// If this expression is the result of a calculation, LineLocaion is the sum of the LineLocations of | ||||||
|  | 	//	all expressions used to make it. In other words, it points to the part of the prompt that was evaluated | ||||||
|  | 	//	to get this new expression. | ||||||
|  | 	// | ||||||
|  | 	// For Operators: | ||||||
|  | 	// Linelocation points to the operator's position in the prompt. | ||||||
|  | 	// If this is a function, it points to the function name. | ||||||
|  | 	// If this is `+`, `!`, or etc, it points to that character. | ||||||
|  | 	// Operator arguments are NOT included in this linelocation. | ||||||
|  | 	// | ||||||
|  | 	// | ||||||
|  | 	// All the above rules are implemented when parsing and evaluating expressions. | ||||||
|  |  | ||||||
| 	Variable(LineLocation, String), | 	Variable(LineLocation, String), | ||||||
| 	Quantity(LineLocation, Quantity), | 	Quantity(LineLocation, Quantity), | ||||||
| 	Constant(LineLocation, Constant), | 	Constant(LineLocation, Constant), | ||||||
|  | |||||||
| @ -21,8 +21,10 @@ pub fn parse( | |||||||
| 	context: &Context, s: &String | 	context: &Context, s: &String | ||||||
| ) -> Result<Expression, (LineLocation, DaisyError)> { | ) -> Result<Expression, (LineLocation, DaisyError)> { | ||||||
|  |  | ||||||
| 	let expressions = stage::tokenize(context, s); | 	let mut expressions = stage::tokenize(context, s); | ||||||
| 	let (_, expressions) = stage::find_subs(expressions); | 	if context.config.enable_substituion { | ||||||
|  | 		(_, expressions) = stage::find_subs(expressions); | ||||||
|  | 	} | ||||||
| 	let g = stage::groupify(context, expressions)?; | 	let g = stage::groupify(context, expressions)?; | ||||||
| 	let g = stage::treeify(context, g)?; | 	let g = stage::treeify(context, g)?; | ||||||
|  |  | ||||||
| @ -33,7 +35,14 @@ pub fn parse_no_context(s: &String) -> Result<Expression, (LineLocation, DaisyEr | |||||||
| 	parse(&Context::new(), s) | 	parse(&Context::new(), s) | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | // Substitiution replaces certain string with pretty unicode characters. | ||||||
|  | // When it is enabled, ALL input strings are substituted. Variable and | ||||||
|  | // operator tokens use the replaced string value. Make sure both the | ||||||
|  | // original and the replaced strings are handled correctly by the parser. | ||||||
| pub fn substitute(context: &Context, s: &String) -> String { | pub fn substitute(context: &Context, s: &String) -> String { | ||||||
|  | 	if !context.config.enable_substituion { return s.clone(); } | ||||||
| 	let (_, s) = substitute_cursor(context, s, s.chars().count()); | 	let (_, s) = substitute_cursor(context, s, s.chars().count()); | ||||||
| 	return s; | 	return s; | ||||||
| } | } | ||||||
| @ -43,34 +52,45 @@ pub fn substitute_cursor( | |||||||
| 	s: &String, // The string to substitute | 	s: &String, // The string to substitute | ||||||
| 	c: usize    // Location of the cursor right now | 	c: usize    // Location of the cursor right now | ||||||
| ) -> ( | ) -> ( | ||||||
| 	usize,  // Location of cursor in substituted string | 	usize,  // New cursor | ||||||
| 	String  // String with substitutions | 	String  // String with substitutions | ||||||
| ) { | ) { | ||||||
|  |  | ||||||
|  | 	if !context.config.enable_substituion { return (c, s.clone()); } | ||||||
| 	if s == "" { return (c, s.clone()) } | 	if s == "" { return (c, s.clone()) } | ||||||
|  | 	 | ||||||
|  | 	 | ||||||
| 	let mut new_s = s.clone(); | 	let mut new_s = s.clone(); | ||||||
|  |  | ||||||
| 	let l = s.chars().count(); |  | ||||||
| 	let expressions = stage::tokenize(context, s); | 	let expressions = stage::tokenize(context, s); | ||||||
| 	let (mut subs, _) = stage::find_subs(expressions); | 	let (mut subs, _) = stage::find_subs(expressions); | ||||||
| 	let mut new_c = l - c; | 	let mut new_c = c.clone(); | ||||||
|  |  | ||||||
| 	while subs.len() > 0 { | 	while subs.len() > 0 { | ||||||
| 		let r = subs.pop_back().unwrap(); |  | ||||||
| 		// Apply substitutions in reverse order | 		// Apply substitutions in reverse order | ||||||
|  | 		// r is the current substitution: (linelocation, string) | ||||||
|  | 		let r = subs.pop_back().unwrap(); | ||||||
|  |  | ||||||
| 		if { // Don't substitute if our cursor is inside the substitution | 		if { // Don't substitute if our cursor is inside the substitution | ||||||
| 			c >= r.0.pos && | 			c >= r.0.pos && | ||||||
| 			c < r.0.pos+r.0.len | 			c < r.0.pos+r.0.len | ||||||
| 		} { continue; } | 		} { continue; } | ||||||
|  |  | ||||||
| 		if c < r.0.pos { | 		// If this substitution is before our cursor, | ||||||
| 			let ct = r.1.chars().count(); | 		// we need to adjust our cursor's position. | ||||||
| 			if ct >= r.0.len { | 		if c > r.0.pos { | ||||||
| 				if new_c >= ct - r.0.len { | 			let c_o = r.0.len; // Old length  | ||||||
| 					new_c += ct - r.0.len | 			let c_n = r.1.chars().count(); // New length | ||||||
| 				} |  | ||||||
| 			} else { | 			if c_n > c_o { | ||||||
| 				new_c -= r.0.len - ct | 				// Move cursor right by difference | ||||||
|  | 				new_c += c_n - c_o; | ||||||
|  |  | ||||||
|  | 			} else if c_n < c_o { | ||||||
|  | 				// Move cursor left by difference | ||||||
|  | 				if new_c >= c_o - c_n { | ||||||
|  | 					new_c -= c_o - c_n; | ||||||
|  | 				} else { new_c = 0; } | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | |||||||
| @ -6,11 +6,69 @@ use super::super::{ | |||||||
| }; | }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | fn sub_string(s: &str) -> Option<&'static str> { | ||||||
|  | 	let r = match s { | ||||||
|  |  | ||||||
|  | 		/* Only found in operator tokens */ | ||||||
|  |  | ||||||
|  | 		"*"    => "×", | ||||||
|  | 		"/"    => "÷", | ||||||
|  | 		"sqrt" => "√", | ||||||
|  | 		"rt"   => "√", | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		/* Only found in word tokens */ | ||||||
|  |  | ||||||
|  | 		// Greek letters | ||||||
|  | 		"alpha"   => "α", | ||||||
|  | 		"beta"    => "β", | ||||||
|  | 		"gamma"   => "γ", | ||||||
|  | 		"delta"   => "δ", | ||||||
|  | 		"epsilon" => "ε", | ||||||
|  | 		"zeta"    => "ζ", | ||||||
|  | 		"eta"     => "η", | ||||||
|  | 		"theta"   => "θ", | ||||||
|  | 		//"iota"    => {Some("ι")}, // looks just like i | ||||||
|  | 		//"kappa"   => {Some("κ")}, // looks just like k | ||||||
|  | 		"lambda"  => "λ", | ||||||
|  | 		"mu"      => "μ", | ||||||
|  | 		//"nu"      => {Some("ν")}, // looks just like v | ||||||
|  | 		"xi"      => "ξ", | ||||||
|  | 		//"omicron" => {Some("ο")}, // looks exactly like o | ||||||
|  | 		"pi"      => "π", | ||||||
|  | 		"rho"     => "ρ", | ||||||
|  | 		"sigma"   => "σ", | ||||||
|  | 		"tau"     => "τ", | ||||||
|  | 		//"upsilon" => {Some("υ")}, // looks just like u | ||||||
|  | 		"phi"     => "φ", | ||||||
|  | 		"chi"     => "χ", | ||||||
|  | 		//"psi"     => {Some("ψ")},  Conflict with pound / square inch | ||||||
|  | 		"omega"   => "ω", | ||||||
|  |  | ||||||
|  | 		// Constants | ||||||
|  | 		"epsilon_zero" => "ε₀", | ||||||
|  | 		"eps_zero"     => "ε₀", | ||||||
|  | 		"g_zero"       => "g₀", | ||||||
|  | 		"mu_zero"      => "μ₀", | ||||||
|  | 		"h_bar"        => "ℏ", | ||||||
|  |  | ||||||
|  | 		// Misc | ||||||
|  | 		"deg" => "°", | ||||||
|  |  | ||||||
|  | 		_ => { return None; } | ||||||
|  | 	}; | ||||||
|  | 	return Some(r); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | // Finds substitutions in an array of tokens. | ||||||
|  | // Returns new token array and substitution list. | ||||||
| pub fn find_subs( | pub fn find_subs( | ||||||
| 	mut g: VecDeque<Token>, | 	mut g: VecDeque<Token>, | ||||||
| ) -> ( | ) -> ( | ||||||
| 	VecDeque<(LineLocation, String)>, | 	VecDeque<(LineLocation, String)>, // List of substrings to replace (in order) | ||||||
| 	VecDeque<Token> | 	VecDeque<Token> // New token array, with updated strings and linelocations | ||||||
| ) { | ) { | ||||||
|  |  | ||||||
| 	// Array of replacements | 	// Array of replacements | ||||||
| @ -24,62 +82,19 @@ pub fn find_subs( | |||||||
| 	while g.len() > 0 { | 	while g.len() > 0 { | ||||||
| 		let mut t = g.pop_front().unwrap(); | 		let mut t = g.pop_front().unwrap(); | ||||||
|  |  | ||||||
|  |  | ||||||
| 		let target: Option<&str> = match &mut t { | 		let target: Option<&str> = match &mut t { | ||||||
| 			Token::Operator(_, s) => { | 			Token::Operator(_, s) => { | ||||||
| 				let target = match &s[..] { | 				let target = sub_string(s); | ||||||
| 					"*" => {Some("×")}, |  | ||||||
| 					"/" => {Some("÷")}, |  | ||||||
| 					"sqrt"    => {Some("√")}, |  | ||||||
| 					"rt"      => {Some("√")}, |  | ||||||
| 					_ => {None} |  | ||||||
| 				}; |  | ||||||
|  |  | ||||||
| 				// Update token contents too. | 				// Update token contents too. | ||||||
| 				// This makes sure that errors also contain the updated text. | 				// This makes errors and printouts use the updated string. | ||||||
| 				if target.is_some() { *s = String::from(target.unwrap()); } | 				if target.is_some() { *s = String::from(target.unwrap()); } | ||||||
| 				target | 				target | ||||||
| 			}, | 			}, | ||||||
|  |  | ||||||
| 			Token::Word(_, s) => { | 			Token::Word(_, s) => { | ||||||
| 				let target = match &s[..] { | 				let target = sub_string(s); | ||||||
| 					// Greek letters |  | ||||||
| 					"alpha"   => {Some("α")}, |  | ||||||
| 					"beta"    => {Some("β")}, |  | ||||||
| 					"gamma"   => {Some("γ")}, |  | ||||||
| 					"delta"   => {Some("δ")}, |  | ||||||
| 					"epsilon" => {Some("ε")}, |  | ||||||
| 					"zeta"    => {Some("ζ")}, |  | ||||||
| 					"eta"     => {Some("η")}, |  | ||||||
| 					"theta"   => {Some("θ")}, |  | ||||||
| 					//"iota"    => {Some("ι")}, |  | ||||||
| 					//"kappa"   => {Some("κ")}, |  | ||||||
| 					"lambda"  => {Some("λ")}, |  | ||||||
| 					"mu"      => {Some("μ")}, |  | ||||||
| 					//"nu"      => {Some("ν")}, |  | ||||||
| 					"xi"      => {Some("ξ")}, |  | ||||||
| 					//"omicron" => {Some("ο")}, |  | ||||||
| 					"pi"      => {Some("π")}, |  | ||||||
| 					"rho"     => {Some("ρ")}, |  | ||||||
| 					"sigma"   => {Some("σ")}, |  | ||||||
| 					"tau"     => {Some("τ")}, |  | ||||||
| 					//"upsilon" => {Some("υ")}, |  | ||||||
| 					"phi"     => {Some("φ")}, |  | ||||||
| 					"chi"     => {Some("χ")}, |  | ||||||
| 					//"psi"     => {Some("ψ")},  Conflict with pound / square inch |  | ||||||
| 					"omega"   => {Some("ω")}, |  | ||||||
|  |  | ||||||
| 					// Constants |  | ||||||
| 					"epsilon_zero" => {Some("ε₀")}, |  | ||||||
| 					"eps_zero"     => {Some("ε₀")}, |  | ||||||
| 					"g_zero"       => {Some("g₀")}, |  | ||||||
| 					"mu_zero"      => {Some("μ₀")}, |  | ||||||
| 					"h_bar"        => {Some("ℏ")}, |  | ||||||
|  |  | ||||||
| 					// Misc |  | ||||||
| 					"deg" => {Some("°")} |  | ||||||
| 					_ => {None} |  | ||||||
| 				}; |  | ||||||
|  |  | ||||||
| 				if target.is_some() { *s = String::from(target.unwrap()); } | 				if target.is_some() { *s = String::from(target.unwrap()); } | ||||||
| 				target | 				target | ||||||
| 			}, | 			}, | ||||||
| @ -88,7 +103,7 @@ pub fn find_subs( | |||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		if target.is_none() { | 		if target.is_none() { | ||||||
| 			// Even if nothing changed, we need to update token location | 			// Even if nothing changed, we need to update the new token's linelocation | ||||||
| 			let l = t.get_mut_linelocation(); | 			let l = t.get_mut_linelocation(); | ||||||
| 			*l = LineLocation{pos: l.pos - offset, len: l.len}; | 			*l = LineLocation{pos: l.pos - offset, len: l.len}; | ||||||
| 		} else { | 		} else { | ||||||
|  | |||||||
| @ -1,10 +1,11 @@ | |||||||
| use std::collections::VecDeque; | use std::collections::VecDeque; | ||||||
| use std::io::Write; | use std::io::Write; | ||||||
| use termion::raw::RawTerminal; | use termion::raw::RawTerminal; | ||||||
| use termion::color; | use daisycalc::FormattedText; | ||||||
| use termion::style; | use daisycalc::parser::substitute_cursor; | ||||||
| use crate::parser::substitute_cursor; | use daisycalc::Context; | ||||||
| use crate::context::Context; | 
 | ||||||
|  | const PROMPT_STR: &str = "==> "; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub struct PromptBuffer { | pub struct PromptBuffer { | ||||||
| @ -38,30 +39,11 @@ impl PromptBuffer { | |||||||
| 
 | 
 | ||||||
| 	// Same as write_primpt, but pretends there is no cursor
 | 	// Same as write_primpt, but pretends there is no cursor
 | ||||||
| 	pub fn write_prompt_nocursor(&mut self, context: &Context, stdout: &mut RawTerminal<std::io::Stdout>) -> Result<(), std::io::Error> { | 	pub fn write_prompt_nocursor(&mut self, context: &Context, stdout: &mut RawTerminal<std::io::Stdout>) -> Result<(), std::io::Error> { | ||||||
| 		// Draw prettyprinted expression
 | 		let tmp = self.cursor; | ||||||
| 		let (_, s) = substitute_cursor(context, &self.get_contents(), self.buffer.chars().count()); | 		self.cursor = 0; | ||||||
| 
 | 		let r = self.write_prompt(context, stdout); | ||||||
| 		write!( | 		self.cursor = tmp; | ||||||
| 			stdout, "\r{}{}==>{}{} {}", | 		return r; | ||||||
| 			style::Bold, |  | ||||||
| 			color::Fg(color::Blue), |  | ||||||
| 			color::Fg(color::Reset), |  | ||||||
| 			style::Reset, |  | ||||||
| 			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()), |  | ||||||
| 				termion::cursor::Left((self.last_print_len - s.chars().count()) as u16) |  | ||||||
| 			)?; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		self.last_print_len = s.chars().count(); |  | ||||||
| 		stdout.flush()?; |  | ||||||
| 		return Ok(()); |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	pub fn write_prompt(&mut self, context: &Context, stdout: &mut RawTerminal<std::io::Stdout>) -> Result<(), std::io::Error> { | 	pub fn write_prompt(&mut self, context: &Context, stdout: &mut RawTerminal<std::io::Stdout>) -> Result<(), std::io::Error> { | ||||||
| @ -69,38 +51,30 @@ impl PromptBuffer { | |||||||
| 		let i = if l == 0 {0} else {l - self.cursor}; | 		let i = if l == 0 {0} else {l - self.cursor}; | ||||||
| 
 | 
 | ||||||
| 		// Draw prettyprinted expression
 | 		// Draw prettyprinted expression
 | ||||||
| 		let (display_cursor, s) = substitute_cursor(context, &self.get_contents(), i); | 		let (display_c, s) = substitute_cursor(context, &self.get_contents(), i); | ||||||
| 	
 | 	
 | ||||||
| 		write!( | 		write!( | ||||||
| 			stdout, "\r{}{}==>{}{} {}", | 			stdout, "\r{}{PROMPT_STR}{}{}", | ||||||
| 			style::Bold, | 			FormattedText::format_map('p', context).unwrap(), | ||||||
| 			color::Fg(color::Blue), | 			FormattedText::format_map('n', context).unwrap(), | ||||||
| 			color::Fg(color::Reset), |  | ||||||
| 			style::Reset, |  | ||||||
| 			s | 			s | ||||||
| 		)?; | 		)?; | ||||||
| 
 | 
 | ||||||
| 		// If this string is shorter, clear the remaining old one.
 | 		// If this string is shorter, clear the remaining old one.
 | ||||||
| 		if s.chars().count() < self.last_print_len { | 		if s.chars().count() < self.last_print_len { | ||||||
| 			write!( |  | ||||||
| 				stdout, "{}{}", |  | ||||||
| 				" ".repeat(self.last_print_len - s.chars().count()), |  | ||||||
| 				termion::cursor::Left((self.last_print_len - s.chars().count()) as u16) |  | ||||||
| 			)?; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 		// Move cursor to correct position
 |  | ||||||
| 		if display_cursor != 0 { |  | ||||||
| 			write!( | 			write!( | ||||||
| 				stdout, "{}", | 				stdout, "{}", | ||||||
| 				termion::cursor::Left(display_cursor as u16) | 				" ".repeat(self.last_print_len - s.chars().count()), | ||||||
| 			)?; | 			)?; | ||||||
| 			stdout.flush()?; |  | ||||||
| 		} | 		} | ||||||
| 		self.last_print_len = s.chars().count(); | 
 | ||||||
|  | 		write!( | ||||||
|  | 			stdout, "\r{}", | ||||||
|  | 			termion::cursor::Right((display_c + PROMPT_STR.chars().count()) as u16) | ||||||
|  | 		)?; | ||||||
| 
 | 
 | ||||||
| 		stdout.flush()?; | 		stdout.flush()?; | ||||||
|  | 		self.last_print_len = s.chars().count(); | ||||||
| 
 | 
 | ||||||
| 		return Ok(()); | 		return Ok(()); | ||||||
| 	} | 	} | ||||||
| @ -8,6 +8,7 @@ use std::ops::{ | |||||||
|  |  | ||||||
| use std::cmp::Ordering; | use std::cmp::Ordering; | ||||||
| use super::ScalarBase; | use super::ScalarBase; | ||||||
|  | use super::dec_to_sci; | ||||||
|  |  | ||||||
|  |  | ||||||
| macro_rules! foward { | macro_rules! foward { | ||||||
| @ -25,16 +26,39 @@ pub struct F64Base where { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl ToString for F64Base { | impl ToString for F64Base { | ||||||
| 	fn to_string(&self) -> String { self.val.to_string() } | 	fn to_string(&self) -> String { | ||||||
|  | 		// Remove negative sign from string | ||||||
|  | 		let mut s = self.val.to_string(); | ||||||
|  | 		 | ||||||
|  | 		let neg = s.starts_with("-"); | ||||||
|  | 		if neg { s = String::from(&s[1..]); } | ||||||
|  | 		 | ||||||
|  | 		// Power of ten | ||||||
|  | 		let mut p: i64 = { | ||||||
|  | 			if let Some(x) = s.find(".") { | ||||||
|  | 				x as i64 | ||||||
|  | 			} else { | ||||||
|  | 				s.len() as i64 | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  | 		p -= 1; | ||||||
|  |  | ||||||
|  | 		// We no longer need a decimal point in our string. | ||||||
|  | 		// also, trim off leading zeros and adjust power. | ||||||
|  | 		let mut s: &str = &s.replace(".", ""); | ||||||
|  | 		s = &s[0..]; | ||||||
|  | 		s = s.trim_end_matches('0'); | ||||||
|  | 		while s.starts_with('0') { | ||||||
|  | 			s = &s[1..]; | ||||||
|  | 			p -= 1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return dec_to_sci(neg, s.to_string(), p); | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| impl ScalarBase for F64Base { | impl ScalarBase for F64Base { | ||||||
|  |  | ||||||
| 	fn from_f64(f: f64) -> Option<F64Base> { |  | ||||||
| 		return Some(F64Base{ val: f }); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	fn from_string(s: &str) -> Option<F64Base> { | 	fn from_string(s: &str) -> Option<F64Base> { | ||||||
| 		let v = s.parse::<f64>(); | 		let v = s.parse::<f64>(); | ||||||
| 		let v = match v { | 		let v = match v { | ||||||
| @ -51,6 +75,7 @@ impl ScalarBase for F64Base { | |||||||
| 	fn is_one(&self) -> bool {self.val == 1f64} | 	fn is_one(&self) -> bool {self.val == 1f64} | ||||||
| 	fn is_negative(&self) -> bool { self.val.is_sign_negative() } | 	fn is_negative(&self) -> bool { self.val.is_sign_negative() } | ||||||
| 	fn is_positive(&self) -> bool { self.val.is_sign_positive() } | 	fn is_positive(&self) -> bool { self.val.is_sign_positive() } | ||||||
|  | 	fn is_int(&self) -> bool { self.val.floor() == self.val } | ||||||
|  |  | ||||||
| 	foward!(abs); | 	foward!(abs); | ||||||
| 	foward!(floor); | 	foward!(floor); | ||||||
| @ -60,9 +85,11 @@ impl ScalarBase for F64Base { | |||||||
| 	foward!(sin); | 	foward!(sin); | ||||||
| 	foward!(cos); | 	foward!(cos); | ||||||
| 	foward!(tan); | 	foward!(tan); | ||||||
| 	foward!(csc); |  | ||||||
| 	foward!(sec); | 	fn csc(&self) -> Option<F64Base> { Some(F64Base{ val: 1f64/self.val.sin() }) } | ||||||
| 	foward!(cot); | 	fn sec(&self) -> Option<F64Base> { Some(F64Base{ val: 1f64/self.val.cos() }) } | ||||||
|  | 	fn cot(&self) -> Option<F64Base> { Some(F64Base{ val: 1f64/self.val.tan() }) } | ||||||
|  |  | ||||||
| 	foward!(asin); | 	foward!(asin); | ||||||
| 	foward!(acos); | 	foward!(acos); | ||||||
| 	foward!(atan); | 	foward!(atan); | ||||||
| @ -70,9 +97,11 @@ impl ScalarBase for F64Base { | |||||||
| 	foward!(sinh); | 	foward!(sinh); | ||||||
| 	foward!(cosh); | 	foward!(cosh); | ||||||
| 	foward!(tanh); | 	foward!(tanh); | ||||||
| 	foward!(csch); |  | ||||||
| 	foward!(sech); | 	fn csch(&self) -> Option<F64Base> { Some(F64Base{ val: 1f64/self.val.sinh() }) } | ||||||
| 	foward!(coth); | 	fn sech(&self) -> Option<F64Base> { Some(F64Base{ val: 1f64/self.val.cosh() }) } | ||||||
|  | 	fn coth(&self) -> Option<F64Base> { Some(F64Base{ val: 1f64/self.val.tanh() }) } | ||||||
|  |  | ||||||
| 	foward!(asinh); | 	foward!(asinh); | ||||||
| 	foward!(acosh); | 	foward!(acosh); | ||||||
| 	foward!(atanh); | 	foward!(atanh); | ||||||
| @ -161,11 +190,11 @@ impl Rem<F64Base> for F64Base { | |||||||
|  |  | ||||||
| 	fn rem(self, modulus: F64Base) -> Self::Output { | 	fn rem(self, modulus: F64Base) -> Self::Output { | ||||||
| 		if { | 		if { | ||||||
| 			(!self.fract().unwrap().is_zero()) || | 			(!self.is_int()) || | ||||||
| 			(!modulus.fract().unwrap().is_zero()) | 			(!modulus.is_int()) | ||||||
| 		} { panic!() } | 		} { panic!() } | ||||||
|  |  | ||||||
| 		F64Base{val : self.val.fract() % modulus.val.fract()} | 		F64Base{val : self.val.round() % modulus.val.round()} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| use rug::Float; | use bigdecimal::BigDecimal; | ||||||
| use rug::Assign; | use bigdecimal::Zero; | ||||||
| use rug::ops::AssignRound; | use bigdecimal::RoundingMode; | ||||||
| use rug::ops::Pow; | use std::str::FromStr; | ||||||
|  |  | ||||||
| use std::ops::{ | use std::ops::{ | ||||||
| 	Add, Sub, Mul, Div, | 	Add, Sub, Mul, Div, | ||||||
| @ -14,158 +14,151 @@ use std::ops::{ | |||||||
| use std::cmp::Ordering; | use std::cmp::Ordering; | ||||||
|  |  | ||||||
| use super::ScalarBase; | use super::ScalarBase; | ||||||
| use super::PRINT_LEN; | use super::dec_to_sci; | ||||||
| use super::FLOAT_PRECISION; |  | ||||||
|  |  | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub struct FloatBase where { | pub struct FloatBase where { | ||||||
| 	pub val: Float | 	pub val: BigDecimal | ||||||
| } | } | ||||||
|  |  | ||||||
| impl FloatBase { | impl FloatBase { | ||||||
| 	pub fn from<T>(a: T) -> Option<FloatBase> where | 	pub fn new(s: &str) -> FloatBase { | ||||||
| 		Float: Assign<T> + AssignRound<T> | 		return FloatBase { | ||||||
| 	{ | 			val: s.parse().unwrap() | ||||||
| 		let v = Float::with_val(FLOAT_PRECISION, a); | 		}; | ||||||
| 		return Some(FloatBase{ val: v }); |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| impl ToString for FloatBase { | impl ToString for FloatBase { | ||||||
| 	fn to_string(&self) -> String { | 	fn to_string(&self) -> String { | ||||||
| 		let (sign, mut string, exp) = self.val.to_sign_string_exp(10, Some(PRINT_LEN)); |  | ||||||
|  |  | ||||||
| 		// zero, nan, or inf. | 		if self.val.is_nan() { | ||||||
| 		let sign = if sign {"-"} else {""}; | 			return "NaN".to_string(); | ||||||
| 		if exp.is_none() { return format!("{sign}{string}"); } | 		} else if self.val.is_inf_neg() { | ||||||
| 		let exp = exp.unwrap(); | 			return "-Inf".to_string(); | ||||||
|  | 		} else if self.val.is_inf_pos() { | ||||||
| 		// Remove trailing zeros. | 			return "+Inf".to_string(); | ||||||
| 		// At this point, string is guaranteed to be nonzero. |  | ||||||
| 		while string.chars().last().unwrap() == '0' { |  | ||||||
| 			string.remove(string.len() - 1); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		let exp_u: usize; |  | ||||||
|  |  | ||||||
| 		if exp < 0 { | 		// Already in scientific notation,we just need to trim significant digits. | ||||||
| 			exp_u = (-exp).try_into().unwrap() | 		let mut _a = self.val.round(32, astro_float::RoundingMode::Up).to_string(); | ||||||
| 		} else { | 		let mut _b = _a.split('e'); | ||||||
| 			exp_u = exp.try_into().unwrap() |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if exp_u >= PRINT_LEN { | 		let mut s = String::from(_b.next().unwrap()); // Decimal | ||||||
| 			// Exponential notation | 		let p: i64 = _b.next().unwrap().parse().unwrap(); // Exponent | ||||||
| 			let pre = &string[0..1]; |  | ||||||
| 			let post = &string[1..]; |  | ||||||
|  |  | ||||||
| 			format!( | 		// Remove negative sign from string | ||||||
| 				"{pre}{}{post}e{}", | 		let neg = s.starts_with("-"); | ||||||
| 				if post.len() != 0 {"."} else {""}, | 		if neg { s = String::from(&s[1..]); } | ||||||
| 				//if exp > 0 {"+"} else {""}, |  | ||||||
| 				exp - 1 |  | ||||||
| 			) |  | ||||||
| 		} else { |  | ||||||
| 			if exp <= 0 { // Decimal, needs `0.` and leading zeros |  | ||||||
| 				format!( |  | ||||||
| 					"{sign}0.{}{string}", |  | ||||||
| 					"0".repeat(exp_u) |  | ||||||
| 				) |  | ||||||
| 			} else if exp_u < string.len() { // Decimal, needs only `.` |  | ||||||
| 				format!( |  | ||||||
| 					"{sign}{}.{}", |  | ||||||
| 					&string[0..exp_u], |  | ||||||
| 					&string[exp_u..] |  | ||||||
| 				) |  | ||||||
| 			} else { // Integer, needs trailing zeros |  | ||||||
| 				format!( |  | ||||||
| 					"{sign}{string}{}", |  | ||||||
| 					"0".repeat(exp_u - string.len()) |  | ||||||
| 				) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
|  | 		// We no longer need a decimal point in our string. | ||||||
|  | 		// also, trim off leading zeros and adjust power. | ||||||
|  | 		let mut s: &str = &s.replace(".", ""); | ||||||
|  | 		s = &s[0..]; | ||||||
|  | 		s = s.trim_end_matches('0'); | ||||||
|  | 		s = s.trim_start_matches('0'); | ||||||
|  |  | ||||||
|  | 		return dec_to_sci(neg, s.to_string(), p); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| macro_rules! foward { |  | ||||||
| 	( $x:ident ) => { |  | ||||||
| 		fn $x(&self) -> Option<FloatBase> { |  | ||||||
| 			Some(FloatBase{ val: self.val.clone().$x()}) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl ScalarBase for FloatBase { | impl ScalarBase for FloatBase { | ||||||
|  |  | ||||||
| 	fn from_f64(f: f64) -> Option<FloatBase> { |  | ||||||
| 		let v = Float::with_val(FLOAT_PRECISION, f); |  | ||||||
| 		return Some(FloatBase{ val: v }); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	fn from_string(s: &str) -> Option<FloatBase> { | 	fn from_string(s: &str) -> Option<FloatBase> { | ||||||
| 		let v = Float::parse(s); | 		let v = BigDecimal::from_str(s); | ||||||
| 		let v = match v { | 		let v = match v { | ||||||
| 			Ok(x) => x, | 			Ok(x) => x, | ||||||
| 			Err(_) => return None | 			Err(_) => return None | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		return Some( | 		return Some(FloatBase{ val: v }); | ||||||
| 			FloatBase{ val: |  | ||||||
| 				Float::with_val(FLOAT_PRECISION, v) |  | ||||||
| 			} |  | ||||||
| 		); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	foward!(fract); | 	//foward!(fract); | ||||||
|  |  | ||||||
| 	fn is_zero(&self) -> bool {self.val.is_zero()} | 	fn is_zero(&self) -> bool {self.val.is_zero()} | ||||||
| 	fn is_one(&self) -> bool {self.val == Float::with_val(FLOAT_PRECISION, 1)} | 	fn is_one(&self) -> bool {self.val == BigDecimal::from_str("1").unwrap()} | ||||||
| 	fn is_negative(&self) -> bool { self.val.is_sign_negative() } | 	fn is_negative(&self) -> bool { self.val.sign() == num::bigint::Sign::Minus } | ||||||
| 	fn is_positive(&self) -> bool { self.val.is_sign_positive() } | 	fn is_positive(&self) -> bool { self.val.sign() == num::bigint::Sign::Plus } | ||||||
|  |  | ||||||
| 	fn is_int(&self) -> bool { | 	fn is_int(&self) -> bool { self.val.is_integer() } | ||||||
| 		self.fract() == FloatBase::from_f64(0f64) |  | ||||||
|  | 	fn abs(&self) -> Option<FloatBase> { Some(FloatBase{ val: self.val.abs() }) } | ||||||
|  | 	fn round(&self) -> Option<FloatBase> { Some(FloatBase{ val: self.val.round(0) }) } | ||||||
|  |  | ||||||
|  | 	fn floor(&self) -> Option<FloatBase> { | ||||||
|  | 		let (_, scale) = self.val.as_bigint_and_exponent(); | ||||||
|  | 		Some(FloatBase{ val: self.val.with_scale_round(scale, RoundingMode::Down) }) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	foward!(abs); | 	fn ceil(&self) -> Option<FloatBase> { | ||||||
| 	foward!(floor); | 		let (_, scale) = self.val.as_bigint_and_exponent(); | ||||||
| 	foward!(ceil); | 		Some(FloatBase{ val: self.val.with_scale_round(scale, RoundingMode::Up) }) | ||||||
| 	foward!(round); |  | ||||||
|  |  | ||||||
| 	foward!(sin); |  | ||||||
| 	foward!(cos); |  | ||||||
| 	foward!(tan); |  | ||||||
| 	foward!(csc); |  | ||||||
| 	foward!(sec); |  | ||||||
| 	foward!(cot); |  | ||||||
| 	foward!(asin); |  | ||||||
| 	foward!(acos); |  | ||||||
| 	foward!(atan); |  | ||||||
|  |  | ||||||
| 	foward!(sinh); |  | ||||||
| 	foward!(cosh); |  | ||||||
| 	foward!(tanh); |  | ||||||
| 	foward!(csch); |  | ||||||
| 	foward!(sech); |  | ||||||
| 	foward!(coth); |  | ||||||
| 	foward!(asinh); |  | ||||||
| 	foward!(acosh); |  | ||||||
| 	foward!(atanh); |  | ||||||
|  |  | ||||||
| 	foward!(exp); |  | ||||||
| 	foward!(ln); |  | ||||||
| 	foward!(log10); |  | ||||||
| 	foward!(log2); |  | ||||||
|  |  | ||||||
| 	fn log(&self, base: FloatBase) -> Option<FloatBase> { |  | ||||||
| 		Some(FloatBase{ val: self.val.clone().log10() } / base.log10().unwrap()) |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	fn pow(&self, base: FloatBase) -> Option<FloatBase> { | 	fn fract(&self) -> Option<FloatBase> { Some(self.clone() - self.floor().unwrap()) } | ||||||
| 		Some(FloatBase{ val: self.val.clone().pow(base.val)}) |  | ||||||
|  |  | ||||||
|  | 	fn sin(&self) -> Option<FloatBase> { | ||||||
|  | 		let c0: BigDecimal = "1.276278962".parse().unwrap(); | ||||||
|  | 		let c1: BigDecimal = "-.285261569".parse().unwrap(); | ||||||
|  | 		let c2: BigDecimal = "0.009118016".parse().unwrap(); | ||||||
|  | 		let c3: BigDecimal = "-.000136587".parse().unwrap(); | ||||||
|  | 		let c4: BigDecimal = "0.000001185".parse().unwrap(); | ||||||
|  | 		let c5: BigDecimal = "-.000000007".parse().unwrap(); | ||||||
|  | 		 | ||||||
|  |  | ||||||
|  | 		// z should be between -0.25 to 0.25 (percent of a full circle) | ||||||
|  | 		let z: BigDecimal = self.val.clone() / 360f64; | ||||||
|  | 		let w = BigDecimal::from(4) * z; | ||||||
|  | 		let x: BigDecimal = 2 * w.clone() * w.clone() - 1; | ||||||
|  |  | ||||||
|  | 		let p = ( | ||||||
|  | 			c0 * 1 + | ||||||
|  | 			c1 * x.clone() + | ||||||
|  | 			c2 * (2 * x.clone()*x.clone() - 1) + | ||||||
|  | 			c3 * (4 * x.clone()*x.clone()*x.clone() - 3 * x.clone()) + | ||||||
|  | 			c4 * (8 * x.clone()*x.clone()*x.clone()*x.clone() - 8 * x.clone()*x.clone() + 1) + | ||||||
|  | 			c5 * (16 * x.clone()*x.clone()*x.clone()*x.clone()*x.clone() - 20 * x.clone()*x.clone()*x.clone() + 5 * x.clone()) | ||||||
|  | 		) * w; | ||||||
|  |  | ||||||
|  | 		return Some(FloatBase{ val: p }) | ||||||
|  | 	} | ||||||
|  | 	fn cos(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn tan(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn csc(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn sec(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn cot(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn asin(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn acos(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn atan(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn sinh(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn cosh(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn tanh(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn csch(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn sech(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn coth(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn asinh(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn acosh(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn atanh(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn exp(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn ln(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn log10(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  | 	fn log2(&self) -> Option<FloatBase> { Some(FloatBase{ val: "1".parse().unwrap() }) } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	fn log(&self, _base: FloatBase) -> Option<FloatBase> { | ||||||
|  | 		Some(FloatBase{ val: "1".parse().unwrap() }) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fn pow(&self, _base: FloatBase) -> Option<FloatBase> { | ||||||
|  | 		Some(FloatBase{ val: "1".parse().unwrap() }) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
| @ -223,7 +216,7 @@ impl Div for FloatBase { | |||||||
|  |  | ||||||
| impl DivAssign for FloatBase where { | impl DivAssign for FloatBase where { | ||||||
| 	fn div_assign(&mut self, other: Self) { | 	fn div_assign(&mut self, other: Self) { | ||||||
| 		self.val /= other.val; | 		self.val = self.val.clone() / other.val; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -240,11 +233,11 @@ impl Rem<FloatBase> for FloatBase { | |||||||
|  |  | ||||||
| 	fn rem(self, modulus: FloatBase) -> Self::Output { | 	fn rem(self, modulus: FloatBase) -> Self::Output { | ||||||
| 		if { | 		if { | ||||||
| 			(!self.fract().unwrap().is_zero()) || | 			(!self.is_int()) || | ||||||
| 			(!modulus.fract().unwrap().is_zero()) | 			(!modulus.is_int()) | ||||||
| 		} { panic!() } | 		} { panic!() } | ||||||
|  |  | ||||||
| 		FloatBase{val : self.val.fract() % modulus.val.fract()} | 		FloatBase{val : self.val.round(0) % modulus.val.round(0)} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,10 +1,92 @@ | |||||||
| const FLOAT_PRECISION: u32 = 1024; | //const FLOAT_PRECISION: u32 = 1024; | ||||||
| const PRINT_LEN: usize = 5; // How many significant digits we will show in output | const SHOW_SIG: usize = 5; // How many significant digits we will show in output | ||||||
|  | const MAX_LEN: usize = 5; // If a scientific exponent is >= this value, do not use scientific notation. | ||||||
|  |  | ||||||
| pub(in self) mod rationalbase; | pub(in self) mod rationalbase; | ||||||
| pub(in self) mod floatbase; |  | ||||||
| //mod f64base; |  | ||||||
|  | // Pick a float implementation. | ||||||
|  | // floatbase is high-precision, f64base is for testing. | ||||||
|  |  | ||||||
|  | //pub(in self) mod floatbase; | ||||||
|  | //pub use floatbase::FloatBase; | ||||||
|  |  | ||||||
|  | pub(in self) mod f64base; | ||||||
|  | pub use f64base::F64Base as FloatBase; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| mod scalar; | mod scalar; | ||||||
| pub use self::scalar::Scalar; | pub use self::scalar::Scalar; | ||||||
| pub use self::scalar::ScalarBase; | pub use self::scalar::ScalarBase; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | // Convert a string to scientific notation, | ||||||
|  | // with parameters SHOW_SIG and MAX_LEN. | ||||||
|  | // | ||||||
|  | // input: | ||||||
|  | //  neg: true if negative | ||||||
|  | //  s: decimal portion. Must contain only digits and a single decimal point. | ||||||
|  | //     zeros must be stripped from both ends. | ||||||
|  | //  p: power of ten to multiply by. | ||||||
|  | // | ||||||
|  | //  So, (-1)^(neg) + (s * 10^p) should give us our number. | ||||||
|  |  | ||||||
|  | #[allow(dead_code)] | ||||||
|  | pub(in self) fn dec_to_sci(neg: bool, mut s: String, p: i64) -> String { | ||||||
|  | 	// Pick significant digits and round | ||||||
|  | 	if s.len() > SHOW_SIG { | ||||||
|  | 		let round; | ||||||
|  | 		if s.len() != SHOW_SIG + 1 { | ||||||
|  | 			round = s[SHOW_SIG..SHOW_SIG+1].parse().unwrap(); | ||||||
|  | 		} else { round = 0; } | ||||||
|  |  | ||||||
|  | 		s = String::from(&s[0..SHOW_SIG]); | ||||||
|  |  | ||||||
|  | 		if round >= 5 { | ||||||
|  | 			let new = s[s.len()-1..s.len()].parse::<u8>().unwrap() + 1u8; | ||||||
|  | 			if new != 10 { | ||||||
|  | 				s = format!("{}{new}", &s[0..s.len()-1]); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s = format!("{s}{}", "0".repeat(SHOW_SIG - s.len())); | ||||||
|  | 	// at this point, s is guaranteed to have exactly SHOW_SIG digits. | ||||||
|  |  | ||||||
|  | 	let neg = if neg {"-"} else {""}; | ||||||
|  |  | ||||||
|  | 	if (p.abs() as usize) < MAX_LEN { | ||||||
|  | 		// Print whole decimal | ||||||
|  |  | ||||||
|  | 		if p >= 0 { | ||||||
|  | 			let q = p as usize; | ||||||
|  |  | ||||||
|  | 			let first = &s[0..q+1]; | ||||||
|  | 			let mut rest = &s[q+1..]; | ||||||
|  | 			rest = rest.trim_end_matches('0'); | ||||||
|  | 			if rest == "" { | ||||||
|  | 				return format!("{neg}{first}"); | ||||||
|  | 			} else { | ||||||
|  | 				return format!("{neg}{first}.{rest}"); | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			let q = p.abs() as usize; | ||||||
|  | 			let t = format!("0.{}{s}", "0".repeat(q-1)); | ||||||
|  | 			return format!("{neg}{}", t.trim_end_matches('0')); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	} else { | ||||||
|  | 		// Print full scientific notation | ||||||
|  |  | ||||||
|  | 		let first = &s[0..1]; | ||||||
|  | 		let mut rest = &s[1..]; | ||||||
|  | 		rest = rest.trim_end_matches('0'); | ||||||
|  | 		if rest == "" { | ||||||
|  | 			return format!("{neg}{first}e{p}"); | ||||||
|  | 		} else { | ||||||
|  | 			return format!("{neg}{first}.{rest}e{p}"); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -1,5 +1,7 @@ | |||||||
| use rug::Rational; | use num::rational::BigRational; | ||||||
| use rug::Integer; | use num::BigInt; | ||||||
|  | use num::Num; | ||||||
|  | use num::Signed; | ||||||
|  |  | ||||||
| use std::ops::{ | use std::ops::{ | ||||||
| 	Add, Sub, Mul, Div, | 	Add, Sub, Mul, Div, | ||||||
| @ -22,7 +24,7 @@ macro_rules! cant_do { | |||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub struct RationalBase where { | pub struct RationalBase where { | ||||||
| 	pub val: Rational | 	pub val: BigRational | ||||||
| } | } | ||||||
|  |  | ||||||
| impl ToString for RationalBase{ | impl ToString for RationalBase{ | ||||||
| @ -33,18 +35,12 @@ impl ToString for RationalBase{ | |||||||
|  |  | ||||||
| impl RationalBase { | impl RationalBase { | ||||||
| 	pub fn from_frac(t: i64, b: i64) -> Option<RationalBase> { | 	pub fn from_frac(t: i64, b: i64) -> Option<RationalBase> { | ||||||
| 		let v = Rational::from((t, b)); | 		let v = BigRational::new_raw(BigInt::from(t), BigInt::from(b)); | ||||||
| 		return Some(RationalBase{ val: v }); | 		return Some(RationalBase{ val: v }); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| impl ScalarBase for RationalBase { | impl ScalarBase for RationalBase { | ||||||
| 	fn from_f64(f: f64) -> Option<RationalBase> { |  | ||||||
| 		let v = Rational::from_f64(f); |  | ||||||
| 		if v.is_none() { return None } |  | ||||||
| 		return Some(RationalBase{ val: v.unwrap() }); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	fn from_string(s: &str) -> Option<RationalBase> { | 	fn from_string(s: &str) -> Option<RationalBase> { | ||||||
| 		// Scientific notation | 		// Scientific notation | ||||||
| 		let mut sci = s.split("e"); | 		let mut sci = s.split("e"); | ||||||
| @ -89,7 +85,7 @@ impl ScalarBase for RationalBase { | |||||||
|  |  | ||||||
|  |  | ||||||
| 		// From fraction string | 		// From fraction string | ||||||
| 		let r = Rational::from_str_radix(&s, 10); | 		let r = BigRational::from_str_radix(&s, 10); | ||||||
| 		let r = match r { | 		let r = match r { | ||||||
| 			Ok(x) => x, | 			Ok(x) => x, | ||||||
| 			Err(_) => return None | 			Err(_) => return None | ||||||
| @ -100,18 +96,13 @@ impl ScalarBase for RationalBase { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
| 	fn fract(&self) -> Option<RationalBase> { | 	fn fract(&self) -> Option<RationalBase> { Some(RationalBase{val: self.val.fract()}) } | ||||||
| 		Some(RationalBase{val: self.val.clone().fract_floor(Integer::new()).0}) | 	fn is_int(&self) -> bool { self.val.is_integer() } | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	fn is_int(&self) -> bool { | 	fn is_zero(&self) -> bool {self.val == BigRational::from_integer(BigInt::from(0))} | ||||||
| 		self.fract() == RationalBase::from_f64(0f64) | 	fn is_one(&self) -> bool {self.val == BigRational::from_integer(BigInt::from(1))} | ||||||
| 	} | 	fn is_negative(&self) -> bool { self.val.is_negative() } | ||||||
|  | 	fn is_positive(&self) -> bool { self.val.is_positive() } | ||||||
| 	fn is_zero(&self) -> bool {self.val == Rational::from((0,1))} |  | ||||||
| 	fn is_one(&self) -> bool {self.val == Rational::from((1,1))} |  | ||||||
| 	fn is_negative(&self) -> bool { self.val.clone().signum() == -1 } |  | ||||||
| 	fn is_positive(&self) -> bool { self.val.clone().signum() == 1 } |  | ||||||
|  |  | ||||||
| 	fn abs(&self) -> Option<RationalBase> {Some(RationalBase{val: self.val.clone().abs()})} | 	fn abs(&self) -> Option<RationalBase> {Some(RationalBase{val: self.val.clone().abs()})} | ||||||
| 	fn floor(&self) -> Option<RationalBase> {Some(RationalBase{val: self.val.clone().floor()})} | 	fn floor(&self) -> Option<RationalBase> {Some(RationalBase{val: self.val.clone().floor()})} | ||||||
| @ -153,9 +144,7 @@ impl Add for RationalBase where { | |||||||
| 	type Output = Self; | 	type Output = Self; | ||||||
|  |  | ||||||
| 	fn add(self, other: Self) -> Self::Output { | 	fn add(self, other: Self) -> Self::Output { | ||||||
| 		Self { | 		Self { val: self.val + other.val } | ||||||
| 			val: self.val + other.val |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -169,9 +158,7 @@ impl Sub for RationalBase { | |||||||
| 	type Output = Self; | 	type Output = Self; | ||||||
|  |  | ||||||
| 	fn sub(self, other: Self) -> Self::Output { | 	fn sub(self, other: Self) -> Self::Output { | ||||||
| 		Self { | 		Self { val: self.val - other.val } | ||||||
| 			val: self.val - other.val |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -185,9 +172,7 @@ impl Mul for RationalBase { | |||||||
| 	type Output = Self; | 	type Output = Self; | ||||||
|  |  | ||||||
| 	fn mul(self, other: Self) -> Self::Output { | 	fn mul(self, other: Self) -> Self::Output { | ||||||
| 		Self { | 		Self { val: self.val * other.val } | ||||||
| 			val: self.val * other.val |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -201,9 +186,7 @@ impl Div for RationalBase { | |||||||
| 	type Output = Self; | 	type Output = Self; | ||||||
|  |  | ||||||
| 	fn div(self, other: Self) -> Self::Output { | 	fn div(self, other: Self) -> Self::Output { | ||||||
| 		Self { | 		Self { val: self.val / other.val } | ||||||
| 			val: self.val / other.val |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -217,9 +200,7 @@ impl Neg for RationalBase where { | |||||||
| 	type Output = Self; | 	type Output = Self; | ||||||
|  |  | ||||||
| 	fn neg(self) -> Self::Output { | 	fn neg(self) -> Self::Output { | ||||||
| 		Self { | 		Self { val: -self.val } | ||||||
| 			val: -self.val |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -228,15 +209,15 @@ impl Rem<RationalBase> for RationalBase { | |||||||
|  |  | ||||||
| 	fn rem(self, modulus: RationalBase) -> Self::Output { | 	fn rem(self, modulus: RationalBase) -> Self::Output { | ||||||
| 		if { | 		if { | ||||||
| 			*self.val.denom() != 1 || | 			*self.val.denom() != BigInt::from(1) || | ||||||
| 			*modulus.val.denom() != 1 | 			*modulus.val.denom() != BigInt::from(1) | ||||||
| 		} { panic!() } | 		} { panic!() } | ||||||
|  |  | ||||||
| 		RationalBase{ | 		RationalBase{ | ||||||
| 			val : Rational::from(( | 			val : BigRational::new_raw( | ||||||
| 				self.val.numer() % modulus.val.numer(), | 				self.val.numer() % modulus.val.numer(), | ||||||
| 				1 | 				BigInt::from(1) | ||||||
| 			)) | 			) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ use std::ops::{ | |||||||
| }; | }; | ||||||
| use std::cmp::Ordering; | use std::cmp::Ordering; | ||||||
|  |  | ||||||
| use super::floatbase::FloatBase as FloatBase; | use super::FloatBase as FloatBase; | ||||||
| use super::rationalbase::RationalBase; | use super::rationalbase::RationalBase; | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -21,7 +21,6 @@ pub trait ScalarBase: | |||||||
| 	PartialEq + PartialOrd | 	PartialEq + PartialOrd | ||||||
| { | { | ||||||
| 	// Creation | 	// Creation | ||||||
| 	fn from_f64(f: f64) -> Option<Self>; |  | ||||||
| 	fn from_string(s: &str) -> Option<Self>; | 	fn from_string(s: &str) -> Option<Self>; | ||||||
|  |  | ||||||
| 	// Utility | 	// Utility | ||||||
| @ -87,8 +86,8 @@ fn to_float(r: Scalar) -> Scalar { | |||||||
| 	match &r { | 	match &r { | ||||||
| 		Scalar::Float {..} => r, | 		Scalar::Float {..} => r, | ||||||
| 		Scalar::Rational {v} => wrap_float!( | 		Scalar::Rational {v} => wrap_float!( | ||||||
| 			FloatBase::from(v.val.numer()).unwrap() / | 			FloatBase::from_string(&v.val.numer().to_string()).unwrap() / | ||||||
| 			FloatBase::from(v.val.denom()).unwrap() | 			FloatBase::from_string(&v.val.denom().to_string()).unwrap() | ||||||
| 		) | 		) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @ -105,13 +104,13 @@ impl ToString for Scalar { | |||||||
| // Creation methods | // Creation methods | ||||||
| impl Scalar { | impl Scalar { | ||||||
| 	pub fn new_float(f: f64) -> Option<Self> { | 	pub fn new_float(f: f64) -> Option<Self> { | ||||||
| 		let v = FloatBase::from_f64(f); | 		let v = FloatBase::from_string(&f.to_string()); | ||||||
| 		if v.is_none() { return None; } | 		if v.is_none() { return None; } | ||||||
| 		return Some(wrap_float!(v.unwrap())); | 		return Some(wrap_float!(v.unwrap())); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	pub fn new_rational(f: f64) -> Option<Self> { | 	pub fn new_rational(f: f64) -> Option<Self> { | ||||||
| 		let r = RationalBase::from_f64(f); | 		let r = RationalBase::from_string(&f.to_string()); | ||||||
| 		if r.is_none() { return None; } | 		if r.is_none() { return None; } | ||||||
| 		return Some(wrap_rational!(r.unwrap())); | 		return Some(wrap_rational!(r.unwrap())); | ||||||
| 	} | 	} | ||||||
| @ -186,7 +185,7 @@ impl Scalar { | |||||||
| 	pub fn is_nan(&self) -> bool { | 	pub fn is_nan(&self) -> bool { | ||||||
| 		match self { | 		match self { | ||||||
| 			Scalar::Float{ v } => {v.val.is_nan()}, | 			Scalar::Float{ v } => {v.val.is_nan()}, | ||||||
| 			Scalar::Rational {..} => {panic!()} | 			Scalar::Rational {..} => {false} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | |||||||
| @ -275,7 +275,6 @@ impl Unit { | |||||||
| 		let mut q = Quantity::new_rational(1f64).unwrap(); | 		let mut q = Quantity::new_rational(1f64).unwrap(); | ||||||
| 		q.set_unit(b); | 		q.set_unit(b); | ||||||
| 		return Some(q); | 		return Some(q); | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										10
									
								
								src/tests.rs
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/tests.rs
									
									
									
									
									
								
							| @ -1,7 +1,7 @@ | |||||||
| // Many of these have been borrowed from insect. | // Many of these have been borrowed from insect. | ||||||
| use crate::parser; | use daisycalc::parser; | ||||||
| use crate::evaluate::evaluate; | use daisycalc::evaluate; | ||||||
| use crate::context::Context; | use daisycalc::Context; | ||||||
|  |  | ||||||
| fn eval_to_str(s: &str) -> Result<String, ()> { | fn eval_to_str(s: &str) -> Result<String, ()> { | ||||||
| 	let g = match parser::parse_no_context(&String::from(s)) { | 	let g = match parser::parse_no_context(&String::from(s)) { | ||||||
| @ -131,7 +131,7 @@ fn operators() { | |||||||
|  |  | ||||||
| 	good_expr("125", "5^(+3)"); | 	good_expr("125", "5^(+3)"); | ||||||
| 	good_expr("125", "+5^3"); | 	good_expr("125", "+5^3"); | ||||||
| 	good_expr("0.2148", "3 ^ (-1.4)"); | 	good_expr("0.21479", "3 ^ (-1.4)"); | ||||||
|  |  | ||||||
| 	// Should parse as ((2^3)^4)^5 | 	// Should parse as ((2^3)^4)^5 | ||||||
| 	good_expr("1.1529e18", "2^3^4^5"); | 	good_expr("1.1529e18", "2^3^4^5"); | ||||||
| @ -162,6 +162,7 @@ fn operators() { | |||||||
|  |  | ||||||
| 	good_expr("2", "6/3"); | 	good_expr("2", "6/3"); | ||||||
| 	good_expr("2", "5%3"); | 	good_expr("2", "5%3"); | ||||||
|  | 	good_expr("4", "2^5 mod 7"); | ||||||
| 	good_expr("8", "5+3"); | 	good_expr("8", "5+3"); | ||||||
| 	good_expr("64", "4^3"); | 	good_expr("64", "4^3"); | ||||||
| 	good_expr("64", "4 ^ 3"); | 	good_expr("64", "4 ^ 3"); | ||||||
| @ -184,6 +185,7 @@ fn operators() { | |||||||
| 	bad_expr("1e5!"); | 	bad_expr("1e5!"); | ||||||
| 	bad_expr("0^(-1)"); | 	bad_expr("0^(-1)"); | ||||||
| 	bad_expr("pi!"); | 	bad_expr("pi!"); | ||||||
|  | 	bad_expr("2.5 mod 8"); | ||||||
| } | } | ||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user