mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Tracked memoization
This commit is contained in:
parent
3760748fdd
commit
ddd3b6a82b
284
Cargo.lock
generated
284
Cargo.lock
generated
@ -17,6 +17,15 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayref"
|
name = "arrayref"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
@ -89,6 +98,12 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.12.1"
|
version = "1.12.1"
|
||||||
@ -107,6 +122,18 @@ 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 = "chrono"
|
||||||
|
version = "0.4.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
|
||||||
|
dependencies = [
|
||||||
|
"iana-time-zone",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codespan-reporting"
|
name = "codespan-reporting"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
@ -123,6 +150,29 @@ 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 = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "comemo"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"comemo-macros",
|
||||||
|
"siphasher",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "comemo-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
@ -132,6 +182,26 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-channel"
|
||||||
|
version = "0.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "csv"
|
name = "csv"
|
||||||
version = "1.1.6"
|
version = "1.1.6"
|
||||||
@ -208,6 +278,18 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filetime"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.24"
|
version = "1.0.24"
|
||||||
@ -230,6 +312,15 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fsevent-sys"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fxhash"
|
name = "fxhash"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@ -274,6 +365,20 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.48"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "237a0714f28b1ee39ccec0770ccb544eb02c9ef2c82bb096230eefcffa6468b0"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"js-sys",
|
||||||
|
"once_cell",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.24.3"
|
version = "0.24.3"
|
||||||
@ -290,6 +395,26 @@ dependencies = [
|
|||||||
"png",
|
"png",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify"
|
||||||
|
version = "0.9.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"inotify-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inotify-sys"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@ -317,6 +442,35 @@ 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 = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b"
|
checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
|
||||||
|
dependencies = [
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kqueue"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4d6112e8f37b59803ac47a42d14f1f3a59bbf72fc6857ffc5be455e28a691f8e"
|
||||||
|
dependencies = [
|
||||||
|
"kqueue-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kqueue-sys"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kurbo"
|
name = "kurbo"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
@ -399,6 +553,18 @@ dependencies = [
|
|||||||
"adler",
|
"adler",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "5.1.2"
|
version = "5.1.2"
|
||||||
@ -410,6 +576,24 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "notify"
|
||||||
|
version = "5.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed2c66da08abae1c024c01d635253e402341b4060a12e99b31c7594063bf490a"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crossbeam-channel",
|
||||||
|
"filetime",
|
||||||
|
"fsevent-sys",
|
||||||
|
"inotify",
|
||||||
|
"kqueue",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"walkdir",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.45"
|
version = "0.1.45"
|
||||||
@ -850,7 +1034,9 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
"chrono",
|
||||||
"codespan-reporting",
|
"codespan-reporting",
|
||||||
|
"comemo",
|
||||||
"csv",
|
"csv",
|
||||||
"dirs",
|
"dirs",
|
||||||
"elsa",
|
"elsa",
|
||||||
@ -863,6 +1049,7 @@ dependencies = [
|
|||||||
"lipsum",
|
"lipsum",
|
||||||
"memmap2",
|
"memmap2",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
|
"notify",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pdf-writer",
|
"pdf-writer",
|
||||||
"pico-args",
|
"pico-args",
|
||||||
@ -1012,6 +1199,60 @@ 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 = "wasm-bindgen"
|
||||||
|
version = "0.2.83"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.83"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.83"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.83"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.83"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "weezl"
|
name = "weezl"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
@ -1049,6 +1290,49 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.36.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.36.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.36.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.36.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.36.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.36.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xi-unicode"
|
name = "xi-unicode"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
37
Cargo.toml
37
Cargo.toml
@ -4,20 +4,6 @@ version = "0.1.0"
|
|||||||
authors = ["The Typst Project Developers"]
|
authors = ["The Typst Project Developers"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["tests"]
|
|
||||||
tests = ["same-file", "walkdir", "elsa", "siphasher"]
|
|
||||||
cli = [
|
|
||||||
"pico-args",
|
|
||||||
"codespan-reporting",
|
|
||||||
"dirs",
|
|
||||||
"memmap2",
|
|
||||||
"same-file",
|
|
||||||
"walkdir",
|
|
||||||
"elsa",
|
|
||||||
"siphasher",
|
|
||||||
]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Workspace
|
# Workspace
|
||||||
typst-macros = { path = "./macros" }
|
typst-macros = { path = "./macros" }
|
||||||
@ -26,13 +12,15 @@ typst-macros = { path = "./macros" }
|
|||||||
bitflags = "1"
|
bitflags = "1"
|
||||||
bytemuck = "1"
|
bytemuck = "1"
|
||||||
fxhash = "0.2"
|
fxhash = "0.2"
|
||||||
lipsum = { git = "https://github.com/reknih/lipsum" }
|
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
typed-arena = "2"
|
typed-arena = "2"
|
||||||
unscanny = "0.1"
|
unscanny = "0.1"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|
||||||
|
# Incremental compilation
|
||||||
|
comemo = { path = "../comemo" }
|
||||||
|
|
||||||
# Text and font handling
|
# Text and font handling
|
||||||
hypher = "0.1"
|
hypher = "0.1"
|
||||||
kurbo = "0.8"
|
kurbo = "0.8"
|
||||||
@ -51,6 +39,7 @@ usvg = { version = "0.22", default-features = false }
|
|||||||
# External implementation of user-facing features
|
# External implementation of user-facing features
|
||||||
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
|
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] }
|
||||||
rex = { git = "https://github.com/laurmaedje/ReX" }
|
rex = { git = "https://github.com/laurmaedje/ReX" }
|
||||||
|
lipsum = { git = "https://github.com/reknih/lipsum" }
|
||||||
csv = "1"
|
csv = "1"
|
||||||
|
|
||||||
# PDF export
|
# PDF export
|
||||||
@ -75,11 +64,29 @@ elsa = { version = "1.7", optional = true }
|
|||||||
dirs = { version = "4", optional = true }
|
dirs = { version = "4", optional = true }
|
||||||
memmap2 = { version = "0.5", optional = true }
|
memmap2 = { version = "0.5", optional = true }
|
||||||
siphasher = { version = "0.3", optional = true }
|
siphasher = { version = "0.3", optional = true }
|
||||||
|
notify = { version = "5", optional = true }
|
||||||
|
chrono = { version = "0.4", default-features = false, features = ["clock", "std"], optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
iai = { git = "https://github.com/reknih/iai" }
|
iai = { git = "https://github.com/reknih/iai" }
|
||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["tests"]
|
||||||
|
tests = ["same-file", "walkdir", "elsa", "siphasher"]
|
||||||
|
cli = [
|
||||||
|
"pico-args",
|
||||||
|
"codespan-reporting",
|
||||||
|
"dirs",
|
||||||
|
"memmap2",
|
||||||
|
"same-file",
|
||||||
|
"walkdir",
|
||||||
|
"elsa",
|
||||||
|
"siphasher",
|
||||||
|
"notify",
|
||||||
|
"chrono",
|
||||||
|
]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
# Faster compilation
|
# Faster compilation
|
||||||
debug = 0
|
debug = 0
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use comemo::{Prehashed, Track, Tracked};
|
||||||
use iai::{black_box, main, Iai};
|
use iai::{black_box, main, Iai};
|
||||||
use unscanny::Scanner;
|
use unscanny::Scanner;
|
||||||
|
|
||||||
@ -76,14 +77,16 @@ fn bench_highlight(iai: &mut Iai) {
|
|||||||
fn bench_eval(iai: &mut Iai) {
|
fn bench_eval(iai: &mut Iai) {
|
||||||
let world = BenchWorld::new();
|
let world = BenchWorld::new();
|
||||||
let id = world.source.id();
|
let id = world.source.id();
|
||||||
iai.run(|| typst::eval::evaluate(&world, id, vec![]).unwrap());
|
let route = typst::eval::Route::default();
|
||||||
|
iai.run(|| typst::eval::eval(world.track(), route.track(), id).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_layout(iai: &mut Iai) {
|
fn bench_layout(iai: &mut Iai) {
|
||||||
let world = BenchWorld::new();
|
let world = BenchWorld::new();
|
||||||
let id = world.source.id();
|
let id = world.source.id();
|
||||||
let module = typst::eval::evaluate(&world, id, vec![]).unwrap();
|
let route = typst::eval::Route::default();
|
||||||
iai.run(|| typst::model::layout(&world, &module.content));
|
let module = typst::eval::eval(world.track(), route.track(), id).unwrap();
|
||||||
|
iai.run(|| typst::model::layout(world.track(), &module.content));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_render(iai: &mut Iai) {
|
fn bench_render(iai: &mut Iai) {
|
||||||
@ -94,41 +97,38 @@ fn bench_render(iai: &mut Iai) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct BenchWorld {
|
struct BenchWorld {
|
||||||
config: Config,
|
config: Prehashed<Config>,
|
||||||
book: FontBook,
|
book: Prehashed<FontBook>,
|
||||||
font: Font,
|
font: Font,
|
||||||
source: Source,
|
source: Source,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BenchWorld {
|
impl BenchWorld {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
|
let config = Config::default();
|
||||||
let font = Font::new(FONT.into(), 0).unwrap();
|
let font = Font::new(FONT.into(), 0).unwrap();
|
||||||
let book = FontBook::from_fonts([&font]);
|
let book = FontBook::from_fonts([&font]);
|
||||||
let id = SourceId::from_raw(0);
|
let id = SourceId::from_u16(0);
|
||||||
let source = Source::new(id, Path::new("bench.typ"), TEXT.into());
|
let source = Source::new(id, Path::new("bench.typ"), TEXT.into());
|
||||||
Self {
|
Self {
|
||||||
config: Config::default(),
|
config: Prehashed::new(config),
|
||||||
book,
|
book: Prehashed::new(book),
|
||||||
font,
|
font,
|
||||||
source,
|
source,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn track(&self) -> Tracked<dyn World> {
|
||||||
|
(self as &dyn World).track()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl World for BenchWorld {
|
impl World for BenchWorld {
|
||||||
fn config(&self) -> &Config {
|
fn config(&self) -> &Prehashed<Config> {
|
||||||
&self.config
|
&self.config
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(&self, path: &Path) -> FileResult<SourceId> {
|
fn book(&self) -> &Prehashed<FontBook> {
|
||||||
Err(FileError::NotFound(path.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn source(&self, _: SourceId) -> &Source {
|
|
||||||
&self.source
|
|
||||||
}
|
|
||||||
|
|
||||||
fn book(&self) -> &FontBook {
|
|
||||||
&self.book
|
&self.book
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,4 +139,12 @@ impl World for BenchWorld {
|
|||||||
fn file(&self, path: &Path) -> FileResult<Buffer> {
|
fn file(&self, path: &Path) -> FileResult<Buffer> {
|
||||||
Err(FileError::NotFound(path.into()))
|
Err(FileError::NotFound(path.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve(&self, path: &Path) -> FileResult<SourceId> {
|
||||||
|
Err(FileError::NotFound(path.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source(&self, _: SourceId) -> &Source {
|
||||||
|
&self.source
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
18
src/diag.rs
18
src/diag.rs
@ -3,6 +3,9 @@
|
|||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::string::FromUtf8Error;
|
||||||
|
|
||||||
|
use comemo::Tracked;
|
||||||
|
|
||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::{Span, Spanned};
|
||||||
use crate::World;
|
use crate::World;
|
||||||
@ -84,13 +87,13 @@ impl Display for Tracepoint {
|
|||||||
/// Enrich a [`SourceResult`] with a tracepoint.
|
/// Enrich a [`SourceResult`] with a tracepoint.
|
||||||
pub trait Trace<T> {
|
pub trait Trace<T> {
|
||||||
/// Add the tracepoint to all errors that lie outside the `span`.
|
/// Add the tracepoint to all errors that lie outside the `span`.
|
||||||
fn trace<F>(self, world: &dyn World, make_point: F, span: Span) -> Self
|
fn trace<F>(self, world: Tracked<dyn World>, make_point: F, span: Span) -> Self
|
||||||
where
|
where
|
||||||
F: Fn() -> Tracepoint;
|
F: Fn() -> Tracepoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Trace<T> for SourceResult<T> {
|
impl<T> Trace<T> for SourceResult<T> {
|
||||||
fn trace<F>(self, world: &dyn World, make_point: F, span: Span) -> Self
|
fn trace<F>(self, world: Tracked<dyn World>, make_point: F, span: Span) -> Self
|
||||||
where
|
where
|
||||||
F: Fn() -> Tracepoint,
|
F: Fn() -> Tracepoint,
|
||||||
{
|
{
|
||||||
@ -146,6 +149,8 @@ pub type FileResult<T> = Result<T, FileError>;
|
|||||||
pub enum FileError {
|
pub enum FileError {
|
||||||
/// A file was not found at this path.
|
/// A file was not found at this path.
|
||||||
NotFound(PathBuf),
|
NotFound(PathBuf),
|
||||||
|
/// A directory was found, but a file was expected.
|
||||||
|
IsDirectory,
|
||||||
/// A file could not be accessed.
|
/// A file could not be accessed.
|
||||||
AccessDenied,
|
AccessDenied,
|
||||||
/// The file was not valid UTF-8, but should have been.
|
/// The file was not valid UTF-8, but should have been.
|
||||||
@ -178,13 +183,20 @@ impl Display for FileError {
|
|||||||
Self::NotFound(path) => {
|
Self::NotFound(path) => {
|
||||||
write!(f, "file not found (searched at {})", path.display())
|
write!(f, "file not found (searched at {})", path.display())
|
||||||
}
|
}
|
||||||
Self::AccessDenied => f.pad("file access denied"),
|
Self::IsDirectory => f.pad("failed to load file (is a directory)"),
|
||||||
|
Self::AccessDenied => f.pad("failed to load file (access denied)"),
|
||||||
Self::InvalidUtf8 => f.pad("file is not valid utf-8"),
|
Self::InvalidUtf8 => f.pad("file is not valid utf-8"),
|
||||||
Self::Other => f.pad("failed to load file"),
|
Self::Other => f.pad("failed to load file"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<FromUtf8Error> for FileError {
|
||||||
|
fn from(_: FromUtf8Error) -> Self {
|
||||||
|
Self::InvalidUtf8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<FileError> for String {
|
impl From<FileError> for String {
|
||||||
fn from(error: FileError) -> Self {
|
fn from(error: FileError) -> Self {
|
||||||
error.to_string()
|
error.to_string()
|
||||||
|
@ -2,7 +2,9 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{Args, Eval, Flow, Scope, Scopes, Value, Vm};
|
use comemo::{Track, Tracked};
|
||||||
|
|
||||||
|
use super::{Args, Eval, Flow, Route, Scope, Scopes, Value, Vm};
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult};
|
||||||
use crate::model::{Content, NodeId, StyleMap};
|
use crate::model::{Content, NodeId, StyleMap};
|
||||||
use crate::source::SourceId;
|
use crate::source::SourceId;
|
||||||
@ -100,8 +102,13 @@ impl Func {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Call the function without an existing virtual machine.
|
/// Call the function without an existing virtual machine.
|
||||||
pub fn call_detached(&self, world: &dyn World, args: Args) -> SourceResult<Value> {
|
pub fn call_detached(
|
||||||
let mut vm = Vm::new(world, vec![], Scopes::new(None));
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
args: Args,
|
||||||
|
) -> SourceResult<Value> {
|
||||||
|
let route = Route::default();
|
||||||
|
let mut vm = Vm::new(world, route.track(), None, Scopes::new(None));
|
||||||
self.call(&mut vm, args)
|
self.call(&mut vm, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,15 +227,12 @@ impl Closure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine the route inside the closure.
|
// Determine the route inside the closure.
|
||||||
let detached = vm.route.is_empty();
|
let detached = vm.location.is_none();
|
||||||
let route = if detached {
|
let fresh = Route::new(self.location);
|
||||||
self.location.into_iter().collect()
|
let route = if detached { fresh.track() } else { vm.route };
|
||||||
} else {
|
|
||||||
vm.route.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Evaluate the body.
|
// Evaluate the body.
|
||||||
let mut sub = Vm::new(vm.world, route, scopes);
|
let mut sub = Vm::new(vm.world, route, self.location, scopes);
|
||||||
let result = self.body.eval(&mut sub);
|
let result = self.body.eval(&mut sub);
|
||||||
|
|
||||||
// Handle control flow.
|
// Handle control flow.
|
||||||
|
@ -34,6 +34,7 @@ pub use vm::*;
|
|||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use comemo::{Track, Tracked};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::diag::{At, SourceResult, StrResult, Trace, Tracepoint};
|
use crate::diag::{At, SourceResult, StrResult, Trace, Tracepoint};
|
||||||
@ -51,24 +52,24 @@ use crate::World;
|
|||||||
/// Returns either a module containing a scope with top-level bindings and
|
/// Returns either a module containing a scope with top-level bindings and
|
||||||
/// layoutable contents or diagnostics in the form of a vector of error
|
/// layoutable contents or diagnostics in the form of a vector of error
|
||||||
/// messages with file and span information.
|
/// messages with file and span information.
|
||||||
pub fn evaluate(
|
#[comemo::memoize]
|
||||||
world: &dyn World,
|
pub fn eval(
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
route: Tracked<Route>,
|
||||||
id: SourceId,
|
id: SourceId,
|
||||||
mut route: Vec<SourceId>,
|
|
||||||
) -> SourceResult<Module> {
|
) -> SourceResult<Module> {
|
||||||
// Prevent cyclic evaluation.
|
// Prevent cyclic evaluation.
|
||||||
if route.contains(&id) {
|
if route.contains(id) {
|
||||||
let path = world.source(id).path().display();
|
let path = world.source(id).path().display();
|
||||||
panic!("Tried to cyclicly evaluate {}", path);
|
panic!("Tried to cyclicly evaluate {}", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
route.push(id);
|
|
||||||
|
|
||||||
// Evaluate the module.
|
// Evaluate the module.
|
||||||
|
let route = unsafe { Route::insert(route, id) };
|
||||||
let ast = world.source(id).ast()?;
|
let ast = world.source(id).ast()?;
|
||||||
let std = &world.config().std;
|
let std = &world.config().std;
|
||||||
let scopes = Scopes::new(Some(std));
|
let scopes = Scopes::new(Some(std));
|
||||||
let mut vm = Vm::new(world, route, scopes);
|
let mut vm = Vm::new(world, route.track(), Some(id), scopes);
|
||||||
let result = ast.eval(&mut vm);
|
let result = ast.eval(&mut vm);
|
||||||
|
|
||||||
// Handle control flow.
|
// Handle control flow.
|
||||||
@ -80,6 +81,39 @@ pub fn evaluate(
|
|||||||
Ok(Module { scope: vm.scopes.top, content: result? })
|
Ok(Module { scope: vm.scopes.top, content: result? })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A route of source ids.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Route {
|
||||||
|
parent: Option<Tracked<'static, Self>>,
|
||||||
|
id: Option<SourceId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Route {
|
||||||
|
/// Create a new, empty route.
|
||||||
|
pub fn new(id: Option<SourceId>) -> Self {
|
||||||
|
Self { id, parent: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a new id into the route.
|
||||||
|
///
|
||||||
|
/// You must guarantee that `outer` lives longer than the resulting
|
||||||
|
/// route is ever used.
|
||||||
|
unsafe fn insert(outer: Tracked<Route>, id: SourceId) -> Route {
|
||||||
|
Route {
|
||||||
|
parent: Some(std::mem::transmute(outer)),
|
||||||
|
id: Some(id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[comemo::track]
|
||||||
|
impl Route {
|
||||||
|
/// Whether the given id is part of the route.
|
||||||
|
fn contains(&self, id: SourceId) -> bool {
|
||||||
|
self.id == Some(id) || self.parent.map_or(false, |parent| parent.contains(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An evaluated module, ready for importing or layouting.
|
/// An evaluated module, ready for importing or layouting.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Module {
|
pub struct Module {
|
||||||
@ -696,7 +730,7 @@ impl Eval for ClosureExpr {
|
|||||||
|
|
||||||
// Define the actual function.
|
// Define the actual function.
|
||||||
Ok(Value::Func(Func::from_closure(Closure {
|
Ok(Value::Func(Func::from_closure(Closure {
|
||||||
location: vm.route.last().copied(),
|
location: vm.location,
|
||||||
name,
|
name,
|
||||||
captured,
|
captured,
|
||||||
params,
|
params,
|
||||||
@ -755,7 +789,7 @@ impl Eval for ShowExpr {
|
|||||||
let body = self.body();
|
let body = self.body();
|
||||||
let span = body.span();
|
let span = body.span();
|
||||||
let func = Func::from_closure(Closure {
|
let func = Func::from_closure(Closure {
|
||||||
location: vm.route.last().copied(),
|
location: vm.location,
|
||||||
name: None,
|
name: None,
|
||||||
captured,
|
captured,
|
||||||
params,
|
params,
|
||||||
@ -940,14 +974,13 @@ fn import(vm: &mut Vm, path: &str, span: Span) -> SourceResult<Module> {
|
|||||||
let id = vm.world.resolve(&full).at(span)?;
|
let id = vm.world.resolve(&full).at(span)?;
|
||||||
|
|
||||||
// Prevent cyclic importing.
|
// Prevent cyclic importing.
|
||||||
if vm.route.contains(&id) {
|
if vm.route.contains(id) {
|
||||||
bail!(span, "cyclic import");
|
bail!(span, "cyclic import");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate the file.
|
// Evaluate the file.
|
||||||
let route = vm.route.clone();
|
|
||||||
let module =
|
let module =
|
||||||
evaluate(vm.world, id, route).trace(vm.world, || Tracepoint::Import, span)?;
|
eval(vm.world, vm.route, id).trace(vm.world, || Tracepoint::Import, span)?;
|
||||||
|
|
||||||
Ok(module)
|
Ok(module)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::{Scopes, Value};
|
use comemo::Tracked;
|
||||||
|
|
||||||
|
use super::{Route, Scopes, Value};
|
||||||
use crate::diag::{SourceError, StrResult};
|
use crate::diag::{SourceError, StrResult};
|
||||||
use crate::source::SourceId;
|
use crate::source::SourceId;
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
@ -8,27 +10,40 @@ use crate::util::PathExt;
|
|||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// A virtual machine.
|
/// A virtual machine.
|
||||||
pub struct Vm<'w> {
|
pub struct Vm<'a> {
|
||||||
/// The core context.
|
/// The core context.
|
||||||
pub world: &'w dyn World,
|
pub world: Tracked<'a, dyn World>,
|
||||||
/// The route of source ids the machine took to reach its current location.
|
/// The route of source ids the machine took to reach its current location.
|
||||||
pub route: Vec<SourceId>,
|
pub route: Tracked<'a, Route>,
|
||||||
|
/// The current location.
|
||||||
|
pub location: Option<SourceId>,
|
||||||
/// The stack of scopes.
|
/// The stack of scopes.
|
||||||
pub scopes: Scopes<'w>,
|
pub scopes: Scopes<'a>,
|
||||||
/// A control flow event that is currently happening.
|
/// A control flow event that is currently happening.
|
||||||
pub flow: Option<Flow>,
|
pub flow: Option<Flow>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w> Vm<'w> {
|
impl<'a> Vm<'a> {
|
||||||
/// Create a new virtual machine.
|
/// Create a new virtual machine.
|
||||||
pub fn new(ctx: &'w dyn World, route: Vec<SourceId>, scopes: Scopes<'w>) -> Self {
|
pub fn new(
|
||||||
Self { world: ctx, route, scopes, flow: None }
|
world: Tracked<'a, dyn World>,
|
||||||
|
route: Tracked<'a, Route>,
|
||||||
|
location: Option<SourceId>,
|
||||||
|
scopes: Scopes<'a>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
world,
|
||||||
|
route,
|
||||||
|
location,
|
||||||
|
scopes,
|
||||||
|
flow: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve a user-entered path to be relative to the compilation
|
/// Resolve a user-entered path to be relative to the compilation
|
||||||
/// environment's root.
|
/// environment's root.
|
||||||
pub fn locate(&self, path: &str) -> StrResult<PathBuf> {
|
pub fn locate(&self, path: &str) -> StrResult<PathBuf> {
|
||||||
if let Some(&id) = self.route.last() {
|
if let Some(id) = self.location {
|
||||||
if let Some(path) = path.strip_prefix('/') {
|
if let Some(path) = path.strip_prefix('/') {
|
||||||
return Ok(self.world.config().root.join(path).normalize());
|
return Ok(self.world.config().root.join(path).normalize());
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||||||
use super::{Font, FontStretch, FontStyle, FontVariant, FontWeight};
|
use super::{Font, FontStretch, FontStyle, FontVariant, FontWeight};
|
||||||
|
|
||||||
/// Metadata about a collection of fonts.
|
/// Metadata about a collection of fonts.
|
||||||
#[derive(Default)]
|
#[derive(Default, Clone, Hash)]
|
||||||
pub struct FontBook {
|
pub struct FontBook {
|
||||||
/// Maps from lowercased family names to font indices.
|
/// Maps from lowercased family names to font indices.
|
||||||
families: BTreeMap<String, Vec<usize>>,
|
families: BTreeMap<String, Vec<usize>>,
|
||||||
@ -144,7 +144,7 @@ impl FontBook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Properties of a single font.
|
/// Properties of a single font.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
pub struct FontInfo {
|
pub struct FontInfo {
|
||||||
/// The typographic font family this font is part of.
|
/// The typographic font family this font is part of.
|
||||||
pub family: String,
|
pub family: String,
|
||||||
@ -377,7 +377,7 @@ fn shared_prefix_words(left: &str, right: &str) -> usize {
|
|||||||
/// - 2 codepoints inside (18, 19)
|
/// - 2 codepoints inside (18, 19)
|
||||||
///
|
///
|
||||||
/// So the resulting encoding is `[2, 3, 4, 3, 3, 1, 2, 2]`.
|
/// So the resulting encoding is `[2, 3, 4, 3, 3, 1, 2, 2]`.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct Coverage(Vec<u32>);
|
pub struct Coverage(Vec<u32>);
|
||||||
|
|
||||||
|
34
src/lib.rs
34
src/lib.rs
@ -21,7 +21,7 @@
|
|||||||
//! [parsed]: parse::parse
|
//! [parsed]: parse::parse
|
||||||
//! [syntax tree]: syntax::SyntaxNode
|
//! [syntax tree]: syntax::SyntaxNode
|
||||||
//! [AST]: syntax::ast
|
//! [AST]: syntax::ast
|
||||||
//! [evaluate]: eval::evaluate
|
//! [evaluate]: eval::eval
|
||||||
//! [module]: eval::Module
|
//! [module]: eval::Module
|
||||||
//! [content]: model::Content
|
//! [content]: model::Content
|
||||||
//! [layouted]: model::layout
|
//! [layouted]: model::layout
|
||||||
@ -51,8 +51,10 @@ pub mod syntax;
|
|||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use comemo::{Prehashed, Track};
|
||||||
|
|
||||||
use crate::diag::{FileResult, SourceResult};
|
use crate::diag::{FileResult, SourceResult};
|
||||||
use crate::eval::Scope;
|
use crate::eval::{Route, Scope};
|
||||||
use crate::font::{Font, FontBook};
|
use crate::font::{Font, FontBook};
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
use crate::model::StyleMap;
|
use crate::model::StyleMap;
|
||||||
@ -64,33 +66,39 @@ use crate::util::Buffer;
|
|||||||
/// Returns either a vector of frames representing individual pages or
|
/// Returns either a vector of frames representing individual pages or
|
||||||
/// diagnostics in the form of a vector of error message with file and span
|
/// diagnostics in the form of a vector of error message with file and span
|
||||||
/// information.
|
/// information.
|
||||||
pub fn typeset(world: &dyn World, main: SourceId) -> SourceResult<Vec<Frame>> {
|
pub fn typeset(
|
||||||
let module = eval::evaluate(world, main, vec![])?;
|
world: &(dyn World + 'static),
|
||||||
model::layout(world, &module.content)
|
main: SourceId,
|
||||||
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
let route = Route::default();
|
||||||
|
let module = eval::eval(world.track(), route.track(), main)?;
|
||||||
|
model::layout(world.track(), &module.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The environment in which typesetting occurs.
|
/// The environment in which typesetting occurs.
|
||||||
|
#[comemo::track]
|
||||||
pub trait World {
|
pub trait World {
|
||||||
/// Access the global configuration.
|
/// Access the global configuration.
|
||||||
fn config(&self) -> &Config;
|
fn config(&self) -> &Prehashed<Config>;
|
||||||
|
|
||||||
/// Try to resolve the unique id of a source file.
|
|
||||||
fn resolve(&self, path: &Path) -> FileResult<SourceId>;
|
|
||||||
|
|
||||||
/// Access a source file by id.
|
|
||||||
fn source(&self, id: SourceId) -> &Source;
|
|
||||||
|
|
||||||
/// Metadata about all known fonts.
|
/// Metadata about all known fonts.
|
||||||
fn book(&self) -> &FontBook;
|
fn book(&self) -> &Prehashed<FontBook>;
|
||||||
|
|
||||||
/// Try to access the font with the given id.
|
/// Try to access the font with the given id.
|
||||||
fn font(&self, id: usize) -> Option<Font>;
|
fn font(&self, id: usize) -> Option<Font>;
|
||||||
|
|
||||||
/// Try to access a file at a path.
|
/// Try to access a file at a path.
|
||||||
fn file(&self, path: &Path) -> FileResult<Buffer>;
|
fn file(&self, path: &Path) -> FileResult<Buffer>;
|
||||||
|
|
||||||
|
/// Try to resolve the unique id of a source file.
|
||||||
|
fn resolve(&self, path: &Path) -> FileResult<SourceId>;
|
||||||
|
|
||||||
|
/// Access a source file by id.
|
||||||
|
fn source(&self, id: SourceId) -> &Source;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The global configuration for typesetting.
|
/// The global configuration for typesetting.
|
||||||
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// The compilation root, relative to which absolute paths are.
|
/// The compilation root, relative to which absolute paths are.
|
||||||
///
|
///
|
||||||
|
@ -14,7 +14,7 @@ impl HideNode {
|
|||||||
impl Layout for HideNode {
|
impl Layout for HideNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
@ -41,7 +41,7 @@ impl ImageNode {
|
|||||||
impl Layout for ImageNode {
|
impl Layout for ImageNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
_: &dyn World,
|
_: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
@ -40,7 +40,7 @@ impl LineNode {
|
|||||||
impl Layout for LineNode {
|
impl Layout for LineNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
_: &dyn World,
|
_: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
@ -78,7 +78,7 @@ impl<const S: ShapeKind> ShapeNode<S> {
|
|||||||
impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
@ -25,7 +25,7 @@ impl MoveNode {
|
|||||||
impl Layout for MoveNode {
|
impl Layout for MoveNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
@ -86,7 +86,7 @@ impl<const T: TransformKind> TransformNode<T> {
|
|||||||
impl<const T: TransformKind> Layout for TransformNode<T> {
|
impl<const T: TransformKind> Layout for TransformNode<T> {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
@ -28,7 +28,7 @@ impl AlignNode {
|
|||||||
impl Layout for AlignNode {
|
impl Layout for AlignNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
@ -28,7 +28,7 @@ impl ColumnsNode {
|
|||||||
impl Layout for ColumnsNode {
|
impl Layout for ColumnsNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
@ -25,7 +25,7 @@ pub enum FlowChild {
|
|||||||
impl Layout for FlowNode {
|
impl Layout for FlowNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
@ -149,7 +149,7 @@ impl FlowLayouter {
|
|||||||
/// Layout a node.
|
/// Layout a node.
|
||||||
pub fn layout_node(
|
pub fn layout_node(
|
||||||
&mut self,
|
&mut self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
node: &LayoutNode,
|
node: &LayoutNode,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
|
@ -33,7 +33,7 @@ impl GridNode {
|
|||||||
impl Layout for GridNode {
|
impl Layout for GridNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
@ -93,7 +93,7 @@ castable! {
|
|||||||
/// Performs grid layout.
|
/// Performs grid layout.
|
||||||
pub struct GridLayouter<'a> {
|
pub struct GridLayouter<'a> {
|
||||||
/// The core context.
|
/// The core context.
|
||||||
world: &'a dyn World,
|
world: Tracked<'a, dyn World>,
|
||||||
/// The grid cells.
|
/// The grid cells.
|
||||||
cells: &'a [LayoutNode],
|
cells: &'a [LayoutNode],
|
||||||
/// The column tracks including gutter tracks.
|
/// The column tracks including gutter tracks.
|
||||||
@ -133,7 +133,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
///
|
///
|
||||||
/// This prepares grid layout by unifying content and gutter tracks.
|
/// This prepares grid layout by unifying content and gutter tracks.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
world: &'a dyn World,
|
world: Tracked<'a, dyn World>,
|
||||||
tracks: Spec<&[TrackSizing]>,
|
tracks: Spec<&[TrackSizing]>,
|
||||||
gutter: Spec<&[TrackSizing]>,
|
gutter: Spec<&[TrackSizing]>,
|
||||||
cells: &'a [LayoutNode],
|
cells: &'a [LayoutNode],
|
||||||
|
@ -28,7 +28,7 @@ impl PadNode {
|
|||||||
impl Layout for PadNode {
|
impl Layout for PadNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
@ -57,7 +57,7 @@ impl PageNode {
|
|||||||
/// Layout the page run into a sequence of frames, one per page.
|
/// Layout the page run into a sequence of frames, one per page.
|
||||||
pub fn layout(
|
pub fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
mut page: usize,
|
mut page: usize,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
@ -180,7 +180,7 @@ impl Marginal {
|
|||||||
/// Resolve the marginal based on the page number.
|
/// Resolve the marginal based on the page number.
|
||||||
pub fn resolve(
|
pub fn resolve(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
page: usize,
|
page: usize,
|
||||||
) -> SourceResult<Option<Content>> {
|
) -> SourceResult<Option<Content>> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
|
@ -21,7 +21,7 @@ impl PlaceNode {
|
|||||||
impl Layout for PlaceNode {
|
impl Layout for PlaceNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
@ -27,7 +27,7 @@ impl StackNode {
|
|||||||
impl Layout for StackNode {
|
impl Layout for StackNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
@ -168,7 +168,7 @@ impl<'a> StackLayouter<'a> {
|
|||||||
/// Layout an arbitrary node.
|
/// Layout an arbitrary node.
|
||||||
pub fn layout_node(
|
pub fn layout_node(
|
||||||
&mut self,
|
&mut self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
node: &LayoutNode,
|
node: &LayoutNode,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
|
@ -48,7 +48,11 @@ impl Show for MathNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: &dyn World, styles: StyleChain) -> SourceResult<Content> {
|
fn realize(
|
||||||
|
&self,
|
||||||
|
_: Tracked<dyn World>,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Content> {
|
||||||
let node = self::rex::RexNode {
|
let node = self::rex::RexNode {
|
||||||
tex: self.formula.clone(),
|
tex: self.formula.clone(),
|
||||||
display: self.display,
|
display: self.display,
|
||||||
@ -64,7 +68,7 @@ impl Show for MathNode {
|
|||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
&self,
|
&self,
|
||||||
_: &dyn World,
|
_: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
mut realized: Content,
|
mut realized: Content,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
|
@ -22,7 +22,7 @@ pub struct RexNode {
|
|||||||
impl Layout for RexNode {
|
impl Layout for RexNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
_: &Regions,
|
_: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
@ -6,6 +6,7 @@ pub use std::io;
|
|||||||
pub use std::num::NonZeroUsize;
|
pub use std::num::NonZeroUsize;
|
||||||
pub use std::sync::Arc;
|
pub use std::sync::Arc;
|
||||||
|
|
||||||
|
pub use comemo::Tracked;
|
||||||
pub use typst_macros::node;
|
pub use typst_macros::node;
|
||||||
|
|
||||||
pub use crate::diag::{with_alternative, At, SourceError, SourceResult, StrResult};
|
pub use crate::diag::{with_alternative, At, SourceError, SourceResult, StrResult};
|
||||||
|
@ -9,7 +9,7 @@ impl DocNode {
|
|||||||
/// Layout the document into a sequence of frames, one per page.
|
/// Layout the document into a sequence of frames, one per page.
|
||||||
pub fn layout(
|
pub fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
let mut frames = vec![];
|
let mut frames = vec![];
|
||||||
|
@ -82,13 +82,13 @@ impl Show for HeadingNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: &dyn World, _: StyleChain) -> SourceResult<Content> {
|
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(Content::block(self.body.clone()))
|
Ok(Content::block(self.body.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
mut realized: Content,
|
mut realized: Content,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
@ -149,7 +149,11 @@ pub enum Leveled<T> {
|
|||||||
|
|
||||||
impl<T: Cast + Clone> Leveled<T> {
|
impl<T: Cast + Clone> Leveled<T> {
|
||||||
/// Resolve the value based on the level.
|
/// Resolve the value based on the level.
|
||||||
pub fn resolve(&self, world: &dyn World, level: NonZeroUsize) -> SourceResult<T> {
|
pub fn resolve(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
level: NonZeroUsize,
|
||||||
|
) -> SourceResult<T> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Self::Value(value) => value.clone(),
|
Self::Value(value) => value.clone(),
|
||||||
Self::Mapping(mapping) => mapping(level),
|
Self::Mapping(mapping) => mapping(level),
|
||||||
|
@ -100,7 +100,11 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, world: &dyn World, styles: StyleChain) -> SourceResult<Content> {
|
fn realize(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Content> {
|
||||||
let mut cells = vec![];
|
let mut cells = vec![];
|
||||||
let mut number = self.start;
|
let mut number = self.start;
|
||||||
|
|
||||||
@ -145,7 +149,7 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
&self,
|
&self,
|
||||||
_: &dyn World,
|
_: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
realized: Content,
|
realized: Content,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
@ -208,7 +212,7 @@ impl Label {
|
|||||||
/// Resolve the value based on the level.
|
/// Resolve the value based on the level.
|
||||||
pub fn resolve(
|
pub fn resolve(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
kind: ListKind,
|
kind: ListKind,
|
||||||
number: usize,
|
number: usize,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
|
@ -22,7 +22,7 @@ impl Show for RefNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: &dyn World, _: StyleChain) -> SourceResult<Content> {
|
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(Content::Text(format_eco!("@{}", self.0)))
|
Ok(Content::Text(format_eco!("@{}", self.0)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,11 @@ impl Show for TableNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, world: &dyn World, styles: StyleChain) -> SourceResult<Content> {
|
fn realize(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Content> {
|
||||||
let fill = styles.get(Self::FILL);
|
let fill = styles.get(Self::FILL);
|
||||||
let stroke = styles.get(Self::STROKE).map(RawStroke::unwrap_or_default);
|
let stroke = styles.get(Self::STROKE).map(RawStroke::unwrap_or_default);
|
||||||
let padding = styles.get(Self::PADDING);
|
let padding = styles.get(Self::PADDING);
|
||||||
@ -110,7 +114,7 @@ impl Show for TableNode {
|
|||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
&self,
|
&self,
|
||||||
_: &dyn World,
|
_: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
realized: Content,
|
realized: Content,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
@ -129,7 +133,12 @@ pub enum Celled<T> {
|
|||||||
|
|
||||||
impl<T: Cast + Clone> Celled<T> {
|
impl<T: Cast + Clone> Celled<T> {
|
||||||
/// Resolve the value based on the cell position.
|
/// Resolve the value based on the cell position.
|
||||||
pub fn resolve(&self, world: &dyn World, x: usize, y: usize) -> SourceResult<T> {
|
pub fn resolve(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
) -> SourceResult<T> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Self::Value(value) => value.clone(),
|
Self::Value(value) => value.clone(),
|
||||||
Self::Func(func, span) => {
|
Self::Func(func, span) => {
|
||||||
|
@ -48,7 +48,11 @@ impl<const L: DecoLine> Show for DecoNode<L> {
|
|||||||
dict! { "body" => Value::Content(self.0.clone()) }
|
dict! { "body" => Value::Content(self.0.clone()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: &dyn World, styles: StyleChain) -> SourceResult<Content> {
|
fn realize(
|
||||||
|
&self,
|
||||||
|
_: Tracked<dyn World>,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Content> {
|
||||||
Ok(self.0.clone().styled(TextNode::DECO, Decoration {
|
Ok(self.0.clone().styled(TextNode::DECO, Decoration {
|
||||||
line: L,
|
line: L,
|
||||||
stroke: styles.get(Self::STROKE).unwrap_or_default(),
|
stroke: styles.get(Self::STROKE).unwrap_or_default(),
|
||||||
|
@ -64,7 +64,7 @@ impl Show for LinkNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: &dyn World, _: StyleChain) -> SourceResult<Content> {
|
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.body.clone().unwrap_or_else(|| match &self.dest {
|
Ok(self.body.clone().unwrap_or_else(|| match &self.dest {
|
||||||
Destination::Url(url) => {
|
Destination::Url(url) => {
|
||||||
let mut text = url.as_str();
|
let mut text = url.as_str();
|
||||||
@ -80,7 +80,7 @@ impl Show for LinkNode {
|
|||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
&self,
|
&self,
|
||||||
_: &dyn World,
|
_: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
mut realized: Content,
|
mut realized: Content,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
|
@ -507,7 +507,7 @@ impl Show for StrongNode {
|
|||||||
dict! { "body" => Value::Content(self.0.clone()) }
|
dict! { "body" => Value::Content(self.0.clone()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: &dyn World, _: StyleChain) -> SourceResult<Content> {
|
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.0.clone().styled(TextNode::BOLD, Toggle))
|
Ok(self.0.clone().styled(TextNode::BOLD, Toggle))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -532,7 +532,7 @@ impl Show for EmphNode {
|
|||||||
dict! { "body" => Value::Content(self.0.clone()) }
|
dict! { "body" => Value::Content(self.0.clone()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: &dyn World, _: StyleChain) -> SourceResult<Content> {
|
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.0.clone().styled(TextNode::ITALIC, Toggle))
|
Ok(self.0.clone().styled(TextNode::ITALIC, Toggle))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ impl ParNode {
|
|||||||
impl Layout for ParNode {
|
impl Layout for ParNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
@ -496,7 +496,7 @@ fn collect<'a>(
|
|||||||
/// Prepare paragraph layout by shaping the whole paragraph and layouting all
|
/// Prepare paragraph layout by shaping the whole paragraph and layouting all
|
||||||
/// contained inline-level nodes.
|
/// contained inline-level nodes.
|
||||||
fn prepare<'a>(
|
fn prepare<'a>(
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
par: &'a ParNode,
|
par: &'a ParNode,
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
segments: Vec<(Segment<'a>, StyleChain<'a>)>,
|
segments: Vec<(Segment<'a>, StyleChain<'a>)>,
|
||||||
@ -561,7 +561,7 @@ fn prepare<'a>(
|
|||||||
/// items for them.
|
/// items for them.
|
||||||
fn shape_range<'a>(
|
fn shape_range<'a>(
|
||||||
items: &mut Vec<Item<'a>>,
|
items: &mut Vec<Item<'a>>,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
bidi: &BidiInfo<'a>,
|
bidi: &BidiInfo<'a>,
|
||||||
range: Range,
|
range: Range,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
@ -627,7 +627,7 @@ fn shared_get<'a, K: Key<'a>>(
|
|||||||
/// Find suitable linebreaks.
|
/// Find suitable linebreaks.
|
||||||
fn linebreak<'a>(
|
fn linebreak<'a>(
|
||||||
p: &'a Preparation<'a>,
|
p: &'a Preparation<'a>,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
width: Length,
|
width: Length,
|
||||||
) -> Vec<Line<'a>> {
|
) -> Vec<Line<'a>> {
|
||||||
match p.styles.get(ParNode::LINEBREAKS) {
|
match p.styles.get(ParNode::LINEBREAKS) {
|
||||||
@ -641,7 +641,7 @@ fn linebreak<'a>(
|
|||||||
/// very unbalanced line, but is fast and simple.
|
/// very unbalanced line, but is fast and simple.
|
||||||
fn linebreak_simple<'a>(
|
fn linebreak_simple<'a>(
|
||||||
p: &'a Preparation<'a>,
|
p: &'a Preparation<'a>,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
width: Length,
|
width: Length,
|
||||||
) -> Vec<Line<'a>> {
|
) -> Vec<Line<'a>> {
|
||||||
let mut lines = vec![];
|
let mut lines = vec![];
|
||||||
@ -701,7 +701,7 @@ fn linebreak_simple<'a>(
|
|||||||
/// text.
|
/// text.
|
||||||
fn linebreak_optimized<'a>(
|
fn linebreak_optimized<'a>(
|
||||||
p: &'a Preparation<'a>,
|
p: &'a Preparation<'a>,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
width: Length,
|
width: Length,
|
||||||
) -> Vec<Line<'a>> {
|
) -> Vec<Line<'a>> {
|
||||||
/// The cost of a line or paragraph layout.
|
/// The cost of a line or paragraph layout.
|
||||||
@ -914,7 +914,7 @@ impl Breakpoints<'_> {
|
|||||||
/// Create a line which spans the given range.
|
/// Create a line which spans the given range.
|
||||||
fn line<'a>(
|
fn line<'a>(
|
||||||
p: &'a Preparation,
|
p: &'a Preparation,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
mut range: Range,
|
mut range: Range,
|
||||||
mandatory: bool,
|
mandatory: bool,
|
||||||
hyphen: bool,
|
hyphen: bool,
|
||||||
@ -1022,7 +1022,7 @@ fn line<'a>(
|
|||||||
/// Combine layouted lines into one frame per region.
|
/// Combine layouted lines into one frame per region.
|
||||||
fn stack(
|
fn stack(
|
||||||
p: &Preparation,
|
p: &Preparation,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
lines: &[Line],
|
lines: &[Line],
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
@ -1072,7 +1072,7 @@ fn stack(
|
|||||||
/// Commit to a line and build its frame.
|
/// Commit to a line and build its frame.
|
||||||
fn commit(
|
fn commit(
|
||||||
p: &Preparation,
|
p: &Preparation,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
line: &Line,
|
line: &Line,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
width: Length,
|
width: Length,
|
||||||
|
@ -59,7 +59,11 @@ impl Show for RawNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: &dyn World, styles: StyleChain) -> SourceResult<Content> {
|
fn realize(
|
||||||
|
&self,
|
||||||
|
_: Tracked<dyn World>,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Content> {
|
||||||
let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase());
|
let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase());
|
||||||
let foreground = THEME
|
let foreground = THEME
|
||||||
.settings
|
.settings
|
||||||
@ -111,7 +115,7 @@ impl Show for RawNode {
|
|||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
&self,
|
&self,
|
||||||
_: &dyn World,
|
_: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
mut realized: Content,
|
mut realized: Content,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
|
@ -14,7 +14,7 @@ impl RepeatNode {
|
|||||||
impl Layout for RepeatNode {
|
impl Layout for RepeatNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
@ -80,7 +80,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
///
|
///
|
||||||
/// The `justification` defines how much extra advance width each
|
/// The `justification` defines how much extra advance width each
|
||||||
/// [justifiable glyph](ShapedGlyph::is_justifiable) will get.
|
/// [justifiable glyph](ShapedGlyph::is_justifiable) will get.
|
||||||
pub fn build(&self, world: &dyn World, justification: Length) -> Frame {
|
pub fn build(&self, world: Tracked<dyn World>, justification: Length) -> Frame {
|
||||||
let (top, bottom) = self.measure(world);
|
let (top, bottom) = self.measure(world);
|
||||||
let size = Size::new(self.width, top + bottom);
|
let size = Size::new(self.width, top + bottom);
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Measure the top and bottom extent of this text.
|
/// Measure the top and bottom extent of this text.
|
||||||
fn measure(&self, world: &dyn World) -> (Length, Length) {
|
fn measure(&self, world: Tracked<dyn World>) -> (Length, Length) {
|
||||||
let mut top = Length::zero();
|
let mut top = Length::zero();
|
||||||
let mut bottom = Length::zero();
|
let mut bottom = Length::zero();
|
||||||
|
|
||||||
@ -199,7 +199,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
/// shaping process if possible.
|
/// shaping process if possible.
|
||||||
pub fn reshape(
|
pub fn reshape(
|
||||||
&'a self,
|
&'a self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
text_range: Range<usize>,
|
text_range: Range<usize>,
|
||||||
) -> ShapedText<'a> {
|
) -> ShapedText<'a> {
|
||||||
if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) {
|
if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) {
|
||||||
@ -218,7 +218,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Push a hyphen to end of the text.
|
/// Push a hyphen to end of the text.
|
||||||
pub fn push_hyphen(&mut self, world: &dyn World) {
|
pub fn push_hyphen(&mut self, world: Tracked<dyn World>) {
|
||||||
families(self.styles).find_map(|family| {
|
families(self.styles).find_map(|family| {
|
||||||
let font = world
|
let font = world
|
||||||
.book()
|
.book()
|
||||||
@ -306,7 +306,7 @@ impl Debug for ShapedText<'_> {
|
|||||||
|
|
||||||
/// Holds shaping results and metadata common to all shaped segments.
|
/// Holds shaping results and metadata common to all shaped segments.
|
||||||
struct ShapingContext<'a> {
|
struct ShapingContext<'a> {
|
||||||
world: &'a dyn World,
|
world: Tracked<'a, dyn World>,
|
||||||
glyphs: Vec<ShapedGlyph>,
|
glyphs: Vec<ShapedGlyph>,
|
||||||
used: Vec<Font>,
|
used: Vec<Font>,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
@ -319,7 +319,7 @@ struct ShapingContext<'a> {
|
|||||||
|
|
||||||
/// Shape text into [`ShapedText`].
|
/// Shape text into [`ShapedText`].
|
||||||
pub fn shape<'a>(
|
pub fn shape<'a>(
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
dir: Dir,
|
dir: Dir,
|
||||||
|
@ -42,7 +42,11 @@ impl<const S: ScriptKind> Show for ShiftNode<S> {
|
|||||||
dict! { "body" => Value::Content(self.0.clone()) }
|
dict! { "body" => Value::Content(self.0.clone()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, world: &dyn World, styles: StyleChain) -> SourceResult<Content> {
|
fn realize(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Content> {
|
||||||
let mut transformed = None;
|
let mut transformed = None;
|
||||||
if styles.get(Self::TYPOGRAPHIC) {
|
if styles.get(Self::TYPOGRAPHIC) {
|
||||||
if let Some(text) = search_text(&self.0, S) {
|
if let Some(text) = search_text(&self.0, S) {
|
||||||
@ -91,7 +95,7 @@ fn search_text(content: &Content, mode: ScriptKind) -> Option<EcoString> {
|
|||||||
|
|
||||||
/// Checks whether the first retrievable family contains all code points of the
|
/// Checks whether the first retrievable family contains all code points of the
|
||||||
/// given string.
|
/// given string.
|
||||||
fn is_shapable(world: &dyn World, text: &str, styles: StyleChain) -> bool {
|
fn is_shapable(world: Tracked<dyn World>, text: &str, styles: StyleChain) -> bool {
|
||||||
for family in styles.get(TextNode::FAMILY).iter() {
|
for family in styles.get(TextNode::FAMILY).iter() {
|
||||||
if let Some(font) = world
|
if let Some(font) = world
|
||||||
.book()
|
.book()
|
||||||
|
@ -10,7 +10,9 @@ pub use data::*;
|
|||||||
pub use math::*;
|
pub use math::*;
|
||||||
pub use string::*;
|
pub use string::*;
|
||||||
|
|
||||||
use crate::eval::{Eval, Scopes, Vm};
|
use comemo::Track;
|
||||||
|
|
||||||
|
use crate::eval::{Eval, Route, Scopes, Vm};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::source::Source;
|
use crate::source::Source;
|
||||||
|
|
||||||
@ -39,7 +41,8 @@ pub fn eval(vm: &mut Vm, args: &mut Args) -> SourceResult<Value> {
|
|||||||
// Evaluate the source.
|
// Evaluate the source.
|
||||||
let std = &vm.world.config().std;
|
let std = &vm.world.config().std;
|
||||||
let scopes = Scopes::new(Some(std));
|
let scopes = Scopes::new(Some(std));
|
||||||
let mut sub = Vm::new(vm.world, vec![], scopes);
|
let route = Route::default();
|
||||||
|
let mut sub = Vm::new(vm.world, route.track(), None, scopes);
|
||||||
let result = ast.eval(&mut sub);
|
let result = ast.eval(&mut sub);
|
||||||
|
|
||||||
// Handle control flow.
|
// Handle control flow.
|
||||||
|
478
src/main.rs
478
src/main.rs
@ -1,15 +1,17 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::{RefCell, RefMut};
|
||||||
use std::collections::{hash_map::Entry, HashMap};
|
use std::collections::HashMap;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Read, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process;
|
use std::process;
|
||||||
|
|
||||||
use codespan_reporting::diagnostic::{Diagnostic, Label};
|
use codespan_reporting::diagnostic::{Diagnostic, Label};
|
||||||
use codespan_reporting::term::{self, termcolor};
|
use codespan_reporting::term::{self, termcolor};
|
||||||
|
use comemo::Prehashed;
|
||||||
use elsa::FrozenVec;
|
use elsa::FrozenVec;
|
||||||
use memmap2::Mmap;
|
use memmap2::Mmap;
|
||||||
|
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
use once_cell::unsync::OnceCell;
|
use once_cell::unsync::OnceCell;
|
||||||
use pico_args::Arguments;
|
use pico_args::Arguments;
|
||||||
use same_file::{is_same_file, Handle};
|
use same_file::{is_same_file, Handle};
|
||||||
@ -19,10 +21,8 @@ use walkdir::WalkDir;
|
|||||||
|
|
||||||
use typst::diag::{FileError, FileResult, SourceError, StrResult};
|
use typst::diag::{FileError, FileResult, SourceError, StrResult};
|
||||||
use typst::font::{Font, FontBook, FontInfo, FontVariant};
|
use typst::font::{Font, FontBook, FontInfo, FontVariant};
|
||||||
use typst::library::text::THEME;
|
|
||||||
use typst::parse::TokenMode;
|
|
||||||
use typst::source::{Source, SourceId};
|
use typst::source::{Source, SourceId};
|
||||||
use typst::util::Buffer;
|
use typst::util::{Buffer, PathExt};
|
||||||
use typst::{Config, World};
|
use typst::{Config, World};
|
||||||
|
|
||||||
type CodespanResult<T> = Result<T, CodespanError>;
|
type CodespanResult<T> = Result<T, CodespanError>;
|
||||||
@ -31,7 +31,6 @@ type CodespanError = codespan_reporting::files::Error;
|
|||||||
/// What to do.
|
/// What to do.
|
||||||
enum Command {
|
enum Command {
|
||||||
Typeset(TypesetCommand),
|
Typeset(TypesetCommand),
|
||||||
Highlight(HighlightCommand),
|
|
||||||
Fonts(FontsCommand),
|
Fonts(FontsCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,6 +39,7 @@ struct TypesetCommand {
|
|||||||
input: PathBuf,
|
input: PathBuf,
|
||||||
output: PathBuf,
|
output: PathBuf,
|
||||||
root: Option<PathBuf>,
|
root: Option<PathBuf>,
|
||||||
|
watch: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
const HELP: &'static str = "\
|
const HELP: &'static str = "\
|
||||||
@ -55,33 +55,13 @@ ARGS:
|
|||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
-h, --help Print this help
|
-h, --help Print this help
|
||||||
|
-w, --watch Watch the inputs and recompile on changes
|
||||||
--root <dir> Configure the root for absolute paths
|
--root <dir> Configure the root for absolute paths
|
||||||
|
|
||||||
SUBCOMMANDS:
|
SUBCOMMANDS:
|
||||||
--highlight Highlight .typ files to HTML
|
|
||||||
--fonts List all discovered system fonts
|
--fonts List all discovered system fonts
|
||||||
";
|
";
|
||||||
|
|
||||||
/// Highlight a .typ file into an HTML file.
|
|
||||||
struct HighlightCommand {
|
|
||||||
input: PathBuf,
|
|
||||||
output: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
const HELP_HIGHLIGHT: &'static str = "\
|
|
||||||
typst --highlight creates highlighted HTML from .typ files
|
|
||||||
|
|
||||||
USAGE:
|
|
||||||
typst --highlight [OPTIONS] <input.typ> [output.html]
|
|
||||||
|
|
||||||
ARGS:
|
|
||||||
<input.typ> Path to input Typst file
|
|
||||||
[output.html] Path to output HTML file
|
|
||||||
|
|
||||||
OPTIONS:
|
|
||||||
-h, --help Print this help
|
|
||||||
";
|
|
||||||
|
|
||||||
/// List discovered system fonts.
|
/// List discovered system fonts.
|
||||||
struct FontsCommand {
|
struct FontsCommand {
|
||||||
variants: bool,
|
variants: bool,
|
||||||
@ -116,14 +96,7 @@ fn parse_args() -> StrResult<Command> {
|
|||||||
let mut args = Arguments::from_env();
|
let mut args = Arguments::from_env();
|
||||||
let help = args.contains(["-h", "--help"]);
|
let help = args.contains(["-h", "--help"]);
|
||||||
|
|
||||||
let command = if args.contains("--highlight") {
|
let command = if args.contains("--fonts") {
|
||||||
if help {
|
|
||||||
print_help(HELP_HIGHLIGHT);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (input, output) = parse_input_output(&mut args, "html")?;
|
|
||||||
Command::Highlight(HighlightCommand { input, output })
|
|
||||||
} else if args.contains("--fonts") {
|
|
||||||
if help {
|
if help {
|
||||||
print_help(HELP_FONTS);
|
print_help(HELP_FONTS);
|
||||||
}
|
}
|
||||||
@ -135,8 +108,9 @@ fn parse_args() -> StrResult<Command> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let root = args.opt_value_from_str("--root").map_err(|_| "missing root path")?;
|
let root = args.opt_value_from_str("--root").map_err(|_| "missing root path")?;
|
||||||
|
let watch = args.contains(["-w", "--watch"]);
|
||||||
let (input, output) = parse_input_output(&mut args, "pdf")?;
|
let (input, output) = parse_input_output(&mut args, "pdf")?;
|
||||||
Command::Typeset(TypesetCommand { input, output, root })
|
Command::Typeset(TypesetCommand { input, output, watch, root })
|
||||||
};
|
};
|
||||||
|
|
||||||
// Don't allow excess arguments.
|
// Don't allow excess arguments.
|
||||||
@ -194,33 +168,84 @@ fn print_error(msg: &str) -> io::Result<()> {
|
|||||||
fn dispatch(command: Command) -> StrResult<()> {
|
fn dispatch(command: Command) -> StrResult<()> {
|
||||||
match command {
|
match command {
|
||||||
Command::Typeset(command) => typeset(command),
|
Command::Typeset(command) => typeset(command),
|
||||||
Command::Highlight(command) => highlight(command),
|
|
||||||
Command::Fonts(command) => fonts(command),
|
Command::Fonts(command) => fonts(command),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a typesetting command.
|
/// Execute a typesetting command.
|
||||||
fn typeset(command: TypesetCommand) -> StrResult<()> {
|
fn typeset(command: TypesetCommand) -> StrResult<()> {
|
||||||
let mut world = SystemWorld::new();
|
let mut config = Config::default();
|
||||||
if let Some(root) = &command.root {
|
if let Some(root) = &command.root {
|
||||||
world.config.root = root.clone();
|
config.root = root.clone();
|
||||||
} else if let Some(dir) = command.input.parent() {
|
} else if let Some(dir) = command.input.parent() {
|
||||||
world.config.root = dir.into();
|
config.root = dir.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Create the world that serves sources, fonts and files.
|
// Create the world that serves sources, fonts and files.
|
||||||
let id = world.resolve(&command.input).map_err(|err| err.to_string())?;
|
let mut world = SystemWorld::new(config);
|
||||||
|
|
||||||
// Typeset.
|
// Typeset.
|
||||||
match typst::typeset(&world, id) {
|
typeset_once(&mut world, &command)?;
|
||||||
|
|
||||||
|
if !command.watch {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup file watching.
|
||||||
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
|
let mut watcher = RecommendedWatcher::new(tx, notify::Config::default())
|
||||||
|
.map_err(|_| "failed to watch directory")?;
|
||||||
|
|
||||||
|
// Watch this directory recursively.
|
||||||
|
watcher
|
||||||
|
.watch(Path::new("."), RecursiveMode::Recursive)
|
||||||
|
.map_err(|_| "failed to watch directory")?;
|
||||||
|
|
||||||
|
// Handle events.
|
||||||
|
let timeout = std::time::Duration::from_millis(100);
|
||||||
|
loop {
|
||||||
|
let mut recompile = false;
|
||||||
|
for event in rx
|
||||||
|
.recv()
|
||||||
|
.into_iter()
|
||||||
|
.chain(std::iter::from_fn(|| rx.recv_timeout(timeout).ok()))
|
||||||
|
{
|
||||||
|
let event = event.map_err(|_| "failed to watch directory")?;
|
||||||
|
if event
|
||||||
|
.paths
|
||||||
|
.iter()
|
||||||
|
.all(|path| is_same_file(path, &command.output).unwrap_or(false))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
recompile |= world.relevant(&event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if recompile {
|
||||||
|
typeset_once(&mut world, &command)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Typeset a single time.
|
||||||
|
fn typeset_once(world: &mut SystemWorld, command: &TypesetCommand) -> StrResult<()> {
|
||||||
|
status(command, Status::Compiling).unwrap();
|
||||||
|
|
||||||
|
world.reset();
|
||||||
|
let main = world.resolve(&command.input).map_err(|err| err.to_string())?;
|
||||||
|
match typst::typeset(world, main) {
|
||||||
// Export the PDF.
|
// Export the PDF.
|
||||||
Ok(frames) => {
|
Ok(frames) => {
|
||||||
let buffer = typst::export::pdf(&frames);
|
let buffer = typst::export::pdf(&frames);
|
||||||
fs::write(&command.output, buffer).map_err(|_| "failed to write PDF file")?;
|
fs::write(&command.output, buffer).map_err(|_| "failed to write PDF file")?;
|
||||||
|
status(command, Status::Success).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print diagnostics.
|
// Print diagnostics.
|
||||||
Err(errors) => {
|
Err(errors) => {
|
||||||
|
status(command, Status::Error).unwrap();
|
||||||
print_diagnostics(&world, *errors)
|
print_diagnostics(&world, *errors)
|
||||||
.map_err(|_| "failed to print diagnostics")?;
|
.map_err(|_| "failed to print diagnostics")?;
|
||||||
}
|
}
|
||||||
@ -229,6 +254,65 @@ fn typeset(command: TypesetCommand) -> StrResult<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clear the terminal and render the status message.
|
||||||
|
fn status(command: &TypesetCommand, status: Status) -> io::Result<()> {
|
||||||
|
if !command.watch {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let esc = 27 as char;
|
||||||
|
let input = command.input.display();
|
||||||
|
let output = command.output.display();
|
||||||
|
let time = chrono::offset::Local::now();
|
||||||
|
let timestamp = time.format("%H:%M:%S");
|
||||||
|
let message = status.message();
|
||||||
|
let color = status.color();
|
||||||
|
|
||||||
|
let mut w = StandardStream::stderr(ColorChoice::Always);
|
||||||
|
write!(w, "{esc}c{esc}[1;1H")?;
|
||||||
|
|
||||||
|
w.set_color(&color)?;
|
||||||
|
write!(w, "watching")?;
|
||||||
|
w.reset()?;
|
||||||
|
writeln!(w, " {input}")?;
|
||||||
|
|
||||||
|
w.set_color(&color)?;
|
||||||
|
write!(w, "writing to")?;
|
||||||
|
w.reset()?;
|
||||||
|
writeln!(w, " {output}")?;
|
||||||
|
|
||||||
|
writeln!(w)?;
|
||||||
|
writeln!(w, "[{timestamp}] {message}")?;
|
||||||
|
writeln!(w)?;
|
||||||
|
|
||||||
|
w.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The status in which the watcher can be.
|
||||||
|
enum Status {
|
||||||
|
Compiling,
|
||||||
|
Success,
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Status {
|
||||||
|
fn message(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Compiling => "compiling ...",
|
||||||
|
Self::Success => "compiled successfully",
|
||||||
|
Self::Error => "compiled with errors",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color(&self) -> termcolor::ColorSpec {
|
||||||
|
let styles = term::Styles::default();
|
||||||
|
match self {
|
||||||
|
Self::Error => styles.header_error,
|
||||||
|
_ => styles.header_note,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Print diagnostic messages to the terminal.
|
/// Print diagnostic messages to the terminal.
|
||||||
fn print_diagnostics(
|
fn print_diagnostics(
|
||||||
world: &SystemWorld,
|
world: &SystemWorld,
|
||||||
@ -263,21 +347,11 @@ fn print_diagnostics(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a highlighting command.
|
|
||||||
fn highlight(command: HighlightCommand) -> StrResult<()> {
|
|
||||||
let input =
|
|
||||||
fs::read_to_string(&command.input).map_err(|_| "failed to load source file")?;
|
|
||||||
|
|
||||||
let html = typst::syntax::highlight_html(&input, TokenMode::Markup, &THEME);
|
|
||||||
fs::write(&command.output, html).map_err(|_| "failed to write HTML file")?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute a font listing command.
|
/// Execute a font listing command.
|
||||||
fn fonts(command: FontsCommand) -> StrResult<()> {
|
fn fonts(command: FontsCommand) -> StrResult<()> {
|
||||||
let world = SystemWorld::new();
|
let mut searcher = FontSearcher::new();
|
||||||
for (name, infos) in world.book().families() {
|
searcher.search_system();
|
||||||
|
for (name, infos) in searcher.book.families() {
|
||||||
println!("{name}");
|
println!("{name}");
|
||||||
if command.variants {
|
if command.variants {
|
||||||
for info in infos {
|
for info in infos {
|
||||||
@ -292,60 +366,50 @@ fn fonts(command: FontsCommand) -> StrResult<()> {
|
|||||||
|
|
||||||
/// A world that provides access to the operating system.
|
/// A world that provides access to the operating system.
|
||||||
struct SystemWorld {
|
struct SystemWorld {
|
||||||
config: Config,
|
config: Prehashed<Config>,
|
||||||
sources: FrozenVec<Box<Source>>,
|
book: Prehashed<FontBook>,
|
||||||
nav: RefCell<HashMap<PathHash, SourceId>>,
|
|
||||||
book: FontBook,
|
|
||||||
fonts: Vec<FontSlot>,
|
fonts: Vec<FontSlot>,
|
||||||
files: RefCell<HashMap<PathHash, Buffer>>,
|
hashes: RefCell<HashMap<PathBuf, FileResult<PathHash>>>,
|
||||||
|
paths: RefCell<HashMap<PathHash, PathSlot>>,
|
||||||
|
sources: FrozenVec<Box<Source>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Holds details about the location of a font and lazily the font itself.
|
||||||
struct FontSlot {
|
struct FontSlot {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
index: u32,
|
index: u32,
|
||||||
font: OnceCell<Option<Font>>,
|
font: OnceCell<Option<Font>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Holds canonical data for all paths pointing to the same entity.
|
||||||
|
#[derive(Default)]
|
||||||
|
struct PathSlot {
|
||||||
|
source: OnceCell<FileResult<SourceId>>,
|
||||||
|
buffer: OnceCell<FileResult<Buffer>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl SystemWorld {
|
impl SystemWorld {
|
||||||
fn new() -> Self {
|
fn new(config: Config) -> Self {
|
||||||
let mut world = Self {
|
let mut searcher = FontSearcher::new();
|
||||||
config: Config::default(),
|
searcher.search_system();
|
||||||
book: FontBook::new(),
|
|
||||||
|
Self {
|
||||||
|
config: Prehashed::new(config),
|
||||||
|
book: Prehashed::new(searcher.book),
|
||||||
|
fonts: searcher.fonts,
|
||||||
|
hashes: RefCell::default(),
|
||||||
|
paths: RefCell::default(),
|
||||||
sources: FrozenVec::new(),
|
sources: FrozenVec::new(),
|
||||||
nav: RefCell::new(HashMap::new()),
|
}
|
||||||
fonts: vec![],
|
|
||||||
files: RefCell::new(HashMap::new()),
|
|
||||||
};
|
|
||||||
world.search_system();
|
|
||||||
world
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl World for SystemWorld {
|
impl World for SystemWorld {
|
||||||
fn config(&self) -> &Config {
|
fn config(&self) -> &Prehashed<Config> {
|
||||||
&self.config
|
&self.config
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(&self, path: &Path) -> FileResult<SourceId> {
|
fn book(&self) -> &Prehashed<FontBook> {
|
||||||
let hash = PathHash::new(path)?;
|
|
||||||
if let Some(&id) = self.nav.borrow().get(&hash) {
|
|
||||||
return Ok(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let text = fs::read_to_string(path).map_err(|e| FileError::from_io(e, path))?;
|
|
||||||
let id = SourceId::from_raw(self.sources.len() as u16);
|
|
||||||
let source = Source::new(id, path, text);
|
|
||||||
self.sources.push(Box::new(source));
|
|
||||||
self.nav.borrow_mut().insert(hash, id);
|
|
||||||
|
|
||||||
Ok(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn source(&self, id: SourceId) -> &Source {
|
|
||||||
&self.sources[id.into_raw() as usize]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn book(&self) -> &FontBook {
|
|
||||||
&self.book
|
&self.book
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,36 +424,178 @@ impl World for SystemWorld {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn file(&self, path: &Path) -> FileResult<Buffer> {
|
fn file(&self, path: &Path) -> FileResult<Buffer> {
|
||||||
let hash = PathHash::new(path)?;
|
self.slot(path)?
|
||||||
Ok(match self.files.borrow_mut().entry(hash) {
|
.buffer
|
||||||
Entry::Occupied(entry) => entry.get().clone(),
|
.get_or_init(|| read(path).map(Buffer::from))
|
||||||
Entry::Vacant(entry) => entry
|
.clone()
|
||||||
.insert(fs::read(path).map_err(|e| FileError::from_io(e, path))?.into())
|
}
|
||||||
.clone(),
|
|
||||||
})
|
fn resolve(&self, path: &Path) -> FileResult<SourceId> {
|
||||||
|
self.slot(path)?
|
||||||
|
.source
|
||||||
|
.get_or_init(|| {
|
||||||
|
let buf = read(path)?;
|
||||||
|
let text = String::from_utf8(buf)?;
|
||||||
|
Ok(self.insert(path, text))
|
||||||
|
})
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source(&self, id: SourceId) -> &Source {
|
||||||
|
&self.sources[id.into_u16() as usize]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A hash that is the same for all paths pointing to the same file.
|
impl SystemWorld {
|
||||||
|
fn slot(&self, path: &Path) -> FileResult<RefMut<PathSlot>> {
|
||||||
|
let mut hashes = self.hashes.borrow_mut();
|
||||||
|
let hash = match hashes.get(path).cloned() {
|
||||||
|
Some(hash) => hash,
|
||||||
|
None => {
|
||||||
|
let hash = PathHash::new(path);
|
||||||
|
if let Ok(canon) = path.canonicalize() {
|
||||||
|
hashes.insert(canon.normalize(), hash.clone());
|
||||||
|
}
|
||||||
|
hashes.insert(path.into(), hash.clone());
|
||||||
|
hash
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
|
||||||
|
Ok(std::cell::RefMut::map(self.paths.borrow_mut(), |paths| {
|
||||||
|
paths.entry(hash).or_default()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(&self, path: &Path, text: String) -> SourceId {
|
||||||
|
let id = SourceId::from_u16(self.sources.len() as u16);
|
||||||
|
let source = Source::new(id, path, text);
|
||||||
|
self.sources.push(Box::new(source));
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn relevant(&mut self, event: ¬ify::Event) -> bool {
|
||||||
|
match &event.kind {
|
||||||
|
notify::EventKind::Any => {}
|
||||||
|
notify::EventKind::Access(_) => return false,
|
||||||
|
notify::EventKind::Create(_) => return true,
|
||||||
|
notify::EventKind::Modify(kind) => match kind {
|
||||||
|
notify::event::ModifyKind::Any => {}
|
||||||
|
notify::event::ModifyKind::Data(_) => {}
|
||||||
|
notify::event::ModifyKind::Metadata(_) => return false,
|
||||||
|
notify::event::ModifyKind::Name(_) => return true,
|
||||||
|
notify::event::ModifyKind::Other => return false,
|
||||||
|
},
|
||||||
|
notify::EventKind::Remove(_) => {}
|
||||||
|
notify::EventKind::Other => return false,
|
||||||
|
}
|
||||||
|
|
||||||
|
event.paths.iter().any(|path| self.dependant(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dependant(&self, path: &Path) -> bool {
|
||||||
|
self.hashes.borrow().contains_key(&path.normalize())
|
||||||
|
|| PathHash::new(path)
|
||||||
|
.map_or(false, |hash| self.paths.borrow().contains_key(&hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.sources.as_mut().clear();
|
||||||
|
self.hashes.borrow_mut().clear();
|
||||||
|
self.paths.borrow_mut().clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A hash that is the same for all paths pointing to the same entity.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
struct PathHash(u128);
|
struct PathHash(u128);
|
||||||
|
|
||||||
impl PathHash {
|
impl PathHash {
|
||||||
fn new(path: &Path) -> FileResult<Self> {
|
fn new(path: &Path) -> FileResult<Self> {
|
||||||
let f = |e| FileError::from_io(e, path);
|
let f = |e| FileError::from_io(e, path);
|
||||||
let file = File::open(path).map_err(f)?;
|
let handle = Handle::from_path(path).map_err(f)?;
|
||||||
if file.metadata().map_err(f)?.is_file() {
|
let mut state = SipHasher::new();
|
||||||
let handle = Handle::from_file(file).map_err(f)?;
|
handle.hash(&mut state);
|
||||||
let mut state = SipHasher::new();
|
Ok(Self(state.finish128().as_u128()))
|
||||||
handle.hash(&mut state);
|
|
||||||
Ok(Self(state.finish128().as_u128()))
|
|
||||||
} else {
|
|
||||||
Err(FileError::NotFound(path.into()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SystemWorld {
|
/// Read a file.
|
||||||
|
fn read(path: &Path) -> FileResult<Vec<u8>> {
|
||||||
|
let f = |e| FileError::from_io(e, path);
|
||||||
|
let mut file = File::open(path).map_err(f)?;
|
||||||
|
if file.metadata().map_err(f)?.is_file() {
|
||||||
|
let mut data = vec![];
|
||||||
|
file.read_to_end(&mut data).map_err(f)?;
|
||||||
|
Ok(data)
|
||||||
|
} else {
|
||||||
|
Err(FileError::IsDirectory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> codespan_reporting::files::Files<'a> for SystemWorld {
|
||||||
|
type FileId = SourceId;
|
||||||
|
type Name = std::path::Display<'a>;
|
||||||
|
type Source = &'a str;
|
||||||
|
|
||||||
|
fn name(&'a self, id: SourceId) -> CodespanResult<Self::Name> {
|
||||||
|
Ok(World::source(self, id).path().display())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source(&'a self, id: SourceId) -> CodespanResult<Self::Source> {
|
||||||
|
Ok(World::source(self, id).text())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_index(&'a self, id: SourceId, given: usize) -> CodespanResult<usize> {
|
||||||
|
let source = World::source(self, id);
|
||||||
|
source
|
||||||
|
.byte_to_line(given)
|
||||||
|
.ok_or_else(|| CodespanError::IndexTooLarge {
|
||||||
|
given,
|
||||||
|
max: source.len_bytes(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_range(
|
||||||
|
&'a self,
|
||||||
|
id: SourceId,
|
||||||
|
given: usize,
|
||||||
|
) -> CodespanResult<std::ops::Range<usize>> {
|
||||||
|
let source = World::source(self, id);
|
||||||
|
source
|
||||||
|
.line_to_range(given)
|
||||||
|
.ok_or_else(|| CodespanError::LineTooLarge { given, max: source.len_lines() })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column_number(
|
||||||
|
&'a self,
|
||||||
|
id: SourceId,
|
||||||
|
_: usize,
|
||||||
|
given: usize,
|
||||||
|
) -> CodespanResult<usize> {
|
||||||
|
let source = World::source(self, id);
|
||||||
|
source.byte_to_column(given).ok_or_else(|| {
|
||||||
|
let max = source.len_bytes();
|
||||||
|
if given <= max {
|
||||||
|
CodespanError::InvalidCharBoundary { given }
|
||||||
|
} else {
|
||||||
|
CodespanError::IndexTooLarge { given, max }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Searches for fonts.
|
||||||
|
struct FontSearcher {
|
||||||
|
book: FontBook,
|
||||||
|
fonts: Vec<FontSlot>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontSearcher {
|
||||||
|
/// Create a new, empty system searcher.
|
||||||
|
fn new() -> Self {
|
||||||
|
Self { book: FontBook::new(), fonts: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
/// Search for fonts in the linux system font directories.
|
/// Search for fonts in the linux system font directories.
|
||||||
#[cfg(all(unix, not(target_os = "macos")))]
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
fn search_system(&mut self) {
|
fn search_system(&mut self) {
|
||||||
@ -466,55 +672,3 @@ impl SystemWorld {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> codespan_reporting::files::Files<'a> for SystemWorld {
|
|
||||||
type FileId = SourceId;
|
|
||||||
type Name = std::path::Display<'a>;
|
|
||||||
type Source = &'a str;
|
|
||||||
|
|
||||||
fn name(&'a self, id: SourceId) -> CodespanResult<Self::Name> {
|
|
||||||
Ok(World::source(self, id).path().display())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn source(&'a self, id: SourceId) -> CodespanResult<Self::Source> {
|
|
||||||
Ok(World::source(self, id).text())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn line_index(&'a self, id: SourceId, given: usize) -> CodespanResult<usize> {
|
|
||||||
let source = World::source(self, id);
|
|
||||||
source
|
|
||||||
.byte_to_line(given)
|
|
||||||
.ok_or_else(|| CodespanError::IndexTooLarge {
|
|
||||||
given,
|
|
||||||
max: source.len_bytes(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn line_range(
|
|
||||||
&'a self,
|
|
||||||
id: SourceId,
|
|
||||||
given: usize,
|
|
||||||
) -> CodespanResult<std::ops::Range<usize>> {
|
|
||||||
let source = World::source(self, id);
|
|
||||||
source
|
|
||||||
.line_to_range(given)
|
|
||||||
.ok_or_else(|| CodespanError::LineTooLarge { given, max: source.len_lines() })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn column_number(
|
|
||||||
&'a self,
|
|
||||||
id: SourceId,
|
|
||||||
_: usize,
|
|
||||||
given: usize,
|
|
||||||
) -> CodespanResult<usize> {
|
|
||||||
let source = World::source(self, id);
|
|
||||||
source.byte_to_column(given).ok_or_else(|| {
|
|
||||||
let max = source.len_bytes();
|
|
||||||
if given <= max {
|
|
||||||
CodespanError::InvalidCharBoundary { given }
|
|
||||||
} else {
|
|
||||||
CodespanError::IndexTooLarge { given, max }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -4,6 +4,7 @@ use std::iter::Sum;
|
|||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
|
|
||||||
|
use comemo::Tracked;
|
||||||
use typed_arena::Arena;
|
use typed_arena::Arena;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -23,7 +24,8 @@ use crate::World;
|
|||||||
/// Layout content into a collection of pages.
|
/// Layout content into a collection of pages.
|
||||||
///
|
///
|
||||||
/// Relayouts until all pinned locations are converged.
|
/// Relayouts until all pinned locations are converged.
|
||||||
pub fn layout(world: &dyn World, content: &Content) -> SourceResult<Vec<Frame>> {
|
#[comemo::memoize]
|
||||||
|
pub fn layout(world: Tracked<dyn World>, content: &Content) -> SourceResult<Vec<Frame>> {
|
||||||
let styles = StyleChain::with_root(&world.config().styles);
|
let styles = StyleChain::with_root(&world.config().styles);
|
||||||
let scratch = Scratch::default();
|
let scratch = Scratch::default();
|
||||||
|
|
||||||
@ -232,7 +234,7 @@ impl Content {
|
|||||||
impl Layout for Content {
|
impl Layout for Content {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
@ -330,9 +332,9 @@ impl Sum for Content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a document or a flow node from content.
|
/// Builds a document or a flow node from content.
|
||||||
struct Builder<'a, 'w> {
|
struct Builder<'a> {
|
||||||
/// The core context.
|
/// The core context.
|
||||||
world: &'w dyn World,
|
world: Tracked<'a, dyn World>,
|
||||||
/// Scratch arenas for building.
|
/// Scratch arenas for building.
|
||||||
scratch: &'a Scratch<'a>,
|
scratch: &'a Scratch<'a>,
|
||||||
/// The current document building state.
|
/// The current document building state.
|
||||||
@ -354,8 +356,8 @@ struct Scratch<'a> {
|
|||||||
templates: Arena<Content>,
|
templates: Arena<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'w> Builder<'a, 'w> {
|
impl<'a> Builder<'a> {
|
||||||
fn new(world: &'w dyn World, scratch: &'a Scratch<'a>, top: bool) -> Self {
|
fn new(world: Tracked<'a, dyn World>, scratch: &'a Scratch<'a>, top: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
world,
|
world,
|
||||||
scratch,
|
scratch,
|
||||||
@ -662,7 +664,7 @@ impl<'a> ParBuilder<'a> {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(self, parent: &mut Builder<'a, '_>) {
|
fn finish(self, parent: &mut Builder<'a>) {
|
||||||
let (mut children, shared) = self.0.finish();
|
let (mut children, shared) = self.0.finish();
|
||||||
if children.is_empty() {
|
if children.is_empty() {
|
||||||
return;
|
return;
|
||||||
@ -746,7 +748,7 @@ impl<'a> ListBuilder<'a> {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(self, parent: &mut Builder<'a, '_>) -> SourceResult<()> {
|
fn finish(self, parent: &mut Builder<'a>) -> SourceResult<()> {
|
||||||
let (items, shared) = self.items.finish();
|
let (items, shared) = self.items.finish();
|
||||||
let kind = match items.items().next() {
|
let kind = match items.items().next() {
|
||||||
Some(item) => item.kind,
|
Some(item) => item.kind,
|
||||||
|
@ -5,6 +5,8 @@ use std::fmt::{self, Debug, Formatter, Write};
|
|||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use comemo::{Prehashed, Tracked};
|
||||||
|
|
||||||
use super::{Barrier, NodeId, Resolve, StyleChain, StyleEntry};
|
use super::{Barrier, NodeId, Resolve, StyleChain, StyleEntry};
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::eval::{RawAlign, RawLength};
|
use crate::eval::{RawAlign, RawLength};
|
||||||
@ -14,7 +16,6 @@ use crate::geom::{
|
|||||||
};
|
};
|
||||||
use crate::library::graphics::MoveNode;
|
use crate::library::graphics::MoveNode;
|
||||||
use crate::library::layout::{AlignNode, PadNode};
|
use crate::library::layout::{AlignNode, PadNode};
|
||||||
use crate::util::Prehashed;
|
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// A node that can be layouted into a sequence of regions.
|
/// A node that can be layouted into a sequence of regions.
|
||||||
@ -24,7 +25,7 @@ pub trait Layout: 'static {
|
|||||||
/// Layout this node into the given regions, producing frames.
|
/// Layout this node into the given regions, producing frames.
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>>;
|
) -> SourceResult<Vec<Frame>>;
|
||||||
@ -214,9 +215,10 @@ impl LayoutNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for LayoutNode {
|
impl Layout for LayoutNode {
|
||||||
|
#[comemo::memoize]
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
@ -285,7 +287,7 @@ struct EmptyNode;
|
|||||||
impl Layout for EmptyNode {
|
impl Layout for EmptyNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
_: &dyn World,
|
_: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
_: StyleChain,
|
_: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
@ -307,7 +309,7 @@ struct SizedNode {
|
|||||||
impl Layout for SizedNode {
|
impl Layout for SizedNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
@ -354,7 +356,7 @@ struct FillNode {
|
|||||||
impl Layout for FillNode {
|
impl Layout for FillNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
@ -379,7 +381,7 @@ struct StrokeNode {
|
|||||||
impl Layout for StrokeNode {
|
impl Layout for StrokeNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
@ -3,13 +3,15 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use comemo::Prehashed;
|
||||||
|
|
||||||
use super::{Interruption, NodeId, StyleChain};
|
use super::{Interruption, NodeId, StyleChain};
|
||||||
use crate::eval::{RawLength, Smart};
|
use crate::eval::{RawLength, Smart};
|
||||||
use crate::geom::{Corners, Length, Numeric, Relative, Sides, Spec};
|
use crate::geom::{Corners, Length, Numeric, Relative, Sides, Spec};
|
||||||
use crate::library::layout::PageNode;
|
use crate::library::layout::PageNode;
|
||||||
use crate::library::structure::{EnumNode, ListNode};
|
use crate::library::structure::{EnumNode, ListNode};
|
||||||
use crate::library::text::ParNode;
|
use crate::library::text::ParNode;
|
||||||
use crate::util::{Prehashed, ReadableTypeId};
|
use crate::util::ReadableTypeId;
|
||||||
|
|
||||||
/// A style property originating from a set rule or constructor.
|
/// A style property originating from a set rule or constructor.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
|
use comemo::Tracked;
|
||||||
|
|
||||||
use super::{Content, Interruption, NodeId, Show, ShowNode, StyleChain, StyleEntry};
|
use super::{Content, Interruption, NodeId, Show, ShowNode, StyleChain, StyleEntry};
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::eval::{Args, Func, Regex, Value};
|
use crate::eval::{Args, Func, Regex, Value};
|
||||||
@ -29,7 +31,7 @@ impl Recipe {
|
|||||||
/// Try to apply the recipe to the target.
|
/// Try to apply the recipe to the target.
|
||||||
pub fn apply(
|
pub fn apply(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
sel: Selector,
|
sel: Selector,
|
||||||
target: Target,
|
target: Target,
|
||||||
@ -75,7 +77,7 @@ impl Recipe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Call the recipe function, with the argument if desired.
|
/// Call the recipe function, with the argument if desired.
|
||||||
fn call<F>(&self, world: &dyn World, arg: F) -> SourceResult<Content>
|
fn call<F>(&self, world: Tracked<dyn World>, arg: F) -> SourceResult<Content>
|
||||||
where
|
where
|
||||||
F: FnOnce() -> Value,
|
F: FnOnce() -> Value,
|
||||||
{
|
{
|
||||||
|
@ -2,10 +2,11 @@ use std::fmt::{self, Debug, Formatter, Write};
|
|||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use comemo::{Prehashed, Tracked};
|
||||||
|
|
||||||
use super::{Content, NodeId, Selector, StyleChain};
|
use super::{Content, NodeId, Selector, StyleChain};
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::eval::Dict;
|
use crate::eval::Dict;
|
||||||
use crate::util::Prehashed;
|
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// A node that can be realized given some styles.
|
/// A node that can be realized given some styles.
|
||||||
@ -18,7 +19,11 @@ pub trait Show: 'static {
|
|||||||
|
|
||||||
/// The base recipe for this node that is executed if there is no
|
/// The base recipe for this node that is executed if there is no
|
||||||
/// user-defined show rule.
|
/// user-defined show rule.
|
||||||
fn realize(&self, world: &dyn World, styles: StyleChain) -> SourceResult<Content>;
|
fn realize(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Content>;
|
||||||
|
|
||||||
/// Finalize this node given the realization of a base or user recipe. Use
|
/// Finalize this node given the realization of a base or user recipe. Use
|
||||||
/// this for effects that should work even in the face of a user-defined
|
/// this for effects that should work even in the face of a user-defined
|
||||||
@ -30,7 +35,7 @@ pub trait Show: 'static {
|
|||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn finalize(
|
fn finalize(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
realized: Content,
|
realized: Content,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
@ -74,13 +79,17 @@ impl Show for ShowNode {
|
|||||||
self.0.encode(styles)
|
self.0.encode(styles)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, world: &dyn World, styles: StyleChain) -> SourceResult<Content> {
|
fn realize(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Content> {
|
||||||
self.0.realize(world, styles)
|
self.0.realize(world, styles)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
&self,
|
&self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
realized: Content,
|
realized: Content,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
|
@ -3,6 +3,8 @@ use std::hash::Hash;
|
|||||||
use std::iter;
|
use std::iter;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use comemo::Tracked;
|
||||||
|
|
||||||
use super::{Barrier, Content, Key, Property, Recipe, Selector, Show, Target};
|
use super::{Barrier, Content, Key, Property, Recipe, Selector, Show, Target};
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::frame::Role;
|
use crate::frame::Role;
|
||||||
@ -279,7 +281,7 @@ impl<'a> StyleChain<'a> {
|
|||||||
/// Apply show recipes in this style chain to a target.
|
/// Apply show recipes in this style chain to a target.
|
||||||
pub fn apply(
|
pub fn apply(
|
||||||
self,
|
self,
|
||||||
world: &dyn World,
|
world: Tracked<dyn World>,
|
||||||
target: Target,
|
target: Target,
|
||||||
) -> SourceResult<Option<Content>> {
|
) -> SourceResult<Option<Content>> {
|
||||||
// Find out how many recipes there any and whether any of their patterns
|
// Find out how many recipes there any and whether any of their patterns
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
//! Source file management.
|
//! Source file management.
|
||||||
|
|
||||||
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use comemo::Prehashed;
|
||||||
use unscanny::Scanner;
|
use unscanny::Scanner;
|
||||||
|
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
@ -13,15 +16,14 @@ use crate::util::{PathExt, StrExt};
|
|||||||
|
|
||||||
/// A source file.
|
/// A source file.
|
||||||
///
|
///
|
||||||
/// _Note_: All line and column indices start at zero, just like byte indices.
|
/// All line and column indices start at zero, just like byte indices. Only for
|
||||||
/// Only for user-facing display, you should add 1 to them.
|
/// user-facing display, you should add 1 to them.
|
||||||
pub struct Source {
|
pub struct Source {
|
||||||
id: SourceId,
|
id: SourceId,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
text: String,
|
text: Prehashed<String>,
|
||||||
lines: Vec<Line>,
|
lines: Vec<Line>,
|
||||||
root: SyntaxNode,
|
root: SyntaxNode,
|
||||||
rev: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Source {
|
impl Source {
|
||||||
@ -38,9 +40,8 @@ impl Source {
|
|||||||
id,
|
id,
|
||||||
path: path.normalize(),
|
path: path.normalize(),
|
||||||
root,
|
root,
|
||||||
text,
|
text: Prehashed::new(text),
|
||||||
lines,
|
lines,
|
||||||
rev: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,14 +88,6 @@ impl Source {
|
|||||||
&self.text
|
&self.text
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The revision number of the file.
|
|
||||||
///
|
|
||||||
/// This is increased on [replacements](Self::replace) and
|
|
||||||
/// [edits](Self::edit).
|
|
||||||
pub fn rev(&self) -> usize {
|
|
||||||
self.rev
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Slice out the part of the source code enclosed by the range.
|
/// Slice out the part of the source code enclosed by the range.
|
||||||
pub fn get(&self, range: Range<usize>) -> Option<&str> {
|
pub fn get(&self, range: Range<usize>) -> Option<&str> {
|
||||||
self.text.get(range)
|
self.text.get(range)
|
||||||
@ -102,12 +95,11 @@ impl Source {
|
|||||||
|
|
||||||
/// Fully replace the source text and increase the revision number.
|
/// Fully replace the source text and increase the revision number.
|
||||||
pub fn replace(&mut self, text: String) {
|
pub fn replace(&mut self, text: String) {
|
||||||
self.text = text;
|
self.text = Prehashed::new(text);
|
||||||
self.lines = vec![Line { byte_idx: 0, utf16_idx: 0 }];
|
self.lines = vec![Line { byte_idx: 0, utf16_idx: 0 }];
|
||||||
self.lines.extend(lines(0, 0, &self.text));
|
self.lines.extend(lines(0, 0, &self.text));
|
||||||
self.root = parse(&self.text);
|
self.root = parse(&self.text);
|
||||||
self.root.numberize(self.id(), Span::FULL).unwrap();
|
self.root.numberize(self.id(), Span::FULL).unwrap();
|
||||||
self.rev = self.rev.wrapping_add(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Edit the source file by replacing the given range and increase the
|
/// Edit the source file by replacing the given range and increase the
|
||||||
@ -117,11 +109,11 @@ impl Source {
|
|||||||
///
|
///
|
||||||
/// The method panics if the `replace` range is out of bounds.
|
/// The method panics if the `replace` range is out of bounds.
|
||||||
pub fn edit(&mut self, replace: Range<usize>, with: &str) -> Range<usize> {
|
pub fn edit(&mut self, replace: Range<usize>, with: &str) -> Range<usize> {
|
||||||
self.rev = self.rev.wrapping_add(1);
|
|
||||||
|
|
||||||
let start_byte = replace.start;
|
let start_byte = replace.start;
|
||||||
let start_utf16 = self.byte_to_utf16(replace.start).unwrap();
|
let start_utf16 = self.byte_to_utf16(replace.start).unwrap();
|
||||||
self.text.replace_range(replace.clone(), with);
|
let mut text = std::mem::take(&mut self.text).into_inner();
|
||||||
|
text.replace_range(replace.clone(), with);
|
||||||
|
self.text = Prehashed::new(text);
|
||||||
|
|
||||||
// Remove invalidated line starts.
|
// Remove invalidated line starts.
|
||||||
let line = self.byte_to_line(start_byte).unwrap();
|
let line = self.byte_to_line(start_byte).unwrap();
|
||||||
@ -246,6 +238,20 @@ impl Source {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for Source {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(f, "Source({})", self.path.display())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for Source {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.id.hash(state);
|
||||||
|
self.path.hash(state);
|
||||||
|
self.text.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A unique identifier for a loaded source file.
|
/// A unique identifier for a loaded source file.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct SourceId(u16);
|
pub struct SourceId(u16);
|
||||||
@ -256,16 +262,13 @@ impl SourceId {
|
|||||||
Self(u16::MAX)
|
Self(u16::MAX)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a source id from the raw underlying value.
|
/// Create a source id from a number.
|
||||||
///
|
pub const fn from_u16(v: u16) -> Self {
|
||||||
/// This should only be called with values returned by
|
|
||||||
/// [`into_raw`](Self::into_raw).
|
|
||||||
pub const fn from_raw(v: u16) -> Self {
|
|
||||||
Self(v)
|
Self(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert into the raw underlying value.
|
/// Extract the underlying number.
|
||||||
pub const fn into_raw(self) -> u16 {
|
pub const fn into_u16(self) -> u16 {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ impl Span {
|
|||||||
"span number outside valid range"
|
"span number outside valid range"
|
||||||
);
|
);
|
||||||
|
|
||||||
let bits = ((id.into_raw() as u64) << Self::BITS) | number;
|
let bits = ((id.into_u16() as u64) << Self::BITS) | number;
|
||||||
Self(to_non_zero(bits))
|
Self(to_non_zero(bits))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ impl Span {
|
|||||||
|
|
||||||
/// The id of the source file the span points into.
|
/// The id of the source file the span points into.
|
||||||
pub const fn source(self) -> SourceId {
|
pub const fn source(self) -> SourceId {
|
||||||
SourceId::from_raw((self.0.get() >> Self::BITS) as u16)
|
SourceId::from_u16((self.0.get() >> Self::BITS) as u16)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The unique number of the span within the source file.
|
/// The unique number of the span within the source file.
|
||||||
@ -157,7 +157,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_span_encoding() {
|
fn test_span_encoding() {
|
||||||
let id = SourceId::from_raw(5);
|
let id = SourceId::from_u16(5);
|
||||||
let span = Span::new(id, 10).with_pos(SpanPos::End);
|
let span = Span::new(id, 10).with_pos(SpanPos::End);
|
||||||
assert_eq!(span.source(), id);
|
assert_eq!(span.source(), id);
|
||||||
assert_eq!(span.number(), 10);
|
assert_eq!(span.number(), 10);
|
||||||
|
@ -2,7 +2,7 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::Prehashed;
|
use comemo::Prehashed;
|
||||||
|
|
||||||
/// A shared buffer that is cheap to clone and hash.
|
/// A shared buffer that is cheap to clone and hash.
|
||||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
use std::any::Any;
|
|
||||||
use std::fmt::{self, Debug, Formatter};
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
/// A wrapper around a type that precomputes its hash.
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct Prehashed<T: ?Sized> {
|
|
||||||
/// The precomputed hash.
|
|
||||||
hash: u64,
|
|
||||||
/// The wrapped item.
|
|
||||||
item: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Hash + 'static> Prehashed<T> {
|
|
||||||
/// Compute an item's hash and wrap it.
|
|
||||||
pub fn new(item: T) -> Self {
|
|
||||||
Self {
|
|
||||||
hash: {
|
|
||||||
// Also hash the TypeId because the type might be converted
|
|
||||||
// through an unsized coercion.
|
|
||||||
let mut state = fxhash::FxHasher64::default();
|
|
||||||
item.type_id().hash(&mut state);
|
|
||||||
item.hash(&mut state);
|
|
||||||
state.finish()
|
|
||||||
},
|
|
||||||
item,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the wrapped value.
|
|
||||||
pub fn into_iter(self) -> T {
|
|
||||||
self.item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ?Sized> Deref for Prehashed<T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Debug + ?Sized> Debug for Prehashed<T> {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
self.item.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ?Sized> Hash for Prehashed<T> {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
state.write_u64(self.hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Eq + ?Sized> Eq for Prehashed<T> {}
|
|
||||||
|
|
||||||
impl<T: ?Sized> PartialEq for Prehashed<T> {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.hash == other.hash
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,11 +3,9 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod eco;
|
mod eco;
|
||||||
mod buffer;
|
mod buffer;
|
||||||
mod hash;
|
|
||||||
|
|
||||||
pub use buffer::Buffer;
|
pub use buffer::Buffer;
|
||||||
pub use eco::EcoString;
|
pub use eco::EcoString;
|
||||||
pub use hash::Prehashed;
|
|
||||||
|
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
#import a, c, from "target.typ"
|
#import a, c, from "target.typ"
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 19-21 file not found (searched at typ/code)
|
// Error: 19-21 failed to load file (is a directory)
|
||||||
#import name from ""
|
#import name from ""
|
||||||
|
|
||||||
---
|
---
|
||||||
|
229
tests/typeset.rs
229
tests/typeset.rs
@ -1,15 +1,15 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::{RefCell, RefMut};
|
||||||
use std::collections::{hash_map::Entry, HashMap};
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::hash::Hash;
|
use std::io::Read;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use comemo::Prehashed;
|
||||||
use elsa::FrozenVec;
|
use elsa::FrozenVec;
|
||||||
use same_file::Handle;
|
use once_cell::unsync::OnceCell;
|
||||||
use siphasher::sip128::{Hasher128, SipHasher};
|
|
||||||
use tiny_skia as sk;
|
use tiny_skia as sk;
|
||||||
use unscanny::Scanner;
|
use unscanny::Scanner;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
@ -24,7 +24,7 @@ use typst::library::text::{TextNode, TextSize};
|
|||||||
use typst::model::StyleMap;
|
use typst::model::StyleMap;
|
||||||
use typst::source::{Source, SourceId};
|
use typst::source::{Source, SourceId};
|
||||||
use typst::syntax::SyntaxNode;
|
use typst::syntax::SyntaxNode;
|
||||||
use typst::util::Buffer;
|
use typst::util::{Buffer, PathExt};
|
||||||
use typst::{bail, Config, World};
|
use typst::{bail, Config, World};
|
||||||
|
|
||||||
const TYP_DIR: &str = "./typ";
|
const TYP_DIR: &str = "./typ";
|
||||||
@ -147,54 +147,65 @@ impl Args {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn config() -> Config {
|
||||||
|
// Set page width to 120pt with 10pt margins, so that the inner page is
|
||||||
|
// exactly 100pt wide. Page height is unbounded and font size is 10pt so
|
||||||
|
// that it multiplies to nice round numbers.
|
||||||
|
let mut styles = StyleMap::new();
|
||||||
|
styles.set(PageNode::WIDTH, Smart::Custom(Length::pt(120.0).into()));
|
||||||
|
styles.set(PageNode::HEIGHT, Smart::Auto);
|
||||||
|
styles.set(
|
||||||
|
PageNode::MARGINS,
|
||||||
|
Sides::splat(Some(Smart::Custom(Length::pt(10.0).into()))),
|
||||||
|
);
|
||||||
|
styles.set(TextNode::SIZE, TextSize(Length::pt(10.0).into()));
|
||||||
|
|
||||||
|
// Hook up helpers into the global scope.
|
||||||
|
let mut std = typst::library::new();
|
||||||
|
std.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
|
||||||
|
std.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
|
||||||
|
std.def_fn("test", move |_, args| {
|
||||||
|
let lhs = args.expect::<Value>("left-hand side")?;
|
||||||
|
let rhs = args.expect::<Value>("right-hand side")?;
|
||||||
|
if lhs != rhs {
|
||||||
|
bail!(args.span, "Assertion failed: {:?} != {:?}", lhs, rhs,);
|
||||||
|
}
|
||||||
|
Ok(Value::None)
|
||||||
|
});
|
||||||
|
std.def_fn("print", move |_, args| {
|
||||||
|
print!("> ");
|
||||||
|
for (i, value) in args.all::<Value>()?.into_iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
print!(", ")
|
||||||
|
}
|
||||||
|
print!("{value:?}");
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
Ok(Value::None)
|
||||||
|
});
|
||||||
|
|
||||||
|
Config { root: PathBuf::new(), std, styles }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A world that provides access to the tests environment.
|
||||||
struct TestWorld {
|
struct TestWorld {
|
||||||
config: Config,
|
|
||||||
print: PrintConfig,
|
print: PrintConfig,
|
||||||
sources: FrozenVec<Box<Source>>,
|
config: Prehashed<Config>,
|
||||||
nav: RefCell<HashMap<PathHash, SourceId>>,
|
book: Prehashed<FontBook>,
|
||||||
book: FontBook,
|
|
||||||
fonts: Vec<Font>,
|
fonts: Vec<Font>,
|
||||||
files: RefCell<HashMap<PathHash, Buffer>>,
|
paths: RefCell<HashMap<PathBuf, PathSlot>>,
|
||||||
|
sources: FrozenVec<Box<Source>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct PathSlot {
|
||||||
|
source: OnceCell<FileResult<SourceId>>,
|
||||||
|
buffer: OnceCell<FileResult<Buffer>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestWorld {
|
impl TestWorld {
|
||||||
fn new(print: PrintConfig) -> Self {
|
fn new(print: PrintConfig) -> Self {
|
||||||
// Set page width to 120pt with 10pt margins, so that the inner page is
|
// Search for fonts.
|
||||||
// exactly 100pt wide. Page height is unbounded and font size is 10pt so
|
|
||||||
// that it multiplies to nice round numbers.
|
|
||||||
let mut styles = StyleMap::new();
|
|
||||||
styles.set(PageNode::WIDTH, Smart::Custom(Length::pt(120.0).into()));
|
|
||||||
styles.set(PageNode::HEIGHT, Smart::Auto);
|
|
||||||
styles.set(
|
|
||||||
PageNode::MARGINS,
|
|
||||||
Sides::splat(Some(Smart::Custom(Length::pt(10.0).into()))),
|
|
||||||
);
|
|
||||||
styles.set(TextNode::SIZE, TextSize(Length::pt(10.0).into()));
|
|
||||||
|
|
||||||
// Hook up helpers into the global scope.
|
|
||||||
let mut std = typst::library::new();
|
|
||||||
std.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
|
|
||||||
std.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
|
|
||||||
std.def_fn("test", move |_, args| {
|
|
||||||
let lhs = args.expect::<Value>("left-hand side")?;
|
|
||||||
let rhs = args.expect::<Value>("right-hand side")?;
|
|
||||||
if lhs != rhs {
|
|
||||||
bail!(args.span, "Assertion failed: {:?} != {:?}", lhs, rhs,);
|
|
||||||
}
|
|
||||||
Ok(Value::None)
|
|
||||||
});
|
|
||||||
std.def_fn("print", move |_, args| {
|
|
||||||
print!("> ");
|
|
||||||
for (i, value) in args.all::<Value>()?.into_iter().enumerate() {
|
|
||||||
if i > 0 {
|
|
||||||
print!(", ")
|
|
||||||
}
|
|
||||||
print!("{value:?}");
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
Ok(Value::None)
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut fonts = vec![];
|
let mut fonts = vec![];
|
||||||
for entry in WalkDir::new(FONT_DIR)
|
for entry in WalkDir::new(FONT_DIR)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -208,56 +219,22 @@ impl TestWorld {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
config: Config { root: PathBuf::new(), std, styles },
|
|
||||||
print,
|
print,
|
||||||
sources: FrozenVec::new(),
|
config: Prehashed::new(config()),
|
||||||
nav: RefCell::new(HashMap::new()),
|
book: Prehashed::new(FontBook::from_fonts(&fonts)),
|
||||||
book: FontBook::from_fonts(&fonts),
|
|
||||||
fonts,
|
fonts,
|
||||||
files: RefCell::new(HashMap::new()),
|
paths: RefCell::default(),
|
||||||
|
sources: FrozenVec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn provide(&mut self, path: &Path, text: String) -> SourceId {
|
|
||||||
let hash = PathHash::new(path).unwrap();
|
|
||||||
if let Some(&id) = self.nav.borrow().get(&hash) {
|
|
||||||
self.sources.as_mut()[id.into_raw() as usize].replace(text);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
let id = SourceId::from_raw(self.sources.len() as u16);
|
|
||||||
let source = Source::new(id, path, text);
|
|
||||||
self.sources.push(Box::new(source));
|
|
||||||
self.nav.borrow_mut().insert(hash, id);
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl World for TestWorld {
|
impl World for TestWorld {
|
||||||
fn config(&self) -> &Config {
|
fn config(&self) -> &Prehashed<Config> {
|
||||||
&self.config
|
&self.config
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(&self, path: &Path) -> FileResult<SourceId> {
|
fn book(&self) -> &Prehashed<FontBook> {
|
||||||
let hash = PathHash::new(path)?;
|
|
||||||
if let Some(&id) = self.nav.borrow().get(&hash) {
|
|
||||||
return Ok(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let text = fs::read_to_string(path).map_err(|e| FileError::from_io(e, path))?;
|
|
||||||
let id = SourceId::from_raw(self.sources.len() as u16);
|
|
||||||
let source = Source::new(id, path, text);
|
|
||||||
self.sources.push(Box::new(source));
|
|
||||||
self.nav.borrow_mut().insert(hash, id);
|
|
||||||
|
|
||||||
Ok(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn source(&self, id: SourceId) -> &Source {
|
|
||||||
&self.sources[id.into_raw() as usize]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn book(&self) -> &FontBook {
|
|
||||||
&self.book
|
&self.book
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,33 +243,67 @@ impl World for TestWorld {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn file(&self, path: &Path) -> FileResult<Buffer> {
|
fn file(&self, path: &Path) -> FileResult<Buffer> {
|
||||||
let hash = PathHash::new(path)?;
|
self.slot(path)
|
||||||
Ok(match self.files.borrow_mut().entry(hash) {
|
.buffer
|
||||||
Entry::Occupied(entry) => entry.get().clone(),
|
.get_or_init(|| read(path).map(Buffer::from))
|
||||||
Entry::Vacant(entry) => entry
|
.clone()
|
||||||
.insert(fs::read(path).map_err(|e| FileError::from_io(e, path))?.into())
|
}
|
||||||
.clone(),
|
|
||||||
})
|
fn resolve(&self, path: &Path) -> FileResult<SourceId> {
|
||||||
|
self.slot(path)
|
||||||
|
.source
|
||||||
|
.get_or_init(|| {
|
||||||
|
let buf = read(path)?;
|
||||||
|
let text = String::from_utf8(buf)?;
|
||||||
|
Ok(self.insert(path, text))
|
||||||
|
})
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source(&self, id: SourceId) -> &Source {
|
||||||
|
&self.sources[id.into_u16() as usize]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A hash that is the same for all paths pointing to the same file.
|
impl TestWorld {
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
fn set(&mut self, path: &Path, text: String) -> SourceId {
|
||||||
struct PathHash(u128);
|
let slot = self.slot(path);
|
||||||
|
if let Some(&Ok(id)) = slot.source.get() {
|
||||||
impl PathHash {
|
drop(slot);
|
||||||
fn new(path: &Path) -> FileResult<Self> {
|
self.sources.as_mut()[id.into_u16() as usize].replace(text);
|
||||||
let f = |e| FileError::from_io(e, path);
|
id
|
||||||
let file = File::open(path).map_err(f)?;
|
|
||||||
if file.metadata().map_err(f)?.is_file() {
|
|
||||||
let handle = Handle::from_file(file).map_err(f)?;
|
|
||||||
let mut state = SipHasher::new();
|
|
||||||
handle.hash(&mut state);
|
|
||||||
Ok(Self(state.finish128().as_u128()))
|
|
||||||
} else {
|
} else {
|
||||||
Err(FileError::NotFound(path.into()))
|
let id = self.insert(path, text);
|
||||||
|
slot.source.set(Ok(id)).unwrap();
|
||||||
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn slot(&self, path: &Path) -> RefMut<PathSlot> {
|
||||||
|
RefMut::map(self.paths.borrow_mut(), |paths| {
|
||||||
|
paths.entry(path.normalize()).or_default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(&self, path: &Path, text: String) -> SourceId {
|
||||||
|
let id = SourceId::from_u16(self.sources.len() as u16);
|
||||||
|
let source = Source::new(id, path, text);
|
||||||
|
self.sources.push(Box::new(source));
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a file.
|
||||||
|
fn read(path: &Path) -> FileResult<Vec<u8>> {
|
||||||
|
let f = |e| FileError::from_io(e, path);
|
||||||
|
let mut file = File::open(path).map_err(f)?;
|
||||||
|
if file.metadata().map_err(f)?.is_file() {
|
||||||
|
let mut data = vec![];
|
||||||
|
file.read_to_end(&mut data).map_err(f)?;
|
||||||
|
Ok(data)
|
||||||
|
} else {
|
||||||
|
Err(FileError::IsDirectory)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test(
|
fn test(
|
||||||
@ -395,7 +406,7 @@ fn test_part(
|
|||||||
) -> (bool, bool, Vec<Frame>) {
|
) -> (bool, bool, Vec<Frame>) {
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
|
|
||||||
let id = world.provide(src_path, text);
|
let id = world.set(src_path, text);
|
||||||
let source = world.source(id);
|
let source = world.source(id);
|
||||||
if world.print.syntax {
|
if world.print.syntax {
|
||||||
println!("Syntax Tree: {:#?}", source.root())
|
println!("Syntax Tree: {:#?}", source.root())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user