Compare commits

...
Author SHA1 Message Date
Dzuchun
243b6ad003 Version bump 2025-01-13 04:55:57 +02:00
Dzuchun
02147c4aee Old-New tokens intertop 2025-01-13 04:48:54 +02:00
Dzuchun
d70e25cc5e Made std optional. 2025-01-13 04:45:07 +02:00
Dzuchun
376dd7ce1a Implemented new lexing 2025-01-13 04:42:09 +02:00
Dzuchun
4bde45ef69 [Cargo.lock] 2025-01-13 04:40:03 +02:00
Dzuchun
a4e75196c6 Ignored proptest files 2025-01-13 03:57:23 +02:00
20 changed files with 2379 additions and 174 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target /target
/proptest-regressions

487
Cargo.lock generated
View file

@ -2,12 +2,39 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.4.0" version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bit-set"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.5.0" version = "1.5.0"
@ -20,6 +47,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "deranged"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [
"powerfmt",
]
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "1.0.0" version = "1.0.0"
@ -41,6 +77,49 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
[[package]]
name = "generic-array"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8c8444bc9d71b935156cc0ccab7f622180808af7867b1daae6547d773591703"
dependencies = [
"typenum",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.15" version = "0.2.15"
@ -52,21 +131,56 @@ dependencies = [
"wasi", "wasi",
] ]
[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "itoa"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.169" version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]] [[package]]
name = "mathvis" name = "mathvis"
version = "0.3.0" version = "0.4.0"
dependencies = [ dependencies = [
"derive_more", "derive_more",
"generic-array",
"hashbrown",
"nom", "nom",
"num", "num",
"paste",
"proptest",
"rand", "rand",
"thiserror", "thiserror",
"tracing",
"tracing-subscriber",
] ]
[[package]] [[package]]
@ -91,6 +205,16 @@ dependencies = [
"minimal-lexical", "minimal-lexical",
] ]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]] [[package]]
name = "num" name = "num"
version = "0.4.3" version = "0.4.3"
@ -124,6 +248,12 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.46" version = "0.1.46"
@ -164,6 +294,45 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.20" version = "0.2.20"
@ -175,13 +344,39 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.92" version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "proptest"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50"
dependencies = [
"bit-set",
"bit-vec",
"bitflags",
"lazy_static",
"num-traits",
"rand",
"rand_chacha",
"rand_xorshift",
"regex-syntax",
"rusty-fork",
"tempfile",
"unarray",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.38" version = "1.0.38"
@ -221,6 +416,75 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "rand_xorshift"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
dependencies = [
"rand_core",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustix"
version = "0.38.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "rusty-fork"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
dependencies = [
"fnv",
"quick-error",
"tempfile",
"wait-timeout",
]
[[package]]
name = "serde"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.96" version = "2.0.96"
@ -232,6 +496,20 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "tempfile"
version = "3.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
dependencies = [
"cfg-if",
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.11" version = "2.0.11"
@ -252,6 +530,105 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "time"
version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [
"deranged",
"itoa",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tracing"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
"once_cell",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
dependencies = [
"nu-ansi-term",
"sharded-slab",
"thread_local",
"time",
"tracing-core",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unarray"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.14" version = "1.0.14"
@ -264,12 +641,116 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "wait-timeout"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.7.35" version = "0.7.35"

View file

@ -1,15 +1,27 @@
[package] [package]
name = "mathvis" name = "mathvis"
version = "0.3.0" version = "0.4.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
derive_more = {version = "1.0.0", features = ["display"]} derive_more = {version = "1.0.0", features = ["display", "as_ref", "deref"]}
nom = "7.1.3" nom = "7.1.3"
num = "0.4.3" num = "0.4.3"
thiserror = "2.0.11" thiserror = "2.0.11"
generic-array = "1.2.0"
tracing = { version = "0.1.41", default-features = false, features = ["attributes"] }
hashbrown = "0.15.2"
[dev-dependencies] [dev-dependencies]
paste = "1.0.15"
rand = "0.8.5" rand = "0.8.5"
tracing-subscriber = { version = "0.3.19", default-features = false, features = ["std", "fmt", "time", "local-time", "ansi"] }
proptest = "1.6.0"
[features]
default = []
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(verbose_logging)'] }

View file

@ -16,14 +16,16 @@
//! //!
//! Variables/functions can be unregistered too. Although, there's not much use for it apart from debugging. //! Variables/functions can be unregistered too. Although, there's not much use for it apart from debugging.
use std::{ use core::fmt::Debug;
collections::{HashMap, HashSet},
fmt::Debug, use alloc::{borrow::ToOwned, boxed::Box, string::String, vec::Vec};
};
use num::complex::Complex64; use num::complex::Complex64;
use thiserror::Error; use thiserror::Error;
use hashbrown::{HashMap, HashSet};
/// Represents types of arguments. /// Represents types of arguments.
/// ///
/// - [`ArgType::Variable`] stands for regular complex variable.s /// - [`ArgType::Variable`] stands for regular complex variable.s
@ -75,7 +77,7 @@ impl AsRef<Args> for ArgsErased {
pub struct ArgsRefErased<'l>(pub &'l Args); pub struct ArgsRefErased<'l>(pub &'l Args);
impl Debug for ArgsRefErased<'_> { impl Debug for ArgsRefErased<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
fn display_map<V>(map: &HashMap<String, V>) -> String { fn display_map<V>(map: &HashMap<String, V>) -> String {
format!( format!(
"{{{}}}", "{{{}}}",
@ -94,7 +96,7 @@ impl Debug for ArgsRefErased<'_> {
} }
impl Debug for ArgsErased { impl Debug for ArgsErased {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
ArgsRefErased(&self.0).fmt(f) ArgsRefErased(&self.0).fmt(f)
} }
} }

View file

@ -27,6 +27,7 @@ impl Operator for Negation {
#[cfg(test)] #[cfg(test)]
mod negation_tests { mod negation_tests {
use alloc::borrow::ToOwned;
use num::complex::Complex64; use num::complex::Complex64;
use crate::evaluation_tree::{ use crate::evaluation_tree::{
@ -111,6 +112,7 @@ impl Operator for Addition {
#[cfg(test)] #[cfg(test)]
mod addition_tests { mod addition_tests {
use alloc::borrow::ToOwned;
use num::complex::Complex64; use num::complex::Complex64;
use crate::evaluation_tree::{ use crate::evaluation_tree::{

View file

@ -6,6 +6,7 @@
//! //!
//! In presented API, only [`NamedFunction`]s are used for now. Although, I might add lots of default functions that will automatically resolve to their [`Functional`] counterparts during parsing. //! In presented API, only [`NamedFunction`]s are used for now. Although, I might add lots of default functions that will automatically resolve to their [`Functional`] counterparts during parsing.
use alloc::string::String;
use num::complex::Complex64; use num::complex::Complex64;
use super::{ use super::{

View file

@ -6,10 +6,11 @@
//! [`EvaluationTree`] can be created by parsing with [`EvaluationTree::from_tokens`] function. //! [`EvaluationTree`] can be created by parsing with [`EvaluationTree::from_tokens`] function.
//! If you need some custom structure, [`EvaluationTree`] can be ignored and bare [`Node`]s used instead, as they both implement [`Evaluatable`] anyway. //! If you need some custom structure, [`EvaluationTree`] can be ignored and bare [`Node`]s used instead, as they both implement [`Evaluatable`] anyway.
use alloc::{boxed::Box, string::String};
use num::{complex::Complex64, Complex}; use num::{complex::Complex64, Complex};
use crate::{ use crate::{
lexer::Token, lexer::OldToken,
parser::{parse, GenerationError}, parser::{parse, GenerationError},
}; };
@ -48,7 +49,7 @@ pub struct EvaluationTree(Node);
impl EvaluationTree { impl EvaluationTree {
/// Attempts to parse a tree from token slice. May fail with [`GenerationError`] to indicate incorrect syntax. /// Attempts to parse a tree from token slice. May fail with [`GenerationError`] to indicate incorrect syntax.
pub fn from_tokens(tokens: &[Token]) -> Result<Self, GenerationError> { pub fn from_tokens(tokens: &[OldToken]) -> Result<Self, GenerationError> {
Ok(Self(parse(tokens)?)) Ok(Self(parse(tokens)?))
} }
} }
@ -182,6 +183,7 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use alloc::borrow::ToOwned;
use num::complex::Complex64; use num::complex::Complex64;
use crate::evaluation_tree::args::{Args, MissingError}; use crate::evaluation_tree::args::{Args, MissingError};

View file

@ -1,5 +1,10 @@
//! This module defines lexer and structs relevant to it. //! This module defines lexer and structs relevant to it.
use alloc::{
borrow::ToOwned,
string::{String, ToString},
vec::Vec,
};
use nom::{ use nom::{
branch::alt, branch::alt,
bytes::complete::tag, bytes::complete::tag,
@ -11,7 +16,17 @@ use nom::{
sequence::{pair, preceded}, sequence::{pair, preceded},
}; };
type Res<'l, T = Token> = nom::IResult<&'l str, T, Error<&'l str>>; use crate::{
display_opt,
lexer_v2::{
number::Number,
token::{SpecialKind, Token},
},
};
use core::str::FromStr;
type Res<'l, T = OldToken> = nom::IResult<&'l str, T, Error<&'l str>>;
/// Represents type of grouping. /// Represents type of grouping.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
@ -36,7 +51,7 @@ pub enum Operator {
/// Represents a single token. /// Represents a single token.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Token { pub enum OldToken {
GroupOpen(GroupingType), GroupOpen(GroupingType),
GroupClose(GroupingType), GroupClose(GroupingType),
Operator(Operator), Operator(Operator),
@ -46,6 +61,75 @@ pub enum Token {
Identifier(String), Identifier(String),
} }
#[derive(Debug, Clone, Copy, thiserror::Error)]
#[error("Unsupported token: {_0}")]
pub struct UnsupportedToken<'s>(Token<&'s str, &'s str, &'s str>);
impl<'s> TryFrom<Token<&'s str, &'s str, &'s str>> for OldToken {
type Error = UnsupportedToken<'s>;
fn try_from(value: Token<&'s str, &'s str, &'s str>) -> Result<Self, Self::Error> {
let token = match value {
Token::Grouping {
kind: crate::lexer_v2::token::GroupingKind::Parentheses,
side: crate::lexer_v2::token::GroupingSide::Left,
} => Self::GroupOpen(GroupingType::Parentheses),
Token::Grouping {
kind: crate::lexer_v2::token::GroupingKind::Brackets,
side: crate::lexer_v2::token::GroupingSide::Left,
} => Self::GroupOpen(GroupingType::Brackets),
Token::Grouping {
kind: crate::lexer_v2::token::GroupingKind::Braces,
side: crate::lexer_v2::token::GroupingSide::Left,
} => Self::GroupOpen(GroupingType::Braces),
Token::Grouping {
kind: crate::lexer_v2::token::GroupingKind::Parentheses,
side: crate::lexer_v2::token::GroupingSide::Right,
} => Self::GroupClose(GroupingType::Parentheses),
Token::Grouping {
kind: crate::lexer_v2::token::GroupingKind::Brackets,
side: crate::lexer_v2::token::GroupingSide::Right,
} => Self::GroupClose(GroupingType::Brackets),
Token::Grouping {
kind: crate::lexer_v2::token::GroupingKind::Braces,
side: crate::lexer_v2::token::GroupingSide::Right,
} => Self::GroupClose(GroupingType::Braces),
Token::Special(SpecialKind::Comma) => Self::Comma,
Token::Special(SpecialKind::Plus) => Self::Operator(Operator::Plus),
Token::Special(SpecialKind::Minus) => Self::Operator(Operator::Minus),
Token::Special(SpecialKind::Star) => Self::Operator(Operator::Star),
Token::Special(SpecialKind::Slash) => Self::Operator(Operator::Slash),
Token::Special(SpecialKind::Cap) => Self::Operator(Operator::Cap),
Token::Number(Number::Integer { int, exp }) => Self::Number(
f64::from_str(&format!("{int}.0{}", display_opt(&exp)))
.map_err(|_| UnsupportedToken(value))?,
),
Token::Number(Number::Decimal {
int,
point,
frac,
exp,
}) => Self::Number(
f64::from_str(&format!("{int}{point}{frac}{}", display_opt(&exp)))
.map_err(|_| UnsupportedToken(value))?,
),
Token::Number(Number::DecimalInt { int, point, exp }) => Self::Number(
f64::from_str(&format!("{int}{point}0{}", display_opt(&exp)))
.map_err(|_| UnsupportedToken(value))?,
),
Token::Number(Number::DecimalFraction { point, frac, exp }) => Self::Number(
f64::from_str(&format!("0{point}{frac}{}", display_opt(&exp)))
.map_err(|_| UnsupportedToken(value))?,
),
Token::Word(word) if *word == "i" => Self::ImaginaryUnit,
Token::Word(word) => Self::Identifier(word.to_string()),
_ => return Err(UnsupportedToken(value)),
};
Ok(token)
}
}
/// Represents token type (used for debug) /// Represents token type (used for debug)
#[derive(Debug, PartialEq, derive_more::Display)] #[derive(Debug, PartialEq, derive_more::Display)]
pub enum TokenType { pub enum TokenType {
@ -58,7 +142,7 @@ pub enum TokenType {
fn paren_open(i: &str) -> Res { fn paren_open(i: &str) -> Res {
map(one_of("([{"), |c| { map(one_of("([{"), |c| {
Token::GroupOpen(match c { OldToken::GroupOpen(match c {
'(' => GroupingType::Parentheses, '(' => GroupingType::Parentheses,
'[' => GroupingType::Brackets, '[' => GroupingType::Brackets,
'{' => GroupingType::Braces, '{' => GroupingType::Braces,
@ -69,7 +153,7 @@ fn paren_open(i: &str) -> Res {
fn paren_close(i: &str) -> Res { fn paren_close(i: &str) -> Res {
map(one_of(")]}"), |c| { map(one_of(")]}"), |c| {
Token::GroupClose(match c { OldToken::GroupClose(match c {
')' => GroupingType::Parentheses, ')' => GroupingType::Parentheses,
']' => GroupingType::Brackets, ']' => GroupingType::Brackets,
'}' => GroupingType::Braces, '}' => GroupingType::Braces,
@ -80,7 +164,7 @@ fn paren_close(i: &str) -> Res {
fn operator(i: &str) -> Res { fn operator(i: &str) -> Res {
map(one_of("+-*/^"), |c| { map(one_of("+-*/^"), |c| {
Token::Operator(match c { OldToken::Operator(match c {
'+' => Operator::Plus, '+' => Operator::Plus,
'-' => Operator::Minus, '-' => Operator::Minus,
'*' => Operator::Star, '*' => Operator::Star,
@ -92,11 +176,11 @@ fn operator(i: &str) -> Res {
} }
fn comma(i: &str) -> Res { fn comma(i: &str) -> Res {
map(pchar(','), |_| Token::Comma)(i) map(pchar(','), |_| OldToken::Comma)(i)
} }
fn imaginary_unit(i: &str) -> Res { fn imaginary_unit(i: &str) -> Res {
map(pchar('i'), |_| Token::ImaginaryUnit)(i) map(pchar('i'), |_| OldToken::ImaginaryUnit)(i)
} }
// source: https://github.com/rust-bakery/nom/blob/main/doc/nom_recipes.md#identifiers // source: https://github.com/rust-bakery/nom/blob/main/doc/nom_recipes.md#identifiers
@ -106,12 +190,12 @@ fn identifier(i: &str) -> Res {
alt((alpha1, tag("_"))), alt((alpha1, tag("_"))),
many0_count(alt((alphanumeric1, tag("_")))), many0_count(alt((alphanumeric1, tag("_")))),
)), )),
|name: &str| Token::Identifier(name.to_owned()), |name: &str| OldToken::Identifier(name.to_owned()),
)(i) )(i)
} }
fn number(i: &str) -> Res { fn number(i: &str) -> Res {
map(double, Token::Number)(i) map(double, OldToken::Number)(i)
} }
fn whitespace(i: &str) -> Res<()> { fn whitespace(i: &str) -> Res<()> {
@ -119,7 +203,7 @@ fn whitespace(i: &str) -> Res<()> {
} }
/// lexes the input /// lexes the input
pub fn lex(i: &str) -> Res<Vec<Token>> { pub fn lex(i: &str) -> Res<Vec<OldToken>> {
fold_many1( fold_many1(
preceded( preceded(
whitespace, whitespace,
@ -143,10 +227,15 @@ pub fn lex(i: &str) -> Res<Vec<Token>> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use alloc::borrow::ToOwned;
use alloc::vec::Vec;
use crate::lexer::GroupingType; use crate::lexer::GroupingType;
use crate::lexer_v2::token::Token;
use crate::lexer_v2::StrTokenParser;
use super::lex; use super::lex;
use super::Token; use super::OldToken;
#[test] #[test]
fn tokens() { fn tokens() {
@ -162,28 +251,50 @@ mod tests {
Ok(( Ok((
"", "",
vec![ vec![
Token::Number(2f64), OldToken::Number(2f64),
Token::Operator(crate::lexer::Operator::Plus), OldToken::Operator(crate::lexer::Operator::Plus),
Token::Identifier("x".to_owned()), OldToken::Identifier("x".to_owned()),
Token::Operator(crate::lexer::Operator::Cap), OldToken::Operator(crate::lexer::Operator::Cap),
Token::Number(2f64), OldToken::Number(2f64),
Token::Operator(crate::lexer::Operator::Minus), OldToken::Operator(crate::lexer::Operator::Minus),
Token::GroupOpen(GroupingType::Parentheses), OldToken::GroupOpen(GroupingType::Parentheses),
Token::Number(3f64), OldToken::Number(3f64),
Token::Identifier("x".to_owned()), OldToken::Identifier("x".to_owned()),
Token::Operator(crate::lexer::Operator::Cap), OldToken::Operator(crate::lexer::Operator::Cap),
Token::Number(2f64), OldToken::Number(2f64),
Token::Operator(crate::lexer::Operator::Minus), OldToken::Operator(crate::lexer::Operator::Minus),
Token::Number(7f64), OldToken::Number(7f64),
Token::Identifier("y".to_owned()), OldToken::Identifier("y".to_owned()),
Token::Operator(crate::lexer::Operator::Slash), OldToken::Operator(crate::lexer::Operator::Slash),
Token::Identifier("sin".to_owned()), OldToken::Identifier("sin".to_owned()),
Token::GroupOpen(GroupingType::Brackets), OldToken::GroupOpen(GroupingType::Brackets),
Token::Identifier("z".to_owned()), OldToken::Identifier("z".to_owned()),
Token::GroupClose(GroupingType::Brackets), OldToken::GroupClose(GroupingType::Brackets),
Token::GroupClose(GroupingType::Parentheses), OldToken::GroupClose(GroupingType::Parentheses),
] ]
)) ))
); );
} }
#[test]
fn tokens_new_to_old_same() {
crate::log_init();
// arrange
let input = "2 + x^2 - (3x^2 - 7y / sin[z])";
// act
let tokens = lex(input).expect("Must be a valid input").1;
let new_tokens = StrTokenParser::<Token>::new(input)
.inspect(|new_token| trace!(?new_token))
.map(|p| {
p.expect("Should not encounter errors")
.try_into()
.expect("Should not have unsupported tokens")
})
.collect::<Vec<_>>();
// assert
assert_eq!(tokens, new_tokens);
}
} }

68
src/lexer_v2/mod.rs Normal file
View file

@ -0,0 +1,68 @@
use core::marker::PhantomData;
pub mod number;
pub mod token;
pub mod word;
pub trait TokenInput {
fn is_empty(&self) -> bool;
fn on_invalid(self) -> Self;
}
pub trait Parseable<'s>: Sized + 's {
fn try_parse(input: &'s str) -> Option<(Self, &'s str)>;
}
impl<'s, T> StrTokenParser<'s, T> {
pub fn new(input: &'s str) -> Self {
Self {
input,
_phantom: PhantomData,
}
}
}
pub struct StrTokenParser<'s, T> {
input: &'s str,
_phantom: PhantomData<T>,
}
fn skip_err(s: &str) -> (&str, &str) {
let next_whitespace = s
.char_indices()
.find_map(|(pos, ch)| ch.is_whitespace().then_some(pos))
.unwrap_or(s.len());
s.split_at(next_whitespace)
}
impl<'s, T: Parseable<'s>> Iterator for StrTokenParser<'s, T> {
type Item = Result<T, &'s str>;
#[tracing::instrument(level = "trace", skip(self), name = "token_parser", fields(input = self.input))]
fn next(&mut self) -> Option<Self::Item> {
let ws = self.input.trim_start();
trace!(ws);
if ws.is_empty() {
// no more tokens, exhausted the input
return None;
}
if let Some((token, rest)) = T::try_parse(ws) {
// successful token parse, return the token
trace!("Parsed token");
self.input = rest;
Some(Ok(token))
} else {
// failed to parse the token
//
// skip to next whitespace character
let (erroneous, new_input) = skip_err(ws);
self.input = new_input;
trace!(erroneous);
Some(Err(erroneous))
}
}
}
#[cfg(test)]
mod prop_tests;

221
src/lexer_v2/number/mod.rs Normal file
View file

@ -0,0 +1,221 @@
use core::{fmt::Display, ops::Not};
use crate::display_opt;
#[inline]
fn valid_sign(ch: char) -> bool {
matches!(ch, '+' | '-')
}
#[inline]
fn valid_exponent_marker(ch: char) -> bool {
matches!(ch, 'e' | 'E')
}
#[inline]
fn valid_decimal_point(ch: char) -> bool {
matches!(ch, '.')
}
#[derive(
Debug,
Clone,
Copy,
Hash,
PartialEq,
Eq,
derive_more::AsRef,
derive_more::Deref,
derive_more::Display,
)]
#[display("{_0}")]
pub struct Digits<D>(D);
impl<'s> Digits<&'s str> {
#[tracing::instrument(level = "trace", ret, skip(input), fields(digits_input = input))]
fn parse(input: &'s str) -> Option<(Self, &'s str)> {
let end_pos = input
.char_indices()
.find_map(|(pos, ch)| ch.is_ascii_digit().not().then_some(pos))
.unwrap_or(input.len());
if end_pos == 0 {
None
} else {
Some((Self(&input[..end_pos]), &input[end_pos..]))
}
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, derive_more::Display)]
#[display("{e}{}{digits}", if let Some(s) = sign.as_ref() { s as &dyn Display } else { &"" as &dyn Display })]
pub struct Exponent<C, D> {
e: C,
sign: Option<C>,
digits: Digits<D>,
}
impl<'s> Exponent<&'s str, &'s str> {
#[tracing::instrument(level = "trace", ret, skip(input), fields(exponent_input = input))]
fn parse(input: &'s str) -> Option<(Self, &'s str)> {
let mut char_inds = input.char_indices();
// if there's no first char, there's no exponent
let (_, e_marker) = char_inds.next()?;
if !valid_exponent_marker(e_marker) {
// does not start with an exponent marker, bail
return None;
}
// exponent marker cannot go by itself
let (mut pos, maybe_sign) = char_inds.next()?;
let e = &input[..pos];
// figure out sign
let sign;
if valid_sign(maybe_sign) {
// second char is a valid sign, query for next one
let (sign_end, _) = char_inds.next()?;
sign = Some(&input[pos..sign_end]);
pos = sign_end;
} else {
sign = None;
}
let (digits, rest) = Digits::parse(&input[pos..])?;
Some((Self { e, sign, digits }, rest))
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, derive_more::Display)]
pub enum Number<C, D> {
#[display("int({int}e{})", display_opt(exp))]
Integer {
int: Digits<D>,
exp: Option<Exponent<C, D>>,
},
#[display("float({int}{point}{frac}{})", display_opt(exp))]
Decimal {
int: Digits<D>,
point: C,
frac: Digits<D>,
exp: Option<Exponent<C, D>>,
},
#[display("float({int}{point}{})", display_opt(exp))]
DecimalInt {
int: Digits<D>,
point: C,
// no frac part
exp: Option<Exponent<C, D>>,
},
#[display("float({point}{})", display_opt(exp))]
DecimalFraction {
// no int part
point: C,
frac: Digits<D>,
exp: Option<Exponent<C, D>>,
},
}
impl<'s> Number<&'s str, &'s str> {
#[tracing::instrument(level = "trace", skip(input), fields(number_input = input), ret)]
pub fn parse(mut input: &'s str) -> Option<(Self, &'s str)> {
let int_part: Option<Digits<&str>>;
let frac_part: Option<Digits<&str>>;
let exp: Option<Exponent<&str, &str>>;
if let Some((digits, rest)) = Digits::parse(input) {
int_part = Some(digits);
input = rest;
trace!(?int_part, input);
} else {
int_part = None;
}
let point = 'point: {
// try parsing decimal point. if it's only a (optional) sign and integer part - it's a normal integer
let mut char_inds = input.char_indices();
let Some((_, maybe_point)) = char_inds.next() else {
break 'point None;
};
trace!(%maybe_point);
// try parsing the sign
if !valid_decimal_point(maybe_point) {
break 'point None;
}
// this is a valid point char, find there it ends
let point;
if let Some((point_end, _)) = char_inds.next() {
point = &input[..point_end];
input = &input[point_end..];
} else {
point = input;
input = "";
};
Some(point)
};
trace!(?point, input);
// try parsing fractional part
if let Some((digits, rest)) = Digits::parse(input) {
frac_part = Some(digits);
input = rest;
trace!(?frac_part, input);
} else {
frac_part = None;
}
// try parsing exponent
if let Some((exponent, rest)) = Exponent::parse(input) {
exp = Some(exponent);
input = rest;
trace!(?exp, input);
} else {
exp = None;
}
trace!(?int_part, ?point, ?frac_part);
match (int_part, point, frac_part) {
(Some(int), None, None) => Some((Self::Integer { int, exp }, input)),
(Some(int), Some(point), Some(frac)) => Some((
Self::Decimal {
int,
point,
frac,
exp,
},
input,
)),
(None, Some(point), Some(frac)) => {
Some((Self::DecimalFraction { point, frac, exp }, input))
}
(Some(int), Some(point), None) => Some((Self::DecimalInt { int, point, exp }, input)),
_ => None,
}
}
}
#[macro_export]
macro_rules! number {
(? $input:literal) => {
'number: {
let Some((number, rest)) = $crate::lexer_v2::number::Number::parse(stringify!($input))
else {
break 'number None;
};
if !rest.trim().is_empty() {
break 'number None;
}
Some(number)
}
};
($input:literal) => {
number!(?$input).expect("Invalid number")
};
}
#[cfg(test)]
mod tests;
#[cfg(test)]
mod prop_tests;

View file

@ -0,0 +1,121 @@
use core::ops::Not;
use super::{Digits, Exponent, Number};
use proptest::{prelude::ProptestConfig, prop_assert_eq, proptest};
use alloc::string::String;
proptest! {
#![proptest_config(ProptestConfig {
cases: 10_000,
max_default_size_range: 200,
..ProptestConfig::default()
})]
#[test]
fn digits_never_panics(input: String) {
let _ = Digits::parse(&input);
}
#[test]
fn digits_never_parse_letters(s in "[^0-9]+") {
prop_assert_eq!(Digits::parse(&s), None);
}
#[test]
fn digits_always_parse_digits(digits in "[0-9]+", garbage in "[^0-9]+") {
let input = format!("{digits}{garbage}");
prop_assert_eq!(Digits::parse(&input), Some((Digits(digits.as_str()), garbage.as_str())));
}
#[test]
fn exponent_never_panics(input: String) {
let _ = Exponent::parse(&input);
}
#[test]
fn exponent_valid_parses(e in "[eE]", sign in "[+-]?", digits in "[0-9]+", sep in "[^0-9]", garbage: String) {
let input = format!("{e}{sign}{digits}{sep}{garbage}");
let garbage = format!("{sep}{garbage}");
prop_assert_eq!(
Exponent::parse(&input),
Some((
Exponent{
e: e.as_str(),
sign: sign.is_empty().not().then_some(sign.as_str()),
digits: Digits(digits.as_str())
},
garbage.as_str())
));
}
#[test]
fn number_never_panics(input: String) {
let _ = Number::parse(&input);
}
#[test]
fn number_parses_ints(digits in "[0-9]+", sep in "[^eE.0-9]", garbage: String) {
let input = format!("{digits}{sep}{garbage}");
let garbage = format!("{sep}{garbage}");
prop_assert_eq!(
Number::parse(&input),
Some((Number::Integer { int: Digits(digits.as_str()), exp: None }, garbage.as_str()))
);
}
#[test]
fn number_parses_doubles(mut part1 in "[0-9]+", mut part2 in "[0-9]*", swap_parts: bool, e in "[eE]", exponent_sign in "[+-]?", exponent in "[0-9]+", sep in "[^0-9]", garbage: String) {
if swap_parts {
core::mem::swap(&mut part1, &mut part2);
}
let input = format!("{part1}.{part2}{e}{exponent_sign}{exponent}{sep}{garbage}");
let garbage = format!("{sep}{garbage}");
let expected = match(part1.is_empty(), part2.is_empty()) {
(false, false) => Number::Decimal {
int: Digits(part1.as_str()),
point: ".",
frac: Digits(part2.as_str()),
exp: Some(Exponent {
e: e.as_str(),
sign: exponent_sign.is_empty().not().then_some(exponent_sign.as_str()),
digits: Digits(exponent.as_str())
})
},
(true, false) => Number::DecimalFraction {
point: ".",
frac: Digits(part2.as_str()),
exp: Some(Exponent {
e: e.as_str(),
sign: exponent_sign.is_empty().not().then_some(exponent_sign.as_str()),
digits: Digits(exponent.as_str())
})
},
(false, true) => Number::DecimalInt {
int: Digits(part1.as_str()),
point: ".",
exp: Some(Exponent {
e: e.as_str(),
sign: exponent_sign.is_empty().not().then_some(exponent_sign.as_str()),
digits: Digits(exponent.as_str())
})
},
(true, true) => unreachable!(),
};
prop_assert_eq!(
Number::parse(&input),
Some((
expected,
garbage.as_str()
))
);
}
#[test]
fn not_parses_garbage(input in "[^0-9.]") {
prop_assert_eq!(
Number::parse(&input),
None,
);
}
}

View file

@ -0,0 +1,335 @@
use super::{Digits, Exponent, Number};
macro_rules! test_digits_parse {
($name:ident, $input:literal, $expected:expr) => {
::paste::paste! {
#[test]
fn [< $name _for_digits >]() {
$crate::log_init();
assert_eq!(Digits::parse($input), $expected);
}
}
};
}
test_digits_parse!(empty_should_fail, "", None);
test_digits_parse!(whitespace_should_fail, " ", None);
test_digits_parse!(letter_should_fail, "a", None);
test_digits_parse!(minus_should_fail, "-", None);
test_digits_parse!(minus_one_should_fail, "-1", None);
test_digits_parse!(zero_should_ok, "0", Some((Digits("0"), "")));
test_digits_parse!(p42_should_ok, "42", Some((Digits("42"), "")));
test_digits_parse!(
number_space_should_leave_space,
"424233 ",
Some((Digits("424233"), " "))
);
test_digits_parse!(
number_space_letter_should_leave_space_letter,
"424233 l",
Some((Digits("424233"), " l"))
);
test_digits_parse!(
number_letter_should_leave_letter,
"2p",
Some((Digits("2"), "p"))
);
test_digits_parse!(
big_number,
"9934023759839048593537636598927402740240740720909742094----bbb",
Some((
Digits("9934023759839048593537636598927402740240740720909742094"),
"----bbb"
))
);
macro_rules! test_exponent_parse {
($name:ident, $input:literal, $expected:expr) => {
::paste::paste! {
#[test]
fn [< $name _for_exponent >]() {
$crate::log_init();
assert_eq!(Exponent::parse($input), $expected);
}
}
};
}
test_exponent_parse!(empty_should_fail, "", None);
test_exponent_parse!(minus_should_fail, "-", None);
test_exponent_parse!(number_should_fail, "423", None);
test_exponent_parse!(only_e_should_fail, "e", None);
test_exponent_parse!(only_cap_e_should_fail, "E", None);
test_exponent_parse!(
e1_should_ok,
"e1",
Some((
Exponent {
e: "e",
sign: None,
digits: Digits("1"),
},
""
))
);
test_exponent_parse!(
emore_should_ok,
"e92324l",
Some((
Exponent {
e: "e",
sign: None,
digits: Digits("92324"),
},
"l"
))
);
test_exponent_parse!(
emore_minus_should_ok,
"e-92324l",
Some((
Exponent {
e: "e",
sign: Some("-"),
digits: Digits("92324"),
},
"l"
))
);
test_exponent_parse!(
emore_plus_should_ok,
"e+92324l",
Some((
Exponent {
e: "e",
sign: Some("+"),
digits: Digits("92324"),
},
"l"
))
);
test_exponent_parse!(emore_plus_plus_should_fail, "e++92324l", None);
test_exponent_parse!(
emore_plus_num_plus_should_ok,
"E+92+",
Some((
Exponent {
e: "E",
sign: Some("+"),
digits: Digits("92")
},
"+"
))
);
macro_rules! test_number_parse {
($name:ident, $input:literal, $expected:expr) => {
::paste::paste! {
#[test]
fn [< $name _for_number >]() {
$crate::log_init();
assert_eq!(Number::parse($input), $expected);
}
}
};
}
test_number_parse!(empty_should_fail, "", None);
test_number_parse!(word_shold_fail, "word", None);
test_number_parse!(
int_should_ok,
"1",
Some((
Number::Integer {
int: Digits("1"),
exp: None
},
""
))
);
test_number_parse!(
int_should_ok2,
"99",
Some((
Number::Integer {
int: Digits("99"),
exp: None
},
""
))
);
test_number_parse!(
int_exp,
"99E2",
Some((
Number::Integer {
int: Digits("99"),
exp: Some(Exponent {
e: "E",
sign: None,
digits: Digits("2")
})
},
""
))
);
test_number_parse!(
int_exp_minus,
"99E-2",
Some((
Number::Integer {
int: Digits("99"),
exp: Some(Exponent {
e: "E",
sign: Some("-"),
digits: Digits("2")
})
},
""
))
);
test_number_parse!(
int_exp_plus,
"99E+2",
Some((
Number::Integer {
int: Digits("99"),
exp: Some(Exponent {
e: "E",
sign: Some("+"),
digits: Digits("2")
})
},
""
))
);
test_number_parse!(
int_plus_plus_should_no_exp,
"99E++2",
Some((
Number::Integer {
int: Digits("99"),
exp: None
},
"E++2"
))
);
test_number_parse!(
dec_int_only,
"2.",
Some((
Number::DecimalInt {
int: Digits("2"),
point: ".",
exp: None
},
""
))
);
test_number_parse!(
dec_int_only_exp,
"2.E-3-",
Some((
Number::DecimalInt {
int: Digits("2"),
point: ".",
exp: Some(Exponent {
e: "E",
sign: Some("-"),
digits: Digits("3")
})
},
"-"
))
);
test_number_parse!(
dec_int_full,
"3.141592--",
Some((
Number::Decimal {
int: Digits("3"),
point: ".",
frac: Digits("141592"),
exp: None
},
"--"
))
);
test_number_parse!(
dec_int_full_exp,
"3141.592e-3+3-",
Some((
Number::Decimal {
int: Digits("3141"),
point: ".",
frac: Digits("592"),
exp: Some(Exponent {
e: "e",
sign: Some("-"),
digits: Digits("3")
})
},
"+3-"
))
);
test_number_parse!(
dec_int_frac_exp,
".592e3+3-",
Some((
Number::DecimalFraction {
point: ".",
frac: Digits("592"),
exp: Some(Exponent {
e: "e",
sign: None,
digits: Digits("3")
})
},
"+3-"
))
);
test_number_parse!(
dec_int_frac,
".592_3+3-",
Some((
Number::DecimalFraction {
point: ".",
frac: Digits("592"),
exp: None
},
"_3+3-"
))
);
test_number_parse!(
dec_int_frac_exp2,
".592E-37",
Some((
Number::DecimalFraction {
point: ".",
frac: Digits("592"),
exp: Some(Exponent {
e: "E",
sign: Some("-"),
digits: Digits("37")
})
},
""
))
);

View file

@ -0,0 +1,19 @@
use super::{token::SpannedToken, StrTokenParser};
use proptest::{prelude::ProptestConfig, proptest};
use alloc::string::String;
proptest! {
#![proptest_config(ProptestConfig {
cases: 30_000,
max_default_size_range: 500,
..ProptestConfig::default()
})]
#[test]
fn parser_never_panics(input: String) {
let parser = StrTokenParser::<'_, SpannedToken<&str>>::new(&input);
let _ = parser.count();
}
}

463
src/lexer_v2/token/mod.rs Normal file
View file

@ -0,0 +1,463 @@
use super::{number::Number, word::Word, Parseable};
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct SpannedToken<Span, D = &'static str, C = &'static str, W = &'static str> {
token: Token<D, C, W>,
span: Span,
}
impl<'s> Parseable<'s> for SpannedToken<&'s str, &'s str, &'s str, &'s str> {
#[inline]
fn try_parse(input: &'s str) -> Option<(Self, &'s str)> {
Token::try_parse(input).map(|(token, rest)| {
(
Self {
token,
span: &input[..input.len() - rest.len()],
},
rest,
)
})
}
}
/// Grouping symbol kind
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, derive_more::Display)]
pub enum GroupingKind {
#[display("paren")]
Parentheses,
#[display("bracket")]
Brackets,
#[display("brace")]
Braces,
#[display("angle")]
Angle,
}
/// Grouping symbol kind
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, derive_more::Display)]
pub enum GroupingSide {
#[display("left")]
Left,
#[display("right")]
Right,
}
/// Represents operators (not necessary corresponding to actual [`crate::evaluation_tree::Operator`]s).
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, derive_more::Display)]
pub enum SpecialKind {
#[display(".")]
Dot,
#[display(",")]
Comma,
#[display("+")]
Plus,
#[display("-")]
Minus,
#[display("*")]
Star,
#[display("/")]
Slash,
#[display("%")]
Percent,
#[display("^")]
Cap,
#[display("|")]
Pipe,
#[display("&")]
Umpersand,
#[display(":")]
Colon,
#[display("!")]
Bang,
#[display("=")]
Equals,
#[display("?")]
Question,
#[display("@")]
At,
#[display("~")]
Tilda,
}
/// Represents a single token.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, derive_more::Display)]
pub enum Token<C = &'static str, D = &'static str, W = &'static str> {
#[display("{kind}_{side}")]
Grouping {
kind: GroupingKind,
side: GroupingSide,
},
#[display("{_0}")]
Special(SpecialKind),
#[display("{_0}")]
Number(Number<D, C>),
#[display("{_0}")]
Word(Word<W>),
}
impl<'s> Parseable<'s> for Token<&'s str, &'s str, &'s str> {
fn try_parse(input: &'s str) -> Option<(Self, &'s str)> {
// if there's no char, there's no token
let c = input.chars().next()?; // (short-circuit)
// try parsing word
//
// can't be confused with any other token, and mot of the tokens are expected to be words
if let Some((word, rest)) = Word::parse(input) {
return Some((Token::word(word), rest));
}
// try parsing number
//
// (same idea)
if let Some((number, rest)) = Number::parse(input) {
return Some((Token::number(number), rest));
}
// token is not a number and not a word, so it is a one-character token
let token = match c {
'(' => Token::left_paren(),
')' => Token::right_paren(),
'[' => Token::left_bracket(),
']' => Token::right_bracket(),
'{' => Token::left_brace(),
'}' => Token::right_brace(),
'.' => Token::dot(),
',' => Token::comma(),
'+' => Token::plus(),
'-' => Token::minus(),
'*' => Token::star(),
'/' => Token::slash(),
'%' => Token::percent(),
'^' => Token::cap(),
'|' => Token::pipe(),
'&' => Token::umpersand(),
':' => Token::colon(),
'!' => Token::bang(),
'=' => Token::equals(),
'?' => Token::question(),
'@' => Token::at(),
'~' => Token::tilda(),
// not a token
_ => return None,
};
let next_pos = input
.char_indices()
// position or the char after first
.nth(1)
// or end of the line, if that's the only char
.map_or(input.len(), |(pos, _)| pos);
Some((token, &input[next_pos..]))
}
}
// helper constructors and macro
impl<C, D, W> Token<C, D, W> {
pub fn dot() -> Self {
Token::Special(SpecialKind::Dot)
}
pub fn comma() -> Self {
Token::Special(SpecialKind::Comma)
}
pub fn left_paren() -> Self {
Self::Grouping {
kind: GroupingKind::Parentheses,
side: GroupingSide::Left,
}
}
pub fn right_paren() -> Self {
Self::Grouping {
kind: GroupingKind::Parentheses,
side: GroupingSide::Right,
}
}
pub fn left_bracket() -> Self {
Self::Grouping {
kind: GroupingKind::Brackets,
side: GroupingSide::Left,
}
}
pub fn right_bracket() -> Self {
Self::Grouping {
kind: GroupingKind::Brackets,
side: GroupingSide::Right,
}
}
pub fn left_brace() -> Self {
Self::Grouping {
kind: GroupingKind::Braces,
side: GroupingSide::Left,
}
}
pub fn right_brace() -> Self {
Self::Grouping {
kind: GroupingKind::Braces,
side: GroupingSide::Right,
}
}
pub fn left_angle() -> Self {
Self::Grouping {
kind: GroupingKind::Angle,
side: GroupingSide::Left,
}
}
pub fn right_angle() -> Self {
Self::Grouping {
kind: GroupingKind::Angle,
side: GroupingSide::Right,
}
}
pub fn plus() -> Self {
Token::Special(SpecialKind::Plus)
}
pub fn minus() -> Self {
Token::Special(SpecialKind::Minus)
}
pub fn star() -> Self {
Token::Special(SpecialKind::Star)
}
pub fn slash() -> Self {
Token::Special(SpecialKind::Slash)
}
pub fn percent() -> Self {
Token::Special(SpecialKind::Percent)
}
pub fn cap() -> Self {
Token::Special(SpecialKind::Cap)
}
pub fn pipe() -> Self {
Token::Special(SpecialKind::Pipe)
}
pub fn umpersand() -> Self {
Token::Special(SpecialKind::Umpersand)
}
pub fn colon() -> Self {
Token::Special(SpecialKind::Colon)
}
pub fn bang() -> Self {
Token::Special(SpecialKind::Bang)
}
pub fn equals() -> Self {
Token::Special(SpecialKind::Equals)
}
pub fn question() -> Self {
Token::Special(SpecialKind::Question)
}
pub fn at() -> Self {
Token::Special(SpecialKind::At)
}
pub fn tilda() -> Self {
Token::Special(SpecialKind::Tilda)
}
pub fn number(n: Number<D, C>) -> Self {
Token::Number(n)
}
pub fn word(w: Word<W>) -> Self {
Token::Word(w)
}
}
#[macro_export]
macro_rules! tokens_inner {
() => {
arr![]
};
( ( $($inner:tt)* ) $($rest:tt)* ) => {
Concat::concat(
Concat::concat(
Concat::concat(
arr![Token::left_paren()],
tokens_inner!($($inner)*)
),
arr![Token::right_paren()],
),
tokens_inner!($($rest)*)
)
};
( [ $($inner:tt)* ] $($rest:tt)* ) => {
Concat::concat(
Concat::concat(
Concat::concat(
arr![Token::left_bracket()],
tokens_inner!($($inner)*)
),
arr![Token::right_bracket()],
),
tokens_inner!($($rest)*)
)
};
( { $($inner:tt)* } $($rest:tt)* ) => {
Concat::concat(
Concat::concat(
Concat::concat(
arr![Token::left_brace()],
tokens_inner!($($inner)*)
),
arr![Token::right_brace()],
),
tokens_inner!($($rest)*)
)
};
(. $($rest:tt)*) => {
Concat::concat(
arr![Token::dot()],
tokens_inner!($($rest)*)
)
};
(, $($rest:tt)*) => {
Concat::concat(
arr![Token::comma()],
tokens_inner!($($rest)*)
)
};
(+ $($rest:tt)*) => {
Concat::concat(
arr![Token::plus()],
tokens_inner!($($rest)*)
)
};
(- $($rest:tt)*) => {
Concat::concat(
arr![Token::minus()],
tokens_inner!($($rest)*)
)
};
(* $($rest:tt)*) => {
Concat::concat(
arr![Token::star()],
tokens_inner!($($rest)*)
)
};
(/ $($rest:tt)*) => {
Concat::concat(
arr![Token::slash()],
tokens_inner!($($rest)*)
)
};
(% $($rest:tt)*) => {
Concat::concat(
arr![Token::percent()],
tokens_inner!($($rest)*)
)
};
(^ $($rest:tt)*) => {
Concat::concat(
arr![Token::cap()],
tokens_inner!($($rest)*)
)
};
(| $($rest:tt)*) => {
Concat::concat(
arr![Token::pipe()],
tokens_inner!($($rest)*)
)
};
(& $($rest:tt)*) => {
Concat::concat(
arr![Token::umpersand()],
tokens_inner!($($rest)*)
)
};
(: $($rest:tt)*) => {
Concat::concat(
arr![Token::colon()],
tokens_inner!($($rest)*)
)
};
(! $($rest:tt)*) => {
Concat::concat(
arr![Token::bang()],
tokens_inner!($($rest)*)
)
};
(= $($rest:tt)*) => {
Concat::concat(
arr![Token::equals()],
tokens_inner!($($rest)*)
)
};
(? $($rest:tt)*) => {
Concat::concat(
arr![Token::question()],
tokens_inner!($($rest)*)
)
};
(@ $($rest:tt)*) => {
Concat::concat(
arr![Token::at()],
tokens_inner!($($rest)*)
)
};
(~ $($rest:tt)*) => {
Concat::concat(
arr![Token::tilda()],
tokens_inner!($($rest)*)
)
};
(< $($rest:tt)*) => {
Concat::concat(
arr![Token::left_angle()],
tokens_inner!($($rest)*)
)
};
(> $($rest:tt)*) => {
Concat::concat(
arr![Token::right_angle()],
tokens_inner!($($rest)*)
)
};
($token:ident $($rest:tt)*) => {
Concat::concat(
arr![Token::word(word!($token))],
tokens_inner!($($rest)*)
)
};
($token:literal $($rest:tt)*) => {
Concat::concat(
arr![Token::number(number!($token))],
tokens_inner!($($rest)*)
)
};
}
#[macro_export]
macro_rules! tokens {
($($rest:tt)*) => {{
#[allow(unused_imports)]
use generic_array::{arr, sequence::Concat};
#[allow(unused_imports)]
use $crate::{lexer_v2::token::Token, word, tokens_inner, number};
tokens_inner!($($rest)*)
}};
}
#[cfg(test)]
mod tests;

166
src/lexer_v2/token/tests.rs Normal file
View file

@ -0,0 +1,166 @@
use super::super::StrTokenParser;
use super::Token;
use crate::{number, word};
use alloc::vec::Vec;
macro_rules! test_tokens {
($(#[$($attr:tt)+])* $name:ident, ($($input:tt)*), [$($expected:expr),*]) => {
::paste::paste! {
#[test]
$(
#[$($attr)*]
)*
fn [< $name _tokens >]() {
$crate::log_init();
let input = tokens!($($input)*);
let expected = generic_array::GenericArray::<Token<&'static str, &'static str, &'static str>, _>::from_array([$($expected),*]);
assert_eq!(input, expected);
}
}
};
}
test_tokens!(empty, (), []);
test_tokens!(dot, (.), [Token::dot()]);
test_tokens!(comma, (,), [Token::comma()]);
test_tokens!(plus, (+), [Token::plus()]);
test_tokens!(minus, (-), [Token::minus()]);
test_tokens!(star, (*), [Token::star()]);
test_tokens!(slash, (/), [Token::slash()]);
test_tokens!(percent, (%), [Token::percent()]);
test_tokens!(cap, (^), [Token::cap()]);
test_tokens!(pipe, (|), [Token::pipe()]);
test_tokens!(umpersand, (&), [Token::umpersand()]);
test_tokens!(colon, (:), [Token::colon()]);
test_tokens!(bang, (!), [Token::bang()]);
test_tokens!(equals, (=), [Token::equals()]);
test_tokens!(question, (?), [Token::question()]);
test_tokens!(at, (@), [Token::at()]);
test_tokens!(tilda, (~), [Token::tilda()]);
test_tokens!(left_angle, (<), [Token::left_angle()]);
test_tokens!(right_angle, (>), [Token::right_angle()]);
test_tokens!(word, (word), [Token::word(word!(word))]);
test_tokens!(word2, (w0r3d), [Token::word(word!(w0r3d))]);
test_tokens!(
long_word,
(SOME_REALLY_LONG_WORD_1111111),
[Token::word(word!(SOME_REALLY_LONG_WORD_1111111))]
);
test_tokens!(
#[should_panic = "Invalid word"]
invalid_word,
(r#not_a_valid_word),
[Token::word(word!(not_a_valid_word))]
);
test_tokens!(number, (1), [Token::number(number!(1))]);
test_tokens!(number2, (1.), [Token::number(number!(1.))]);
test_tokens!(number3, (1.), [Token::number(number!(1.))]);
test_tokens!(number4, (1.0E0), [Token::number(number!(1.0E0))]);
test_tokens!(
negative_number,
(-24.003E6),
[Token::minus(), Token::number(number!(24.003E6))]
);
test_tokens!(
very_much_positive_number,
(+ + + 42.0E-5),
[Token::plus(), Token::plus(), Token::plus(), Token::number(number!(42.0E-5))]
);
test_tokens!(
random_expr,
(3 + 4 - 6 + twelve - 00 - ONLY),
[
Token::number(number!(3)),
Token::plus(),
Token::number(number!(4)),
Token::minus(),
Token::number(number!(6)),
Token::plus(),
Token::word(word!(twelve)),
Token::minus(),
Token::number(number!(00)),
Token::minus(),
Token::word(word!(ONLY))
]
);
test_tokens!(
parens,
((2 + 3.4e-21) | [a - _BB45] {} / 10),
[
Token::left_paren(),
Token::number(number!(2)),
Token::plus(),
Token::number(number!(3.4e-21)),
Token::right_paren(),
Token::pipe(),
Token::left_bracket(),
Token::word(word!(a)),
Token::minus(),
Token::word(word!(_BB45)),
Token::right_bracket(),
Token::left_brace(),
Token::right_brace(),
Token::slash(),
Token::number(number!(10))
]
);
macro_rules! test_token_parser {
($name:ident, $input:literal, [$($token:expr),*$(,)?]) => {
::paste::paste! {
#[test]
fn [< $name _tokens_iter >]() {
$crate::log_init();
let iter = StrTokenParser::<'_, Token>::new($input);
let parsed_tokens = iter.collect::<Vec<_>>();
assert_eq!(parsed_tokens.as_slice(), &[$($token),*]);
}
}
};
}
test_token_parser!(empty_parses, "", []);
test_token_parser!(plus_pares, "+", [Ok(Token::plus())]);
test_token_parser!(
expr_parses,
"+3 - b4_A[AA/ #iim 3e-67+ (17.00E-67c ]}- an mee#34\nb) \t ---",
[
Ok(Token::plus()),
Ok(Token::number(number!(3))),
Ok(Token::minus()),
Ok(Token::word(word!(b4_A))),
Ok(Token::left_bracket()),
Ok(Token::word(word!(AA))),
Ok(Token::slash()),
Err("#iim"),
Ok(Token::number(number!(3e-67))),
Ok(Token::plus()),
Ok(Token::left_paren()),
Ok(Token::number(number!(17.00E-67))),
Ok(Token::word(word!(c))),
Ok(Token::right_bracket()),
Ok(Token::right_brace()),
Ok(Token::minus()),
Ok(Token::word(word!(an))),
Ok(Token::word(word!(mee))),
Err("#34"),
Ok(Token::word(word!(b))),
Ok(Token::right_paren()),
Ok(Token::minus()),
Ok(Token::minus()),
Ok(Token::minus()),
]
);

70
src/lexer_v2/word/mod.rs Normal file
View file

@ -0,0 +1,70 @@
use core::ops::Not;
#[derive(
Debug,
Clone,
Copy,
Hash,
PartialEq,
Eq,
derive_more::AsRef,
derive_more::Deref,
derive_more::Display,
)]
#[display("{_0}")]
pub struct Word<W>(W);
#[inline]
fn valid_start(ch: char) -> bool {
matches!(ch, 'a'..='z' | 'A'..='Z' | '_')
}
#[inline]
fn valid_continue(ch: char) -> bool {
matches!(ch, 'a'..='z' | 'A'..='Z' | '_' | '0'..='9')
}
impl<'s> Word<&'s str> {
pub fn parse(input: &'s str) -> Option<(Self, &'s str)> {
let mut char_inds = input
// be a good (cat)boy and iterate over characters
.char_indices();
// get first non-whitespace char (if there's none, we are done)
let (_, first_char) = char_inds.next()?;
if !valid_start(first_char) {
// first non-whitespace is not a valid started for word; bail
return None;
}
// word will end, once we
let end_pos = char_inds
// find next first char that is not a valid continue
.find_map(|(pos, ch)| valid_continue(ch).not().then_some(pos))
// if there none, word is up to the end of string
.unwrap_or(input.len());
Some((Self(&input[..end_pos]), &input[end_pos..]))
}
}
#[macro_export]
macro_rules! word {
(? $input:ident) => {
'word: {
let Some((word, rest)) = $crate::lexer_v2::word::Word::parse(stringify!($input)) else {
break 'word None;
};
if !rest.trim().is_empty() {
break 'word None;
}
Some(word)
}
};
($input:ident) => {
word!(?$input).expect("Invalid word")
};
}
#[cfg(test)]
mod tests;
#[cfg(test)]
mod prop_tests;

View file

@ -0,0 +1,37 @@
use super::Word;
use proptest::{prelude::ProptestConfig, prop_assert_eq, proptest};
use alloc::string::String;
proptest! {
#![proptest_config(ProptestConfig {
cases: 10_000,
max_default_size_range: 200,
..ProptestConfig::default()
})]
#[test]
fn never_panics(input: String) {
let _ = Word::parse(&input);
}
#[test]
fn parses_words(word in "[a-zA-Z_][a-zA-Z0-9_]*", sep in "[^a-zA-Z0-9_]", garbage: String) {
let input = format!("{word}{sep}{garbage}");
let garbage = format!("{sep}{garbage}");
prop_assert_eq!(
Word::parse(&input),
Some((Word(word.as_str()), garbage.as_str()))
);
}
#[test]
fn not_parses_garbage(input in "[^a-zA-Z_]*") {
prop_assert_eq!(
Word::parse(&input),
None,
);
}
}

View file

@ -0,0 +1,36 @@
use super::Word;
macro_rules! test_word_parse {
($name:ident, $input:literal, $expected:expr) => {
#[test]
fn $name() {
assert_eq!(Word::parse($input), $expected);
}
};
}
test_word_parse!(empty_should_fail, "", None);
test_word_parse!(number_should_fail, "1", None);
test_word_parse!(whitespace_should_fail, " ", None);
test_word_parse!(non_ascii_should_fail, "слово", None);
test_word_parse!(minus_should_fail, "-", None);
test_word_parse!(letter_should_ok, "a", Some((Word("a"), "")));
test_word_parse!(letter_should_ok2, "U", Some((Word("U"), "")));
test_word_parse!(underscore_should_ok, "_", Some((Word("_"), "")));
test_word_parse!(
letter_space_should_leave_space,
"a ",
Some((Word("a"), " "))
);
test_word_parse!(
letter_something_should_keep_something,
"a something",
Some((Word("a"), " something"))
);
test_word_parse!(
a_minus_b_should_leave_minus_b,
"a-b",
Some((Word("a"), "-b"))
);

View file

@ -1,6 +1,56 @@
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#![no_std]
use core::fmt::Display;
pub mod evaluation_tree; pub mod evaluation_tree;
pub mod lexer; pub mod lexer;
pub mod parser; pub mod parser;
pub mod lexer_v2;
#[macro_use]
extern crate tracing;
// #[cfg(test)] // <-- TODO: make optional
#[macro_use]
extern crate alloc;
#[cfg(test)]
#[allow(unused)]
pub(crate) fn log_init() {
use tracing::level_filters::LevelFilter;
use tracing_subscriber::fmt::{self, format::FmtSpan, time::OffsetTime};
#[cfg(verbose_logging)]
if fmt::fmt()
.with_max_level(LevelFilter::TRACE)
.with_span_events(FmtSpan::ACTIVE)
.with_line_number(true)
.with_level(true)
.with_target(true)
.with_timer(OffsetTime::local_rfc_3339().expect("Failed to get local timer"))
.compact()
.try_init()
.is_ok()
{
info!("Log up!");
} else {
eprintln!("Log is up?");
}
}
#[allow(
clippy::ref_option,
reason = "Use case. Don't want to call `.as_ref()` on each site"
)]
pub(crate) fn display_opt<T: Display>(t: &Option<T>) -> &dyn Display {
if let Some(t) = t.as_ref() {
t as &dyn Display
} else {
&"" as &dyn Display
}
}
#[cfg(test)]
#[macro_use]
extern crate std;

View file

@ -1,6 +1,11 @@
//! This module defines parser and relevant structs. //! This module defines parser and relevant structs.
use std::num::NonZeroUsize; use core::num::NonZeroUsize;
use alloc::{
string::{String, ToString},
vec::Vec,
};
use nom::{ use nom::{
branch::alt, branch::alt,
@ -9,6 +14,7 @@ use nom::{
sequence::tuple, sequence::tuple,
Err, Needed, Err, Needed,
}; };
use num::complex::Complex64; use num::complex::Complex64;
use thiserror::Error; use thiserror::Error;
@ -18,7 +24,7 @@ use crate::{
functions::NamedFunction, functions::NamedFunction,
Constant, Node, Variable, Constant, Node, Variable,
}, },
lexer::{Operator, Token, TokenType}, lexer::{OldToken, Operator, TokenType},
}; };
/// Represents error occurred during tree generation. /// Represents error occurred during tree generation.
@ -26,13 +32,13 @@ use crate::{
pub enum GenerationError<'l> { pub enum GenerationError<'l> {
/// Error emitted by `nom` /// Error emitted by `nom`
#[error("{0:?}")] #[error("{0:?}")]
NomError(NError<&'l [Token]>), NomError(NError<&'l [OldToken]>),
/// There are no more tokens, but some were expected /// There are no more tokens, but some were expected
#[error("No tokens")] #[error("No tokens")]
Empty(Needed), Empty(Needed),
/// Unexpected token type encountered /// Unexpected token type encountered
#[error("Bad token: {} expected, but {:?} found", .0, .1)] #[error("Bad token: {} expected, but {:?} found", .0, .1)]
BadToken(TokenType, &'l Token), BadToken(TokenType, &'l OldToken),
/// There's an unpaired grouping /// There's an unpaired grouping
#[error("Paren was left unclosed")] #[error("Paren was left unclosed")]
UnpairedGrouping, UnpairedGrouping,
@ -41,12 +47,12 @@ pub enum GenerationError<'l> {
NoToken, NoToken,
} }
impl<'l> ParseError<&'l [Token]> for GenerationError<'l> { impl<'l> ParseError<&'l [OldToken]> for GenerationError<'l> {
fn from_error_kind(input: &'l [Token], kind: nom::error::ErrorKind) -> Self { fn from_error_kind(input: &'l [OldToken], kind: nom::error::ErrorKind) -> Self {
Self::NomError(NError::new(input, kind)) Self::NomError(NError::new(input, kind))
} }
fn append(_input: &[Token], _kind: nom::error::ErrorKind, other: Self) -> Self { fn append(_input: &[OldToken], _kind: nom::error::ErrorKind, other: Self) -> Self {
// not really sure what this is about, // not really sure what this is about,
// but it seems that `nom` itself implements this function in a similar fashion, // but it seems that `nom` itself implements this function in a similar fashion,
// so I guess it's not much of a harm. // so I guess it's not much of a harm.
@ -55,10 +61,10 @@ impl<'l> ParseError<&'l [Token]> for GenerationError<'l> {
} }
} }
type Res<'l, T = Node<Complex64>> = Result<(&'l [Token], T), Err<GenerationError<'l>>>; type Res<'l, T = Node<Complex64>> = Result<(&'l [OldToken], T), Err<GenerationError<'l>>>;
/// parses tokens into a node. /// parses tokens into a node.
pub fn parse(tokens: &[Token]) -> Result<Node<Complex64>, GenerationError> { pub fn parse(tokens: &[OldToken]) -> Result<Node<Complex64>, GenerationError> {
all_consuming(node)(tokens) all_consuming(node)(tokens)
.map(|(_, n)| n) .map(|(_, n)| n)
.map_err(|err| match err { .map_err(|err| match err {
@ -67,12 +73,12 @@ pub fn parse(tokens: &[Token]) -> Result<Node<Complex64>, GenerationError> {
}) })
} }
fn node(tokens: &[Token]) -> Res { fn node(tokens: &[OldToken]) -> Res {
assert_parens(tokens)?; assert_parens(tokens)?;
alt((all_consuming(map_parser(paren, raw_node)), raw_node))(tokens) alt((all_consuming(map_parser(paren, raw_node)), raw_node))(tokens)
} }
fn raw_node(tokens: &[Token]) -> Res { fn raw_node(tokens: &[OldToken]) -> Res {
alt(( alt((
all_consuming(function_call), all_consuming(function_call),
all_consuming(variable), all_consuming(variable),
@ -81,7 +87,7 @@ fn raw_node(tokens: &[Token]) -> Res {
))(tokens) ))(tokens)
} }
fn bin_operator(tokens: &[Token]) -> Res { fn bin_operator(tokens: &[OldToken]) -> Res {
let (_, (node1, op, node2)) = find_bin_operator(tokens)?; let (_, (node1, op, node2)) = find_bin_operator(tokens)?;
let (_, node1) = node(node1)?; let (_, node1) = node(node1)?;
let (_, node2) = node(node2)?; let (_, node2) = node(node2)?;
@ -97,7 +103,7 @@ fn bin_operator(tokens: &[Token]) -> Res {
)) ))
} }
fn find_bin_operator(tokens: &[Token]) -> Res<(&[Token], Operator, &[Token])> { fn find_bin_operator(tokens: &[OldToken]) -> Res<(&[OldToken], Operator, &[OldToken])> {
static PREDESCENCE: [Operator; 5] = [ static PREDESCENCE: [Operator; 5] = [
Operator::Plus, Operator::Plus,
Operator::Minus, Operator::Minus,
@ -112,9 +118,9 @@ fn find_bin_operator(tokens: &[Token]) -> Res<(&[Token], Operator, &[Token])> {
.enumerate() .enumerate()
.filter_map(|(i, token)| { .filter_map(|(i, token)| {
match token { match token {
Token::GroupOpen(_) => inner += 1, OldToken::GroupOpen(_) => inner += 1,
Token::GroupClose(_) => inner -= 1, OldToken::GroupClose(_) => inner -= 1,
Token::Operator(op) if op == operator => { OldToken::Operator(op) if op == operator => {
if inner == 0 { if inner == 0 {
return Some(i); return Some(i);
} }
@ -135,7 +141,7 @@ fn find_bin_operator(tokens: &[Token]) -> Res<(&[Token], Operator, &[Token])> {
Err(Err::Error(GenerationError::NoToken)) Err(Err::Error(GenerationError::NoToken))
} }
fn constant(tokens: &[Token]) -> Res { fn constant(tokens: &[OldToken]) -> Res {
let (rest, val) = alt(( let (rest, val) = alt((
map( map(
tuple((opt(sign), real_part, sign, imaginary_part)), tuple((opt(sign), real_part, sign, imaginary_part)),
@ -165,11 +171,11 @@ fn constant(tokens: &[Token]) -> Res {
Ok((rest, Constant(val).into())) Ok((rest, Constant(val).into()))
} }
fn real_part(tokens: &[Token]) -> Res<f64> { fn real_part(tokens: &[OldToken]) -> Res<f64> {
number(tokens) number(tokens)
} }
fn imaginary_part(tokens: &[Token]) -> Res<f64> { fn imaginary_part(tokens: &[OldToken]) -> Res<f64> {
alt(( alt((
map(tuple((number, opt(star), imaginary_unit)), |(n, _, ())| n), map(tuple((number, opt(star), imaginary_unit)), |(n, _, ())| n),
map(tuple((imaginary_unit, star, number)), |((), (), n)| n), map(tuple((imaginary_unit, star, number)), |((), (), n)| n),
@ -177,7 +183,7 @@ fn imaginary_part(tokens: &[Token]) -> Res<f64> {
))(tokens) ))(tokens)
} }
fn function_call(tokens: &[Token]) -> Res { fn function_call(tokens: &[OldToken]) -> Res {
map( map(
tuple(( tuple((
opt(sign), opt(sign),
@ -202,7 +208,7 @@ fn function_call(tokens: &[Token]) -> Res {
)(tokens) )(tokens)
} }
fn variable(tokens: &[Token]) -> Res { fn variable(tokens: &[OldToken]) -> Res {
map(tuple((opt(sign), identifier)), |(minus, name)| { map(tuple((opt(sign), identifier)), |(minus, name)| {
if let Some(true) = minus { if let Some(true) = minus {
Negation::new(Variable::new(name)).into() Negation::new(Variable::new(name)).into()
@ -214,14 +220,14 @@ fn variable(tokens: &[Token]) -> Res {
// general tokens // general tokens
fn assert_parens(tokens: &[Token]) -> Res<()> { fn assert_parens(tokens: &[OldToken]) -> Res<()> {
let mut stack = Vec::new(); let mut stack = Vec::new();
for token in tokens { for token in tokens {
if let Token::GroupOpen(t) = token { if let OldToken::GroupOpen(t) = token {
stack.push(t); stack.push(t);
} }
if let Token::GroupClose(t) = token { if let OldToken::GroupClose(t) = token {
let Some(&e) = stack.last() else { let Some(&e) = stack.last() else {
return Err(Err::Failure(GenerationError::UnpairedGrouping)); return Err(Err::Failure(GenerationError::UnpairedGrouping));
}; };
@ -238,13 +244,13 @@ fn assert_parens(tokens: &[Token]) -> Res<()> {
} }
} }
fn number(tokens: &[Token]) -> Res<f64> { fn number(tokens: &[OldToken]) -> Res<f64> {
let token = tokens let token = tokens
.first() .first()
.ok_or(Err::Error(GenerationError::Empty(Needed::Size( .ok_or(Err::Error(GenerationError::Empty(Needed::Size(
NonZeroUsize::try_from(1).expect("1 is not zero"), NonZeroUsize::try_from(1).expect("1 is not zero"),
))))?; ))))?;
let Token::Number(val) = token else { let OldToken::Number(val) = token else {
return Err(Err::Error(GenerationError::BadToken( return Err(Err::Error(GenerationError::BadToken(
TokenType::Constant, TokenType::Constant,
token, token,
@ -253,13 +259,13 @@ fn number(tokens: &[Token]) -> Res<f64> {
Ok((&tokens[1..], *val)) Ok((&tokens[1..], *val))
} }
fn plus(tokens: &[Token]) -> Res<()> { fn plus(tokens: &[OldToken]) -> Res<()> {
let t = tokens let t = tokens
.first() .first()
.ok_or(Err::Error(GenerationError::Empty(Needed::Size( .ok_or(Err::Error(GenerationError::Empty(Needed::Size(
NonZeroUsize::try_from(1).expect("1 is not zero"), NonZeroUsize::try_from(1).expect("1 is not zero"),
))))?; ))))?;
let Token::Operator(Operator::Plus) = t else { let OldToken::Operator(Operator::Plus) = t else {
return Err(Err::Error(GenerationError::BadToken( return Err(Err::Error(GenerationError::BadToken(
TokenType::Operator, TokenType::Operator,
t, t,
@ -268,13 +274,13 @@ fn plus(tokens: &[Token]) -> Res<()> {
Ok((&tokens[1..], ())) Ok((&tokens[1..], ()))
} }
fn minus(tokens: &[Token]) -> Res<()> { fn minus(tokens: &[OldToken]) -> Res<()> {
let t = tokens let t = tokens
.first() .first()
.ok_or(Err::Error(GenerationError::Empty(Needed::Size( .ok_or(Err::Error(GenerationError::Empty(Needed::Size(
NonZeroUsize::try_from(1).expect("1 is not zero"), NonZeroUsize::try_from(1).expect("1 is not zero"),
))))?; ))))?;
let Token::Operator(Operator::Minus) = t else { let OldToken::Operator(Operator::Minus) = t else {
return Err(Err::Error(GenerationError::BadToken( return Err(Err::Error(GenerationError::BadToken(
TokenType::Operator, TokenType::Operator,
t, t,
@ -283,13 +289,13 @@ fn minus(tokens: &[Token]) -> Res<()> {
Ok((&tokens[1..], ())) Ok((&tokens[1..], ()))
} }
fn star(tokens: &[Token]) -> Res<()> { fn star(tokens: &[OldToken]) -> Res<()> {
let t = tokens let t = tokens
.first() .first()
.ok_or(Err::Error(GenerationError::Empty(Needed::Size( .ok_or(Err::Error(GenerationError::Empty(Needed::Size(
NonZeroUsize::try_from(1).expect("1 is not zero"), NonZeroUsize::try_from(1).expect("1 is not zero"),
))))?; ))))?;
let Token::Operator(Operator::Star) = t else { let OldToken::Operator(Operator::Star) = t else {
return Err(Err::Error(GenerationError::BadToken( return Err(Err::Error(GenerationError::BadToken(
TokenType::Operator, TokenType::Operator,
t, t,
@ -298,13 +304,13 @@ fn star(tokens: &[Token]) -> Res<()> {
Ok((&tokens[1..], ())) Ok((&tokens[1..], ()))
} }
fn imaginary_unit(tokens: &[Token]) -> Res<()> { fn imaginary_unit(tokens: &[OldToken]) -> Res<()> {
let t = tokens let t = tokens
.first() .first()
.ok_or(Err::Error(GenerationError::Empty(Needed::Size( .ok_or(Err::Error(GenerationError::Empty(Needed::Size(
NonZeroUsize::try_from(1).expect("1 is not zero"), NonZeroUsize::try_from(1).expect("1 is not zero"),
))))?; ))))?;
let Token::ImaginaryUnit = t else { let OldToken::ImaginaryUnit = t else {
return Err(Err::Error(GenerationError::BadToken( return Err(Err::Error(GenerationError::BadToken(
TokenType::Operator, TokenType::Operator,
t, t,
@ -313,13 +319,13 @@ fn imaginary_unit(tokens: &[Token]) -> Res<()> {
Ok((&tokens[1..], ())) Ok((&tokens[1..], ()))
} }
fn identifier(tokens: &[Token]) -> Res<String> { fn identifier(tokens: &[OldToken]) -> Res<String> {
let t = tokens let t = tokens
.first() .first()
.ok_or(Err::Error(GenerationError::Empty(Needed::Size( .ok_or(Err::Error(GenerationError::Empty(Needed::Size(
NonZeroUsize::try_from(1).expect("1 is not zero"), NonZeroUsize::try_from(1).expect("1 is not zero"),
))))?; ))))?;
let Token::Identifier(name) = t else { let OldToken::Identifier(name) = t else {
return Err(Err::Error(GenerationError::BadToken( return Err(Err::Error(GenerationError::BadToken(
TokenType::Operator, TokenType::Operator,
t, t,
@ -328,21 +334,21 @@ fn identifier(tokens: &[Token]) -> Res<String> {
Ok((&tokens[1..], name.to_string())) Ok((&tokens[1..], name.to_string()))
} }
fn comma_separated(tokens: &[Token]) -> Res<(Node<Complex64>, Node<Complex64>)> { fn comma_separated(tokens: &[OldToken]) -> Res<(Node<Complex64>, Node<Complex64>)> {
let (_, (arg1, arg2)) = find_comma(tokens)?; let (_, (arg1, arg2)) = find_comma(tokens)?;
let (_, arg1) = node(arg1)?; let (_, arg1) = node(arg1)?;
let (_, arg2) = node(arg2)?; let (_, arg2) = node(arg2)?;
Ok((&[], (arg1, arg2))) Ok((&[], (arg1, arg2)))
} }
fn find_comma(tokens: &[Token]) -> Res<(&[Token], &[Token])> { fn find_comma(tokens: &[OldToken]) -> Res<(&[OldToken], &[OldToken])> {
let mut inline = Vec::new(); let mut inline = Vec::new();
for (i, token) in tokens.iter().enumerate() { for (i, token) in tokens.iter().enumerate() {
match token { match token {
Token::GroupOpen(t) => { OldToken::GroupOpen(t) => {
inline.push(t); inline.push(t);
} }
Token::GroupClose(t) => { OldToken::GroupClose(t) => {
if &t if &t
!= inline != inline
.last() .last()
@ -353,7 +359,7 @@ fn find_comma(tokens: &[Token]) -> Res<(&[Token], &[Token])> {
inline.pop(); inline.pop();
} }
Token::Comma => { OldToken::Comma => {
if inline.is_empty() { if inline.is_empty() {
return Ok((&[], (&tokens[..i], &tokens[i + 1..]))); return Ok((&[], (&tokens[..i], &tokens[i + 1..])));
} }
@ -364,17 +370,17 @@ fn find_comma(tokens: &[Token]) -> Res<(&[Token], &[Token])> {
Err(Err::Error(GenerationError::NoToken)) Err(Err::Error(GenerationError::NoToken))
} }
fn sign(tokens: &[Token]) -> Res<bool> { fn sign(tokens: &[OldToken]) -> Res<bool> {
alt((map(plus, |()| false), map(minus, |()| true)))(tokens) alt((map(plus, |()| false), map(minus, |()| true)))(tokens)
} }
fn paren(tokens: &[Token]) -> Res<&[Token]> { fn paren(tokens: &[OldToken]) -> Res<&[OldToken]> {
let t = tokens let t = tokens
.first() .first()
.ok_or(Err::Error(GenerationError::Empty(Needed::Size( .ok_or(Err::Error(GenerationError::Empty(Needed::Size(
NonZeroUsize::try_from(1).expect("1 is not zero"), NonZeroUsize::try_from(1).expect("1 is not zero"),
))))?; ))))?;
let Token::GroupOpen(t) = t else { let OldToken::GroupOpen(t) = t else {
return Err(Err::Error(GenerationError::BadToken( return Err(Err::Error(GenerationError::BadToken(
TokenType::Grouping, TokenType::Grouping,
t, t,
@ -384,12 +390,12 @@ fn paren(tokens: &[Token]) -> Res<&[Token]> {
let mut inner = 1; let mut inner = 1;
for i in 1..tokens.len() { for i in 1..tokens.len() {
let token = &tokens[i]; let token = &tokens[i];
if let Token::GroupOpen(e) = token { if let OldToken::GroupOpen(e) = token {
if t == e { if t == e {
inner += 1; inner += 1;
} }
} }
if let Token::GroupClose(e) = token { if let OldToken::GroupClose(e) = token {
if t == e { if t == e {
inner -= 1; inner -= 1;
@ -407,6 +413,7 @@ fn paren(tokens: &[Token]) -> Res<&[Token]> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use alloc::borrow::ToOwned;
use num::complex::Complex64; use num::complex::Complex64;
use rand::Rng; use rand::Rng;
@ -417,7 +424,7 @@ mod tests {
functions::NamedFunction, functions::NamedFunction,
Constant, Evaluatable, Node, Variable, Constant, Evaluatable, Node, Variable,
}, },
lexer::{GroupingType, Operator, Token}, lexer::{GroupingType, OldToken, Operator},
}; };
use super::parse; use super::parse;
@ -482,122 +489,122 @@ mod tests {
// following tests reinforce that complex constant recognition works as expected: // following tests reinforce that complex constant recognition works as expected:
test! {constant1, test! {constant1,
tokens = [Token::Number(1.0f64)], tokens = [OldToken::Number(1.0f64)],
expected = Constant::from(1.0f64)} expected = Constant::from(1.0f64)}
test! {constant2, test! {constant2,
tokens = [Token::ImaginaryUnit], tokens = [OldToken::ImaginaryUnit],
expected = Constant::from(1.0f64).tr()} expected = Constant::from(1.0f64).tr()}
test! {constant3, test! {constant3,
tokens = [ tokens = [
Token::Number(1.0f64), OldToken::Number(1.0f64),
Token::Operator(Operator::Star), OldToken::Operator(Operator::Star),
Token::ImaginaryUnit OldToken::ImaginaryUnit
], expected = Constant::from(1.0f64).tr()} ], expected = Constant::from(1.0f64).tr()}
test! {constant4, test! {constant4,
tokens = [ tokens = [
Token::Number(1.0f64), OldToken::Number(1.0f64),
Token::Operator(Operator::Plus), OldToken::Operator(Operator::Plus),
Token::ImaginaryUnit OldToken::ImaginaryUnit
], expected = Constant(Complex64::new(1.0, 1.0))} ], expected = Constant(Complex64::new(1.0, 1.0))}
test! {constant5, test! {constant5,
tokens = [ tokens = [
Token::Operator(Operator::Minus), OldToken::Operator(Operator::Minus),
Token::Number(1.0f64), OldToken::Number(1.0f64),
Token::Operator(Operator::Plus), OldToken::Operator(Operator::Plus),
Token::Number(1.0), OldToken::Number(1.0),
Token::Operator(Operator::Star), OldToken::Operator(Operator::Star),
Token::ImaginaryUnit OldToken::ImaginaryUnit
], expected = Constant(Complex64::new(-1.0, 1.0))} ], expected = Constant(Complex64::new(-1.0, 1.0))}
test! {constant6, test! {constant6,
tokens = [ tokens = [
Token::Operator(Operator::Minus), OldToken::Operator(Operator::Minus),
Token::Number(1.0f64), OldToken::Number(1.0f64),
Token::Operator(Operator::Plus), OldToken::Operator(Operator::Plus),
Token::Number(1.0), Token::ImaginaryUnit OldToken::Number(1.0), OldToken::ImaginaryUnit
], expected = Constant(Complex64::new(-1.0, 1.0))} ], expected = Constant(Complex64::new(-1.0, 1.0))}
test! {constant7, test! {constant7,
tokens = [ tokens = [
Token::Operator(Operator::Minus), OldToken::Operator(Operator::Minus),
Token::ImaginaryUnit, OldToken::ImaginaryUnit,
Token::Operator(Operator::Star), OldToken::Operator(Operator::Star),
Token::Number(1.0f64), OldToken::Number(1.0f64),
Token::Operator(Operator::Plus), OldToken::Operator(Operator::Plus),
Token::Number(1.0) OldToken::Number(1.0)
], expected = Constant(Complex64::new(1.0, -1.0))} ], expected = Constant(Complex64::new(1.0, -1.0))}
test! {constant8, test! {constant8,
tokens = [ tokens = [
Token::Operator(Operator::Minus), OldToken::Operator(Operator::Minus),
Token::ImaginaryUnit, OldToken::ImaginaryUnit,
Token::Operator(Operator::Plus), OldToken::Operator(Operator::Plus),
Token::Number(1.0) OldToken::Number(1.0)
], expected = Constant(Complex64::new(1.0, -1.0))} ], expected = Constant(Complex64::new(1.0, -1.0))}
test! {constant9, test! {constant9,
tokens = [ tokens = [
Token::Operator(Operator::Minus), OldToken::Operator(Operator::Minus),
Token::ImaginaryUnit OldToken::ImaginaryUnit
], expected = Constant(Complex64::new(0.0, -1.0))} ], expected = Constant(Complex64::new(0.0, -1.0))}
// following tests reinforce that variable recognition works as expected: // following tests reinforce that variable recognition works as expected:
test! {variable1, test! {variable1,
tokens = [Token::Identifier("x".to_owned())], tokens = [OldToken::Identifier("x".to_owned())],
expected = Variable::new("x")} expected = Variable::new("x")}
test! {variable2, test! {variable2,
tokens = [Token::Identifier("a_123".to_owned())], tokens = [OldToken::Identifier("a_123".to_owned())],
expected = Variable::new("a_123")} expected = Variable::new("a_123")}
// following tests reinforce that function calls work as expected: // following tests reinforce that function calls work as expected:
test! {function1, test! {function1,
tokens = [ tokens = [
Token::Identifier("sin".to_owned()), OldToken::Identifier("sin".to_owned()),
Token::GroupOpen(GroupingType::Parentheses), OldToken::GroupOpen(GroupingType::Parentheses),
Token::Identifier("x".to_owned()), OldToken::Identifier("x".to_owned()),
Token::GroupClose(GroupingType::Parentheses) OldToken::GroupClose(GroupingType::Parentheses)
], expected = NamedFunction::new("sin", Variable::new("x"))} ], expected = NamedFunction::new("sin", Variable::new("x"))}
test! {function2, test! {function2,
tokens = [ tokens = [
Token::Identifier("sum_sq".to_owned()), OldToken::Identifier("sum_sq".to_owned()),
Token::GroupOpen(GroupingType::Parentheses), OldToken::GroupOpen(GroupingType::Parentheses),
Token::Identifier("x".to_owned()), OldToken::Identifier("x".to_owned()),
Token::Comma, OldToken::Comma,
Token::Identifier("y".to_owned()), OldToken::Identifier("y".to_owned()),
Token::GroupClose(GroupingType::Parentheses) OldToken::GroupClose(GroupingType::Parentheses)
], expected = NamedFunction::new("sum_sq", (Variable::new("x"), Variable::new("y")))} ], expected = NamedFunction::new("sum_sq", (Variable::new("x"), Variable::new("y")))}
test! {function3, test! {function3,
tokens = [ tokens = [
Token::Operator(Operator::Minus), OldToken::Operator(Operator::Minus),
Token::Identifier("sin".to_owned()), OldToken::Identifier("sin".to_owned()),
Token::GroupOpen(GroupingType::Parentheses), OldToken::GroupOpen(GroupingType::Parentheses),
Token::Identifier("x".to_owned()), OldToken::Identifier("x".to_owned()),
Token::GroupClose(GroupingType::Parentheses) OldToken::GroupClose(GroupingType::Parentheses)
], expected = Negation::new(NamedFunction::new("sin", Variable::new("x")))} ], expected = Negation::new(NamedFunction::new("sin", Variable::new("x")))}
// following tests reinforce correct binary operator parsing // following tests reinforce correct binary operator parsing
// x + y * 2 // x + y * 2
test! {bin_operator1, tokens = [ test! {bin_operator1, tokens = [
Token::Identifier("x".to_owned()), OldToken::Identifier("x".to_owned()),
Token::Operator(Operator::Plus), OldToken::Operator(Operator::Plus),
Token::Identifier("y".to_owned()), OldToken::Identifier("y".to_owned()),
Token::Operator(Operator::Star), OldToken::Operator(Operator::Star),
Token::Number(2f64) OldToken::Number(2f64)
], expected = Addition::new(Variable::new("x"), Multiplication::new(Variable::new("y"), Constant(2f64.into())))} ], expected = Addition::new(Variable::new("x"), Multiplication::new(Variable::new("y"), Constant(2f64.into())))}
// [-x + 4] * sin{y}^2 // [-x + 4] * sin{y}^2
test! {bin_operator2, tokens = [ test! {bin_operator2, tokens = [
Token::GroupOpen(GroupingType::Brackets), OldToken::GroupOpen(GroupingType::Brackets),
Token::Operator(Operator::Minus), OldToken::Operator(Operator::Minus),
Token::Identifier("x".to_owned()), OldToken::Identifier("x".to_owned()),
Token::Operator(Operator::Plus), OldToken::Operator(Operator::Plus),
Token::Number(4f64), OldToken::Number(4f64),
Token::GroupClose(GroupingType::Brackets), OldToken::GroupClose(GroupingType::Brackets),
Token::Operator(Operator::Star), OldToken::Operator(Operator::Star),
Token::Identifier("sin".to_owned()), OldToken::Identifier("sin".to_owned()),
Token::GroupOpen(GroupingType::Braces), OldToken::GroupOpen(GroupingType::Braces),
Token::Identifier("y".to_owned()), OldToken::Identifier("y".to_owned()),
Token::GroupClose(GroupingType::Braces), OldToken::GroupClose(GroupingType::Braces),
Token::Operator(Operator::Cap), OldToken::Operator(Operator::Cap),
Token::Number(2f64) OldToken::Number(2f64)
], expected = Multiplication::new( ], expected = Multiplication::new(
Addition::new( Addition::new(
Negation::new(Variable::new("x")), Negation::new(Variable::new("x")),
@ -614,24 +621,24 @@ mod tests {
// z ^ [z] + sin(1.0 / e ^ {i * phi}) // z ^ [z] + sin(1.0 / e ^ {i * phi})
test! {bin_operator3, tokens = [ test! {bin_operator3, tokens = [
Token::Identifier("z".to_owned()), OldToken::Identifier("z".to_owned()),
Token::Operator(Operator::Cap), OldToken::Operator(Operator::Cap),
Token::GroupOpen(GroupingType::Brackets), OldToken::GroupOpen(GroupingType::Brackets),
Token::Identifier("z".to_owned()), OldToken::Identifier("z".to_owned()),
Token::GroupClose(GroupingType::Brackets), OldToken::GroupClose(GroupingType::Brackets),
Token::Operator(Operator::Plus), OldToken::Operator(Operator::Plus),
Token::Identifier("sin".to_owned()), OldToken::Identifier("sin".to_owned()),
Token::GroupOpen(GroupingType::Parentheses), OldToken::GroupOpen(GroupingType::Parentheses),
Token::Number(1.0), OldToken::Number(1.0),
Token::Operator(Operator::Slash), OldToken::Operator(Operator::Slash),
Token::Identifier("e".to_owned()), OldToken::Identifier("e".to_owned()),
Token::Operator(Operator::Cap), OldToken::Operator(Operator::Cap),
Token::GroupOpen(GroupingType::Braces), OldToken::GroupOpen(GroupingType::Braces),
Token::ImaginaryUnit, OldToken::ImaginaryUnit,
Token::Operator(Operator::Star), OldToken::Operator(Operator::Star),
Token::Identifier("phi".to_owned()), OldToken::Identifier("phi".to_owned()),
Token::GroupClose(GroupingType::Braces), OldToken::GroupClose(GroupingType::Braces),
Token::GroupClose(GroupingType::Parentheses) OldToken::GroupClose(GroupingType::Parentheses)
], expected = Addition::new( ], expected = Addition::new(
Exponentiation::new( Exponentiation::new(
Variable::new("z"), Variable::new("z"),