mirror of
https://codeberg.org/icewind/vmt-parser.git
synced 2026-06-03 20:14:06 +02:00
vdf work
This commit is contained in:
commit
5b10005022
17 changed files with 1246 additions and 0 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
use flake
|
||||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
target
|
||||||
|
*.bench
|
||||||
|
*.obj
|
||||||
|
result
|
||||||
|
.direnv
|
||||||
252
Cargo.lock
generated
Normal file
252
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,252 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "beef"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "logos"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c000ca4d908ff18ac99b93a062cb8958d331c3220719c52e77cb19cc6ac5d2c1"
|
||||||
|
dependencies = [
|
||||||
|
"logos-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "logos-codegen"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc487311295e0002e452025d6b580b77bb17286de87b57138f3b5db711cded68"
|
||||||
|
dependencies = [
|
||||||
|
"beef",
|
||||||
|
"fnv",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex-syntax 0.6.29",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "logos-derive"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbfc0d229f1f42d790440136d941afd806bc9e949e2bcb8faa813b0f00d1267e"
|
||||||
|
dependencies = [
|
||||||
|
"logos-codegen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miette"
|
||||||
|
version = "5.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e"
|
||||||
|
dependencies = [
|
||||||
|
"miette-derive",
|
||||||
|
"once_cell",
|
||||||
|
"thiserror",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miette-derive"
|
||||||
|
version = "5.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parse-display"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c6509d08722b53e8dafe97f2027b22ccbe3a5db83cb352931e9716b0aa44bc5c"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"parse-display-derive",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parse-display-derive"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68517892c8daf78da08c0db777fcc17e07f2f63ef70041718f8a7630ad84f341"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"regex-syntax 0.7.5",
|
||||||
|
"structmeta",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.70"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax 0.8.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax 0.8.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "structmeta"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ad9e09554f0456d67a69c1584c9798ba733a5b50349a6c0d0948710523922d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"structmeta-derive",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "structmeta-derive"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.41"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vmt-parser"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"logos",
|
||||||
|
"miette",
|
||||||
|
"parse-display",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "vmt-parser"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
logos = "0.13.0"
|
||||||
|
thiserror = "1.0.50"
|
||||||
|
miette = "5.10.0"
|
||||||
|
parse-display = "0.8.2"
|
||||||
105
flake.lock
generated
Normal file
105
flake.lock
generated
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"naersk": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1698420672,
|
||||||
|
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "naersk",
|
||||||
|
"rev": "aeb58d5e8faead8980a807c840232697982d47b9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "naersk",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1702346276,
|
||||||
|
"narHash": "sha256-eAQgwIWApFQ40ipeOjVSoK4TEHVd6nbSd9fApiHIw5A=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "cf28ee258fd5f9a52de6b9865cdb93a1f96d09b7",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"ref": "nixos-23.11",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"naersk": "naersk",
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"rust-overlay": "rust-overlay",
|
||||||
|
"utils": "utils"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rust-overlay": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": [
|
||||||
|
"utils"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1702520151,
|
||||||
|
"narHash": "sha256-jxJWosN7hgcW+dFT8V3EBDCYUOjv5tpjEBRmlakS7tU=",
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"rev": "d6a1d8f80dbcda4c13993b859a3574c3dde61072",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1701680307,
|
||||||
|
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
101
flake.nix
Normal file
101
flake.nix
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "nixpkgs/nixos-23.11";
|
||||||
|
utils.url = "github:numtide/flake-utils";
|
||||||
|
naersk.url = "github:nix-community/naersk";
|
||||||
|
naersk.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||||
|
rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
rust-overlay.inputs.flake-utils.follows = "utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = {
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
utils,
|
||||||
|
naersk,
|
||||||
|
rust-overlay,
|
||||||
|
}:
|
||||||
|
utils.lib.eachDefaultSystem (system: let
|
||||||
|
overlays = [(import rust-overlay)];
|
||||||
|
pkgs = (import nixpkgs) {
|
||||||
|
inherit system overlays;
|
||||||
|
};
|
||||||
|
inherit (pkgs) lib callPackage rust-bin mkShell;
|
||||||
|
inherit (lib.sources) sourceByRegex;
|
||||||
|
|
||||||
|
msrv = (fromTOML (readFile ./Cargo.toml)).package.rust-version;
|
||||||
|
inherit (builtins) fromTOML readFile;
|
||||||
|
toolchain = rust-bin.stable.latest.default;
|
||||||
|
msrvToolchain = rust-bin.stable."${msrv}".default;
|
||||||
|
|
||||||
|
naersk' = callPackage naersk {
|
||||||
|
rustc = toolchain;
|
||||||
|
cargo = toolchain;
|
||||||
|
};
|
||||||
|
msrvNaersk = callPackage naersk {
|
||||||
|
rustc = msrvToolchain;
|
||||||
|
cargo = msrvToolchain;
|
||||||
|
};
|
||||||
|
|
||||||
|
src = sourceByRegex ./. ["Cargo.*" "(src|derive|benches|tests|examples|koth_bagel.*)(/.*)?"];
|
||||||
|
nearskOpt = {
|
||||||
|
pname = "vbsp";
|
||||||
|
root = src;
|
||||||
|
};
|
||||||
|
in rec {
|
||||||
|
packages = {
|
||||||
|
check = naersk'.buildPackage (nearskOpt
|
||||||
|
// {
|
||||||
|
mode = "check";
|
||||||
|
});
|
||||||
|
clippy = naersk'.buildPackage (nearskOpt
|
||||||
|
// {
|
||||||
|
mode = "clippy";
|
||||||
|
});
|
||||||
|
test = naersk'.buildPackage (nearskOpt
|
||||||
|
// {
|
||||||
|
release = false;
|
||||||
|
mode = "test";
|
||||||
|
});
|
||||||
|
msrv = msrvNaersk.buildPackage (nearskOpt
|
||||||
|
// {
|
||||||
|
mode = "check";
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
devShells = let
|
||||||
|
tools = with pkgs; [
|
||||||
|
bacon
|
||||||
|
cargo-edit
|
||||||
|
cargo-outdated
|
||||||
|
cargo-audit
|
||||||
|
cargo-msrv
|
||||||
|
cargo-semver-checks
|
||||||
|
(writeShellApplication {
|
||||||
|
name = "cargo-fuzz";
|
||||||
|
runtimeInputs = [cargo-fuzz toolchain];
|
||||||
|
text = ''
|
||||||
|
# shellcheck disable=SC2068
|
||||||
|
RUSTC_BOOTSTRAP=1 cargo-fuzz $@
|
||||||
|
'';
|
||||||
|
})
|
||||||
|
(writeShellApplication {
|
||||||
|
name = "cargo-expand";
|
||||||
|
runtimeInputs = [cargo-expand toolchain];
|
||||||
|
text = ''
|
||||||
|
# shellcheck disable=SC2068
|
||||||
|
RUSTC_BOOTSTRAP=1 cargo-expand $@
|
||||||
|
'';
|
||||||
|
})
|
||||||
|
];
|
||||||
|
in {
|
||||||
|
default = mkShell {
|
||||||
|
nativeBuildInputs = [toolchain] ++ tools;
|
||||||
|
};
|
||||||
|
msrv = mkShell {
|
||||||
|
nativeBuildInputs = [msrvToolchain] ++ tools;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
10
src/error.rs
Normal file
10
src/error.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
use crate::vdf::VdfError;
|
||||||
|
use miette::Diagnostic;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error(transparent)]
|
||||||
|
#[diagnostic(transparent)]
|
||||||
|
Vdf(VdfError),
|
||||||
|
}
|
||||||
6
src/lib.rs
Normal file
6
src/lib.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
mod error;
|
||||||
|
pub mod vdf;
|
||||||
|
|
||||||
|
pub use error::Error;
|
||||||
|
|
||||||
|
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
32
src/vdf/entry/array.rs
Normal file
32
src/vdf/entry/array.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use super::Entry;
|
||||||
|
|
||||||
|
/// An array of entries (items that have the same key).
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Array(Vec<Entry>);
|
||||||
|
|
||||||
|
impl From<Entry> for Array {
|
||||||
|
fn from(value: Entry) -> Self {
|
||||||
|
Array(vec![value])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Entry> for Array {
|
||||||
|
fn into(self) -> Entry {
|
||||||
|
Entry::Array(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Array {
|
||||||
|
type Target = Vec<Entry>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for Array {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
164
src/vdf/entry/mod.rs
Normal file
164
src/vdf/entry/mod.rs
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
use std::slice;
|
||||||
|
|
||||||
|
/// The kinds of entry.
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum Entry {
|
||||||
|
/// A table.
|
||||||
|
Table(Table),
|
||||||
|
|
||||||
|
/// An array (entries with the same key).
|
||||||
|
Array(Array),
|
||||||
|
|
||||||
|
/// A statement (the values starting with #).
|
||||||
|
Statement(Statement),
|
||||||
|
|
||||||
|
/// A value.
|
||||||
|
Value(Value),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entry {
|
||||||
|
/// Lookup an entry with a path.
|
||||||
|
pub fn lookup<S: AsRef<str>>(&self, path: S) -> Option<&Entry> {
|
||||||
|
let mut current = self;
|
||||||
|
|
||||||
|
for name in path.as_ref().split('.') {
|
||||||
|
if let Some(entry) = current.get(name.trim()) {
|
||||||
|
current = entry;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to get the named entry.
|
||||||
|
pub fn get<S: AsRef<str>>(&self, name: S) -> Option<&Entry> {
|
||||||
|
match self {
|
||||||
|
&Entry::Table(ref value) =>
|
||||||
|
value.get(name.as_ref()),
|
||||||
|
|
||||||
|
&Entry::Array(ref value) =>
|
||||||
|
name.as_ref().parse::<usize>().ok().and_then(|i| value.get(i)),
|
||||||
|
|
||||||
|
_ =>
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to convert the entry to the given type.
|
||||||
|
pub fn to<T: Parse>(&self) -> Option<T> {
|
||||||
|
if let &Entry::Value(ref value) = self {
|
||||||
|
value.to::<T>()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to take the entry as a table.
|
||||||
|
pub fn as_table(&self) -> Option<&Table> {
|
||||||
|
if let &Entry::Table(ref value) = self {
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to take the entry as a slice.
|
||||||
|
pub fn as_slice(&self) -> Option<&[Entry]> {
|
||||||
|
if let &Entry::Array(ref value) = self {
|
||||||
|
Some(value.as_slice())
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
unsafe {
|
||||||
|
Some(slice::from_raw_parts(self, 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to take the entry as a statement.
|
||||||
|
pub fn as_statement(&self) -> Option<&Statement> {
|
||||||
|
if let &Entry::Statement(ref value) = self {
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to take the entry as a value.
|
||||||
|
pub fn as_value(&self) -> Option<&Value> {
|
||||||
|
if let &Entry::Value(ref value) = self {
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to take the entry as a string.
|
||||||
|
pub fn as_str(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
&Entry::Value(ref value) =>
|
||||||
|
Some(&*value),
|
||||||
|
|
||||||
|
&Entry::Statement(ref value) =>
|
||||||
|
Some(&*value),
|
||||||
|
|
||||||
|
_ =>
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parsable types.
|
||||||
|
pub trait Parse: Sized {
|
||||||
|
/// Try to parse the string.
|
||||||
|
fn parse(string: &str) -> Option<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! from_str {
|
||||||
|
(for) => ();
|
||||||
|
|
||||||
|
(for $ty:ident $($rest:tt)*) => (
|
||||||
|
from_str!($ty);
|
||||||
|
from_str!(for $($rest)*);
|
||||||
|
);
|
||||||
|
|
||||||
|
($ty:ident) => (
|
||||||
|
impl Parse for $ty {
|
||||||
|
fn parse(string: &str) -> Option<Self> {
|
||||||
|
string.parse::<$ty>().ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
|
||||||
|
from_str!(for IpAddr Ipv4Addr Ipv6Addr SocketAddr SocketAddrV4 SocketAddrV6);
|
||||||
|
from_str!(for i8 i16 i32 i64 isize u8 u16 u32 u64 usize f32 f64);
|
||||||
|
|
||||||
|
impl Parse for bool {
|
||||||
|
fn parse(string: &str) -> Option<Self> {
|
||||||
|
match string {
|
||||||
|
"0" => Some(false),
|
||||||
|
"1" => Some(true),
|
||||||
|
v => v.parse::<bool>().ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod table;
|
||||||
|
pub use self::table::Table;
|
||||||
|
|
||||||
|
mod array;
|
||||||
|
pub use self::array::Array;
|
||||||
|
|
||||||
|
mod statement;
|
||||||
|
pub use self::statement::Statement;
|
||||||
|
|
||||||
|
mod value;
|
||||||
|
pub use self::value::Value;
|
||||||
27
src/vdf/entry/statement.rs
Normal file
27
src/vdf/entry/statement.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
use super::Entry;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
/// A statement.
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Statement(String);
|
||||||
|
|
||||||
|
impl From<Cow<'_, str>> for Statement {
|
||||||
|
fn from(value: Cow<'_, str>) -> Self {
|
||||||
|
Statement(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Entry> for Statement {
|
||||||
|
fn into(self) -> Entry {
|
||||||
|
Entry::Statement(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Statement {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
76
src/vdf/entry/table.rs
Normal file
76
src/vdf/entry/table.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
use super::super::{Event, Item, Reader, Result};
|
||||||
|
use super::{Array, Entry, Statement, Value};
|
||||||
|
use crate::vdf::error::StatementInTableError;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
/// A table of entries.
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Table(HashMap<String, Entry>);
|
||||||
|
|
||||||
|
fn insert(map: &mut HashMap<String, Entry>, key: String, value: Entry) {
|
||||||
|
if !map.contains_key(&key) {
|
||||||
|
map.insert(key, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(&mut Entry::Array(ref mut array)) = map.get_mut(&key) {
|
||||||
|
array.push(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut array = Array::from(map.remove(&key).unwrap());
|
||||||
|
array.push(value);
|
||||||
|
|
||||||
|
map.insert(key, array.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Table {
|
||||||
|
/// Load a table from the given `Reader`.
|
||||||
|
pub fn load(reader: &mut Reader) -> Result<Table> {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match reader.event()? {
|
||||||
|
Event::Entry {
|
||||||
|
key: Item::Statement { .. },
|
||||||
|
span,
|
||||||
|
..
|
||||||
|
} => return Err(StatementInTableError::new(span.into()).into()),
|
||||||
|
Event::Entry {
|
||||||
|
key: Item::Value { content: key, .. },
|
||||||
|
value: Item::Statement { content: value, .. },
|
||||||
|
..
|
||||||
|
} => insert(&mut map, key.into(), Statement::from(value).into()),
|
||||||
|
|
||||||
|
Event::Entry {
|
||||||
|
key: Item::Value { content: key, .. },
|
||||||
|
value: Item::Value { content: value, .. },
|
||||||
|
..
|
||||||
|
} => insert(&mut map, key.into(), Value::from(value).into()),
|
||||||
|
|
||||||
|
Event::GroupStart { name, .. } => {
|
||||||
|
insert(&mut map, name.into(), Table::load(reader)?.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
Event::GroupEnd { .. } | Event::End { .. } => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Table(map));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Entry> for Table {
|
||||||
|
fn into(self) -> Entry {
|
||||||
|
Entry::Table(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Table {
|
||||||
|
type Target = HashMap<String, Entry>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/vdf/entry/value.rs
Normal file
33
src/vdf/entry/value.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
use super::{Entry, Parse};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Value(String);
|
||||||
|
|
||||||
|
impl From<Cow<'_, str>> for Value {
|
||||||
|
fn from(value: Cow<'_, str>) -> Value {
|
||||||
|
Value(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Entry> for Value {
|
||||||
|
fn into(self) -> Entry {
|
||||||
|
Entry::Value(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Value {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value {
|
||||||
|
/// Try to convert the value to the given type.
|
||||||
|
pub fn to<T: Parse>(&self) -> Option<T> {
|
||||||
|
T::parse(&self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
122
src/vdf/error.rs
Normal file
122
src/vdf/error.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
use crate::vdf::Token;
|
||||||
|
use miette::{Diagnostic, SourceSpan};
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Any error that occurred while trying to parse the vdf file
|
||||||
|
#[derive(Error, Debug, Clone, Diagnostic)]
|
||||||
|
pub enum VdfError {
|
||||||
|
#[error(transparent)]
|
||||||
|
#[diagnostic(transparent)]
|
||||||
|
/// A token that wasn't expected was found while parsing
|
||||||
|
UnexpectedToken(#[from] UnexpectedTokenError),
|
||||||
|
#[error(transparent)]
|
||||||
|
#[diagnostic(transparent)]
|
||||||
|
/// No valid token found
|
||||||
|
NoValidToken(#[from] NoValidTokenError),
|
||||||
|
#[error(transparent)]
|
||||||
|
#[diagnostic(transparent)]
|
||||||
|
/// An unexpected statement was found inside a table
|
||||||
|
StatementInTable(#[from] StatementInTableError),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ExpectedTokens<'a>(&'a [Token]);
|
||||||
|
|
||||||
|
impl Display for ExpectedTokens<'_> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut tokens = self.0.iter();
|
||||||
|
if let Some(token) = tokens.next() {
|
||||||
|
write!(f, "{}", token)?;
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
for token in tokens {
|
||||||
|
write!(f, ", {}", token)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A token that wasn't expected was found while parsing
|
||||||
|
#[derive(Debug, Clone, Diagnostic)]
|
||||||
|
#[diagnostic(code(php_literal_parser::unexpected_token))]
|
||||||
|
pub struct UnexpectedTokenError {
|
||||||
|
#[label("Expected {}", ExpectedTokens(self.expected))]
|
||||||
|
err_span: SourceSpan,
|
||||||
|
pub expected: &'static [Token],
|
||||||
|
pub found: Option<Token>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnexpectedTokenError {
|
||||||
|
pub fn new(expected: &'static [Token], found: Option<Token>, err_span: SourceSpan) -> Self {
|
||||||
|
UnexpectedTokenError {
|
||||||
|
err_span,
|
||||||
|
expected,
|
||||||
|
found,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for UnexpectedTokenError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match &self.found {
|
||||||
|
Some(token) => write!(
|
||||||
|
f,
|
||||||
|
"Unexpected token, found {} expected one of {}",
|
||||||
|
token,
|
||||||
|
ExpectedTokens(self.expected)
|
||||||
|
),
|
||||||
|
None => write!(
|
||||||
|
f,
|
||||||
|
"Unexpected end of input expected one of {}",
|
||||||
|
ExpectedTokens(self.expected)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for UnexpectedTokenError {}
|
||||||
|
|
||||||
|
/// A token that wasn't expected was found while parsing
|
||||||
|
#[derive(Debug, Clone, Diagnostic)]
|
||||||
|
#[diagnostic(code(php_literal_parser::unexpected_token))]
|
||||||
|
pub struct NoValidTokenError {
|
||||||
|
#[label("Expected {}", ExpectedTokens(self.expected))]
|
||||||
|
err_span: SourceSpan,
|
||||||
|
pub expected: &'static [Token],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoValidTokenError {
|
||||||
|
pub fn new(expected: &'static [Token], err_span: SourceSpan) -> Self {
|
||||||
|
NoValidTokenError { err_span, expected }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for NoValidTokenError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"No valid token found, expected one of {}",
|
||||||
|
ExpectedTokens(self.expected)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for NoValidTokenError {}
|
||||||
|
|
||||||
|
/// An unexpected statement was found inside a table
|
||||||
|
#[derive(Debug, Clone, Diagnostic, Error)]
|
||||||
|
#[diagnostic(code(php_literal_parser::unexpected_token))]
|
||||||
|
#[error("An unexpected statement was found inside a table")]
|
||||||
|
pub struct StatementInTableError {
|
||||||
|
#[label("Unexpected statement")]
|
||||||
|
err_span: SourceSpan,
|
||||||
|
}
|
||||||
|
impl StatementInTableError {
|
||||||
|
pub fn new(err_span: SourceSpan) -> Self {
|
||||||
|
StatementInTableError { err_span }
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/vdf/mod.rs
Normal file
10
src/vdf/mod.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
pub mod entry;
|
||||||
|
mod error;
|
||||||
|
mod parser;
|
||||||
|
mod reader;
|
||||||
|
|
||||||
|
pub use error::VdfError;
|
||||||
|
|
||||||
|
pub type Result<T, E = VdfError> = std::result::Result<T, E>;
|
||||||
|
pub use parser::Token;
|
||||||
|
pub use reader::{Event, Item, Reader};
|
||||||
102
src/vdf/parser.rs
Normal file
102
src/vdf/parser.rs
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
use logos::Logos;
|
||||||
|
use parse_display::Display;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
/// Parser token.
|
||||||
|
#[derive(PartialEq, Debug, Logos, Display, Clone)]
|
||||||
|
#[logos(skip r"[ \t\n\f]+")] // whitespace
|
||||||
|
#[logos(skip r"//[^\n]*")] // comments
|
||||||
|
pub enum Token {
|
||||||
|
/// A group is starting.
|
||||||
|
#[token("{")]
|
||||||
|
#[display("start of group")]
|
||||||
|
GroupStart,
|
||||||
|
/// A group is ending.
|
||||||
|
#[token("}")]
|
||||||
|
#[display("end of group")]
|
||||||
|
GroupEnd,
|
||||||
|
/// An enclosed or bare item.
|
||||||
|
#[regex("(\"([^\"\\\\]|\\\\.)*\")|([^# \t\n{}\"][^ \"\t\n]*)", priority = 0)]
|
||||||
|
#[display("item")]
|
||||||
|
Item,
|
||||||
|
/// An enclosed or bare statement.
|
||||||
|
#[regex("(\"#([^\"\\\\]|\\\\.)*\")|(#[^ \"\t\n]+)")]
|
||||||
|
#[display("statement")]
|
||||||
|
Statement,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Token;
|
||||||
|
use logos::Logos;
|
||||||
|
|
||||||
|
fn get_token(input: &str) -> Option<Result<Token, <Token as Logos>::Error>> {
|
||||||
|
let mut lex = Token::lexer(input);
|
||||||
|
lex.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_tokens(input: &str) -> Result<Vec<(Token, &str)>, <Token as Logos>::Error> {
|
||||||
|
Token::lexer(input)
|
||||||
|
.spanned()
|
||||||
|
.map(|(res, span)| res.map(|token| (token, &input[span])))
|
||||||
|
// .map(|res| dbg!(res))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn next() {
|
||||||
|
assert_eq!(get_token("test"), Some(Ok(Token::Item)));
|
||||||
|
assert_eq!(get_token("\"test\""), Some(Ok(Token::Item)));
|
||||||
|
assert_eq!(get_token("\"\""), Some(Ok(Token::Item)));
|
||||||
|
assert_eq!(get_token("\"\" "), Some(Ok(Token::Item)));
|
||||||
|
assert_eq!(get_token("#test"), Some(Ok(Token::Statement)));
|
||||||
|
assert_eq!(get_token("\"#test\""), Some(Ok(Token::Statement)));
|
||||||
|
assert_eq!(get_token("{"), Some(Ok(Token::GroupStart)));
|
||||||
|
assert_eq!(get_token("}"), Some(Ok(Token::GroupEnd)));
|
||||||
|
assert_eq!(get_token("//test more"), None);
|
||||||
|
|
||||||
|
assert_eq!(get_token("test"), Some(Ok(Token::Item)));
|
||||||
|
assert_eq!(get_token("#test"), Some(Ok(Token::Statement)));
|
||||||
|
|
||||||
|
assert_eq!(get_token("lol wut"), Some(Ok(Token::Item)));
|
||||||
|
assert_eq!(get_token("#lol wut"), Some(Ok(Token::Statement)));
|
||||||
|
|
||||||
|
assert_eq!(get_token("lol{"), Some(Ok(Token::Item)));
|
||||||
|
assert_eq!(get_token("#lol{"), Some(Ok(Token::Statement)));
|
||||||
|
|
||||||
|
assert_eq!(get_token("lol}"), Some(Ok(Token::Item)));
|
||||||
|
assert_eq!(get_token("#lol}"), Some(Ok(Token::Statement)));
|
||||||
|
|
||||||
|
assert_eq!(get_token("\"test\""), Some(Ok(Token::Item)));
|
||||||
|
assert_eq!(get_token("\"#test\""), Some(Ok(Token::Statement)));
|
||||||
|
|
||||||
|
assert_eq!(get_token("\"te\\\"st\""), Some(Ok(Token::Item)));
|
||||||
|
assert_eq!(get_token("\"te\\st\""), Some(Ok(Token::Item)));
|
||||||
|
assert_eq!(get_token("\"#te\\\"st\""), Some(Ok(Token::Statement)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tokenize() {
|
||||||
|
assert_eq!(
|
||||||
|
get_tokens(
|
||||||
|
r#"foo { // eol comment
|
||||||
|
"asd" "bar"
|
||||||
|
// a comment
|
||||||
|
#include other
|
||||||
|
empty ""
|
||||||
|
}"#
|
||||||
|
),
|
||||||
|
Ok(vec![
|
||||||
|
(Token::Item, "foo"),
|
||||||
|
(Token::GroupStart, "{"),
|
||||||
|
(Token::Item, r#""asd""#),
|
||||||
|
(Token::Item, r#""bar""#),
|
||||||
|
(Token::Statement, r#"#include"#),
|
||||||
|
(Token::Item, r#"other"#),
|
||||||
|
(Token::Item, r#"empty"#),
|
||||||
|
(Token::Item, r#""""#),
|
||||||
|
(Token::GroupEnd, "}")
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
190
src/vdf/reader.rs
Normal file
190
src/vdf/reader.rs
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
use super::{Result, Token};
|
||||||
|
use crate::vdf::error::{NoValidTokenError, UnexpectedTokenError};
|
||||||
|
use logos::{Lexer, Span, SpannedIter};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
/// Kinds of item.
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum Item<'a> {
|
||||||
|
/// A statement, the ones starting with #.
|
||||||
|
Statement { content: Cow<'a, str>, span: Span },
|
||||||
|
|
||||||
|
/// A value.
|
||||||
|
Value { content: Cow<'a, str>, span: Span },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Item<'a> {
|
||||||
|
pub fn span(&self) -> Span {
|
||||||
|
match self {
|
||||||
|
Item::Statement { span, .. } => span.clone(),
|
||||||
|
Item::Value { span, .. } => span.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_content(self) -> Cow<'a, str> {
|
||||||
|
match self {
|
||||||
|
Item::Statement { content, .. } => content,
|
||||||
|
Item::Value { content, .. } => content,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reader event.
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum Event<'a> {
|
||||||
|
/// A group with the given name is starting.
|
||||||
|
GroupStart { name: Cow<'a, str>, span: Span },
|
||||||
|
|
||||||
|
/// A group has ended.
|
||||||
|
GroupEnd { span: Span },
|
||||||
|
|
||||||
|
/// An entry.
|
||||||
|
Entry {
|
||||||
|
key: Item<'a>,
|
||||||
|
value: Item<'a>,
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// EOF has been reached.
|
||||||
|
End { span: Span },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Event<'_> {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn span(&self) -> Span {
|
||||||
|
match self {
|
||||||
|
Event::GroupStart { span, .. } => span.clone(),
|
||||||
|
Event::GroupEnd { span, .. } => span.clone(),
|
||||||
|
Event::Entry { span, .. } => span.clone(),
|
||||||
|
Event::End { span, .. } => span.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A VDF token reader.
|
||||||
|
pub struct Reader<'a> {
|
||||||
|
lexer: SpannedIter<'a, Token>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for Reader<'a> {
|
||||||
|
fn from(content: &'a str) -> Self {
|
||||||
|
Reader {
|
||||||
|
lexer: Lexer::new(content).spanned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Reader<'a> {
|
||||||
|
/// Get the next event, this does copies.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn event(&mut self) -> Result<Event> {
|
||||||
|
let key = match self.lexer.next() {
|
||||||
|
None => {
|
||||||
|
return Ok(Event::End {
|
||||||
|
span: self.lexer.span(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Some((Err(_), span)) => {
|
||||||
|
return Err(NoValidTokenError::new(
|
||||||
|
&[Token::Item, Token::GroupEnd, Token::Statement],
|
||||||
|
span.into(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
Some((Ok(Token::GroupEnd), span)) => return Ok(Event::GroupEnd { span }),
|
||||||
|
Some((Ok(Token::GroupStart), span)) => {
|
||||||
|
return Err(UnexpectedTokenError::new(
|
||||||
|
&[Token::Item, Token::GroupEnd, Token::Statement],
|
||||||
|
Some(Token::GroupStart),
|
||||||
|
span.into(),
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((Ok(Token::Item), span)) => Item::Value {
|
||||||
|
content: string(self.lexer.slice()),
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
|
||||||
|
Some((Ok(Token::Statement), span)) => Item::Statement {
|
||||||
|
content: string(self.lexer.slice()),
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = match self.lexer.next() {
|
||||||
|
None => {
|
||||||
|
return Err(UnexpectedTokenError::new(
|
||||||
|
&[Token::Item, Token::GroupEnd, Token::Statement],
|
||||||
|
None,
|
||||||
|
self.lexer.span().into(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((Err(_), span)) => {
|
||||||
|
return Err(NoValidTokenError::new(
|
||||||
|
&[Token::Item, Token::GroupEnd, Token::Statement],
|
||||||
|
span.into(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((Ok(Token::GroupEnd), span)) => {
|
||||||
|
return Err(UnexpectedTokenError::new(
|
||||||
|
&[Token::Item, Token::GroupStart, Token::Statement],
|
||||||
|
Some(Token::GroupEnd),
|
||||||
|
span.into(),
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((Ok(Token::GroupStart), span)) => {
|
||||||
|
return Ok(Event::GroupStart {
|
||||||
|
name: key.into_content(),
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((Ok(Token::Item), span)) => Item::Value {
|
||||||
|
content: string(self.lexer.slice()),
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
|
||||||
|
Some((Ok(Token::Statement), span)) => Item::Statement {
|
||||||
|
content: string(self.lexer.slice()),
|
||||||
|
span,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let span = key.span().start..value.span().end;
|
||||||
|
Ok(Event::Entry { key, value, span })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string(source: &str) -> Cow<str> {
|
||||||
|
if source.contains('\\') {
|
||||||
|
let mut buffer = source.bytes();
|
||||||
|
let mut string = Vec::with_capacity(buffer.len());
|
||||||
|
|
||||||
|
while let Some(byte) = buffer.next() {
|
||||||
|
if byte == b'\\' {
|
||||||
|
match buffer.next() {
|
||||||
|
Some(b'\\') => string.push(b'\\'),
|
||||||
|
Some(b'n') => string.push(b'\n'),
|
||||||
|
Some(b't') => string.push(b'\t'),
|
||||||
|
Some(b'r') => string.push(b'\r'),
|
||||||
|
Some(b'"') => string.push(b'"'),
|
||||||
|
Some(byte) => string.extend_from_slice(&[b'\\', byte]),
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
string.push(byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String::from_utf8(string).unwrap().into()
|
||||||
|
} else {
|
||||||
|
source.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue