From 5eb2f2b9687146363974ea645de22a8441e890a1 Mon Sep 17 00:00:00 2001 From: Dominik Nakamura Date: Thu, 31 Aug 2023 21:45:38 +0900 Subject: [PATCH] chore: initial commit --- .editorconfig | 13 + .github/workflows/book.yml | 47 + .gitignore | 2 + .vscode/launch.json | 13 + .vscode/settings.json | 2 + Cargo.lock | 1380 +++++++++++++++++ Cargo.toml | 23 + Justfile | 28 + LICENSE.md | 21 + README.md | 7 + book/.gitignore | 1 + book/Justfile | 67 + book/book.toml | 17 + book/src/SUMMARY.md | 42 + book/src/cli/index.md | 1 + book/src/compiler/index.md | 3 + book/src/general_idea.md | 56 + book/src/generators/index.md | 3 + book/src/guide/creating.md | 3 + book/src/guide/examples.md | 5 + book/src/guide/examples/01.stef | 18 + book/src/guide/generating.md | 3 + book/src/guide/installation.md | 3 + book/src/introduction.md | 39 + book/src/misc/license.md | 1 + book/src/schema/arrays.md | 42 + book/src/schema/arrays/basic.go | 7 + book/src/schema/arrays/basic.kt | 5 + book/src/schema/arrays/basic.py | 6 + book/src/schema/arrays/basic.rs | 3 + book/src/schema/arrays/basic.stef | 3 + book/src/schema/arrays/basic.ts | 7 + book/src/schema/attributes.md | 38 + book/src/schema/constants.md | 50 + book/src/schema/constants/basic.go | 30 + book/src/schema/constants/basic.kt | 24 + book/src/schema/constants/basic.py | 19 + book/src/schema/constants/basic.rs | 19 + book/src/schema/constants/basic.stef | 19 + book/src/schema/constants/basic.ts | 19 + book/src/schema/enums.md | 84 + book/src/schema/enums/advanced.go | 25 + book/src/schema/enums/advanced.kt | 15 + book/src/schema/enums/advanced.py | 19 + book/src/schema/enums/advanced.rs | 5 + book/src/schema/enums/advanced.stef | 7 + book/src/schema/enums/advanced.ts | 17 + book/src/schema/enums/basic.go | 11 + book/src/schema/enums/basic.kt | 7 + book/src/schema/enums/basic.py | 8 + book/src/schema/enums/basic.rs | 6 + book/src/schema/enums/basic.stef | 4 + book/src/schema/enums/basic.ts | 5 + book/src/schema/imports.md | 9 + book/src/schema/imports/basic.stef | 1 + book/src/schema/index.md | 164 ++ book/src/schema/modules.md | 17 + book/src/schema/modules/basic.stef | 3 + book/src/schema/modules/nesting.stef | 7 + book/src/schema/references.md | 9 + book/src/schema/references/basic.stef | 0 book/src/schema/statics.md | 3 + book/src/schema/structs.md | 204 +++ book/src/schema/structs/generics.go | 8 + book/src/schema/structs/generics.kt | 6 + book/src/schema/structs/generics.py | 12 + book/src/schema/structs/generics.rs | 4 + book/src/schema/structs/generics.stef | 4 + book/src/schema/structs/generics.ts | 9 + book/src/schema/structs/named.go | 16 + book/src/schema/structs/named.kt | 7 + book/src/schema/structs/named.py | 8 + book/src/schema/structs/named.rs | 5 + book/src/schema/structs/named.stef | 4 + book/src/schema/structs/named.ts | 10 + book/src/schema/structs/unit.go | 5 + book/src/schema/structs/unit.kt | 3 + book/src/schema/structs/unit.py | 6 + book/src/schema/structs/unit.rs | 1 + book/src/schema/structs/unit.stef | 1 + book/src/schema/structs/unit.ts | 1 + book/src/schema/structs/unnamed.go | 15 + book/src/schema/structs/unnamed.kt | 6 + book/src/schema/structs/unnamed.py | 6 + book/src/schema/structs/unnamed.rs | 1 + book/src/schema/structs/unnamed.stef | 1 + book/src/schema/structs/unnamed.ts | 7 + book/src/schema/tuples.md | 58 + book/src/schema/tuples/basic.go | 22 + book/src/schema/tuples/basic.kt | 20 + book/src/schema/tuples/basic.py | 6 + book/src/schema/tuples/basic.rs | 3 + book/src/schema/tuples/basic.stef | 3 + book/src/schema/tuples/basic.ts | 7 + book/src/schema/type-aliases.md | 50 + book/src/schema/type-aliases/basic.go | 7 + book/src/schema/type-aliases/basic.kt | 5 + book/src/schema/type-aliases/basic.py | 10 + book/src/schema/type-aliases/basic.rs | 3 + book/src/schema/type-aliases/basic.stef | 3 + book/src/schema/type-aliases/basic.ts | 3 + book/src/wire-format/index.md | 54 + cliff.toml | 82 + crates/stef-benches/Cargo.toml | 29 + crates/stef-benches/LICENSE.md | 1 + crates/stef-benches/README.md | 1 + crates/stef-benches/benches/parser.rs | 155 ++ crates/stef-benches/benches/varint.rs | 75 + crates/stef-benches/src/lib.rs | 1 + crates/stef-benches/src/varint.rs | 345 +++++ crates/stef-build/Cargo.toml | 23 + crates/stef-build/src/lib.rs | 486 ++++++ crates/stef-cli/Cargo.toml | 18 + crates/stef-cli/LICENSE.md | 1 + crates/stef-cli/README.md | 1 + crates/stef-cli/src/cli.rs | 42 + crates/stef-cli/src/main.rs | 71 + crates/stef-compiler/Cargo.toml | 14 + crates/stef-compiler/LICENSE.md | 1 + crates/stef-compiler/README.md | 1 + crates/stef-compiler/src/lib.rs | 1 + crates/stef-derive/Cargo.toml | 18 + crates/stef-derive/src/attributes.rs | 195 +++ crates/stef-derive/src/cause.rs | 482 ++++++ crates/stef-derive/src/error.rs | 175 +++ crates/stef-derive/src/lib.rs | 25 + crates/stef-parser/Cargo.toml | 28 + crates/stef-parser/LICENSE.md | 1 + crates/stef-parser/README.md | 1 + crates/stef-parser/src/error.rs | 178 +++ crates/stef-parser/src/ext.rs | 188 +++ crates/stef-parser/src/highlight.rs | 23 + crates/stef-parser/src/lib.rs | 885 +++++++++++ crates/stef-parser/src/location.rs | 26 + crates/stef-parser/src/parser.rs | 197 +++ crates/stef-parser/src/parser/aliases.rs | 69 + crates/stef-parser/src/parser/attributes.rs | 126 ++ crates/stef-parser/src/parser/consts.rs | 137 ++ crates/stef-parser/src/parser/enums.rs | 177 +++ crates/stef-parser/src/parser/fields.rs | 152 ++ crates/stef-parser/src/parser/generics.rs | 74 + crates/stef-parser/src/parser/imports.rs | 106 ++ crates/stef-parser/src/parser/literals.rs | 248 +++ crates/stef-parser/src/parser/modules.rs | 104 ++ crates/stef-parser/src/parser/structs.rs | 119 ++ crates/stef-parser/src/parser/types.rs | 178 +++ .../stef-parser/tests/inputs/alias-basic.stef | 2 + .../tests/inputs/attribute-multi.stef | 2 + .../tests/inputs/attribute-single.stef | 2 + .../tests/inputs/attribute-unit.stef | 2 + .../tests/inputs/attributes-min-ws.stef | 3 + .../stef-parser/tests/inputs/attributes.stef | 6 + .../stef-parser/tests/inputs/const-basic.stef | 6 + .../tests/inputs/const-string.stef | 17 + .../stef-parser/tests/inputs/enum-basic.stef | 11 + .../tests/inputs/enum-generics.stef | 9 + .../tests/inputs/enum-many-ws.stef | 17 + .../stef-parser/tests/inputs/enum-min-ws.stef | 1 + .../tests/inputs/import-basic.stef | 2 + .../tests/inputs/invalid/alias_name.stef | 1 + .../tests/inputs/invalid/const_literal.stef | 1 + .../inputs/invalid/const_literal_bool.stef | 1 + .../inputs/invalid/const_literal_float.stef | 1 + .../tests/inputs/invalid/const_name.stef | 1 + .../tests/inputs/invalid/enum_name.stef | 3 + .../tests/inputs/invalid/field_name.stef | 3 + .../tests/inputs/invalid/mod_name.stef | 1 + .../tests/inputs/invalid/struct_name.stef | 3 + .../tests/inputs/module-basic.stef | 12 + .../tests/inputs/schema-basic.stef | 15 + .../tests/inputs/struct-basic.stef | 6 + .../tests/inputs/struct-generics.stef | 5 + .../tests/inputs/struct-many-ws.stef | 11 + .../tests/inputs/struct-min-ws.stef | 1 + .../stef-parser/tests/inputs/types-basic.stef | 23 + .../tests/inputs/types-generic.stef | 7 + .../tests/inputs/types-nested.stef | 3 + .../stef-parser/tests/inputs/types-ref.stef | 4 + crates/stef-parser/tests/parser.rs | 56 + .../parser__error@alias_name.stef.snap | 30 + .../parser__error@const_literal.stef.snap | 26 + ...parser__error@const_literal_bool.stef.snap | 35 + ...arser__error@const_literal_float.stef.snap | 26 + .../parser__error@const_name.stef.snap | 27 + .../parser__error@enum_name.stef.snap | 29 + .../parser__error@field_name.stef.snap | 44 + .../parser__error@mod_name.stef.snap | 27 + .../parser__error@struct_name.stef.snap | 29 + .../parser__parse@alias-basic.stef.snap | 26 + .../parser__parse@attribute-multi.stef.snap | 48 + .../parser__parse@attribute-single.stef.snap | 33 + .../parser__parse@attribute-unit.stef.snap | 29 + .../parser__parse@attributes-min-ws.stef.snap | 71 + .../parser__parse@attributes.stef.snap | 71 + .../parser__parse@const-basic.stef.snap | 85 + .../parser__parse@const-string.stef.snap | 57 + .../parser__parse@enum-basic.stef.snap | 99 ++ .../parser__parse@enum-generics.stef.snap | 124 ++ .../parser__parse@enum-many-ws.stef.snap | 95 ++ .../parser__parse@enum-min-ws.stef.snap | 123 ++ .../parser__parse@import-basic.stef.snap | 29 + .../parser__parse@module-basic.stef.snap | 85 + .../parser__parse@schema-basic.stef.snap | 135 ++ .../parser__parse@struct-basic.stef.snap | 51 + .../parser__parse@struct-generics.stef.snap | 64 + .../parser__parse@struct-many-ws.stef.snap | 52 + .../parser__parse@struct-min-ws.stef.snap | 65 + .../parser__parse@types-basic.stef.snap | 246 +++ .../parser__parse@types-generic.stef.snap | 90 ++ .../parser__parse@types-nested.stef.snap | 48 + .../parser__parse@types-ref.stef.snap | 62 + .../parser__print@alias-basic.stef.snap | 8 + .../parser__print@attribute-multi.stef.snap | 9 + .../parser__print@attribute-single.stef.snap | 9 + .../parser__print@attribute-unit.stef.snap | 9 + .../parser__print@attributes-min-ws.stef.snap | 9 + .../parser__print@attributes.stef.snap | 9 + .../parser__print@const-basic.stef.snap | 12 + .../parser__print@const-string.stef.snap | 10 + .../parser__print@enum-basic.stef.snap | 18 + .../parser__print@enum-generics.stef.snap | 16 + .../parser__print@enum-many-ws.stef.snap | 16 + .../parser__print@enum-min-ws.stef.snap | 16 + .../parser__print@import-basic.stef.snap | 8 + .../parser__print@module-basic.stef.snap | 19 + .../parser__print@schema-basic.stef.snap | 22 + .../parser__print@struct-basic.stef.snap | 13 + .../parser__print@struct-generics.stef.snap | 12 + .../parser__print@struct-many-ws.stef.snap | 12 + .../parser__print@struct-min-ws.stef.snap | 12 + .../parser__print@types-basic.stef.snap | 30 + .../parser__print@types-generic.stef.snap | 14 + .../parser__print@types-nested.stef.snap | 10 + .../parser__print@types-ref.stef.snap | 11 + crates/stef/Cargo.toml | 15 + crates/stef/src/lib.rs | 403 +++++ crates/stef/src/varint.rs | 92 ++ deny.toml | 16 + rustfmt.toml | 7 + vscode-extension/.gitignore | 3 + vscode-extension/.vscodeignore | 3 + vscode-extension/LICENSE.md | 1 + vscode-extension/language-configuration.json | 30 + vscode-extension/package.json | 51 + vscode-extension/pnpm-lock.yaml | 772 +++++++++ .../syntaxes/stef.tmLanguage.yaml | 183 +++ 246 files changed, 12558 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/workflows/book.yml create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Justfile create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 book/.gitignore create mode 100644 book/Justfile create mode 100644 book/book.toml create mode 100644 book/src/SUMMARY.md create mode 100644 book/src/cli/index.md create mode 100644 book/src/compiler/index.md create mode 100644 book/src/general_idea.md create mode 100644 book/src/generators/index.md create mode 100644 book/src/guide/creating.md create mode 100644 book/src/guide/examples.md create mode 100644 book/src/guide/examples/01.stef create mode 100644 book/src/guide/generating.md create mode 100644 book/src/guide/installation.md create mode 100644 book/src/introduction.md create mode 120000 book/src/misc/license.md create mode 100644 book/src/schema/arrays.md create mode 100644 book/src/schema/arrays/basic.go create mode 100644 book/src/schema/arrays/basic.kt create mode 100644 book/src/schema/arrays/basic.py create mode 100644 book/src/schema/arrays/basic.rs create mode 100644 book/src/schema/arrays/basic.stef create mode 100644 book/src/schema/arrays/basic.ts create mode 100644 book/src/schema/attributes.md create mode 100644 book/src/schema/constants.md create mode 100644 book/src/schema/constants/basic.go create mode 100644 book/src/schema/constants/basic.kt create mode 100644 book/src/schema/constants/basic.py create mode 100644 book/src/schema/constants/basic.rs create mode 100644 book/src/schema/constants/basic.stef create mode 100644 book/src/schema/constants/basic.ts create mode 100644 book/src/schema/enums.md create mode 100644 book/src/schema/enums/advanced.go create mode 100644 book/src/schema/enums/advanced.kt create mode 100644 book/src/schema/enums/advanced.py create mode 100644 book/src/schema/enums/advanced.rs create mode 100644 book/src/schema/enums/advanced.stef create mode 100644 book/src/schema/enums/advanced.ts create mode 100644 book/src/schema/enums/basic.go create mode 100644 book/src/schema/enums/basic.kt create mode 100644 book/src/schema/enums/basic.py create mode 100644 book/src/schema/enums/basic.rs create mode 100644 book/src/schema/enums/basic.stef create mode 100644 book/src/schema/enums/basic.ts create mode 100644 book/src/schema/imports.md create mode 100644 book/src/schema/imports/basic.stef create mode 100644 book/src/schema/index.md create mode 100644 book/src/schema/modules.md create mode 100644 book/src/schema/modules/basic.stef create mode 100644 book/src/schema/modules/nesting.stef create mode 100644 book/src/schema/references.md create mode 100644 book/src/schema/references/basic.stef create mode 100644 book/src/schema/statics.md create mode 100644 book/src/schema/structs.md create mode 100644 book/src/schema/structs/generics.go create mode 100644 book/src/schema/structs/generics.kt create mode 100644 book/src/schema/structs/generics.py create mode 100644 book/src/schema/structs/generics.rs create mode 100644 book/src/schema/structs/generics.stef create mode 100644 book/src/schema/structs/generics.ts create mode 100644 book/src/schema/structs/named.go create mode 100644 book/src/schema/structs/named.kt create mode 100644 book/src/schema/structs/named.py create mode 100644 book/src/schema/structs/named.rs create mode 100644 book/src/schema/structs/named.stef create mode 100644 book/src/schema/structs/named.ts create mode 100644 book/src/schema/structs/unit.go create mode 100644 book/src/schema/structs/unit.kt create mode 100644 book/src/schema/structs/unit.py create mode 100644 book/src/schema/structs/unit.rs create mode 100644 book/src/schema/structs/unit.stef create mode 100644 book/src/schema/structs/unit.ts create mode 100644 book/src/schema/structs/unnamed.go create mode 100644 book/src/schema/structs/unnamed.kt create mode 100644 book/src/schema/structs/unnamed.py create mode 100644 book/src/schema/structs/unnamed.rs create mode 100644 book/src/schema/structs/unnamed.stef create mode 100644 book/src/schema/structs/unnamed.ts create mode 100644 book/src/schema/tuples.md create mode 100644 book/src/schema/tuples/basic.go create mode 100644 book/src/schema/tuples/basic.kt create mode 100644 book/src/schema/tuples/basic.py create mode 100644 book/src/schema/tuples/basic.rs create mode 100644 book/src/schema/tuples/basic.stef create mode 100644 book/src/schema/tuples/basic.ts create mode 100644 book/src/schema/type-aliases.md create mode 100644 book/src/schema/type-aliases/basic.go create mode 100644 book/src/schema/type-aliases/basic.kt create mode 100644 book/src/schema/type-aliases/basic.py create mode 100644 book/src/schema/type-aliases/basic.rs create mode 100644 book/src/schema/type-aliases/basic.stef create mode 100644 book/src/schema/type-aliases/basic.ts create mode 100644 book/src/wire-format/index.md create mode 100644 cliff.toml create mode 100644 crates/stef-benches/Cargo.toml create mode 120000 crates/stef-benches/LICENSE.md create mode 120000 crates/stef-benches/README.md create mode 100644 crates/stef-benches/benches/parser.rs create mode 100644 crates/stef-benches/benches/varint.rs create mode 100644 crates/stef-benches/src/lib.rs create mode 100644 crates/stef-benches/src/varint.rs create mode 100644 crates/stef-build/Cargo.toml create mode 100644 crates/stef-build/src/lib.rs create mode 100644 crates/stef-cli/Cargo.toml create mode 120000 crates/stef-cli/LICENSE.md create mode 120000 crates/stef-cli/README.md create mode 100644 crates/stef-cli/src/cli.rs create mode 100644 crates/stef-cli/src/main.rs create mode 100644 crates/stef-compiler/Cargo.toml create mode 120000 crates/stef-compiler/LICENSE.md create mode 120000 crates/stef-compiler/README.md create mode 100644 crates/stef-compiler/src/lib.rs create mode 100644 crates/stef-derive/Cargo.toml create mode 100644 crates/stef-derive/src/attributes.rs create mode 100644 crates/stef-derive/src/cause.rs create mode 100644 crates/stef-derive/src/error.rs create mode 100644 crates/stef-derive/src/lib.rs create mode 100644 crates/stef-parser/Cargo.toml create mode 120000 crates/stef-parser/LICENSE.md create mode 120000 crates/stef-parser/README.md create mode 100644 crates/stef-parser/src/error.rs create mode 100644 crates/stef-parser/src/ext.rs create mode 100644 crates/stef-parser/src/highlight.rs create mode 100644 crates/stef-parser/src/lib.rs create mode 100644 crates/stef-parser/src/location.rs create mode 100644 crates/stef-parser/src/parser.rs create mode 100644 crates/stef-parser/src/parser/aliases.rs create mode 100644 crates/stef-parser/src/parser/attributes.rs create mode 100644 crates/stef-parser/src/parser/consts.rs create mode 100644 crates/stef-parser/src/parser/enums.rs create mode 100644 crates/stef-parser/src/parser/fields.rs create mode 100644 crates/stef-parser/src/parser/generics.rs create mode 100644 crates/stef-parser/src/parser/imports.rs create mode 100644 crates/stef-parser/src/parser/literals.rs create mode 100644 crates/stef-parser/src/parser/modules.rs create mode 100644 crates/stef-parser/src/parser/structs.rs create mode 100644 crates/stef-parser/src/parser/types.rs create mode 100644 crates/stef-parser/tests/inputs/alias-basic.stef create mode 100644 crates/stef-parser/tests/inputs/attribute-multi.stef create mode 100644 crates/stef-parser/tests/inputs/attribute-single.stef create mode 100644 crates/stef-parser/tests/inputs/attribute-unit.stef create mode 100644 crates/stef-parser/tests/inputs/attributes-min-ws.stef create mode 100644 crates/stef-parser/tests/inputs/attributes.stef create mode 100644 crates/stef-parser/tests/inputs/const-basic.stef create mode 100644 crates/stef-parser/tests/inputs/const-string.stef create mode 100644 crates/stef-parser/tests/inputs/enum-basic.stef create mode 100644 crates/stef-parser/tests/inputs/enum-generics.stef create mode 100644 crates/stef-parser/tests/inputs/enum-many-ws.stef create mode 100644 crates/stef-parser/tests/inputs/enum-min-ws.stef create mode 100644 crates/stef-parser/tests/inputs/import-basic.stef create mode 100644 crates/stef-parser/tests/inputs/invalid/alias_name.stef create mode 100644 crates/stef-parser/tests/inputs/invalid/const_literal.stef create mode 100644 crates/stef-parser/tests/inputs/invalid/const_literal_bool.stef create mode 100644 crates/stef-parser/tests/inputs/invalid/const_literal_float.stef create mode 100644 crates/stef-parser/tests/inputs/invalid/const_name.stef create mode 100644 crates/stef-parser/tests/inputs/invalid/enum_name.stef create mode 100644 crates/stef-parser/tests/inputs/invalid/field_name.stef create mode 100644 crates/stef-parser/tests/inputs/invalid/mod_name.stef create mode 100644 crates/stef-parser/tests/inputs/invalid/struct_name.stef create mode 100644 crates/stef-parser/tests/inputs/module-basic.stef create mode 100644 crates/stef-parser/tests/inputs/schema-basic.stef create mode 100644 crates/stef-parser/tests/inputs/struct-basic.stef create mode 100644 crates/stef-parser/tests/inputs/struct-generics.stef create mode 100644 crates/stef-parser/tests/inputs/struct-many-ws.stef create mode 100644 crates/stef-parser/tests/inputs/struct-min-ws.stef create mode 100644 crates/stef-parser/tests/inputs/types-basic.stef create mode 100644 crates/stef-parser/tests/inputs/types-generic.stef create mode 100644 crates/stef-parser/tests/inputs/types-nested.stef create mode 100644 crates/stef-parser/tests/inputs/types-ref.stef create mode 100644 crates/stef-parser/tests/parser.rs create mode 100644 crates/stef-parser/tests/snapshots/parser__error@alias_name.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__error@const_literal.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__error@const_literal_bool.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__error@const_literal_float.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__error@const_name.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__error@enum_name.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__error@field_name.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__error@mod_name.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__error@struct_name.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@alias-basic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@attribute-multi.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@attribute-single.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@attribute-unit.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@attributes-min-ws.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@attributes.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@const-basic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@const-string.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@enum-basic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@enum-generics.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@enum-many-ws.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@enum-min-ws.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@import-basic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@module-basic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@schema-basic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@struct-basic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@struct-generics.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@struct-many-ws.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@struct-min-ws.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@types-basic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@types-generic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@types-nested.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__parse@types-ref.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@alias-basic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@attribute-multi.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@attribute-single.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@attribute-unit.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@attributes-min-ws.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@attributes.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@const-basic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@const-string.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@enum-basic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@enum-generics.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@enum-many-ws.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@enum-min-ws.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@import-basic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@module-basic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@schema-basic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@struct-basic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@struct-generics.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@struct-many-ws.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@struct-min-ws.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@types-basic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@types-generic.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@types-nested.stef.snap create mode 100644 crates/stef-parser/tests/snapshots/parser__print@types-ref.stef.snap create mode 100644 crates/stef/Cargo.toml create mode 100644 crates/stef/src/lib.rs create mode 100644 crates/stef/src/varint.rs create mode 100644 deny.toml create mode 100644 rustfmt.toml create mode 100644 vscode-extension/.gitignore create mode 100644 vscode-extension/.vscodeignore create mode 120000 vscode-extension/LICENSE.md create mode 100644 vscode-extension/language-configuration.json create mode 100644 vscode-extension/package.json create mode 100644 vscode-extension/pnpm-lock.yaml create mode 100644 vscode-extension/syntaxes/stef.tmLanguage.yaml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8deec94 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +end_of_line = lf +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{json,yaml,yml}] +indent_size = 2 + +[Justfile] +indent_size = 2 diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml new file mode 100644 index 0000000..01a687b --- /dev/null +++ b/.github/workflows/book.yml @@ -0,0 +1,47 @@ +name: Book +on: + push: + branches: [main] + paths: ["book/**"] +permissions: + contents: read + pages: write + id-token: write +concurrency: + group: pages + cancel-in-progress: false +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Install cargo-binstall + uses: taiki-e/install-action@v2 + with: + tool: cargo-binstall + - name: Install tools + run: cargo binstall --no-confirm lychee@0.13.0 mdbook@0.4.32 mdbook-toc@0.14.0 + - name: Setup pages + id: pages + uses: actions/configure-pages@v3 + - name: Build Book + run: mdbook build book + - name: Check links + run: lychee 'book/src/**/*.md' 'book/book/**/*.html' + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + path: book/book + deploy: + name: Deploy + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..35a423d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +.lycheecache diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8618963 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Extension", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}/vscode-extension" + ] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2c63c08 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..100cfba --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1380 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstream" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "bstr" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size 0.2.6", +] + +[[package]] +name = "clap_derive" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "color-eyre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +dependencies = [ + "backtrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "errno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "globset" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indoc" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c785eefb63ebd0e33416dfcb8d6da0bf27ce752843a45632a67bf10d4d4b5c4" + +[[package]] +name = "insta" +version = "1.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0770b0a3d4c70567f0d58331f3088b0e4c4f56c9b8d764efe654b4a5d46de3a" +dependencies = [ + "console", + "globset", + "lazy_static", + "linked-hash-map", + "similar", + "walkdir", + "yaml-rust", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.2", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi 0.3.2", + "rustix 0.38.10", + "windows-sys 0.48.0", +] + +[[package]] +name = "is_ci" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libmimalloc-sys" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25d058a81af0d1c22d7a1c948576bee6d673f7af3c0f35564abd6c81122f513d" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miette" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" +dependencies = [ + "is-terminal", + "miette-derive", + "once_cell", + "owo-colors", + "supports-color 2.0.0", + "supports-hyperlinks", + "supports-unicode", + "terminal_size 0.1.17", + "textwrap", + "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 = "mimalloc" +version = "0.1.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "972e5f23f6716f62665760b0f4cbf592576a80c7b879ba9beaafc0e558894127" +dependencies = [ + "libmimalloc-sys", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.2", + "libc", +] + +[[package]] +name = "object" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +dependencies = [ + "supports-color 1.3.1", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "prettyplease" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +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 = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "regex" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys 0.4.5", + "windows-sys 0.48.0", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "similar" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" + +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + +[[package]] +name = "stef" +version = "0.1.0" +dependencies = [ + "bytes", + "paste", + "thiserror", +] + +[[package]] +name = "stef-benches" +version = "0.1.0" +dependencies = [ + "criterion", + "indoc", + "mimalloc", + "serde", + "stef-parser", +] + +[[package]] +name = "stef-build" +version = "0.1.0" +dependencies = [ + "indoc", + "miette", + "pretty_assertions", + "prettyplease", + "proc-macro2", + "quote", + "stef-parser", + "syn", + "thiserror", +] + +[[package]] +name = "stef-cli" +version = "0.1.0" +dependencies = [ + "clap", + "color-eyre", + "miette", + "mimalloc", + "stef-parser", +] + +[[package]] +name = "stef-compiler" +version = "0.1.0" +dependencies = [ + "stef-parser", +] + +[[package]] +name = "stef-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "stef-parser" +version = "0.1.0" +dependencies = [ + "indoc", + "insta", + "miette", + "owo-colors", + "stef-derive", + "winnow", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "supports-color" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ba6faf2ca7ee42fdd458f4347ae0a9bd6bcc445ad7cb57ad82b383f18870d6f" +dependencies = [ + "atty", + "is_ci", +] + +[[package]] +name = "supports-color" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354" +dependencies = [ + "is-terminal", + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84231692eb0d4d41e4cdd0cabfdd2e6cd9e255e65f80c9aa7c98dd502b4233d" +dependencies = [ + "is-terminal", +] + +[[package]] +name = "supports-unicode" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6c2cb240ab5dd21ed4906895ee23fe5a48acdbd15a3ce388e7b62a9b66baf7" +dependencies = [ + "is-terminal", +] + +[[package]] +name = "syn" +version = "2.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "terminal_size" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" +dependencies = [ + "rustix 0.37.23", + "windows-sys 0.48.0", +] + +[[package]] +name = "textwrap" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..48eae2f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,23 @@ +[workspace] +members = ["crates/*"] +resolver = "2" + +[workspace.package] +version = "0.1.0" +authors = ["Dominik Nakamura ). diff --git a/book/.gitignore b/book/.gitignore new file mode 100644 index 0000000..5a0bf03 --- /dev/null +++ b/book/.gitignore @@ -0,0 +1 @@ +/book diff --git a/book/Justfile b/book/Justfile new file mode 100644 index 0000000..cc34f1d --- /dev/null +++ b/book/Justfile @@ -0,0 +1,67 @@ +check: + #!/usr/bin/env fish + + echo src/**/*.stef | xargs -n 1 -P 8 -- just check-stef + echo src/**/*.rs | xargs -n 1 -P 8 -- just check-rust + echo src/**/*.go | xargs -n 1 -P 8 -- just check-go + echo src/**/*.kt | xargs -n 1 -P 8 -- just check-kotlin + echo src/**/*.ts | xargs -n 1 -P 8 -- just check-typescript + echo src/**/*.py | xargs -n 1 -P 8 -- just check-python + +@check-stef file: + echo checking {{file}} + cargo run --quiet --package stef-cli -- check {{file}} + +@check-rust file: + echo checking {{file}} + rustc --crate-type lib --allow dead_code --out-dir $(mktemp -d) {{file}} + +@check-go file: + echo checking {{file}} + go build -o $(mktemp) {{file}} + +@check-kotlin file: + echo checking {{file}} + kotlinc -d $(mktemp -d) {{file}} + +@check-typescript file: + echo checking {{file}} + tsc --strict --target es2020 --outDir $(mktemp -d) {{file}} + +@check-python file: + echo checking {{file}} + mypy --strict --no-error-summary --cache-dir /tmp {{file}} + +format: + #!/usr/bin/env fish + + just format-stef src/**/*.stef + just format-rust src/**/*.rs + just format-go src/**/*.go + just format-kotlin src/**/*.kt + just format-typescript src/**/*.ts + just format-python src/**/*.py + +@format-stef +files: + cargo run --quiet --package stef-cli -- format {{files}} + +@format-rust +files: + rustup run nightly rustfmt --edition 2021 {{files}} + +@format-go +files: + gofmt -w {{files}} + +@format-kotlin +files: + ktlint -F --disabled_rules=filename {{files}} + +@format-typescript +files: + prettier --log-level warn -w {{files}} + +@format-python +files: + black -q {{files}} + +dev: + mdbook serve + +build: + mdbook build diff --git a/book/book.toml b/book/book.toml new file mode 100644 index 0000000..d8c4f0e --- /dev/null +++ b/book/book.toml @@ -0,0 +1,17 @@ +[book] +title = "STEF" +authors = ["Dominik Nakamura"] +description = "Strongly Typed Encoding Format" +language = "en" +multilingual = false +src = "src" + +[preprocessor.toc] +command = "mdbook-toc" +renderer = ["html"] + +[output.html] +git-repository-url = "https://github.com/dnaka91/stef" + +[output.html.playground] +runnable = false diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md new file mode 100644 index 0000000..7693ad5 --- /dev/null +++ b/book/src/SUMMARY.md @@ -0,0 +1,42 @@ +# Summary + + + + +[Introduction](introduction.md) +[General idea](general_idea.md) + +--- + +# User Guide + +- [Installation]() +- [Creating schemas]() +- [Generating code]() +- [Examples](guide/examples.md) + +# Reference Guide + +- [Command Line Interface]() + - [init]() + - [check]() + - [format]() +- [Schema](schema/index.md) + - [Structs](schema/structs.md) + - [Enums](schema/enums.md) + - [Arrays](schema/arrays.md) + - [Tuples](schema/tuples.md) + - [Constants](schema/constants.md) + - [Statics]() + - [Type aliases](schema/type-aliases.md) + - [Modules](schema/modules.md) + - [Imports]() + - [References]() + - [Attributes](schema/attributes.md) +- [Wire format](wire-format/index.md) +- [Compiler]() +- [Generators]() + +--- + +[License](misc/license.md) diff --git a/book/src/cli/index.md b/book/src/cli/index.md new file mode 100644 index 0000000..93c4278 --- /dev/null +++ b/book/src/cli/index.md @@ -0,0 +1 @@ +# Command Line Interface diff --git a/book/src/compiler/index.md b/book/src/compiler/index.md new file mode 100644 index 0000000..88af276 --- /dev/null +++ b/book/src/compiler/index.md @@ -0,0 +1,3 @@ +# Compiler + + diff --git a/book/src/general_idea.md b/book/src/general_idea.md new file mode 100644 index 0000000..974c27e --- /dev/null +++ b/book/src/general_idea.md @@ -0,0 +1,56 @@ +# General idea + +A data format and schema, very similar to Google's Protobuf, but with the full fledged type system of Rust. + +- It should look and feel very close to Rust. Not just the type system, but the way the schema language looks like should resemble it very closely. +- Wire format very close to what `postcard` does, of course with adjustments here and there to include the concepts of Protobuf (like field markers). +- Each language generator should be natively integrated into the language's ecosystem. For example, +for Rust it would be a crate that can generate the code in a `build.rs` file, or for Kotlin it would +be a Gradle plugin. + +## Additions and improvements over Protobuf + +Things that should definitely be added or improved over Protobuf. Also, keep _cap'n proto_ in mind, it has several similar additions like constants and generics. + +- Generics +- Tuples +- Rust enums +- Type aliases +- Full range of integer types and basic Rust types +- Support for borrowed data like `&str` and `&[u8]` that reference into the parsed buffer. +- Support for streamed parsing +- Immutable-ish types like `Box`. +- Shared types like `Rc` / `Arc`, even on the wire level. They'd be put into a global ID to value map and be referenced in the fields. (maybe for later, seems hard to get right?) +- Fields are required by default, marked optional with the `Option` type. +- Rust style attributes for further customization. For example, `#[fixed]` to force an integer field not to be compressed (instead of extra types like `fixed32` in Protobuf). +- Avoid type repetition like all variants of an enum being prefixed with the enum name. Instead, add the prefix in the generator for languages that need it (like C). +- Nesting, exactly like in Rust with modules, but inside of other elements like structs respectively. +- Allow for modules inside a file to further scope and group elements together. +- Constants (maybe?) +- Statics (maybe?) + +## Lending from Cap'n Proto + +These features are concepts from cap'n proto that are missing from Protobuf, and might be interesting to adopt into the new schema language (and aren't listed in the previous section yet). + +- Default values: maybe +- Unions: **NO** (Rust enums solve this) +- Groups: **NO** (Rust enums solve this) +- Interfaces: _probably not_ + - Don't really see the need for this. Not interested in defining interfaces, this is supposed to be a data encoding format after all. Exchanging data, not functionality or contracts. +- Unique IDs: **NO** (never really got the point of those) + +## What about gRPC + +Would recommend people to simply build their own system on top of QUIC, as gRPC is all HTTP/2 under the hood and not as lightweight as it promises to be. + +Eventually as a future plan, could introduce Rust traits that can define services in some form. This might reduce flexibility, but allow to have some convenient thin wrapper over QUIC that reduces boilerplate and can be a solution for simple or common setups. + +## Initial language support + +- Rust (obviously) +- Go +- Kotlin +- Java (automatically through Kotlin, maybe separate generator later) +- TypeScript +- Python (?) diff --git a/book/src/generators/index.md b/book/src/generators/index.md new file mode 100644 index 0000000..a243598 --- /dev/null +++ b/book/src/generators/index.md @@ -0,0 +1,3 @@ +# Generators + + diff --git a/book/src/guide/creating.md b/book/src/guide/creating.md new file mode 100644 index 0000000..9d996b2 --- /dev/null +++ b/book/src/guide/creating.md @@ -0,0 +1,3 @@ +# Creating schemas + + diff --git a/book/src/guide/examples.md b/book/src/guide/examples.md new file mode 100644 index 0000000..4dd07d1 --- /dev/null +++ b/book/src/guide/examples.md @@ -0,0 +1,5 @@ +# Examples + +```rust,ignore +{{#include examples/01.stef}} +``` diff --git a/book/src/guide/examples/01.stef b/book/src/guide/examples/01.stef new file mode 100644 index 0000000..c72a71f --- /dev/null +++ b/book/src/guide/examples/01.stef @@ -0,0 +1,18 @@ +use other::schema::DateTime; + +struct User { + first_name: string @1, + last_name: string @2, + birthday: DateTime @3, + gender: option @4, + /// Latitude and longitude. + current_location: (f64, f64) @5, +} + +mod user { + enum Gender { + Male @1, + Female @2, + NonBinary(string @1) @3, + } +} diff --git a/book/src/guide/generating.md b/book/src/guide/generating.md new file mode 100644 index 0000000..84ba8a2 --- /dev/null +++ b/book/src/guide/generating.md @@ -0,0 +1,3 @@ +# Generating code + + diff --git a/book/src/guide/installation.md b/book/src/guide/installation.md new file mode 100644 index 0000000..0071ad7 --- /dev/null +++ b/book/src/guide/installation.md @@ -0,0 +1,3 @@ +# Installation + + diff --git a/book/src/introduction.md b/book/src/introduction.md new file mode 100644 index 0000000..11eda9e --- /dev/null +++ b/book/src/introduction.md @@ -0,0 +1,39 @@ +# Strongly Typed Encoding Format + +Data format and schema, with a type system as strong as Rust's. + +
+ + + + + + + + + + + + + + + +
+ +As the name suggests, STEF is a data encoding format, that borrows a lot from existing formats like [Protobuf](https://protobuf.dev), [Cap'n Proto](https://capnproto.org) and [Flatbuffers](https://flatbuffers.org), but in contrast vastly extends the type system to make it as strong as Rust's. + +## Why a stronger type system? + +Firs and foremost, I personally really enjoy the Rust programming language and its strict but flexible type system. + +In the many years that I have used Protobufs, they always have disappointed with the few supported types. Most of the time that resulted in an additional round of validation after decoding the format, to ensure all data is in a valid form. + +Many of these validations could be avoided by a stronger type system, which would rule out many wrong states by refusing them upfront. + +By extending the type system, data structures can be defined in a way, that ensures the data is already in a valid state after decoding. + +## Why not use an existing data format? + +### Protobuf + +### Cap'n Proto diff --git a/book/src/misc/license.md b/book/src/misc/license.md new file mode 120000 index 0000000..368ec7e --- /dev/null +++ b/book/src/misc/license.md @@ -0,0 +1 @@ +../../../LICENSE.md \ No newline at end of file diff --git a/book/src/schema/arrays.md b/book/src/schema/arrays.md new file mode 100644 index 0000000..d3ba758 --- /dev/null +++ b/book/src/schema/arrays.md @@ -0,0 +1,42 @@ +# Arrays + + + + +## Schema + +```rust,ignore +{{#include arrays/basic.stef}} +``` + +## Languages + +### Rust + +```rust +{{#include arrays/basic.rs}} +``` + +### Go + +```go +{{#include arrays/basic.go:5:}} +``` + +### Kotlin + +```kotlin +{{#include arrays/basic.kt:3:}} +``` + +### TypeScript + +```typescript +{{#include arrays/basic.ts}} +``` + +### Python + +```python +{{#include arrays/basic.py}} +``` diff --git a/book/src/schema/arrays/basic.go b/book/src/schema/arrays/basic.go new file mode 100644 index 0000000..59b6fe3 --- /dev/null +++ b/book/src/schema/arrays/basic.go @@ -0,0 +1,7 @@ +package main + +func main() {} + +type Sample struct { + Options [5]bool +} diff --git a/book/src/schema/arrays/basic.kt b/book/src/schema/arrays/basic.kt new file mode 100644 index 0000000..02e6d32 --- /dev/null +++ b/book/src/schema/arrays/basic.kt @@ -0,0 +1,5 @@ +fun main() {} + +data class Sample( + val options: Array, +) diff --git a/book/src/schema/arrays/basic.py b/book/src/schema/arrays/basic.py new file mode 100644 index 0000000..9191d56 --- /dev/null +++ b/book/src/schema/arrays/basic.py @@ -0,0 +1,6 @@ +from dataclasses import dataclass + + +@dataclass +class Sample: + options: list[bool] diff --git a/book/src/schema/arrays/basic.rs b/book/src/schema/arrays/basic.rs new file mode 100644 index 0000000..debbcde --- /dev/null +++ b/book/src/schema/arrays/basic.rs @@ -0,0 +1,3 @@ +struct Sample { + options: [bool; 5], +} diff --git a/book/src/schema/arrays/basic.stef b/book/src/schema/arrays/basic.stef new file mode 100644 index 0000000..7c3b7a0 --- /dev/null +++ b/book/src/schema/arrays/basic.stef @@ -0,0 +1,3 @@ +struct Sample { + options: [bool; 5] @1, +} diff --git a/book/src/schema/arrays/basic.ts b/book/src/schema/arrays/basic.ts new file mode 100644 index 0000000..9f4cc27 --- /dev/null +++ b/book/src/schema/arrays/basic.ts @@ -0,0 +1,7 @@ +class Sample { + options: boolean[]; + + constructor(options: boolean[]) { + this.options = options; + } +} diff --git a/book/src/schema/attributes.md b/book/src/schema/attributes.md new file mode 100644 index 0000000..94b0c8f --- /dev/null +++ b/book/src/schema/attributes.md @@ -0,0 +1,38 @@ +# Attributes + +The attributes allow to further apply configuration to elements, while not affecting the schema itself. These rather provide either extra information or customize how language specific code is generated. + +For example, they allow to mark elements as _deprecated_ or define how a certain value is represented in code, off the default variant. + + + +## Schema + +Attributes can come in 3 forms. + +- Unit, just having the name of the attribute itself. +- Singe-value, with a literal assignment (can be optional). +- Multi-value, as an object that can have multiple sub-attributes. + +### Unit + +```rust,ignore +#[deprecated] +struct Sample {} +``` + +### Single-value + +```rust,ignore +#[deprecated = "Don't use anymore"] +struct Sample {} +``` + +### Multi-value + +```rust,ignore +struct Sample { + #[validate(min = 1, max = 100)] + age: u8 @1, +} +``` diff --git a/book/src/schema/constants.md b/book/src/schema/constants.md new file mode 100644 index 0000000..2c7c67d --- /dev/null +++ b/book/src/schema/constants.md @@ -0,0 +1,50 @@ +# Constants + + + + +| Language | Example | +| ------------- | ------------------------------- | +| Schema / Rust | `const NAME: T = ;` | +| Go | `const Name T = ` | +| Kotlin | `const val NAME: T = ` | +| TypeScript | `const name: T = ;` | +| Python | `NAME: T = ` | + +## Schema + +```rust,ignore +{{#include constants/basic.stef}} +``` + +## Languages + +### Rust + +```rust +{{#include constants/basic.rs}} +``` + +### Go + +```go +{{#include constants/basic.go:7:}} +``` + +### Kotlin + +```kotlin +{{#include constants/basic.kt:3:}} +``` + +### TypeScript + +```typescript +{{#include constants/basic.ts}} +``` + +### Python + +```python +{{#include constants/basic.py}} +``` diff --git a/book/src/schema/constants/basic.go b/book/src/schema/constants/basic.go new file mode 100644 index 0000000..9bf4e3e --- /dev/null +++ b/book/src/schema/constants/basic.go @@ -0,0 +1,30 @@ +package main + +import "math/big" + +func main() {} + +const ( + ValueBool bool = true + + ValueU8 uint8 = 1 + ValueU16 uint16 = 2 + ValueU32 uint32 = 3 + ValueU64 uint64 = 4 + + ValueI8 int8 = -1 + ValueI16 int16 = -2 + ValueI32 int32 = -3 + ValueI64 int64 = -4 + + ValueF32 float32 = 1.0 + ValueF64 float64 = 2.0 + + ValueStr string = "abc" +) + +var ( + ValueU128 *big.Int = big.NewInt(5) + ValueI128 *big.Int = big.NewInt(-5) + ValueBytes []byte = []byte{1, 2, 3} +) diff --git a/book/src/schema/constants/basic.kt b/book/src/schema/constants/basic.kt new file mode 100644 index 0000000..474f4cc --- /dev/null +++ b/book/src/schema/constants/basic.kt @@ -0,0 +1,24 @@ +import java.math.BigInteger + +fun main() {} + +const val VALUE_BOOL: Boolean = true + +const val VALUE_U8: UByte = 1u +const val VALUE_U16: UShort = 2u +const val VALUE_U32: UInt = 3u +const val VALUE_U64: ULong = 4u + +const val VALUE_I8: Byte = -1 +const val VALUE_I16: Short = -2 +const val VALUE_I32: Int = -3 +const val VALUE_I64: Long = -4 + +const val VALUE_F32: Float = 1.0f +const val VALUE_F64: Double = 2.0 + +const val VALUE_STR: String = "abc" + +val VALUE_U128: BigInteger = BigInteger.valueOf(5) +val VALUE_I128: BigInteger = BigInteger.valueOf(-5) +val VALUE_BYTES: ByteArray = byteArrayOf(1, 2, 3) diff --git a/book/src/schema/constants/basic.py b/book/src/schema/constants/basic.py new file mode 100644 index 0000000..5f15ce2 --- /dev/null +++ b/book/src/schema/constants/basic.py @@ -0,0 +1,19 @@ +VALUE_BOOL: bool = True + +VALUE_U8: int = 1 +VALUE_U16: int = 2 +VALUE_U32: int = 3 +VALUE_U64: int = 4 +VALUE_U128: int = 5 + +VALUE_I8: int = -1 +VALUE_I16: int = -2 +VALUE_I32: int = -3 +VALUE_I64: int = -4 +VALUE_I128: int = -5 + +VALUE_F32: float = 1.0 +VALUE_F64: float = 2.0 + +VALUE_STR: str = "abc" +VALUE_BYTES: bytes = bytes([1, 2, 3]) diff --git a/book/src/schema/constants/basic.rs b/book/src/schema/constants/basic.rs new file mode 100644 index 0000000..917a894 --- /dev/null +++ b/book/src/schema/constants/basic.rs @@ -0,0 +1,19 @@ +const VALUE_BOOL: bool = true; + +const VALUE_U8: u8 = 1; +const VALUE_U16: u16 = 2; +const VALUE_U32: u32 = 3; +const VALUE_U64: u64 = 4; +const VALUE_U128: u128 = 5; + +const VALUE_I8: i8 = -1; +const VALUE_I16: i16 = -2; +const VALUE_I32: i32 = -3; +const VALUE_I64: i64 = -4; +const VALUE_I128: i128 = -5; + +const VALUE_F32: f32 = 1.0; +const VALUE_F64: f64 = 2.0; + +const VALUE_STR: &str = "abc"; +const VALUE_BYTES: &[u8] = &[1, 2, 3]; diff --git a/book/src/schema/constants/basic.stef b/book/src/schema/constants/basic.stef new file mode 100644 index 0000000..b099acd --- /dev/null +++ b/book/src/schema/constants/basic.stef @@ -0,0 +1,19 @@ +const VALUE_BOOL: bool = true; + +const VALUE_U8: u8 = 1; +const VALUE_U16: u16 = 2; +const VALUE_U32: u32 = 3; +const VALUE_U64: u64 = 4; +const VALUE_U128: u128 = 5; + +const VALUE_I8: i8 = -1; +const VALUE_I16: i16 = -2; +const VALUE_I32: i32 = -3; +const VALUE_I64: i64 = -4; +const VALUE_I128: i128 = -5; + +const VALUE_F32: f32 = 1.0; +const VALUE_F64: f64 = 2.0; + +const VALUE_STR: &string = "abc"; +const VALUE_BYTES: &bytes = [1, 2, 3]; diff --git a/book/src/schema/constants/basic.ts b/book/src/schema/constants/basic.ts new file mode 100644 index 0000000..84a4c72 --- /dev/null +++ b/book/src/schema/constants/basic.ts @@ -0,0 +1,19 @@ +const VALUE_BOOL: boolean = true; + +const VALUE_U8: number = 1; +const VALUE_U16: number = 2; +const VALUE_U32: number = 3; +const VALUE_U64: bigint = 4n; +const VALUE_U128: bigint = 5n; + +const VALUE_I8: number = -1; +const VALUE_I16: number = -2; +const VALUE_I32: number = -3; +const VALUE_I64: bigint = -4n; +const VALUE_I128: bigint = -5n; + +const VALUE_F32: number = 1.0; +const VALUE_F64: number = 2.0; + +const VALUE_STR: string = "abc"; +const VALUE_BYTES: number[] = [1, 2, 3]; diff --git a/book/src/schema/enums.md b/book/src/schema/enums.md new file mode 100644 index 0000000..fb35d7d --- /dev/null +++ b/book/src/schema/enums.md @@ -0,0 +1,84 @@ +# Enums + + + + +## Basic + +### Schema {#basic-schema} + +```rust,ignore +{{#include enums/basic.stef}} +``` + +### Languages {#basic-lang} + +#### Rust {#basic-lang-rs} + +```rust +{{#include enums/basic.rs}} +``` + +#### Go {#basic-lang-go} + +```go +{{#include enums/basic.go:5:}} +``` + +#### Kotlin {#basic-lang-kt} + +```kotlin +{{#include enums/basic.kt:3:}} +``` + +#### TypeScript {#basic-lang-ts} + +```typescript +{{#include enums/basic.ts}} +``` + +#### Python {#basic-lang-py} + +```python +{{#include enums/basic.py:3:}} +``` + +## Advanced + +### Schema {#advanced-schema} + +```rust,ignore +{{#include enums/advanced.stef}} +``` + +### Languages {#advanced-lang} + +#### Rust {#advanced-lang-rs} + +```rust +{{#include enums/advanced.rs}} +``` + +#### Go {#advanced-lang-go} + +```go +{{#include enums/advanced.go:5:}} +``` + +#### Kotlin {#advanced-lang-kt} + +```kotlin +{{#include enums/advanced.kt:3:}} +``` + +#### TypeScript {#advanced-lang-ts} + +```typescript +{{#include enums/advanced.ts}} +``` + +#### Python {#advanced-lang-py} + +```python +{{#include enums/advanced.py:3:}} +``` diff --git a/book/src/schema/enums/advanced.go b/book/src/schema/enums/advanced.go new file mode 100644 index 0000000..6d8422b --- /dev/null +++ b/book/src/schema/enums/advanced.go @@ -0,0 +1,25 @@ +package main + +func main() {} + +type SampleVariant interface { + sealed() +} + +type Sample SampleVariant + +type SampleVariant1 struct { + F1 uint32 + F2 uint16 +} + +func (v SampleVariant1) sealed() {} + +type SampleVariant2 struct { + Field1 int64 + Field2 bool +} + +func (v SampleVariant2) sealed() {} + +// N variants... diff --git a/book/src/schema/enums/advanced.kt b/book/src/schema/enums/advanced.kt new file mode 100644 index 0000000..748aa0d --- /dev/null +++ b/book/src/schema/enums/advanced.kt @@ -0,0 +1,15 @@ +fun main() {} + +sealed class Sample { + class Variant1( + val f1: UInt, + val f2: UShort, + ) : Sample() + + class Variant2( + val field1: Long, + val field2: Boolean, + ) : Sample() + + // N variants... +} diff --git a/book/src/schema/enums/advanced.py b/book/src/schema/enums/advanced.py new file mode 100644 index 0000000..fd3d6cf --- /dev/null +++ b/book/src/schema/enums/advanced.py @@ -0,0 +1,19 @@ +from dataclasses import dataclass +from typing import Literal + + +@dataclass +class SampleVariant1: + tag: Literal["Variant1"] + f1: int + f2: int + + +@dataclass +class SampleVariant2: + tag: Literal["Variant2"] + field1: int + field2: bool + + +Sample = SampleVariant1 | SampleVariant2 diff --git a/book/src/schema/enums/advanced.rs b/book/src/schema/enums/advanced.rs new file mode 100644 index 0000000..db6f699 --- /dev/null +++ b/book/src/schema/enums/advanced.rs @@ -0,0 +1,5 @@ +enum Sample { + Variant1(u32, u16), + Variant2 { field1: i64, field2: bool }, + // N variants... +} diff --git a/book/src/schema/enums/advanced.stef b/book/src/schema/enums/advanced.stef new file mode 100644 index 0000000..e772389 --- /dev/null +++ b/book/src/schema/enums/advanced.stef @@ -0,0 +1,7 @@ +enum Sample { + Variant1(u32 @1, u16 @2) @1, + Variant2 { + field1: i64 @1, + field2: bool @2, + } @2, +} diff --git a/book/src/schema/enums/advanced.ts b/book/src/schema/enums/advanced.ts new file mode 100644 index 0000000..87c64e8 --- /dev/null +++ b/book/src/schema/enums/advanced.ts @@ -0,0 +1,17 @@ +enum SampleKind { + Variant1, + Variant2, + // N variants... +} + +interface SampleVariant1 { + kind: SampleKind.Variant1; + f1: number; + f2: number; +} + +interface SampleVariant2 { + kind: SampleKind.Variant2; + field1: number; + field2: boolean; +} diff --git a/book/src/schema/enums/basic.go b/book/src/schema/enums/basic.go new file mode 100644 index 0000000..35a4c01 --- /dev/null +++ b/book/src/schema/enums/basic.go @@ -0,0 +1,11 @@ +package main + +func main() {} + +type Sample uint32 + +const ( + SampleVariant1 Sample = 1 + SampleVariant2 Sample = 2 + // N variants... +) diff --git a/book/src/schema/enums/basic.kt b/book/src/schema/enums/basic.kt new file mode 100644 index 0000000..a8da07c --- /dev/null +++ b/book/src/schema/enums/basic.kt @@ -0,0 +1,7 @@ +fun main() {} + +enum class Sample(val value: Int) { + Variant1(1), + Variant2(2), + // N variants... +} diff --git a/book/src/schema/enums/basic.py b/book/src/schema/enums/basic.py new file mode 100644 index 0000000..946ca58 --- /dev/null +++ b/book/src/schema/enums/basic.py @@ -0,0 +1,8 @@ +from dataclasses import dataclass +from enum import Enum + + +@dataclass +class Sample(Enum): + VARIANT1 = 1 + VARIANT2 = 2 diff --git a/book/src/schema/enums/basic.rs b/book/src/schema/enums/basic.rs new file mode 100644 index 0000000..edc719a --- /dev/null +++ b/book/src/schema/enums/basic.rs @@ -0,0 +1,6 @@ +#[repr(u32)] +enum Sample { + Variant1 = 1, + Variant2 = 2, + // N variants... +} diff --git a/book/src/schema/enums/basic.stef b/book/src/schema/enums/basic.stef new file mode 100644 index 0000000..84f6831 --- /dev/null +++ b/book/src/schema/enums/basic.stef @@ -0,0 +1,4 @@ +enum Sample { + Variant1 @1, + Variant2 @2, +} diff --git a/book/src/schema/enums/basic.ts b/book/src/schema/enums/basic.ts new file mode 100644 index 0000000..a49bca5 --- /dev/null +++ b/book/src/schema/enums/basic.ts @@ -0,0 +1,5 @@ +enum Sample { + Variant1 = 1, + Variant2 = 2, + // N variants... +} diff --git a/book/src/schema/imports.md b/book/src/schema/imports.md new file mode 100644 index 0000000..949604d --- /dev/null +++ b/book/src/schema/imports.md @@ -0,0 +1,9 @@ +# Imports + + + +## Schema + +```rust,ignore +{{#include imports/basic.stef}} +``` diff --git a/book/src/schema/imports/basic.stef b/book/src/schema/imports/basic.stef new file mode 100644 index 0000000..da8b7a2 --- /dev/null +++ b/book/src/schema/imports/basic.stef @@ -0,0 +1 @@ +use other::schema::Sample; diff --git a/book/src/schema/index.md b/book/src/schema/index.md new file mode 100644 index 0000000..57c8fb8 --- /dev/null +++ b/book/src/schema/index.md @@ -0,0 +1,164 @@ +# Schema + +Schemas are an essential part of STEF. They define the structure of the data, and thus, how to en- and decode to or from the raw bytes. + + + +## Type mapping + +The following describes all the built-in types of the language, together with the equivalent type in each of the supported programming languages. + +When generating source code, these are the types that are used. + +### Basic types + +Basic types are the most primitive, like boolean, integers, strings and raw binary data. The are distinct and don't carry any special behavior. + +Some of these are not natively supported in each language, turning into a common type. For example the different integer types are all `number` in TypeScript or `int` in Python. + +| Schema | Rust | Go | Kotlin | TypeScript | Python | +| ------- | -------- | --------- | ------------ | ---------- | ------ | +| bool | bool | bool | Boolean | boolean | bool | +| u8 | u8 | uint8 | UByte | number | int | +| u16 | u16 | uint16 | UShort | number | int | +| u32 | u32 | uint32 | UInt | number | int | +| u64 | u64 | uint64 | ULong | bigint | int | +| u128 | u128 | [big.Int] | [BigInteger] | bigint | int | +| i8 | i8 | int8 | Byte | number | int | +| i16 | i16 | int16 | Short | number | int | +| i32 | i32 | int32 | Int | number | int | +| i64 | i64 | int64 | Long | bigint | int | +| i128 | i128 | [big.Int] | [BigInteger] | bigint | int | +| f32 | f32 | float32 | Float | number | float | +| f64 | f64 | float64 | Double | number | float | +| string | String | string | String | string | str | +| &string | &str | string | String | string | str | +| bytes | Vec\ | []byte | ByteArray | Uint8Array | bytes | +| &bytes | &\[u8] | []byte | ByteArray | Uint8Array | bytes | + +[big.Int]: https://pkg.go.dev/math/big#Int +[BigInteger]: https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/math/BigInteger.html + +### Generics + +Generic types have one or more type parameters. That means they are not bound to a single type, but can be used together with any other type. + +For example, a vector `vec` (list of values) can be used as `vec` to create a list of 32-bit unsigned integers, or as `vec` to crate a list of text values. + +#### Vectors `vec` + +Containers for multiple values of a single type. + +| Language | Definition | +| ---------- | ---------- | +| Rust | Vec\ | +| Go | []T | +| Kotlin | List\ | +| TypeScript | T\[] | +| Python | list\[T] | + +#### Hash maps `hash_map` + +Mapping from keys to values, also called dictionaries in some languages. The key must be unique in the map and inserting a new value with the same key will replace the old value. + +| Language | Definition | +| ---------- | -------------- | +| Rust | HashMap\ | +| Go | map\[K]V | +| Kotlin | Map\ | +| TypeScript | Map\ | +| Python | dict\[K, V] | + +#### Hash sets `hash_set` + +Collection of distinct values. These are basically like a hash map without an associated value, and can be used to ensure that each contained value is only present once. + +| Language | Definition | +| ---------- | --------------- | +| Rust | HashSet\ | +| Go | map\[T]struct{} | +| Kotlin | Set\ | +| TypeScript | Set\ | +| Python | set\[T] | + +#### Optionals `option` + +Optional values may be present or missing. By default each value must be present in the wire format and this type allows to declare them as potentially absent. + +| Language | Definition | +| ---------- | -------------- | +| Rust | Option\ | +| Go | \*T | +| Kotlin | T? | +| TypeScript | T \| undefined | +| Python | T \| None | + +### Extended types + +These types have special meanings that either allow for more compact representation on the wire format, or are beneficial in low-level languages that give fine grained control over memory. + +#### Non-zero values `non_zero` + +This wrapper type defines, that the contained type is not zero. Depending on the type this can have a different meaning. + +- `u8`-`u128`, `i8`-`i128`: The integer is guaranteed to be non-zero. +- `string`, `bytes`: The value is guaranteed to contain at least one character or byte. + +The reason for this type is two-fold. First off, it allows to be more strict about certain values, where a zero number or empty string is not allowed. + +Then, on the wire format when combined with an `option`, it allows to use use less bytes for the value. As the value can't take be zero or empty, this state can be used to represent the missing state of the option. + +| Language | Definition | +| ---------- | ----------- | +| Rust | NonZero\ | +| Go | T | +| Kotlin | T | +| TypeScript | T | +| Python | T | + +#### Boxed strings `box` + +Specialized type, that currently only has a specific meaning for Rust. It describes a string value that lives on the heap and is immutable (in contrast to a `string` which can be mutated). + +As strings are immutable in most other languages, it's equivalent to the `string` type for these. + +| Language | Definition | +| ---------- | ---------- | +| Rust | Box\ | +| Go | string | +| Kotlin | String | +| TypeScript | string | +| Python | str | + +#### Boxed bytes `box` + +Specialized type, that describes an immutable byte array, and is specific to Rust. + +Byte arrays are mutable in other languages as well, but they don't have a reasonable way of defining those as immutable. + +| Language | Definition | +| ---------- | ----------- | +| Rust | Box\<\[u8]> | +| Go | \[]byte | +| Kotlin | ByteArray | +| TypeScript | Uint8Array | +| Python | bytes | + +## Identifiers + +Identifier are an integral part of schemas and are attached to named and unnamed fields inside a struct or enum. + +As the wire format doesn't contain any field names, fields have to be identified in some way. This is done by identifiers, which are [varint](../wire-format/index.md#varint-encoding) encoded integers. + +## Naming + +| Item | Convention | +| --------------- | ---------------------- | +| Modules | `snake_case` | +| Structs | `UpperCamelCase` | +| Struct fields | `snake_case` | +| Enums | `UpperCamelCase` | +| Enum variants | `UpperCamelCase` | +| Constants | `SCREAMING_SNAKE_CASE` | +| Type parameters | `UpperCamelCase` | +| Type aliases | `UpperCamelCase` | diff --git a/book/src/schema/modules.md b/book/src/schema/modules.md new file mode 100644 index 0000000..2746b6d --- /dev/null +++ b/book/src/schema/modules.md @@ -0,0 +1,17 @@ +# Modules + + + +## Schema + +```rust,ignore +{{#include modules/basic.stef}} +``` + +## Nesting + +### Schema {#nesting-schema} + +```rust,ignore +{{#include modules/nesting.stef}} +``` diff --git a/book/src/schema/modules/basic.stef b/book/src/schema/modules/basic.stef new file mode 100644 index 0000000..9a8bb6d --- /dev/null +++ b/book/src/schema/modules/basic.stef @@ -0,0 +1,3 @@ +mod sample { + struct Sample +} diff --git a/book/src/schema/modules/nesting.stef b/book/src/schema/modules/nesting.stef new file mode 100644 index 0000000..e55e1be --- /dev/null +++ b/book/src/schema/modules/nesting.stef @@ -0,0 +1,7 @@ +mod a { + mod b { + mod c { + struct Sample + } + } +} diff --git a/book/src/schema/references.md b/book/src/schema/references.md new file mode 100644 index 0000000..cca9294 --- /dev/null +++ b/book/src/schema/references.md @@ -0,0 +1,9 @@ +# References + + + +## Schema + +```rust,ignore +{{#include references/basic.stef}} +``` diff --git a/book/src/schema/references/basic.stef b/book/src/schema/references/basic.stef new file mode 100644 index 0000000..e69de29 diff --git a/book/src/schema/statics.md b/book/src/schema/statics.md new file mode 100644 index 0000000..0a8edda --- /dev/null +++ b/book/src/schema/statics.md @@ -0,0 +1,3 @@ +# Statics + + diff --git a/book/src/schema/structs.md b/book/src/schema/structs.md new file mode 100644 index 0000000..40af44e --- /dev/null +++ b/book/src/schema/structs.md @@ -0,0 +1,204 @@ +# Structs + +Structs are a short naming for _structures_ and define a series of data elements with their respective type. Those individual elements can be named or unnamed. + +In schema they are declared with the `struct` keyword followed by the name and the types contained. + +The name must start with an uppercase ASCII character (`A-Z`), and _may_ be followed by zero or more upper- and lowercase ASCII characters and digits (`A-Z`, `a-z`, `0-9`). + +Note that acronyms should be written in strict _CamelCase_, meaning `Html` instead of `HTML` or `Api` instead of `API`. + +Individual fields in both named and unnamed form are separated by a comma `,`, and it's recommended to even give the last field a trailing comma. This allows for simpler diffs in version control systems. + + + + +## Named + +The likely most common form is a named struct. Named means that each element is represented as a field with a name to identify it. + +To declare the struct as named the content is contained in curly braces `{...}`. + +A single field is defined as `name: type @id`, the name, its type and [ID]. Field names must start with a lowercase ASCII character (`a-z`) and _may_ be followed by zero or more lowercase ASCII characters, digits and underscores (`a-z`, `0-9`, `_`). + +[ID]: ./index.md#identifiers + +### Schema {#named-schema} + +Here is a basic named schema with two fields `field1` and `field2`. The first one is a 32-bit unsigned integer and assigned the ID 1. The second one is a 16-bit unsigned integer and assigned the ID 2. + +```rust,ignore +{{#include structs/named.stef}} +``` + +### Languages {#named-lang} + +These samples describe how the schema would be defined in each language, when generating the code for it. + +#### Rust {#banamedbasicsic-lang-rs} + +```rust +{{#include structs/named.rs}} +``` + +#### Go {#named-lang-go} + +As Go allows to create new instances without declaring a value for each field (they default to the zero value), an additional constructor is created. + +```go +{{#include structs/named.go:5:}} +``` + +#### Kotlin {#named-lang-kt} + +```kotlin +{{#include structs/named.kt:3:}} +``` + +#### TypeScript {#named-lang-ts} + +An additional constructor is required, to ensure all fields are properly initialized when creating new instances. + +```typescript +{{#include structs/named.ts}} +``` + +#### Python {#named-lang-py} + +In Python the `typing.TypedDict` type is used to define the fields of a class. + +```python +{{#include structs/named.py:4:}} +``` + +## Unnamed + +This variant is very similar to named structs, but in contrast lack a field name. They can be convenient if the data type is rather compact and explicit field names aren't needed. For example a position with the horizontal and vertical offset. + +To declare the struct as unnamed the content is contained in parenthesis `(...)`. + +A single field is defined as `type @id`, the name and [ID]. + +### Schema #{unnamed-schema} + +```rust,ignore +{{#include structs/unnamed.stef}} +``` + +### Languages #{unnamed-lang} + +#### Rust {#unnamed-lang-rs} + +```rust +{{#include structs/unnamed.rs}} +``` + +#### Go {#unnamed-lang-go} + +```go +{{#include structs/unnamed.go:5:}} +``` + +#### Kotlin {#unnamed-lang-kt} + +```kotlin +{{#include structs/unnamed.kt:3:}} +``` + +#### TypeScript {#unnamed-lang-ts} + +```typescript +{{#include structs/unnamed.ts}} +``` + +#### Python {#unnamed-lang-py} + +```python +{{#include structs/unnamed.py}} +``` + +## Unit + +In addition to the above, a struct can completely omit field definitions. That is call a unit struct and doesn't carry any data. It doesn't take any space in encoded form either. + +For most languages, this type doesn't take up any memory either. Creating some vector or list of said type would require zero bytes. + +Instead, it's only the type that carries information. + +### Schema {#unit-schema} + +```rust,ignore +{{#include structs/unit.stef}} +``` + +### Languages {#unit-lang} + +#### Rust {#unit-lang-rs} + +```rust +{{#include structs/unit.rs}} +``` + +#### Go {#unit-lang-go} + +```go +{{#include structs/unit.go:5:}} +``` + +#### Kotlin {#unit-lang-kt} + +```kotlin +{{#include structs/unit.kt:3:}} +``` + +#### TypeScript {#unit-lang-ts} + +```typescript +{{#include structs/unit.ts}} +``` + +#### Python {#unit-lang-py} + +```python +{{#include structs/unit.py}} +``` + +## Generics + +### Schema {#generics-schema} + +```rust,ignore +{{#include structs/generics.stef}} +``` + +### Languages {#generics-lang} + +#### Rust {#generics-lang-rs} + +```rust +{{#include structs/generics.rs}} +``` + +#### Go {#generics-lang-go} + +```go +{{#include structs/generics.go:5:}} +``` + +#### Kotlin {#generics-lang-kt} + +```kotlin +{{#include structs/generics.kt:3:}} +``` + +#### TypeScript {#generics-lang-ts} + +```typescript +{{#include structs/generics.ts}} +``` + +#### Python {#generics-lang-py} + +```python +{{#include structs/generics.py:3:}} +``` diff --git a/book/src/schema/structs/generics.go b/book/src/schema/structs/generics.go new file mode 100644 index 0000000..0c6b0e1 --- /dev/null +++ b/book/src/schema/structs/generics.go @@ -0,0 +1,8 @@ +package main + +func main() {} + +type Pair[K any, V any] struct { + Key K + Value V +} diff --git a/book/src/schema/structs/generics.kt b/book/src/schema/structs/generics.kt new file mode 100644 index 0000000..a2a2612 --- /dev/null +++ b/book/src/schema/structs/generics.kt @@ -0,0 +1,6 @@ +fun main() {} + +data class Pair( + val key: K, + val value: V, +) diff --git a/book/src/schema/structs/generics.py b/book/src/schema/structs/generics.py new file mode 100644 index 0000000..1c69c9c --- /dev/null +++ b/book/src/schema/structs/generics.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass +from typing import Generic, TypeVar + + +K = TypeVar("K") +V = TypeVar("V") + + +@dataclass +class Pair(Generic[K, V]): + key: K + value: V diff --git a/book/src/schema/structs/generics.rs b/book/src/schema/structs/generics.rs new file mode 100644 index 0000000..ef4f9b6 --- /dev/null +++ b/book/src/schema/structs/generics.rs @@ -0,0 +1,4 @@ +struct Pair { + pub key: K, + pub value: V, +} diff --git a/book/src/schema/structs/generics.stef b/book/src/schema/structs/generics.stef new file mode 100644 index 0000000..6ff6d68 --- /dev/null +++ b/book/src/schema/structs/generics.stef @@ -0,0 +1,4 @@ +struct Pair { + key: K @1, + value: V @2, +} diff --git a/book/src/schema/structs/generics.ts b/book/src/schema/structs/generics.ts new file mode 100644 index 0000000..3b23438 --- /dev/null +++ b/book/src/schema/structs/generics.ts @@ -0,0 +1,9 @@ +class Pair { + key: K; + value: V; + + constructor(key: K, value: V) { + this.key = key; + this.value = value; + } +} diff --git a/book/src/schema/structs/named.go b/book/src/schema/structs/named.go new file mode 100644 index 0000000..750b9b9 --- /dev/null +++ b/book/src/schema/structs/named.go @@ -0,0 +1,16 @@ +package main + +func main() {} + +type Sample struct { + Field1 uint32 + Field2 uint16 + // N fields... +} + +func NewSample(field1 uint32, field2 uint16) Sample { + return Sample{ + Field1: field1, + Field2: field2, + } +} diff --git a/book/src/schema/structs/named.kt b/book/src/schema/structs/named.kt new file mode 100644 index 0000000..75191d1 --- /dev/null +++ b/book/src/schema/structs/named.kt @@ -0,0 +1,7 @@ +fun main() {} + +data class Sample( + val field1: UInt, + val field2: UShort, + // N fields... +) diff --git a/book/src/schema/structs/named.py b/book/src/schema/structs/named.py new file mode 100644 index 0000000..81ce53e --- /dev/null +++ b/book/src/schema/structs/named.py @@ -0,0 +1,8 @@ +from dataclasses import dataclass +from typing import TypedDict + + +@dataclass +class Sample: + field1: int + field2: int diff --git a/book/src/schema/structs/named.rs b/book/src/schema/structs/named.rs new file mode 100644 index 0000000..5a6ed48 --- /dev/null +++ b/book/src/schema/structs/named.rs @@ -0,0 +1,5 @@ +struct Sample { + pub field1: u32, + pub field2: u16, + // N fields... +} diff --git a/book/src/schema/structs/named.stef b/book/src/schema/structs/named.stef new file mode 100644 index 0000000..50e1e04 --- /dev/null +++ b/book/src/schema/structs/named.stef @@ -0,0 +1,4 @@ +struct Sample { + field1: u32 @1, + field2: u16 @2, +} diff --git a/book/src/schema/structs/named.ts b/book/src/schema/structs/named.ts new file mode 100644 index 0000000..00d62cc --- /dev/null +++ b/book/src/schema/structs/named.ts @@ -0,0 +1,10 @@ +class Sample { + field1: number; + field2: number; + // N fields... + + constructor(field1: number, field2: number) { + this.field1 = field1; + this.field2 = field2; + } +} diff --git a/book/src/schema/structs/unit.go b/book/src/schema/structs/unit.go new file mode 100644 index 0000000..52a7d37 --- /dev/null +++ b/book/src/schema/structs/unit.go @@ -0,0 +1,5 @@ +package main + +func main() {} + +type Sample struct{} diff --git a/book/src/schema/structs/unit.kt b/book/src/schema/structs/unit.kt new file mode 100644 index 0000000..22a428f --- /dev/null +++ b/book/src/schema/structs/unit.kt @@ -0,0 +1,3 @@ +fun main() {} + +class Sample diff --git a/book/src/schema/structs/unit.py b/book/src/schema/structs/unit.py new file mode 100644 index 0000000..112a814 --- /dev/null +++ b/book/src/schema/structs/unit.py @@ -0,0 +1,6 @@ +from dataclasses import dataclass + + +@dataclass +class Sample: + pass diff --git a/book/src/schema/structs/unit.rs b/book/src/schema/structs/unit.rs new file mode 100644 index 0000000..b2e3fb8 --- /dev/null +++ b/book/src/schema/structs/unit.rs @@ -0,0 +1 @@ +struct Sample; diff --git a/book/src/schema/structs/unit.stef b/book/src/schema/structs/unit.stef new file mode 100644 index 0000000..6a88dfb --- /dev/null +++ b/book/src/schema/structs/unit.stef @@ -0,0 +1 @@ +struct Sample diff --git a/book/src/schema/structs/unit.ts b/book/src/schema/structs/unit.ts new file mode 100644 index 0000000..7b6e568 --- /dev/null +++ b/book/src/schema/structs/unit.ts @@ -0,0 +1 @@ +class Sample {} diff --git a/book/src/schema/structs/unnamed.go b/book/src/schema/structs/unnamed.go new file mode 100644 index 0000000..48b405b --- /dev/null +++ b/book/src/schema/structs/unnamed.go @@ -0,0 +1,15 @@ +package main + +func main() {} + +type Sample struct { + F1 uint32 + F2 uint16 +} + +func NewSample(f1 uint32, f2 uint16) Sample { + return Sample{ + F1: f1, + F2: f2, + } +} diff --git a/book/src/schema/structs/unnamed.kt b/book/src/schema/structs/unnamed.kt new file mode 100644 index 0000000..3f7a8df --- /dev/null +++ b/book/src/schema/structs/unnamed.kt @@ -0,0 +1,6 @@ +fun main() {} + +data class Sample( + val f1: UInt, + val f2: UShort, +) diff --git a/book/src/schema/structs/unnamed.py b/book/src/schema/structs/unnamed.py new file mode 100644 index 0000000..9f587d1 --- /dev/null +++ b/book/src/schema/structs/unnamed.py @@ -0,0 +1,6 @@ +from dataclasses import dataclass + + +@dataclass +class Sample(tuple[int, int]): + pass diff --git a/book/src/schema/structs/unnamed.rs b/book/src/schema/structs/unnamed.rs new file mode 100644 index 0000000..2af2c78 --- /dev/null +++ b/book/src/schema/structs/unnamed.rs @@ -0,0 +1 @@ +struct Sample(u32, u16); diff --git a/book/src/schema/structs/unnamed.stef b/book/src/schema/structs/unnamed.stef new file mode 100644 index 0000000..033a29c --- /dev/null +++ b/book/src/schema/structs/unnamed.stef @@ -0,0 +1 @@ +struct Sample(u32 @1, u16 @2) diff --git a/book/src/schema/structs/unnamed.ts b/book/src/schema/structs/unnamed.ts new file mode 100644 index 0000000..7159209 --- /dev/null +++ b/book/src/schema/structs/unnamed.ts @@ -0,0 +1,7 @@ +class Sample { + fields: [number, number]; + + constructor(fields: [number, number]) { + this.fields = fields; + } +} diff --git a/book/src/schema/tuples.md b/book/src/schema/tuples.md new file mode 100644 index 0000000..cd92f64 --- /dev/null +++ b/book/src/schema/tuples.md @@ -0,0 +1,58 @@ +# Tuples + + + + +Tuples allow for the definition of a set of types, without having to define an explicit struct for it. These do not have any associated [ID](index.md#identifiers), meaning the order of declaration matters, and any modification to the type definition is generally incompatible. + +The maximum amount of types in a tuple are **12**. + +## Schema + +In the schema, tuples are declared with parenthesis `(` and `)`, each type separated by a comma `,`, forming a definition like `(T1, T2, TN...)`. + +```rust,ignore +{{#include tuples/basic.stef}} +``` + +## Languages + +### Rust + +In Rust we have tuples and they look the same as in the schema, minus the ID. + +```rust +{{#include tuples/basic.rs}} +``` + +### Go + +There is no native support for tuples. Thus, types for tuples should be provided as part of the encompassing library, for up to N types. + +```go +{{#include tuples/basic.go:5:}} +``` + +### Kotlin + +Again, no native support for tuples. The `Map.Entry` interface exists, but seems not to be meant as tuple replacement. Therefore, generic types should be provided together with the library, for up to N types. + +```kotlin +{{#include tuples/basic.kt:3:}} +``` + +### TypeScript + +Typescript has support for tuples, defined as `[T1, T2, TN...]`. + +```typescript +{{#include tuples/basic.ts}} +``` + +### Python + +Python has support for tuples, defined as `tuple[T1, T2, TN...]`. + +```python +{{#include tuples/basic.py}} +``` diff --git a/book/src/schema/tuples/basic.go b/book/src/schema/tuples/basic.go new file mode 100644 index 0000000..73b2b1b --- /dev/null +++ b/book/src/schema/tuples/basic.go @@ -0,0 +1,22 @@ +package main + +func main() {} + +type Tuple2[T1 any, T2 any] struct { + F1 T1 + F2 T2 +} + +/* +type Tuple3[T1 any, T2 any, T3 any] struct { + F1 T1 + F2 T2 + F2 T3 +} + +...and so on... +*/ + +type Sample struct { + Size Tuple2[uint32, uint32] +} diff --git a/book/src/schema/tuples/basic.kt b/book/src/schema/tuples/basic.kt new file mode 100644 index 0000000..ea37d28 --- /dev/null +++ b/book/src/schema/tuples/basic.kt @@ -0,0 +1,20 @@ +fun main() {} + +data class Tuple2( + val f1: T1, + val f2: T2, +) + +/* +data class Tuple3( + val f1: T1, + val f2: T2, + val f3: T3 +) + +...and so on... +*/ + +data class Sample( + val size: Tuple2, +) diff --git a/book/src/schema/tuples/basic.py b/book/src/schema/tuples/basic.py new file mode 100644 index 0000000..c6dee7f --- /dev/null +++ b/book/src/schema/tuples/basic.py @@ -0,0 +1,6 @@ +from dataclasses import dataclass + + +@dataclass +class Sample: + size: tuple[int, int] diff --git a/book/src/schema/tuples/basic.rs b/book/src/schema/tuples/basic.rs new file mode 100644 index 0000000..8c1d5e7 --- /dev/null +++ b/book/src/schema/tuples/basic.rs @@ -0,0 +1,3 @@ +struct Sample { + size: (u32, u32), +} diff --git a/book/src/schema/tuples/basic.stef b/book/src/schema/tuples/basic.stef new file mode 100644 index 0000000..ba0ce28 --- /dev/null +++ b/book/src/schema/tuples/basic.stef @@ -0,0 +1,3 @@ +struct Sample { + size: (u32, u32) @1, +} diff --git a/book/src/schema/tuples/basic.ts b/book/src/schema/tuples/basic.ts new file mode 100644 index 0000000..10f73df --- /dev/null +++ b/book/src/schema/tuples/basic.ts @@ -0,0 +1,7 @@ +class Sample { + size: [number, number]; + + constructor(size: [number, number]) { + this.size = size; + } +} diff --git a/book/src/schema/type-aliases.md b/book/src/schema/type-aliases.md new file mode 100644 index 0000000..dfc20d8 --- /dev/null +++ b/book/src/schema/type-aliases.md @@ -0,0 +1,50 @@ +# Type aliases + + + + +| Language | Example | +| ------------- | ------------------ | +| Schema / Rust | `type A = T;` | +| Go | `type A = T` | +| Kotlin | `typealias A = T` | +| TypeScript | `type A = T;` | +| Python | `A: TypeAlias = T` | + +## Schema + +```rust,ignore +{{#include type-aliases/basic.stef}} +``` + +## Languages + +### Rust + +```rust +{{#include type-aliases/basic.rs}} +``` + +### Go + +```go +{{#include type-aliases/basic.go:5:}} +``` + +### Kotlin + +```kotlin +{{#include type-aliases/basic.kt:3:}} +``` + +### TypeScript + +```typescript +{{#include type-aliases/basic.ts}} +``` + +### Python + +```python +{{#include type-aliases/basic.py:3:}} +``` diff --git a/book/src/schema/type-aliases/basic.go b/book/src/schema/type-aliases/basic.go new file mode 100644 index 0000000..dbc5695 --- /dev/null +++ b/book/src/schema/type-aliases/basic.go @@ -0,0 +1,7 @@ +package main + +func main() {} + +type Sample struct{} + +type Other = Sample diff --git a/book/src/schema/type-aliases/basic.kt b/book/src/schema/type-aliases/basic.kt new file mode 100644 index 0000000..f7e70c4 --- /dev/null +++ b/book/src/schema/type-aliases/basic.kt @@ -0,0 +1,5 @@ +fun main() {} + +class Sample + +typealias Other = Sample diff --git a/book/src/schema/type-aliases/basic.py b/book/src/schema/type-aliases/basic.py new file mode 100644 index 0000000..91f5649 --- /dev/null +++ b/book/src/schema/type-aliases/basic.py @@ -0,0 +1,10 @@ +from dataclasses import dataclass +from typing import TypeAlias + + +@dataclass +class Sample: + pass + + +Other: TypeAlias = Sample diff --git a/book/src/schema/type-aliases/basic.rs b/book/src/schema/type-aliases/basic.rs new file mode 100644 index 0000000..53b5fba --- /dev/null +++ b/book/src/schema/type-aliases/basic.rs @@ -0,0 +1,3 @@ +struct Sample {} + +type Other = Sample; diff --git a/book/src/schema/type-aliases/basic.stef b/book/src/schema/type-aliases/basic.stef new file mode 100644 index 0000000..4aa64e9 --- /dev/null +++ b/book/src/schema/type-aliases/basic.stef @@ -0,0 +1,3 @@ +struct Sample + +type Other = Sample; diff --git a/book/src/schema/type-aliases/basic.ts b/book/src/schema/type-aliases/basic.ts new file mode 100644 index 0000000..b99ed6a --- /dev/null +++ b/book/src/schema/type-aliases/basic.ts @@ -0,0 +1,3 @@ +class Sample {} + +type Other = Sample; diff --git a/book/src/wire-format/index.md b/book/src/wire-format/index.md new file mode 100644 index 0000000..0b9ef20 --- /dev/null +++ b/book/src/wire-format/index.md @@ -0,0 +1,54 @@ +# Wire format + + + +The wire format takes a lot of inspiration from both [bincode](https://github.com/bincode-org/bincode) and [postcard](https://github.com/jamesmunns/postcard). As tags are involved to identify fields, it takes some ideas from [Protobuf](https://protobuf.dev) and [Cap'n Proto](https://capnproto.org) as well. + +## Integers + +The encoding for all integer types, with 2-bytes or more, is identical, thus the type only dictates limitations on the allowed value range that is valid for it. + +The integer types are: + +- Unsigned `u16`, `u32`, `u64`, `u128`. +- Signed `i16`, `i32`, `i64`, `i128`. + +### Varint encoding + +There are different varint encodings, that all work very similarly, but some end up using less bytes for certain value ranges than others. Generally the both encodings found in `postcard` and `bincode` look fitting. + +- `postcard` uses the regular encoding as it's described in many articles and websites, as well as used in the Protobuf format. It creates a chain, where the last bit of each byte tells whether the more data follows or the end is reached. Only for very large numbers (towards the limits of `u64` and `u128`) it consumes more bytes. +- `bincode` uses a slightly different approach where the first byte always tells how many bytes follow, instead of each byte carrying a marker bit. + +| Integer value | postcard | bincode | +| ------------- | -------- | ------- | +| 1u8 | 1 | 1 | +| u8::MAX | 2 | 3 | +| u16::MAX | 3 | 3 | +| u32::MAX | 5 | 5 | +| u64::MAX | 10 | 9 | +| u128::MAX | 19 | 17 | + +As the table shows, both `u64::MAX` and `u128::MAX` take up less space in the `bincode` encoding. But in contrast, the `u8::MAX` takes up an additional byte. This gap in `bincode` happens, because the values `251-255` are used as markers to tell whether `2`, `4`, `8` or `16` bytes follow. Thus these values must be encoded differently and take up the same space as `u16::MAX`. + +## Floating point numbers + +## Strings and bytes + +Both strings (`string`, `&string`) and bytes (`bytes`, `&bytes`) use the same encoding. The difference is that strings ensure valid UTF-8, while bytes are arbitrary data. + +The encoding is the length in _varint_ encoding, followed by the data byte-by-byte. + +### References + +The reference versions `&str` and `&[u8]` have identical encoding to their owned versions. The difference lies in the decoding process. Instead of copying the payload out of the raw data stream, they can reference directly into said stream. + +Depending on the support of the programming language, this might not be possible. In that case, they have the same behavior as the owned version. + +## Tuples and arrays + +Both tuples and arrays have a known length as defined in the schema. Therefore, the types are encoded in sequence and can be decoded without any further information like the length. + +## Structs + +## Enums diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 0000000..14c7b36 --- /dev/null +++ b/cliff.toml @@ -0,0 +1,82 @@ +# git-cliff ~ default configuration file +# https://git-cliff.org/docs/configuration +# +# Lines starting with "#" are comments. +# Configuration options are organized into tables and keys. +# See documentation for more information on available options. + +[changelog] +# changelog header +header = """ +# Changelog\n +All notable changes to this project will be documented in this file.\n +""" +# template for the changelog body +# https://tera.netlify.app/docs +body = """ +{% if version %}\ + ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else %}\ + ## [unreleased] +{% endif %}\ +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | upper_first }} + {% for commit in commits %} + - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\ + {% endfor %} +{% endfor %}\n +""" +# remove the leading and trailing whitespace from the template +trim = true +# changelog footer +footer = """ + +""" +# postprocessors +postprocessors = [ + { pattern = '', replace = "https://github.com/dnaka91/stef" }, # replace repository URL +] +[git] +# parse the commits based on https://www.conventionalcommits.org +conventional_commits = true +# filter out the commits that are not conventional +filter_unconventional = true +# process each line of a commit as an individual commit +split_commits = false +# regex for preprocessing the commit messages +commit_preprocessors = [ + { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))" }, # replace issue numbers +] +# regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "Features" }, + { message = "^fix", group = "Bug Fixes" }, + { message = "^doc", group = "Documentation" }, + { message = "^perf", group = "Performance" }, + { message = "^refactor", group = "Refactor" }, + { message = "^style", group = "Styling" }, + { message = "^test", group = "Testing" }, + { message = "^chore\\(release\\): prepare for", skip = true }, + { message = "^chore\\(deps\\)", skip = true }, + { message = "^chore\\(pr\\)", skip = true }, + { message = "^chore\\(pull\\)", skip = true }, + { message = "^chore|ci", group = "Miscellaneous Tasks" }, + { body = ".*security", group = "Security" }, + { message = "^revert", group = "Revert" }, +] +# protect breaking changes from being skipped due to matching a skipping commit_parser +protect_breaking_commits = false +# filter out the commits that are not matched by commit parsers +filter_commits = false +# glob pattern for matching git tags +tag_pattern = "v[0-9]*" +# regex for skipping tags +skip_tags = "alpha|beta" +# regex for ignoring tags +ignore_tags = "" +# sort the tags topologically +topo_order = false +# sort the commits inside sections by oldest/newest order +sort_commits = "oldest" +# limit the number of commits included in the changelog. +# limit_commits = 42 diff --git a/crates/stef-benches/Cargo.toml b/crates/stef-benches/Cargo.toml new file mode 100644 index 0000000..74ff601 --- /dev/null +++ b/crates/stef-benches/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "stef-benches" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +readme.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +include = ["src/**/*"] +publish = false + +[[bench]] +name = "parser" +harness = false + +[[bench]] +name = "varint" +harness = false + +[dependencies] +mimalloc = "0.1.38" +stef-parser = { path = "../stef-parser" } + +[dev-dependencies] +criterion = "0.5.1" +indoc = "2.0.3" +serde = "1.0.188" diff --git a/crates/stef-benches/LICENSE.md b/crates/stef-benches/LICENSE.md new file mode 120000 index 0000000..f0608a6 --- /dev/null +++ b/crates/stef-benches/LICENSE.md @@ -0,0 +1 @@ +../../LICENSE.md \ No newline at end of file diff --git a/crates/stef-benches/README.md b/crates/stef-benches/README.md new file mode 120000 index 0000000..fe84005 --- /dev/null +++ b/crates/stef-benches/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/crates/stef-benches/benches/parser.rs b/crates/stef-benches/benches/parser.rs new file mode 100644 index 0000000..4e7312a --- /dev/null +++ b/crates/stef-benches/benches/parser.rs @@ -0,0 +1,155 @@ +use std::{fmt::Write, hint::black_box}; + +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use indoc::indoc; + +#[global_allocator] +static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; + +fn parser(c: &mut Criterion) { + c.bench_function("basic", |b| { + let input = indoc! {r#" + use other::one::Type1; + use other::two; + + const VALUE1: u32 = 100; + const VALUE2: &string = "hello, world!"; + + /// Some sample + #[deprecated = "outdated"] + struct SampleStruct { + field1: bool @1, + field2: u32 @2, + field3: hash_map> @3, + field4: T1 @4, + field5: T2 @5, + /// Comment on a field + field6: Type1 @6, + /// Another field comment + field7: two::Type2 @7, + } + + enum Gender { + Male @1, + Female @2, + Other(hash_map> @1) @3, + } + + /// Redefined type with fixed types + type SampleTyped = SampleStruct; + "#}; + + stef_parser::Schema::parse(input).unwrap(); + + b.iter(|| stef_parser::Schema::parse(black_box(input))) + }); + + for count in [1, 10, 100, 1000] { + let schema = generate_schema(count); + stef_parser::Schema::parse(&schema).unwrap(); + + c.bench_with_input(BenchmarkId::new("large_schema", count), &count, |b, _| { + b.iter(|| stef_parser::Schema::parse(black_box(&schema))) + }); + } + + for count in [1, 10, 100, 1000] { + let schema = generate_schema(count); + let schema = stef_parser::Schema::parse(&schema).unwrap(); + + c.bench_with_input(BenchmarkId::new("print", count), &count, |b, _| { + b.iter(|| black_box(&schema).to_string()) + }); + } +} + +fn generate_schema(count: usize) -> String { + let mut input = String::new(); + + for i in 1..=count { + writeln!(&mut input, "use some::other::module{i};").unwrap(); + } + + input.push('\n'); + + for i in 1..=count { + writeln!( + &mut input, + "const VALUE_BOOL_{i}: bool = {};", + if i % 2 == 0 { "true" } else { "false" } + ) + .unwrap(); + writeln!(&mut input, "const VALUE_INT_{i}: u32 = {i};").unwrap(); + writeln!( + &mut input, + "const VALUE_FLOAT_{i}: f64 = {};", + i as f64 / 19.0 + ) + .unwrap(); + writeln!(&mut input, "const VALUE_STR_{i}: &string = \"{i}\";").unwrap(); + writeln!(&mut input, "const VALUE_BYTES_{i}: &bytes = [{}];", i % 256).unwrap(); + } + + input.push('\n'); + + for i in 1..=count { + writeln!(&mut input, "/// Some comment {i}").unwrap(); + } + + for i in 1..=count { + writeln!(&mut input, "#[unit_attribute_{i}]").unwrap(); + writeln!(&mut input, "#[single_value_{i} = \"value_{i}\"]").unwrap(); + writeln!( + &mut input, + "#[multi_value_{i}(value_a = true, value_b(test1, test2), value_c)]" + ) + .unwrap(); + } + + input.push_str("struct SampleNamed {\n"); + for i in 1..=count { + writeln!(&mut input, " field_str_{i:05}: string @{i},").unwrap(); + writeln!( + &mut input, + " field_gen_{i:05}: vec)>> @{},", + i + count + ) + .unwrap(); + } + input.push_str("}\n"); + + input.push_str("\nstruct SampleUnnamed("); + for i in 1..=count { + write!(&mut input, "string @{i},").unwrap(); + write!( + &mut input, + "vec)>> @{},", + i + count + ) + .unwrap(); + } + input.push_str(")\n"); + + input.push_str("\nenum SampleEnum {\n"); + for i in 1..=count { + writeln!(&mut input, " VariantNamed{i} {{").unwrap(); + writeln!(&mut input, " field_str: string @1,").unwrap(); + writeln!(&mut input, " field_gen: vec> @2,").unwrap(); + writeln!(&mut input, " }} @{i},").unwrap(); + write!(&mut input, " VariantUnnamed{i}(").unwrap(); + write!(&mut input, "string @1, vec> @2").unwrap(); + writeln!(&mut input, ") @{},", i + count).unwrap(); + } + input.push_str("}\n"); + + input.push('\n'); + + for i in 1..=count { + writeln!(&mut input, "type Alias{i} = SampleNamed;").unwrap(); + } + + input +} + +criterion_group!(benches, parser); +criterion_main!(benches); diff --git a/crates/stef-benches/benches/varint.rs b/crates/stef-benches/benches/varint.rs new file mode 100644 index 0000000..4b24049 --- /dev/null +++ b/crates/stef-benches/benches/varint.rs @@ -0,0 +1,75 @@ +use std::hint::black_box; + +use criterion::{ + criterion_group, criterion_main, AxisScale, BenchmarkId, Criterion, PlotConfiguration, +}; +use stef_benches::varint; + +fn varint(c: &mut Criterion) { + let mut g = c.benchmark_group("varint"); + g.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic)); + + for i in [ + 1, + u8::MAX.into(), + u16::MAX.into(), + u32::MAX.into(), + u64::MAX.into(), + u128::MAX, + ] { + g.bench_with_input(BenchmarkId::new("leb128", i), &i, |b, i| { + let mut buf = [0; 19]; + let value = *i; + b.iter(|| { + varint::postcard::encode(black_box(value), black_box(&mut buf)); + varint::postcard::decode(black_box(&buf)) + }); + }); + g.bench_with_input(BenchmarkId::new("bincode", i), &i, |b, i| { + let mut buf = [0; 19]; + let value = *i; + b.iter(|| { + varint::bincode::encode_u128(black_box(value), black_box(&mut buf)); + varint::bincode::decode_u128(black_box(&buf)) + }); + }); + } + g.finish(); + + let mut g = c.benchmark_group("varint_signed"); + g.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic)); + + for i in [ + -1, + i8::MIN.into(), + i16::MIN.into(), + i32::MIN.into(), + i64::MIN.into(), + i128::MIN, + ] { + g.bench_with_input(BenchmarkId::new("leb128", i), &i, |b, i| { + let mut buf = [0; 19]; + let value = *i; + b.iter(|| { + varint::postcard::encode_i128(black_box(value), black_box(&mut buf)); + varint::postcard::decode_i128(black_box(&buf)) + }); + }); + g.bench_with_input(BenchmarkId::new("bincode", i), &i, |b, i| { + let mut buf = [0; 19]; + let value = *i; + b.iter(|| { + varint::bincode::encode_i128(black_box(value), black_box(&mut buf)); + varint::bincode::decode_i128(black_box(&buf)) + }); + }); + } + g.finish(); +} + +criterion_group! { + name = benches; + config = Criterion::default().plotting_backend(criterion::PlottingBackend::Plotters); + targets = varint +} +criterion_main!(benches); diff --git a/crates/stef-benches/src/lib.rs b/crates/stef-benches/src/lib.rs new file mode 100644 index 0000000..9da70d8 --- /dev/null +++ b/crates/stef-benches/src/lib.rs @@ -0,0 +1 @@ +pub mod varint; diff --git a/crates/stef-benches/src/varint.rs b/crates/stef-benches/src/varint.rs new file mode 100644 index 0000000..5c06161 --- /dev/null +++ b/crates/stef-benches/src/varint.rs @@ -0,0 +1,345 @@ +pub mod postcard { + #[inline] + const fn max_size() -> usize { + (std::mem::size_of::() * 8 + 7) / 7 + } + + #[inline] + pub fn encode(mut value: u128, buf: &mut [u8]) { + for b in buf.iter_mut().take(max_size::()) { + *b = value.to_le_bytes()[0]; + if value < 128 { + break; + } + + *b |= 0x80; + value >>= 7; + } + } + + #[inline] + pub fn encode_i128(value: i128, buf: &mut [u8]) { + encode(encode_zigzag(value), buf); + } + + #[inline] + pub fn encode_zigzag(value: i128) -> u128 { + ((value << 1) ^ (value >> 127)) as u128 + } + + #[inline] + pub fn decode(buf: &[u8]) -> u128 { + let mut value = 0; + for (i, b) in buf.iter().copied().enumerate().take(max_size::()) { + value |= ((b & 0x7f) as u128) << (7 * i); + + if b & 0x80 == 0 { + return value; + } + } + + panic!("bad input") + } + + #[inline] + pub fn decode_i128(buf: &[u8]) -> i128 { + decode_zigzag(decode(buf)) + } + + #[inline] + pub fn decode_zigzag(value: u128) -> i128 { + ((value >> 1) as i128) ^ (-((value & 0b1) as i128)) + } + + #[test] + fn roundtrip() { + let mut buf = [0; max_size::()]; + + for value in [ + 1, + u8::MAX.into(), + u16::MAX.into(), + u32::MAX.into(), + u64::MAX.into(), + u128::MAX, + ] { + encode(value, &mut buf); + let output = decode(&buf); + assert_eq!(value, output); + } + + for value in [ + -1, + i8::MIN.into(), + i16::MIN.into(), + i32::MIN.into(), + i64::MIN.into(), + i128::MIN, + ] { + encode_i128(value, &mut buf); + let output = decode_i128(&buf); + assert_eq!(value, output); + } + } +} + +pub mod bincode { + #[inline] + pub fn encode_u16(value: u16, buf: &mut [u8]) { + if value <= 250 { + buf[0] = value.to_le_bytes()[0]; + } else { + buf[0] = 251; + buf[1..3].copy_from_slice(&value.to_le_bytes()[..2]); + } + } + + #[inline] + pub fn encode_i16(value: i16, buf: &mut [u8]) { + encode_u16( + if value < 0 { + !(value as u16) * 2 + 1 + } else { + (value as u16) * 2 + }, + buf, + ); + } + + #[inline] + pub fn encode_u32(value: u32, buf: &mut [u8]) { + if value <= 250 { + buf[0] = value.to_le_bytes()[0]; + } else if value <= u16::MAX.into() { + buf[0] = 251; + buf[1..3].copy_from_slice(&value.to_le_bytes()[..2]); + } else { + buf[0] = 252; + buf[1..5].copy_from_slice(&value.to_le_bytes()[..4]); + } + } + + #[inline] + pub fn encode_i32(value: i32, buf: &mut [u8]) { + encode_u32( + if value < 0 { + !(value as u32) * 2 + 1 + } else { + (value as u32) * 2 + }, + buf, + ); + } + + #[inline] + pub fn encode_u64(value: u64, buf: &mut [u8]) { + if value <= 250 { + buf[0] = value.to_le_bytes()[0]; + } else if value <= u16::MAX.into() { + buf[0] = 251; + buf[1..3].copy_from_slice(&value.to_le_bytes()[..2]); + } else if value <= u32::MAX.into() { + buf[0] = 252; + buf[1..5].copy_from_slice(&value.to_le_bytes()[..4]); + } else { + buf[0] = 253; + buf[1..9].copy_from_slice(&value.to_le_bytes()[..8]); + } + } + + #[inline] + pub fn encode_i64(value: i64, buf: &mut [u8]) { + encode_u64( + if value < 0 { + !(value as u64) * 2 + 1 + } else { + (value as u64) * 2 + }, + buf, + ); + } + + #[inline] + pub fn encode_u128(value: u128, buf: &mut [u8]) { + if value <= 250 { + buf[0] = value.to_le_bytes()[0]; + } else if value <= u16::MAX.into() { + buf[0] = 251; + buf[1..3].copy_from_slice(&value.to_le_bytes()[..2]); + } else if value <= u32::MAX.into() { + buf[0] = 252; + buf[1..5].copy_from_slice(&value.to_le_bytes()[..4]); + } else if value <= u64::MAX.into() { + buf[0] = 253; + buf[1..9].copy_from_slice(&value.to_le_bytes()[..8]); + } else { + buf[0] = 254; + buf[1..17].copy_from_slice(&value.to_le_bytes()[..16]); + } + } + + #[inline] + pub fn encode_i128(value: i128, buf: &mut [u8]) { + encode_u128( + if value < 0 { + !(value as u128) * 2 + 1 + } else { + (value as u128) * 2 + }, + buf, + ); + } + + #[inline] + pub fn decode_u16(buf: &[u8]) -> u16 { + match buf[0] { + byte @ 0..=250 => byte.into(), + 251 => { + let mut b = [0; 2]; + b.copy_from_slice(&buf[1..3]); + u16::from_le_bytes(b) + } + _ => panic!("bad input"), + } + } + + #[inline] + pub fn decode_i16(buf: &[u8]) -> i16 { + let value = decode_u16(buf); + if value % 2 == 0 { + (value / 2) as i16 + } else { + !(value / 2) as i16 + } + } + + #[inline] + pub fn decode_u32(buf: &[u8]) -> u32 { + match buf[0] { + byte @ 0..=250 => byte.into(), + 251 => { + let mut b = [0; 2]; + b.copy_from_slice(&buf[1..3]); + u16::from_le_bytes(b).into() + } + 252 => { + let mut b = [0; 4]; + b.copy_from_slice(&buf[1..5]); + u32::from_le_bytes(b) + } + _ => panic!("bad input"), + } + } + + #[inline] + pub fn decode_i32(buf: &[u8]) -> i32 { + let value = decode_u32(buf); + if value % 2 == 0 { + (value / 2) as i32 + } else { + !(value / 2) as i32 + } + } + + #[inline] + pub fn decode_u64(buf: &[u8]) -> u64 { + match buf[0] { + byte @ 0..=250 => byte.into(), + 251 => { + let mut b = [0; 2]; + b.copy_from_slice(&buf[1..3]); + u16::from_le_bytes(b).into() + } + 252 => { + let mut b = [0; 4]; + b.copy_from_slice(&buf[1..5]); + u32::from_le_bytes(b).into() + } + 253 => { + let mut b = [0; 8]; + b.copy_from_slice(&buf[1..9]); + u64::from_le_bytes(b) + } + _ => panic!("bad input"), + } + } + + #[inline] + pub fn decode_i64(buf: &[u8]) -> i64 { + let value = decode_u64(buf); + if value % 2 == 0 { + (value / 2) as i64 + } else { + !(value / 2) as i64 + } + } + + #[inline] + pub fn decode_u128(buf: &[u8]) -> u128 { + match buf[0] { + byte @ 0..=250 => byte.into(), + 251 => { + let mut b = [0; 2]; + b.copy_from_slice(&buf[1..3]); + u16::from_le_bytes(b).into() + } + 252 => { + let mut b = [0; 4]; + b.copy_from_slice(&buf[1..5]); + u32::from_le_bytes(b).into() + } + 253 => { + let mut b = [0; 8]; + b.copy_from_slice(&buf[1..9]); + u64::from_le_bytes(b).into() + } + 254 => { + let mut b = [0; 16]; + b.copy_from_slice(&buf[1..17]); + u128::from_le_bytes(b) + } + _ => panic!("bad input"), + } + } + + #[inline] + pub fn decode_i128(buf: &[u8]) -> i128 { + let value = decode_u128(buf); + if value % 2 == 0 { + (value / 2) as i128 + } else { + !(value / 2) as i128 + } + } + + #[test] + fn roundtrip() { + let mut buf = [0; 17]; + + for value in [ + 1, + u8::MAX.into(), + u16::MAX.into(), + u32::MAX.into(), + u64::MAX.into(), + u128::MAX, + ] { + encode_u128(value, &mut buf); + let output = decode_u128(&buf); + assert_eq!(value, output); + } + + for value in [ + -1, + i8::MIN.into(), + i16::MIN.into(), + i32::MIN.into(), + i64::MIN.into(), + i128::MIN, + ] { + encode_i128(value, &mut buf); + let output = decode_i128(&buf); + assert_eq!(value, output); + } + } +} diff --git a/crates/stef-build/Cargo.toml b/crates/stef-build/Cargo.toml new file mode 100644 index 0000000..81f963c --- /dev/null +++ b/crates/stef-build/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "stef-build" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +readme.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +miette.workspace = true +proc-macro2 = { version = "1.0.66", default-features = false } +quote = { version = "1.0.33", default-features = false } +stef-parser = { path = "../stef-parser" } +thiserror = "1.0.47" + +[dev-dependencies] +indoc = "2.0.3" +pretty_assertions = "1.4.0" +prettyplease = "0.2.12" +syn = "2.0.29" diff --git a/crates/stef-build/src/lib.rs b/crates/stef-build/src/lib.rs new file mode 100644 index 0000000..3734f9d --- /dev/null +++ b/crates/stef-build/src/lib.rs @@ -0,0 +1,486 @@ +#![deny(rust_2018_idioms, clippy::all, clippy::pedantic)] +#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)] + +use std::path::{Path, PathBuf}; + +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, ToTokens}; +use stef_parser::{ + Comment, Const, DataType, Definition, Enum, ExternalType, Fields, Generics, Import, Literal, + Module, NamedField, Schema, Struct, TypeAlias, UnnamedField, Variant, +}; +use thiserror::Error; + +type Result = std::result::Result; + +#[derive(Debug, Error)] +pub enum Error { + #[error("failed reading schema file at {file:?}")] + Read { + #[source] + source: std::io::Error, + file: PathBuf, + }, +} + +pub fn compile(schemas: &[impl AsRef], _includes: &[impl AsRef]) -> Result<()> { + for schema in schemas { + let input = std::fs::read_to_string(schema).map_err(|e| Error::Read { + source: e, + file: schema.as_ref().to_owned(), + })?; + + let schema = Schema::parse(&input).unwrap(); + let code = compile_schema(&schema); + + println!("{code}"); + } + + Ok(()) +} + +fn compile_schema(Schema { definitions }: &Schema<'_>) -> TokenStream { + let definitions = definitions.iter().map(compile_definition); + + quote! { #(#definitions)* } +} + +fn compile_definition(definition: &Definition<'_>) -> TokenStream { + let definition = match definition { + Definition::Module(m) => compile_module(m), + Definition::Struct(s) => compile_struct(s), + Definition::Enum(e) => compile_enum(e), + Definition::TypeAlias(a) => compile_alias(a), + Definition::Const(c) => compile_const(c), + Definition::Import(i) => compile_import(i), + }; + + quote! { #definition } +} + +fn compile_module( + Module { + comment, + name, + definitions, + }: &Module<'_>, +) -> TokenStream { + let comment = compile_comment(comment); + let name = Ident::new(name, Span::call_site()); + let definitions = definitions.iter().map(compile_definition); + + quote! { + #comment + pub mod #name { + #(#definitions)* + } + } +} + +fn compile_struct( + Struct { + comment, + attributes: _, + name, + generics, + fields, + }: &Struct<'_>, +) -> TokenStream { + let comment = compile_comment(comment); + let name = Ident::new(name, Span::call_site()); + let generics = compile_generics(generics); + let fields = compile_fields(fields, true); + + quote! { + #comment + pub struct #name #generics #fields + } +} + +fn compile_enum( + Enum { + comment, + attributes: _, + name, + generics, + variants, + }: &Enum<'_>, +) -> TokenStream { + let comment = compile_comment(comment); + let name = Ident::new(name, Span::call_site()); + let generics = compile_generics(generics); + let variants = variants.iter().map(compile_variant); + + quote! { + #comment + pub enum #name #generics { + #(#variants,)* + } + } +} + +fn compile_variant( + Variant { + comment, + name, + fields, + id: _, + }: &Variant<'_>, +) -> TokenStream { + let comment = compile_comment(comment); + let name = Ident::new(name, Span::call_site()); + let fields = compile_fields(fields, false); + + quote! { + #comment + #name #fields + } +} + +fn compile_alias( + TypeAlias { + comment, + alias, + target, + }: &TypeAlias<'_>, +) -> TokenStream { + let comment = compile_comment(comment); + let alias = compile_data_type(alias); + let target = compile_data_type(target); + + quote! { + #comment + pub type #alias = #target; + } +} + +fn compile_const( + Const { + comment, + name, + ty, + value, + }: &Const<'_>, +) -> TokenStream { + let comment = compile_comment(comment); + let name = Ident::new(name, Span::call_site()); + let ty = compile_const_data_type(ty); + let value = compile_literal(value); + + quote! { + #comment + const #name: #ty = #value; + } +} + +fn compile_import(Import { segments, element }: &Import<'_>) -> TokenStream { + let segments = segments.iter().enumerate().map(|(i, segment)| { + let segment = Ident::new(segment, Span::call_site()); + if i > 0 { + quote! {::#segment} + } else { + quote! {#segment} + } + }); + let element = element.map(|element| { + let element = Ident::new(element, Span::call_site()); + quote! { ::#element} + }); + + quote! { + use #(#segments)*#element; + } +} + +fn compile_comment(Comment(lines): &Comment<'_>) -> TokenStream { + let lines = lines.iter().map(|line| format!(" {line}")); + quote! { #(#[doc = #lines])* } +} + +fn compile_generics(Generics(types): &Generics<'_>) -> Option { + (!types.is_empty()).then(|| quote! { <#(#types,)*> }) +} + +fn compile_fields(fields: &Fields<'_>, public: bool) -> TokenStream { + match fields { + Fields::Named(named) => { + let fields = named.iter().map( + |NamedField { + comment, + name, + ty, + id: _, + }| { + let comment = compile_comment(comment); + let public = public.then(|| quote! { pub }); + let name = Ident::new(name, Span::call_site()); + let ty = compile_data_type(ty); + quote! { + #comment + #public #name: #ty + } + }, + ); + + quote! { { + #(#fields,)* + } } + } + Fields::Unnamed(unnamed) => { + let fields = unnamed.iter().map(|UnnamedField { ty, id: _ }| { + let ty = compile_data_type(ty); + quote! { #ty } + }); + + quote! { (#(#fields,)*) } + } + Fields::Unit => quote! {}, + } +} + +fn compile_data_type(ty: &DataType<'_>) -> TokenStream { + match ty { + DataType::Bool => quote! { bool }, + DataType::U8 => quote! { u8 }, + DataType::U16 => quote! { u16 }, + DataType::U32 => quote! { u32 }, + DataType::U64 => quote! { u64 }, + DataType::U128 => quote! { u128 }, + DataType::I8 => quote! { i8 }, + DataType::I16 => quote! { i16 }, + DataType::I32 => quote! { i32 }, + DataType::I64 => quote! { i64 }, + DataType::I128 => quote! { i128 }, + DataType::F32 => quote! { f32 }, + DataType::F64 => quote! { f64 }, + DataType::String | DataType::StringRef => quote! { String }, + DataType::Bytes | DataType::BytesRef => quote! { Vec }, + DataType::Vec(ty) => { + let ty = compile_data_type(ty); + quote! { Vec<#ty> } + } + DataType::HashMap(kv) => { + let k = compile_data_type(&kv.0); + let v = compile_data_type(&kv.1); + quote! { HashMap<#k, #v> } + } + DataType::HashSet(ty) => { + let ty = compile_data_type(ty); + quote! { HashSet<#ty> } + } + DataType::Option(ty) => { + let ty = compile_data_type(ty); + quote! { Option<#ty> } + } + DataType::NonZero(ty) => match **ty { + DataType::U8 => quote! { NonZeroU8 }, + DataType::U16 => quote! { NonZeroU16 }, + DataType::U32 => quote! { NonZeroU32 }, + DataType::U64 => quote! { NonZeroU64 }, + DataType::U128 => quote! { NonZeroU128 }, + DataType::I8 => quote! { NonZeroI8 }, + DataType::I16 => quote! { NonZeroI16 }, + DataType::I32 => quote! { NonZeroI32 }, + DataType::I64 => quote! { NonZeroI64 }, + DataType::I128 => quote! { NonZeroI128 }, + _ => compile_data_type(ty), + }, + DataType::BoxString => quote! { Box }, + DataType::BoxBytes => quote! { Box<[u8]> }, + DataType::Tuple(types) => { + let types = types.iter().map(compile_data_type); + quote! { (#(#types,)*) } + } + DataType::Array(ty, size) => { + let ty = compile_data_type(ty); + let size = proc_macro2::Literal::u32_unsuffixed(*size); + quote! { [#ty; #size] } + } + DataType::External(ExternalType { + path, + name, + generics, + }) => { + let name = Ident::new(name, Span::call_site()); + let generics = (!generics.is_empty()).then(|| { + let types = generics.iter().map(compile_data_type); + quote! { <#(#types,)*> } + }); + + quote! { + #(#path::)* #name #generics + } + } + } +} + +fn compile_const_data_type(ty: &DataType<'_>) -> TokenStream { + match ty { + DataType::Bool => quote! { bool }, + DataType::U8 => quote! { u8 }, + DataType::U16 => quote! { u16 }, + DataType::U32 => quote! { u32 }, + DataType::U64 => quote! { u64 }, + DataType::U128 => quote! { u128 }, + DataType::I8 => quote! { i8 }, + DataType::I16 => quote! { i16 }, + DataType::I32 => quote! { i32 }, + DataType::I64 => quote! { i64 }, + DataType::I128 => quote! { i128 }, + DataType::F32 => quote! { f32 }, + DataType::F64 => quote! { f64 }, + DataType::String | DataType::StringRef => quote! { &str }, + DataType::Bytes | DataType::BytesRef => quote! { &[u8] }, + _ => panic!("invalid data type for const"), + } +} + +fn compile_literal(literal: &Literal) -> TokenStream { + match literal { + Literal::Bool(b) => quote! { #b }, + Literal::Int(i) => proc_macro2::Literal::i128_unsuffixed(*i).into_token_stream(), + Literal::Float(f) => proc_macro2::Literal::f64_unsuffixed(*f).into_token_stream(), + Literal::String(s) => proc_macro2::Literal::string(s).into_token_stream(), + Literal::Bytes(b) => proc_macro2::Literal::byte_string(b).into_token_stream(), + } +} + +#[cfg(test)] +mod tests { + use indoc::indoc; + use pretty_assertions::assert_eq; + + use super::*; + + fn parse(input: &str, expect: &str) { + let parsed = Schema::parse(input).unwrap(); + println!("==========\n{parsed}"); + + let compiled = compile_schema(&parsed); + println!("----------\n{compiled}"); + + let pretty = prettyplease::unparse(&syn::parse2(compiled.clone()).unwrap()); + println!("----------\n{pretty}=========="); + + assert_eq!(expect, pretty); + } + + #[test] + fn basic_module() { + let input = indoc! {r#" + /// Hello world! + mod sample {} + "#}; + let expect = indoc! {r#" + /// Hello world! + pub mod sample {} + "#}; + + parse(input, expect); + } + + #[test] + fn basic_struct() { + let input = indoc! {r#" + /// Hello world! + struct Sample { + field1: u32 @1, + field2: bytes @2, + field3: (bool, [i16; 4]) @3, + } + "#}; + let expect = indoc! {r#" + /// Hello world! + pub struct Sample { + pub field1: u32, + pub field2: Vec, + pub field3: (bool, [i16; 4]), + } + "#}; + + parse(input, expect); + } + + #[test] + fn basic_enum() { + let input = indoc! {r#" + /// Hello world! + enum Sample { + Variant1 @1, + Variant2(u32 @1, u8 @2) @2, + Variant3 { + field1: string @1, + field2: Vec @2, + } @3, + } + "#}; + let expect = indoc! {r#" + /// Hello world! + pub enum Sample { + Variant1, + Variant2(u32, u8), + Variant3 { field1: String, field2: Vec }, + } + "#}; + + parse(input, expect); + } + + #[test] + fn basic_alias() { + let input = indoc! {r#" + /// Hello world! + type Sample = String; + "#}; + let expect = indoc! {r#" + /// Hello world! + pub type Sample = String; + "#}; + + parse(input, expect); + } + + #[test] + fn basic_const() { + let input = indoc! {r#" + /// A bool. + const BOOL: bool = true; + /// An integer. + const INT: u32 = 100; + /// A float. + const FLOAT: f64 = 5.0; + /// A string. + const STRING: string = "hello"; + /// Some bytes. + const BYTES: bytes = [1, 2, 3]; + "#}; + let expect = indoc! {r#" + /// A bool. + const BOOL: bool = true; + /// An integer. + const INT: u32 = 100; + /// A float. + const FLOAT: f64 = 5.0; + /// A string. + const STRING: &str = "hello"; + /// Some bytes. + const BYTES: &[u8] = b"\x01\x02\x03"; + "#}; + + parse(input, expect); + } + + #[test] + fn basic_import() { + let input = indoc! {r#" + use other::module; + use other::module::Type; + "#}; + let expect = indoc! {r#" + use other::module; + use other::module::Type; + "#}; + + parse(input, expect); + } +} diff --git a/crates/stef-cli/Cargo.toml b/crates/stef-cli/Cargo.toml new file mode 100644 index 0000000..8c37d6c --- /dev/null +++ b/crates/stef-cli/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "stef-cli" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +readme.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +include = ["src/**/*"] + +[dependencies] +clap = { version = "4.4.0", features = ["derive", "wrap_help"] } +color-eyre.workspace = true +miette = { workspace = true, features = ["fancy-no-backtrace"] } +mimalloc = "0.1.38" +stef-parser = { path = "../stef-parser" } diff --git a/crates/stef-cli/LICENSE.md b/crates/stef-cli/LICENSE.md new file mode 120000 index 0000000..f0608a6 --- /dev/null +++ b/crates/stef-cli/LICENSE.md @@ -0,0 +1 @@ +../../LICENSE.md \ No newline at end of file diff --git a/crates/stef-cli/README.md b/crates/stef-cli/README.md new file mode 120000 index 0000000..fe84005 --- /dev/null +++ b/crates/stef-cli/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/crates/stef-cli/src/cli.rs b/crates/stef-cli/src/cli.rs new file mode 100644 index 0000000..cbb51ae --- /dev/null +++ b/crates/stef-cli/src/cli.rs @@ -0,0 +1,42 @@ +use std::path::PathBuf; + +use clap::{Parser, Subcommand}; + +#[derive(Parser)] +#[command(about, author, version, propagate_version = true)] +pub struct Cli { + #[command(subcommand)] + pub cmd: Option, +} + +#[derive(Subcommand)] +pub enum Command { + Init { + path: Option, + }, + Check { + #[arg(num_args(1..))] + files: Vec, + }, + Format { + #[arg(num_args(1..))] + files: Vec, + }, +} + +impl Cli { + pub fn parse() -> Self { + ::parse() + } +} + +#[cfg(test)] +mod tests { + use super::Cli; + + #[test] + fn verify_cli() { + use clap::CommandFactory; + Cli::command().debug_assert(); + } +} diff --git a/crates/stef-cli/src/main.rs b/crates/stef-cli/src/main.rs new file mode 100644 index 0000000..dd27cdb --- /dev/null +++ b/crates/stef-cli/src/main.rs @@ -0,0 +1,71 @@ +mod cli; + +use std::{fs, path::PathBuf, process::ExitCode}; + +use miette::{Context, IntoDiagnostic, Result}; +use stef_parser::Schema; + +use self::cli::Cli; + +#[global_allocator] +static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; + +fn main() -> ExitCode { + let cli = Cli::parse(); + + if let Some(cmd) = cli.cmd { + let result = match cmd { + cli::Command::Init { path } => { + println!("TODO: create basic setup at {path:?}"); + Ok(()) + } + cli::Command::Check { files } => check(files), + cli::Command::Format { files } => format(files), + }; + + return match result { + Ok(()) => ExitCode::SUCCESS, + Err(e) => { + eprintln!("{e:?}"); + ExitCode::FAILURE + } + }; + } + + ExitCode::SUCCESS +} + +fn check(files: Vec) -> Result<()> { + for file in files { + let buf = fs::read_to_string(&file) + .into_diagnostic() + .wrap_err_with(|| format!("Failed reading {file:?}"))?; + + if let Err(e) = Schema::parse(&buf).wrap_err("Failed parsing schema file") { + eprintln!("{e:?}"); + } + } + + Ok(()) +} + +fn format(files: Vec) -> Result<()> { + for file in files { + let buf = fs::read_to_string(&file).into_diagnostic()?; + let schema = match Schema::parse(&buf).wrap_err("Failed parsing schema file") { + Ok(schema) => schema, + Err(e) => { + eprintln!("{e:?}"); + continue; + } + }; + + let formatted = schema.to_string(); + + if buf != formatted { + fs::write(file, &buf).into_diagnostic()?; + } + } + + Ok(()) +} diff --git a/crates/stef-compiler/Cargo.toml b/crates/stef-compiler/Cargo.toml new file mode 100644 index 0000000..02baf6d --- /dev/null +++ b/crates/stef-compiler/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "stef-compiler" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +readme.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +include = ["src/**/*"] + +[dependencies] +stef-parser = { path = "../stef-parser" } diff --git a/crates/stef-compiler/LICENSE.md b/crates/stef-compiler/LICENSE.md new file mode 120000 index 0000000..f0608a6 --- /dev/null +++ b/crates/stef-compiler/LICENSE.md @@ -0,0 +1 @@ +../../LICENSE.md \ No newline at end of file diff --git a/crates/stef-compiler/README.md b/crates/stef-compiler/README.md new file mode 120000 index 0000000..fe84005 --- /dev/null +++ b/crates/stef-compiler/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/crates/stef-compiler/src/lib.rs b/crates/stef-compiler/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/crates/stef-compiler/src/lib.rs @@ -0,0 +1 @@ + diff --git a/crates/stef-derive/Cargo.toml b/crates/stef-derive/Cargo.toml new file mode 100644 index 0000000..200daf6 --- /dev/null +++ b/crates/stef-derive/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "stef-derive" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +readme.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.66" +quote = "1.0.33" +syn = "2.0.29" diff --git a/crates/stef-derive/src/attributes.rs b/crates/stef-derive/src/attributes.rs new file mode 100644 index 0000000..60c000d --- /dev/null +++ b/crates/stef-derive/src/attributes.rs @@ -0,0 +1,195 @@ +use proc_macro2::{Ident, Span}; +use syn::{ + meta::ParseNestedMeta, parenthesized, punctuated::Punctuated, token::Comma, Attribute, Expr, + LitStr, Path, +}; + +macro_rules! bail { + ($tokens:expr, $($arg:tt)*) => { + return Err(syn::Error::new($tokens, format!($($arg)*))) + }; +} + +pub struct StructAttributes { + pub msg: LitStr, + pub code: Path, + pub help: Punctuated, + pub rename: Option, +} + +impl StructAttributes { + pub fn parse(attrs: &[Attribute], span: Span) -> syn::Result { + let mut msg = None; + let mut code = None; + let mut help = None; + + for attr in attrs.iter().filter(|attr| attr.path().is_ident("err")) { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("msg") { + let content; + parenthesized!(content in meta.input); + msg = Some(content.parse()?); + } else if meta.path.is_ident("code") { + let content; + parenthesized!(content in meta.input); + code = Some(content.parse()?); + } else if meta.path.is_ident("help") { + let content; + parenthesized!(content in meta.input); + help = Some(Punctuated::parse_terminated(&content)?); + } + + Ok(()) + })?; + } + + let mut rename = None; + + if let Some(attr) = attrs + .iter() + .rev() + .find(|attr| attr.path().is_ident("rename")) + { + rename = Some(attr.parse_args()?); + } + + let Some(msg) = msg else { + bail!(span, "missing `msg` attribute") + }; + let Some(code) = code else { + bail!(span, "missing `code` attribute") + }; + let Some(help) = help else { + bail!(span, "missing `help` attribute") + }; + + Ok(Self { + msg, + code, + help, + rename, + }) + } +} + +pub struct EnumAttributes { + pub rename: Option, +} + +impl EnumAttributes { + pub fn parse(attrs: &[Attribute], _span: Span) -> syn::Result { + let mut rename = None; + + for attr in attrs.iter().filter(|attr| attr.path().is_ident("rename")) { + rename = Some(attr.parse_args()?); + } + + Ok(Self { rename }) + } +} + +pub enum VariantAttributes { + Error { + msg: LitStr, + code: Path, + help: Punctuated, + }, + External, + Forward, +} + +impl VariantAttributes { + pub fn parse(attrs: &[Attribute], span: Span) -> syn::Result { + for attr in attrs { + if attr.path().is_ident("err") { + let mut msg = None; + let mut code = None; + let mut help = None; + + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("msg") { + let content; + parenthesized!(content in meta.input); + msg = Some(content.parse()?); + } else if meta.path.is_ident("code") { + let content; + parenthesized!(content in meta.input); + code = Some(content.parse()?); + } else if meta.path.is_ident("help") { + let content; + parenthesized!(content in meta.input); + help = Some(Punctuated::parse_terminated(&content)?); + } + + Ok(()) + })?; + + let Some(msg) = msg else { + bail!(span, "missing `msg` attribute") + }; + let Some(code) = code else { + bail!(span, "missing `code` attribute") + }; + let Some(help) = help else { + bail!(span, "missing `help` attribute") + }; + + return Ok(Self::Error { msg, code, help }); + } else if attr.path().is_ident("external") { + return Ok(Self::External); + } else if attr.path().is_ident("forward") { + return Ok(Self::Forward); + } + } + + bail!( + span, + "none of the `err`, `external` or `forward` attributes found" + ) + } +} + +pub struct FieldAttributes { + pub label: (bool, Option), +} + +impl FieldAttributes { + pub fn parse(attrs: &[Attribute]) -> syn::Result> { + Ok( + if let FieldAttributesParser { label: Some(label) } = + FieldAttributesParser::parse_all(attrs)? + { + Some(Self { label }) + } else { + None + }, + ) + } +} + +#[derive(Default)] +struct FieldAttributesParser { + label: Option<(bool, Option)>, +} + +impl FieldAttributesParser { + fn parse_all(attrs: &[Attribute]) -> syn::Result { + let mut value = Self::default(); + + for attr in attrs.iter().filter(|attr| attr.path().is_ident("err")) { + attr.parse_nested_meta(|meta| value.parse(meta))?; + } + + Ok(value) + } + + fn parse(&mut self, meta: ParseNestedMeta<'_>) -> syn::Result<()> { + if meta.path.is_ident("label") { + let content; + parenthesized!(content in meta.input); + self.label = Some((true, content.parse()?)); + } + + Ok(()) + } +} diff --git a/crates/stef-derive/src/cause.rs b/crates/stef-derive/src/cause.rs new file mode 100644 index 0000000..24a746a --- /dev/null +++ b/crates/stef-derive/src/cause.rs @@ -0,0 +1,482 @@ +use std::fmt::Write; + +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use syn::{ + spanned::Spanned, Data, DeriveInput, Field, Fields, GenericArgument, Index, PathArguments, + Type, TypePath, Variant, +}; + +use crate::attributes::{EnumAttributes, FieldAttributes, VariantAttributes}; + +macro_rules! bail { + ($tokens:expr, $($arg:tt)*) => { + return Err(syn::Error::new($tokens.span(), format!($($arg)*))) + }; +} + +pub fn expand(derive: DeriveInput) -> syn::Result { + let ident = &derive.ident; + let span = derive.span(); + + let Data::Enum(data) = derive.data else { + bail!(derive, "only enums supported") + }; + + let Some(first) = data.variants.first() else { + bail!(data.variants, "enum musn't be empty"); + }; + + is_parser_variant(first)?; + + let attrs = EnumAttributes::parse(&derive.attrs, span)?; + + let variants = data + .variants + .iter() + .skip(1) + .map(VariantInfo::parse) + .collect::>>()?; + + let error_impl = expand_error(ident, &variants)?; + let miette_impl = expand_miette(ident, &attrs, &variants)?; + let winnow_impl = expand_winnow(ident, &variants)?; + + Ok(quote! { + #error_impl + #miette_impl + #winnow_impl + }) +} + +fn is_parser_variant(variant: &Variant) -> syn::Result<()> { + if variant.ident != "Parser" { + bail!(variant, "first variant must be named `Parser`"); + } + + let Fields::Unnamed(fields) = &variant.fields else { + bail!(variant, "first variant must be unnamed"); + }; + + let Some(field) = fields.unnamed.first().filter(|_| fields.unnamed.len() == 1) else { + bail!( + fields, + "first variant must only contain a single unnamed field" + ); + }; + + let Type::Path(ty) = &field.ty else { + bail!(field, "first variant type invalid"); + }; + + if !compare_path(ty, &[&["ErrorKind"], &["winnow", "error", "ErrorKind"]]) { + bail!(ty, "first variant type must be `ErrorKind`"); + } + + Ok(()) +} + +fn compare_path(ty: &TypePath, paths: &[&[&str]]) -> bool { + ty.qself.is_none() + && paths.iter().any(|&path| { + ty.path.segments.len() == path.len() + && ty + .path + .segments + .iter() + .zip(path) + .all(|(a, b)| a.arguments.is_none() && a.ident == b) + }) +} + +struct VariantInfo<'a> { + variant: &'a Variant, + attr: VariantAttributes, + fields: Vec<(&'a Field, Option)>, +} + +impl<'a> VariantInfo<'a> { + fn parse(variant: &'a Variant) -> syn::Result { + Ok(Self { + variant, + attr: VariantAttributes::parse(&variant.attrs, variant.span())?, + fields: match &variant.fields { + Fields::Named(fields) => fields + .named + .iter() + .map(|f| Ok((f, FieldAttributes::parse(&f.attrs)?))) + .collect::>>()?, + Fields::Unnamed(fields) => fields + .unnamed + .iter() + .map(|f| Ok((f, FieldAttributes::parse(&f.attrs)?))) + .collect::>>()?, + _ => Vec::new(), + }, + }) + } +} + +fn expand_error(ident: &Ident, variants: &[VariantInfo<'_>]) -> syn::Result { + let sources = variants.iter().map(|v| { + let ident = &v.variant.ident; + + match &v.attr { + VariantAttributes::Error { .. } => quote! { + Self::#ident { .. } => None + }, + VariantAttributes::External => { + if v.fields.len() == 1 { + quote! { + Self::#ident(inner) => Some(inner) + } + } else { + quote! { + Self::#ident { cause, .. } => Some(cause) + } + } + } + VariantAttributes::Forward => quote! { + Self::#ident(inner) => inner.source() + }, + } + }); + + let fmts = variants.iter().map(|v| { + let ident = &v.variant.ident; + + match &v.attr { + VariantAttributes::Error { msg, .. } => quote! { + Self::#ident { .. } => f.write_str(#msg) + }, + VariantAttributes::External => { + if v.fields.len() == 1 { + quote! { + Self::#ident(inner) => inner.fmt(f) + } + } else { + quote! { + Self::#ident { cause, ..} => cause.fmt(f) + } + } + } + VariantAttributes::Forward => quote! { + Self::#ident(inner) => inner.fmt(f) + }, + } + }); + + let froms = variants.iter().filter_map(|v| { + matches!(v.attr, VariantAttributes::Forward).then(|| { + let variant_ident = &v.variant.ident; + let Fields::Unnamed(fields) = &v.variant.fields else { + panic!("expected unnamed fields") + }; + let ty = &fields.unnamed[0].ty; + + let boxed_type = match ty { + Type::Path(ty) => ty.path.segments.first().and_then(|seg| { + if seg.ident != "Box" { + return None; + } + + match &seg.arguments { + PathArguments::AngleBracketed(args) if args.args.len() == 1 => { + match &args.args[0] { + GenericArgument::Type(ty) => Some(ty), + _ => None, + } + } + _ => None, + } + }), + _ => None, + }; + + let from_boxed = boxed_type.map(|ty| { + quote! { + impl From<#ty> for #ident { + fn from(source: #ty) -> Self { + Self::#variant_ident(Box::new(source)) + } + } + } + }); + + quote! { + impl From<#ty> for #ident { + fn from(source: #ty) -> Self { + Self::#variant_ident(source) + } + } + + #from_boxed + } + }) + }); + + Ok(quote! { + impl std::error::Error for #ident { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Parser(kind) => None, + #(#sources,)* + } + } + } + + impl std::fmt::Display for #ident { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Parser(kind) => kind.fmt(f), + #(#fmts,)* + } + } + } + + #(#froms)* + }) +} + +fn expand_miette( + ident: &Ident, + attrs: &EnumAttributes, + variants: &[VariantInfo<'_>], +) -> syn::Result { + let codes = variants.iter().map(|v| { + let ident = &v.variant.ident; + + match &v.attr { + VariantAttributes::Error { code, .. } => { + let code = code.segments.iter().fold(String::new(), |mut acc, seg| { + if !acc.is_empty() { + acc.push_str("::"); + } + write!(&mut acc, "{}", seg.ident).ok(); + acc + }); + + quote! { + Self::#ident { .. } => Some(Box::new(#code)) + } + } + VariantAttributes::External => quote! { + Self::#ident { .. } => None + }, + VariantAttributes::Forward => quote! { + Self::#ident(inner) => inner.code() + }, + } + }); + + let helps = variants.iter().map(|v| { + let ident = &v.variant.ident; + + let fields = v.fields.iter().enumerate().map(|(i, (field, _))| { + field + .ident + .clone() + .unwrap_or_else(|| quote::format_ident!("_{i}")) + }); + + match &v.attr { + VariantAttributes::Error { help, .. } => quote! { + #[allow(unused_variables)] + Self::#ident { #(#fields,)* } => Some(Box::new(format!(#help))) + }, + VariantAttributes::External => quote! { + Self::#ident { .. } => None + }, + VariantAttributes::Forward => quote! { + Self::#ident(inner) => inner.help() + }, + } + }); + + let labels = variants.iter().map(|v| { + let ident = &v.variant.ident; + + match &v.attr { + VariantAttributes::Error {.. } => { + let fields = v + .fields + .iter() + .enumerate() + .filter_map(|(i, (f, info))| info.as_ref().map(|info| (i, f, info))) + .filter_map(|(i, field, info)| { + info.label.0.then(|| { + let idx = Index::from(i); + let name = field + .ident + .clone() + .unwrap_or_else(|| quote::format_ident!("_{i}")); + let text = info.label.1.as_ref().map(|s| quote!{ Some(#s.to_owned()) }); + ( + if field.ident.is_some() { + quote! { #name } + } else { + quote! { #idx: #name } + }, + quote! { + miette::LabeledSpan::new_with_span(#text, #name.clone()) + }, + ) + }) + }) + .collect::>(); + + let fields_names = fields.iter().map(|(name, _)| name); + let fields_contents = fields.iter().map(|(_, content)| content); + + quote! { + #[allow(unused_variables)] + Self::#ident { #(#fields_names,)* .. } => Some(Box::new(vec![#(#fields_contents,)*].into_iter())) + } + } + VariantAttributes::External => quote! { + Self::#ident { .. } => None + }, + VariantAttributes::Forward => quote! { + Self::#ident(inner) => inner.labels() + }, + } + }); + + let relateds = variants.iter().map(|v| { + let ident = &v.variant.ident; + + if matches!(v.attr, VariantAttributes::Forward) { + quote! { + Self::#ident(inner) => inner.related() + } + } else { + quote! { + Self::#ident { .. } => None + } + } + }); + + let enum_ident = attrs.rename.as_ref().unwrap_or(ident); + + let urls = variants.iter().map(|v| { + let ident = &v.variant.ident; + + match &v.attr { + VariantAttributes::Error { .. } => { + let item = format!("enum.{enum_ident}.html#variant.{ident}"); + + quote! { + Self::#ident { .. } => Some(Box::new( + concat!( + "https://docs.rs/", + env!("CARGO_PKG_NAME"), "/", + env!("CARGO_PKG_VERSION"), "/", + env!("CARGO_CRATE_NAME"), + "/error/", + #item, + ) + )) + } + } + VariantAttributes::External => quote! { + Self::#ident { .. } => None + }, + VariantAttributes::Forward => quote! { + Self::#ident(inner) => inner.url() + }, + } + }); + + Ok(quote! { + impl miette::Diagnostic for #ident { + fn code(&self) -> Option> { + match self { + Self::Parser(_) => None, + #(#codes,)* + } + } + + fn help(&self) -> Option> { + match self { + Self::Parser(_) => None, + #(#helps,)* + } + } + + fn labels(&self) -> Option +'_>> { + match self { + Self::Parser(_) => None, + #(#labels,)* + } + } + + fn related(&self) -> Option + '_>> { + match self { + Self::Parser(_) => None, + #(#relateds,)* + } + } + + fn url(&self) -> Option> { + match self { + Self::Parser(_) => None, + #(#urls,)* + } + } + } + }) +} + +fn expand_winnow(ident: &Ident, variants: &[VariantInfo<'_>]) -> syn::Result { + let externals = variants.iter().filter_map(|v| { + (matches!(v.attr, VariantAttributes::External)).then(|| { + let variant_ident = &v.variant.ident; + + match v.fields.len() { + 1 => { + let ty = &v.fields[0].0.ty; + + Ok(quote! { + impl winnow::error::FromExternalError for #ident { + fn from_external_error(_: &I, _: winnow::error::ErrorKind, e: #ty) -> Self { + Self::#variant_ident(e) + } + } + }) + } + 2 => { + let ty = &v.fields[1].0.ty; + + Ok(quote! { + impl winnow::error::FromExternalError for #ident + where + I: winnow::stream::Location, + { + fn from_external_error(input: &I, _: winnow::error::ErrorKind, e: #ty) -> Self { + Self::#variant_ident { + at: input.location(), + cause: e, + } + } + } + }) + } + _ => bail!(v.variant, "external variants must have 1 or 2 fields only"), + } + }) + }).collect::>>()?; + + Ok(quote! { + impl winnow::error::ParserError for #ident { + fn from_error_kind(_: &I, kind: winnow::error::ErrorKind) -> Self { + Self::Parser(kind) + } + + fn append(self, _: &I, _: winnow::error::ErrorKind) -> Self { + self + } + } + + #(#externals)* + }) +} diff --git a/crates/stef-derive/src/error.rs b/crates/stef-derive/src/error.rs new file mode 100644 index 0000000..091839a --- /dev/null +++ b/crates/stef-derive/src/error.rs @@ -0,0 +1,175 @@ +use std::fmt::Write; + +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use syn::{spanned::Spanned, Data, DeriveInput, Field, Fields, FieldsNamed}; + +use crate::attributes::{FieldAttributes, StructAttributes}; + +macro_rules! bail { + ($tokens:expr, $($arg:tt)*) => { + return Err(syn::Error::new($tokens.span(), format!($($arg)*))) + }; +} + +pub fn expand(derive: DeriveInput) -> syn::Result { + let ident = &derive.ident; + let span = derive.span(); + + let Data::Struct(data) = derive.data else { + bail!(derive, "only structs supported") + }; + + let Fields::Named(FieldsNamed { named: fields, .. }) = data.fields else { + bail!(data.fields, "only named structs supported") + }; + + let Some(_cause_field) = fields.iter().find(|f| { + f.ident + .as_ref() + .map(|ident| ident == "cause") + .unwrap_or(false) + }) else { + bail!(fields, "struct musn't be empty"); + }; + + let info = StructInfo { + attr: StructAttributes::parse(&derive.attrs, span)?, + fields: fields + .iter() + .map(|f| Ok((f, FieldAttributes::parse(&f.attrs)?))) + .collect::>>()?, + }; + + let error_impl = expand_error(ident, &info)?; + let miette_impl = expand_miette(ident, &info)?; + + Ok(quote! { + #error_impl + #miette_impl + + impl ::winnow::error::ParserError for #ident { + fn from_error_kind(_: &I, kind: ::winnow::error::ErrorKind) -> Self { + Self{ + at: Default::default(), + cause: Cause::Parser(kind), + } + } + + fn append(self, _: &I, _: ::winnow::error::ErrorKind) -> Self { + self + } + } + }) +} + +struct StructInfo<'a> { + attr: StructAttributes, + fields: Vec<(&'a Field, Option)>, +} + +fn expand_error(ident: &Ident, info: &StructInfo<'_>) -> syn::Result { + let StructAttributes { msg, .. } = &info.attr; + + Ok(quote! { + impl std::error::Error for #ident { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.cause as &(dyn std::error::Error + 'static)) + } + } + + impl std::fmt::Display for #ident { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(#msg) + } + } + }) +} + +fn expand_miette(ident: &Ident, info: &StructInfo<'_>) -> syn::Result { + let StructAttributes { code, help, .. } = &info.attr; + + let code = code.segments.iter().fold(String::new(), |mut acc, seg| { + if !acc.is_empty() { + acc.push_str("::"); + } + write!(&mut acc, "{}", seg.ident).ok(); + acc + }); + + let struct_ident = info.attr.rename.as_ref().unwrap_or(ident); + + let url = { + let item = format!("struct.{struct_ident}.html"); + + quote! { + Some(Box::new( + concat!( + "https://docs.rs/", + env!("CARGO_PKG_NAME"), "/", + env!("CARGO_PKG_VERSION"), "/", + env!("CARGO_CRATE_NAME"), + "/error/", + #item, + ) + )) + } + }; + + let labels = info + .fields + .iter() + .enumerate() + .filter_map(|(i, (field, info))| { + info.as_ref().map(|info| { + let name = field + .ident + .clone() + .unwrap_or_else(|| quote::format_ident!("unnamed_{i}")); + let text = info + .label + .1 + .as_ref() + .map(|s| quote! { Some(#s.to_owned()) }); + ( + if field.ident.is_some() { + quote! { #name } + } else { + quote! { #i: #name } + }, + quote! { + miette::LabeledSpan::new_with_span(#text, #name.clone()) + }, + ) + }) + }) + .collect::>(); + + let labels_names = labels.iter().map(|v| &v.0); + let labels_contents = labels.iter().map(|v| &v.1); + + Ok(quote! { + impl miette::Diagnostic for #ident { + fn code(&self) -> Option> { + Some(Box::new(#code)) + } + + fn help(&self) -> Option> { + Some(Box::new(format!(#help))) + } + + fn labels(&self) -> Option +'_>> { + let Self { #(#labels_names,)* .. } = self; + Some(Box::new(vec![#(#labels_contents,)*].into_iter())) + } + + fn related(&self) -> Option + '_>> { + Some(Box::new(std::iter::once::<&dyn miette::Diagnostic>(&self.cause))) + } + + fn url(&self) -> Option> { + #url + } + } + }) +} diff --git a/crates/stef-derive/src/lib.rs b/crates/stef-derive/src/lib.rs new file mode 100644 index 0000000..b930453 --- /dev/null +++ b/crates/stef-derive/src/lib.rs @@ -0,0 +1,25 @@ +use syn::{parse_macro_input, DeriveInput}; + +mod attributes; +mod cause; +mod error; + +#[proc_macro_derive(ParserError, attributes(err, rename))] +pub fn parser_error(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + match error::expand(input) { + Ok(expanded) => expanded.into(), + Err(e) => e.into_compile_error().into(), + } +} + +#[proc_macro_derive(ParserErrorCause, attributes(err, external, forward, rename))] +pub fn parser_error_cause(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + match cause::expand(input) { + Ok(expanded) => expanded.into(), + Err(e) => e.into_compile_error().into(), + } +} diff --git a/crates/stef-parser/Cargo.toml b/crates/stef-parser/Cargo.toml new file mode 100644 index 0000000..c9ae261 --- /dev/null +++ b/crates/stef-parser/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "stef-parser" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +readme.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +include = ["src/**/*", "tests/**/*.rs"] + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] + +[dependencies] +miette.workspace = true +owo-colors = { version = "3.5.0", features = ["supports-colors"] } +stef-derive = { path = "../stef-derive" } +winnow = "0.5.15" + +[dev-dependencies] +indoc = "2.0.3" +insta = { version = "1.31.0", features = ["glob"] } +miette = { workspace = true, features = ["fancy-no-backtrace"] } + +[features] +debug = [] diff --git a/crates/stef-parser/LICENSE.md b/crates/stef-parser/LICENSE.md new file mode 120000 index 0000000..f0608a6 --- /dev/null +++ b/crates/stef-parser/LICENSE.md @@ -0,0 +1 @@ +../../LICENSE.md \ No newline at end of file diff --git a/crates/stef-parser/README.md b/crates/stef-parser/README.md new file mode 120000 index 0000000..fe84005 --- /dev/null +++ b/crates/stef-parser/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/crates/stef-parser/src/error.rs b/crates/stef-parser/src/error.rs new file mode 100644 index 0000000..51d1d59 --- /dev/null +++ b/crates/stef-parser/src/error.rs @@ -0,0 +1,178 @@ +//! Possible errors that can occur when parsing schema files. +//! +//! +//! The root element is the [`ParseSchemaError`], which forms a tree of errors down to a specific +//! error that caused parsing to fail. + +#![allow(missing_docs, clippy::module_name_repetitions)] + +use std::{ + error::Error, + fmt::{self, Display}, +}; + +use miette::Diagnostic; +use winnow::error::ErrorKind; + +pub use crate::parser::{ + ParseAliasCause, ParseAliasError, ParseAttributeCause, ParseAttributeError, ParseCommentCause, + ParseCommentError, ParseConstCause, ParseConstError, ParseEnumCause, ParseEnumError, + ParseFieldsCause, ParseFieldsError, ParseGenericsCause, ParseGenericsError, ParseIdCause, + ParseIdError, ParseImportCause, ParseImportError, ParseLiteralCause, ParseLiteralError, + ParseModuleCause, ParseModuleError, ParseStructCause, ParseStructError, ParseTypeCause, + ParseTypeError, +}; + +/// Reason why a `STEF` schema definition was invalid. +#[derive(Debug, Diagnostic)] +pub enum ParseSchemaError { + Parser(ErrorKind), + #[diagnostic(transparent)] + Definition(ParseDefinitionError), +} + +impl Error for ParseSchemaError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::Parser(kind) => kind.source(), + Self::Definition(inner) => inner.source(), + } + } +} + +impl Display for ParseSchemaError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Parser(kind) => kind.fmt(f), + Self::Definition(inner) => inner.fmt(f), + } + } +} + +impl From for ParseSchemaError { + fn from(value: ParseDefinitionError) -> Self { + Self::Definition(value) + } +} + +impl winnow::error::ParserError for ParseSchemaError { + fn from_error_kind(_: &I, kind: winnow::error::ErrorKind) -> Self { + Self::Parser(kind) + } + + fn append(self, _: &I, _: winnow::error::ErrorKind) -> Self { + self + } +} + +/// Reason why a single definition was invalid. +#[derive(Debug, Diagnostic)] +pub enum ParseDefinitionError { + Parser(ErrorKind), + #[diagnostic(transparent)] + Comment(ParseCommentError), + #[diagnostic(transparent)] + Attribute(ParseAttributeError), + #[diagnostic(transparent)] + Module(ParseModuleError), + #[diagnostic(transparent)] + Struct(ParseStructError), + #[diagnostic(transparent)] + Enum(ParseEnumError), + #[diagnostic(transparent)] + Const(ParseConstError), + #[diagnostic(transparent)] + Alias(ParseAliasError), + #[diagnostic(transparent)] + Import(ParseImportError), +} + +impl Error for ParseDefinitionError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::Parser(kind) => kind.source(), + Self::Comment(inner) => inner.source(), + Self::Attribute(inner) => inner.source(), + Self::Module(inner) => inner.source(), + Self::Struct(inner) => inner.source(), + Self::Enum(inner) => inner.source(), + Self::Const(inner) => inner.source(), + Self::Alias(inner) => inner.source(), + Self::Import(inner) => inner.source(), + } + } +} + +impl Display for ParseDefinitionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Parser(kind) => kind.fmt(f), + Self::Comment(inner) => inner.fmt(f), + Self::Attribute(inner) => inner.fmt(f), + Self::Module(inner) => inner.fmt(f), + Self::Struct(inner) => inner.fmt(f), + Self::Enum(inner) => inner.fmt(f), + Self::Const(inner) => inner.fmt(f), + Self::Alias(inner) => inner.fmt(f), + Self::Import(inner) => inner.fmt(f), + } + } +} + +impl From for ParseDefinitionError { + fn from(value: ParseCommentError) -> Self { + Self::Comment(value) + } +} + +impl From for ParseDefinitionError { + fn from(value: ParseAttributeError) -> Self { + Self::Attribute(value) + } +} + +impl From for ParseDefinitionError { + fn from(value: ParseModuleError) -> Self { + Self::Module(value) + } +} + +impl From for ParseDefinitionError { + fn from(value: ParseStructError) -> Self { + Self::Struct(value) + } +} + +impl From for ParseDefinitionError { + fn from(value: ParseEnumError) -> Self { + Self::Enum(value) + } +} + +impl From for ParseDefinitionError { + fn from(value: ParseConstError) -> Self { + Self::Const(value) + } +} + +impl From for ParseDefinitionError { + fn from(value: ParseAliasError) -> Self { + Self::Alias(value) + } +} + +impl From for ParseDefinitionError { + fn from(value: ParseImportError) -> Self { + Self::Import(value) + } +} + +impl winnow::error::ParserError for ParseDefinitionError { + fn from_error_kind(_: &I, kind: winnow::error::ErrorKind) -> Self { + Self::Parser(kind) + } + + fn append(self, _: &I, _: winnow::error::ErrorKind) -> Self { + self + } +} diff --git a/crates/stef-parser/src/ext.rs b/crates/stef-parser/src/ext.rs new file mode 100644 index 0000000..c6857ec --- /dev/null +++ b/crates/stef-parser/src/ext.rs @@ -0,0 +1,188 @@ +#![allow(clippy::inline_always)] + +use std::marker::PhantomData; + +use winnow::{stream::Location, PResult, Parser}; + +pub(crate) trait ParserExt { + fn map_err(self, map: G) -> MapErr + where + G: Fn(E) -> E2, + Self: Parser + Sized; + + fn map_err_loc(self, map: G) -> MapErrLoc + where + G: Fn(usize, E) -> E2, + Self: Parser + Sized; + + fn map_err_input(self, map: G) -> MapErrInput + where + G: Fn(I, E) -> E2, + Self: Parser + Sized; +} + +impl ParserExt for T { + #[inline(always)] + fn map_err(self, map: G) -> MapErr + where + G: Fn(E) -> E2, + Self: Parser + Sized, + { + MapErr::new(self, map) + } + + #[inline(always)] + fn map_err_loc(self, map: G) -> MapErrLoc + where + G: Fn(usize, E) -> E2, + Self: Parser + Sized, + { + MapErrLoc::new(self, map) + } + + #[inline(always)] + fn map_err_input(self, map: G) -> MapErrInput + where + G: Fn(I, E) -> E2, + Self: Parser + Sized, + { + MapErrInput::new(self, map) + } +} + +pub(crate) struct MapErr +where + F: Parser, + G: Fn(E) -> E2, +{ + parser: F, + map: G, + i: PhantomData, + o: PhantomData, + e: PhantomData, + e2: PhantomData, +} + +impl MapErr +where + F: Parser, + G: Fn(E) -> E2, +{ + #[inline(always)] + pub(crate) fn new(parser: F, map: G) -> Self { + Self { + parser, + map, + i: PhantomData, + o: PhantomData, + e: PhantomData, + e2: PhantomData, + } + } +} + +impl Parser for MapErr +where + F: Parser, + G: Fn(E) -> E2, +{ + #[inline] + fn parse_next(&mut self, i: &mut I) -> PResult { + match self.parser.parse_next(i) { + Ok(o) => Ok(o), + Err(e) => Err(e.map(|e| (self.map)(e))), + } + } +} + +pub(crate) struct MapErrLoc +where + F: Parser, + G: Fn(usize, E) -> E2, +{ + parser: F, + map: G, + i: PhantomData, + o: PhantomData, + e: PhantomData, + e2: PhantomData, +} + +impl MapErrLoc +where + F: Parser, + G: Fn(usize, E) -> E2, +{ + #[inline(always)] + pub(crate) fn new(parser: F, map: G) -> Self { + Self { + parser, + map, + i: PhantomData, + o: PhantomData, + e: PhantomData, + e2: PhantomData, + } + } +} + +impl Parser for MapErrLoc +where + F: Parser, + G: Fn(usize, E) -> E2, + I: Location, +{ + #[inline] + fn parse_next(&mut self, i: &mut I) -> PResult { + match self.parser.parse_next(i) { + Ok(o) => Ok(o), + Err(e) => Err(e.map(|e| (self.map)(i.location(), e))), + } + } +} + +pub(crate) struct MapErrInput +where + F: Parser, + G: Fn(I, E) -> E2, +{ + parser: F, + map: G, + i: PhantomData, + o: PhantomData, + e: PhantomData, + e2: PhantomData, +} + +impl MapErrInput +where + F: Parser, + G: Fn(I, E) -> E2, +{ + #[inline(always)] + pub(crate) fn new(parser: F, map: G) -> Self { + Self { + parser, + map, + i: PhantomData, + o: PhantomData, + e: PhantomData, + e2: PhantomData, + } + } +} + +impl Parser for MapErrInput +where + F: Parser, + G: Fn(I, E) -> E2, + I: Copy, +{ + #[inline] + fn parse_next(&mut self, i: &mut I) -> PResult { + match self.parser.parse_next(i) { + Ok(o) => Ok(o), + Err(e) => Err(e.map(|e| (self.map)(*i, e))), + } + } +} diff --git a/crates/stef-parser/src/highlight.rs b/crates/stef-parser/src/highlight.rs new file mode 100644 index 0000000..c4e2efc --- /dev/null +++ b/crates/stef-parser/src/highlight.rs @@ -0,0 +1,23 @@ +use std::fmt::Display; + +use owo_colors::{OwoColorize, Stream}; + +pub fn sample(value: impl Display) -> String { + if cfg!(feature = "debug") { + format!("❬B❭{value}❬B❭") + } else { + value + .if_supports_color(Stream::Stderr, OwoColorize::bright_blue) + .to_string() + } +} + +pub fn value(value: impl Display) -> String { + if cfg!(feature = "debug") { + format!("❬Y❭{value}❬Y❭") + } else { + value + .if_supports_color(Stream::Stderr, OwoColorize::bright_yellow) + .to_string() + } +} diff --git a/crates/stef-parser/src/lib.rs b/crates/stef-parser/src/lib.rs new file mode 100644 index 0000000..86abfc5 --- /dev/null +++ b/crates/stef-parser/src/lib.rs @@ -0,0 +1,885 @@ +//! Parser for `STEF` schema files. +//! +//! The main entry point for schema parsing is the [`Schema::parse`] function. For convenience, the +//! [`from_str`] and [`from_slice`] functions are provided. +//! +//! # Example +//! +//! Parse a basic `STEF` schema and print it back out. +//! +//! ``` +//! let schema = stef_parser::Schema::parse("struct Sample(u32 @1)").unwrap(); +//! +//! // Pretty print the schema itself +//! println!("{schema}"); +//! // Print the data structures themselves. +//! println!("{schema:#?}"); +//! ``` + +#![forbid(unsafe_code)] +#![deny(rust_2018_idioms, clippy::all)] +#![warn(missing_docs, clippy::pedantic)] + +use std::fmt::{self, Display}; + +pub use miette::{Diagnostic, LabeledSpan}; +use miette::{IntoDiagnostic, Report, Result}; +use winnow::Parser; + +pub mod error; +mod ext; +mod highlight; +mod location; +mod parser; + +trait Print { + /// Default indentation, 4 spaces. + const INDENT: &'static str = " "; + + /// Write to the given formatter (like [`Display::fmt`]) but in addition, take the current + /// indentation level into account. + fn print(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result; + + /// Helper to write out the indentation for the given level. + fn indent(f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { + for _ in 0..level { + f.write_str(Self::INDENT)?; + } + + Ok(()) + } +} + +/// Shorthand for calling [`Schema::parse`]. +/// +/// # Errors +/// +/// Fails if the schema is not proper. The returned error will try to describe the problem as +/// precise as possible. +pub fn from_str(schema: &str) -> Result> { + Schema::parse(schema) +} + +/// Shorthand for calling [`Schema::parse`], but converts the byte slice to valid [`&str`] first. +/// +/// # Errors +/// +/// Fails if the schema is not proper. The returned error will try to describe the problem as +/// precise as possible. Or, in case the given bytes are not valid UTF-8. +pub fn from_slice(schema: &[u8]) -> Result> { + let s = std::str::from_utf8(schema).into_diagnostic()?; + Schema::parse(s) +} + +/// Uppermost element, describing a single _`STEF` Schema_ file. +#[derive(Debug, PartialEq)] +pub struct Schema<'a> { + /// List of all the definitions that make up the schema. + pub definitions: Vec>, +} + +impl<'a> Schema<'a> { + /// Try to parse the given schema. + /// + /// # Errors + /// + /// Fails if the schema is not proper. The returned error will try to describe the problem as + /// precise as possible. + pub fn parse(input: &'a str) -> Result { + parser::parse_schema + .parse(winnow::Located::new(input)) + .map_err(|e| Report::new(e.into_inner()).with_source_code(input.to_owned())) + } +} + +impl<'a> Display for Schema<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for definition in &self.definitions { + writeln!(f, "{definition}")?; + } + Ok(()) + } +} + +/// Possible elements that can appear inside a [`Schema`] or [`Module`]. +#[derive(Debug, PartialEq)] +pub enum Definition<'a> { + /// Module declaration to organize other definitions into scopes. + Module(Module<'a>), + /// Data structure. + Struct(Struct<'a>), + /// Enum definition. + Enum(Enum<'a>), + /// Type aliasing definition. + TypeAlias(TypeAlias<'a>), + /// Const value declaration. + Const(Const<'a>), + /// Import declaration of other schemas. + Import(Import<'a>), +} + +impl<'a> Print for Definition<'a> { + fn print(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { + match self { + Definition::Module(v) => v.print(f, level), + Definition::Struct(v) => v.print(f, level), + Definition::Enum(v) => v.print(f, level), + Definition::TypeAlias(v) => v.print(f, level), + Definition::Const(v) => v.print(f, level), + Definition::Import(v) => v.print(f, level), + } + } +} + +impl<'a> Display for Definition<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.print(f, 0) + } +} + +impl<'a> Definition<'a> { + fn with_comment(mut self, comment: Comment<'a>) -> Self { + match &mut self { + Definition::Module(m) => m.comment = comment, + Definition::Struct(s) => s.comment = comment, + Definition::Enum(e) => e.comment = comment, + Definition::TypeAlias(a) => a.comment = comment, + Definition::Const(c) => c.comment = comment, + Definition::Import(_) => {} + } + self + } + + fn with_attributes(mut self, attributes: Attributes<'a>) -> Self { + match &mut self { + Definition::Struct(s) => s.attributes = attributes, + Definition::Enum(e) => e.attributes = attributes, + Definition::Module(_) + | Definition::TypeAlias(_) + | Definition::Const(_) + | Definition::Import(_) => {} + } + self + } +} + +/// Scoping mechanism to categorize elements. +/// +/// ```txt +/// mod my_mod { +/// struct Sample(u32 @1) +/// } +/// ``` +#[derive(Debug, PartialEq)] +pub struct Module<'a> { + /// Optional module-level comment. + pub comment: Comment<'a>, + /// Unique name of the module, within the current scope. + pub name: &'a str, + /// List of definitions that are scoped within this module. + pub definitions: Vec>, +} + +impl<'a> Print for Module<'a> { + fn print(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { + let Self { + comment, + name, + definitions, + } = self; + + comment.print(f, level)?; + + Self::indent(f, level)?; + writeln!(f, "mod {name} {{")?; + + for (i, definition) in definitions.iter().enumerate() { + if i > 0 { + f.write_str("\n")?; + } + definition.print(f, level + 1)?; + } + + Self::indent(f, level)?; + f.write_str("}\n") + } +} + +impl<'a> Display for Module<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.print(f, 0) + } +} + +/// Rust-ish struct. +/// +/// ```txt +/// /// Named +/// struct Sample { +/// value: u32 @1, +/// } +/// +/// /// Unnamed +/// struct Sample(u32 @1) +/// +/// /// Unit +/// struct Sample +/// ``` +#[derive(Debug, PartialEq)] +pub struct Struct<'a> { + /// Optional struct-level comment. + pub comment: Comment<'a>, + /// Optional attributes to customize the behavior. + pub attributes: Attributes<'a>, + /// Unique name for this struct (within its scope). + pub name: &'a str, + /// Potential generics. + pub generics: Generics<'a>, + /// Fields of the struct, if any. + pub fields: Fields<'a>, +} + +impl<'a> Print for Struct<'a> { + fn print(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { + let indent = Self::INDENT.repeat(level); + let Self { + comment, + attributes, + name, + generics, + fields: kind, + } = self; + + comment.print(f, level)?; + attributes.print(f, level)?; + write!(f, "{indent}struct {name}{generics}")?; + kind.print(f, level)?; + f.write_str("\n") + } +} + +impl<'a> Display for Struct<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.print(f, 0) + } +} + +/// Rust-ish enum. +/// +/// ```txt +/// /// Optional comment +/// enum Sample { +/// /// Unit variant +/// One @1, +/// /// Unnamed (tuple) variant +/// Two(u8 @1) @2, +/// /// Named (struct) variant +/// Three { +/// value: u8 @1, +/// } @3, +/// } +/// ``` +#[derive(Debug, PartialEq)] +pub struct Enum<'a> { + /// Optional enum-level comment. + pub comment: Comment<'a>, + /// Optional attributes to customize the behavior. + pub attributes: Attributes<'a>, + /// Unique name for this enum, within its current scope. + pub name: &'a str, + /// Potential generics. + pub generics: Generics<'a>, + /// List of possible variants that the enum can represent. + pub variants: Vec>, +} + +impl<'a> Print for Enum<'a> { + fn print(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { + let Self { + comment, + attributes, + name, + generics, + variants, + } = self; + + comment.print(f, level)?; + attributes.print(f, level)?; + + Self::indent(f, level)?; + writeln!(f, "enum {name}{generics} {{")?; + + for variant in variants { + variant.print(f, level + 1)?; + f.write_str("\n")?; + } + + Self::indent(f, level)?; + f.write_str("}\n") + } +} + +impl<'a> Display for Enum<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.print(f, 0) + } +} + +/// Single variant of an enum. +#[derive(Debug, Eq, PartialEq)] +pub struct Variant<'a> { + /// Optional variant-level comment. + pub comment: Comment<'a>, + /// Unique for this variant, within the enum it belongs to. + pub name: &'a str, + /// Fields of this variant, if any. + pub fields: Fields<'a>, + /// Identifier for this variant, that must be unique within the current enum. + pub id: Id, +} + +impl<'a> Print for Variant<'a> { + fn print(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { + let Self { + comment, + name, + fields, + id, + } = self; + + comment.print(f, level)?; + + Self::indent(f, level)?; + f.write_str(name)?; + + fields.print(f, level)?; + write!(f, " {id},") + } +} + +impl<'a> Display for Variant<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.print(f, 0) + } +} + +/// Alias (re-name) from one type to another. +/// +/// ```txt +/// /// Basic +/// type A = B; +/// +/// /// With generics +/// type A = hash_map; +/// ``` +#[derive(Debug, Eq, PartialEq)] +pub struct TypeAlias<'a> { + /// Optional comment. + pub comment: Comment<'a>, + /// New data type definition. + pub alias: DataType<'a>, + /// Original type that is being aliased. + pub target: DataType<'a>, +} + +impl<'a> Print for TypeAlias<'a> { + fn print(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { + let Self { + comment, + alias, + target, + } = self; + + comment.print(f, level)?; + + Self::indent(f, level)?; + write!(f, "type {alias} = {target};") + } +} + +/// Possible kinds in which a the fields of a struct or enum variant can be represented. +#[derive(Debug, Eq, PartialEq)] +pub enum Fields<'a> { + /// List of named fields. + /// + /// ```txt + /// Sample { + /// a: u8 @1, + /// b: bool @2, + /// c: i32 @3, + /// } + /// ``` + Named(Vec>), + /// List of types without an explicit name. + /// + /// ```txt + /// Sample(u8 @1, bool @2, i32 @3) + /// ``` + Unnamed(Vec>), + /// No attached value. + /// + /// ```txt + /// Sample + /// ``` + Unit, +} + +impl<'a> Print for Fields<'a> { + fn print(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { + match self { + Fields::Named(fields) => { + f.write_str(" {\n")?; + + for field in fields { + field.print(f, level + 1)?; + f.write_str(",\n")?; + } + + Self::indent(f, level)?; + f.write_str("}") + } + Fields::Unnamed(elements) => concat(f, "(", elements, ", ", ")"), + Fields::Unit => Ok(()), + } + } +} + +impl<'a> Display for Fields<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.print(f, 0) + } +} + +/// Single named field. +/// +/// ```txt +/// field: u32 @1 +/// ┬──── ┬── ┬─ +/// │ │ ╰─── ID +/// │ ╰─────── Type +/// ╰────────────── Name +/// ``` +#[derive(Debug, Eq, PartialEq)] +pub struct NamedField<'a> { + /// Optional field-level comment. + pub comment: Comment<'a>, + /// Unique name for this field, within the current element. + pub name: &'a str, + /// Data type that defines the shape of the contained data. + pub ty: DataType<'a>, + /// Identifier for this field, that must be unique within the current element. + pub id: Id, +} + +impl<'a> Print for NamedField<'a> { + fn print(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { + let Self { + comment, + name, + ty, + id, + } = self; + + comment.print(f, level)?; + + Self::indent(f, level)?; + write!(f, "{name}: {ty} {id}") + } +} + +impl<'a> Display for NamedField<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.print(f, 0) + } +} + +/// Single unnamed field. +/// +/// ```txt +/// u32 @1 +/// ┬── ┬─ +/// │ ╰─── ID +/// ╰─────── Type +/// ``` +#[derive(Debug, Eq, PartialEq)] +pub struct UnnamedField<'a> { + /// Data type that defines the shape of the contained data. + pub ty: DataType<'a>, + /// Identifier for this field, that must be unique within the current element. + pub id: Id, +} + +impl<'a> Display for UnnamedField<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { ty, id } = self; + write!(f, "{ty} {id}") + } +} + +/// Comments above any other element. +/// +/// ```txt +/// /// This is a comment. +/// ┬───────────────── +/// ╰─── Content +/// ``` +#[derive(Debug, Default, Eq, PartialEq)] +pub struct Comment<'a>(pub Vec<&'a str>); + +impl<'a> Print for Comment<'a> { + fn print(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { + let lines = &self.0; + + for line in lines { + Self::indent(f, level)?; + writeln!(f, "/// {line}")?; + } + + Ok(()) + } +} + +impl<'a> Display for Comment<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.print(f, 0) + } +} + +/// Collection of attributes, aggregated together into a single declaration block. +#[derive(Debug, Default, PartialEq)] +pub struct Attributes<'a>(pub Vec>); + +impl<'a> Print for Attributes<'a> { + fn print(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { + if self.0.is_empty() { + return Ok(()); + } + + let values = &self.0; + + Self::indent(f, level)?; + concat(f, "#[", values, ", ", "]\n") + } +} + +impl<'a> Display for Attributes<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.print(f, 0) + } +} + +/// Single attribute, that describes metadata for the attached element. +#[derive(Debug, PartialEq)] +pub struct Attribute<'a> { + /// Identifier of the attribute. + pub name: &'a str, + /// Potential value(s) associated with the attribute. + pub value: AttributeValue<'a>, +} + +impl<'a> Print for Attribute<'a> { + fn print(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { + let indent = Self::INDENT.repeat(level); + let Self { name, value } = self; + + write!(f, "{indent}{name}{value}") + } +} + +impl<'a> Display for Attribute<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.print(f, 0) + } +} + +/// Value of an [`Attribute`] that can take one of several shapes. +#[derive(Debug, PartialEq)] +pub enum AttributeValue<'a> { + /// No value, the attribute is representative by itself. + Unit, + /// Single literal value. + Single(Literal), + /// Multiple values, represented as sub-attributes. + Multi(Vec>), +} + +impl<'a> Print for AttributeValue<'a> { + fn print(&self, f: &mut fmt::Formatter<'_>, _level: usize) -> fmt::Result { + match self { + Self::Unit => Ok(()), + Self::Single(lit) => write!(f, " = {lit}"), + Self::Multi(attrs) => concat(f, "(", attrs, ", ", ")"), + } + } +} + +impl<'a> Display for AttributeValue<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.print(f, 0) + } +} + +/// Possible data type that describes the shape of a field. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum DataType<'a> { + /// Boolean `true` or `false`. + Bool, + /// 8-bit unsigned integer. + U8, + /// 16-bit unsigned integer. + U16, + /// 32-bit unsigned integer. + U32, + /// 64-bit unsigned integer. + U64, + /// 128-bit unsigned integer. + U128, + /// 8-bit signed integer. + I8, + /// 16-bit signed integer. + I16, + /// 32-bit signed integer. + I32, + /// 64-bit signed integer. + I64, + /// 128-bit signed integer. + I128, + /// 32-bit floating point number. + F32, + /// 64-bit floating point number. + F64, + /// UTF-8 encoded string. + String, + /// Reference version of an UTF-8 encoded string. + StringRef, + /// Vector of `u8` bytes. + Bytes, + /// Reference version (slice) of `u8` bytes. + BytesRef, + /// Vector of another data type. + Vec(Box>), + /// Key-value hash map of data types. + HashMap(Box<(DataType<'a>, DataType<'a>)>), + /// Hash set of data types (each entry is unique). + HashSet(Box>), + /// Optional value. + Option(Box>), + /// Non-zero value. + /// - Integers: `n > 0` + /// - Collections: `len() > 0` + NonZero(Box>), + /// Boxed version of a string that is immutable. + BoxString, + /// Boxed version of a byte vector that is immutable. + BoxBytes, + /// Fixed size list of up to 12 types. + Tuple(Vec>), + /// Continuous list of values with a single time and known length. + Array(Box>, u32), + /// Any external, non-standard data type (like a user defined struct or enum). + External(ExternalType<'a>), +} + +impl<'a> Display for DataType<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DataType::Bool => f.write_str("bool"), + DataType::U8 => f.write_str("u8"), + DataType::U16 => f.write_str("u16"), + DataType::U32 => f.write_str("u32"), + DataType::U64 => f.write_str("u64"), + DataType::U128 => f.write_str("u128"), + DataType::I8 => f.write_str("i8"), + DataType::I16 => f.write_str("i16"), + DataType::I32 => f.write_str("i32"), + DataType::I64 => f.write_str("i64"), + DataType::I128 => f.write_str("i128"), + DataType::F32 => f.write_str("f32"), + DataType::F64 => f.write_str("f64"), + DataType::String => f.write_str("string"), + DataType::StringRef => f.write_str("&string"), + DataType::Bytes => f.write_str("bytes"), + DataType::BytesRef => f.write_str("&bytes"), + DataType::Vec(t) => write!(f, "vec<{t}>"), + DataType::HashMap(kv) => write!(f, "hash_map<{}, {}>", kv.0, kv.1), + DataType::HashSet(t) => write!(f, "hash_set<{t}>"), + DataType::Option(t) => write!(f, "option<{t}>"), + DataType::NonZero(t) => write!(f, "non_zero<{t}>"), + DataType::BoxString => f.write_str("box"), + DataType::BoxBytes => f.write_str("box"), + DataType::Tuple(l) => concat(f, "(", l, ", ", ")"), + DataType::Array(t, size) => write!(f, "[{t}; {size}]"), + DataType::External(t) => t.fmt(f), + } + } +} + +/// Type that is not part of the built-in list of types. +/// +/// This is usually a user-defined type like a struct or an enum. However, this can be the name of +/// a generic as well, as the type's origin is unknown at this point. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ExternalType<'a> { + /// Optional path, if the type wasn't fully imported with a `use` statement. + pub path: Vec<&'a str>, + /// Unique name of the type within the current scope (or the module if prefixed with a path). + pub name: &'a str, + /// Potential generic type arguments. + pub generics: Vec>, +} + +impl<'a> Display for ExternalType<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + path, + name, + generics, + } = self; + + for segment in path { + write!(f, "{segment}::")?; + } + name.fmt(f)?; + concat(f, "<", generics, ", ", ">") + } +} + +/// Container of generic arguments for an element. +/// +/// ```txt +/// +/// ``` +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct Generics<'a>(pub Vec<&'a str>); + +impl<'a> Display for Generics<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + concat(f, "<", &self.0, ", ", ">") + } +} + +/// Unique identifier for an element. +/// +/// ```txt +/// @1 +/// ``` +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Id(pub u32); + +impl Display for Id { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self(id) = *self; + write!(f, "@{id}") + } +} + +/// Declaration of a constant value. +#[derive(Debug, PartialEq)] +pub struct Const<'a> { + /// Optional element-level comment. + pub comment: Comment<'a>, + /// Unique identifier of this constant. + pub name: &'a str, + /// Type of the value. + pub ty: DataType<'a>, + /// Literal value that this declaration represents. + pub value: Literal, +} + +impl<'a> Print for Const<'a> { + fn print(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { + let Self { + comment, + name, + ty, + value, + } = self; + + comment.print(f, level)?; + + Self::indent(f, level)?; + write!(f, "const {name}: {ty} = {value};") + } +} + +/// In-schema definition of a literal value. +#[derive(Clone, Debug, PartialEq)] +pub enum Literal { + /// Boolean `true` or `false` value. + Bool(bool), + /// Integer number. + Int(i128), + /// Floating point number. + Float(f64), + /// UTF-8 encoded string. + String(String), + /// Raw vector of bytes. + Bytes(Vec), +} + +impl Display for Literal { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Literal::Bool(v) => v.fmt(f), + Literal::Int(v) => v.fmt(f), + Literal::Float(v) => v.fmt(f), + Literal::String(ref v) => write!(f, "{v:?}"), + Literal::Bytes(ref v) => write!(f, "{v:?}"), + } + } +} + +/// Import declaration for an external schema. +#[derive(Debug, PartialEq)] +pub struct Import<'a> { + /// Individual elements that form the import path. + pub segments: Vec<&'a str>, + /// Optional final element that allows to fully import the type, making it look as it would be + /// defined in the current schema. + pub element: Option<&'a str>, +} + +impl<'a> Print for Import<'a> { + fn print(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result { + let Self { segments, element } = self; + + Self::indent(f, level)?; + f.write_str("use ")?; + + for (i, segment) in segments.iter().enumerate() { + if i > 0 { + f.write_str("::")?; + } + f.write_str(segment)?; + } + + if let Some(element) = element { + write!(f, "::{element}")?; + } + + f.write_str(";") + } +} + +fn concat( + f: &mut fmt::Formatter<'_>, + open: &str, + values: &[impl Display], + sep: &str, + close: &str, +) -> fmt::Result { + if values.is_empty() { + return Ok(()); + } + + f.write_str(open)?; + + for (i, value) in values.iter().enumerate() { + if i > 0 { + f.write_str(sep)?; + } + value.fmt(f)?; + } + + f.write_str(close) +} diff --git a/crates/stef-parser/src/location.rs b/crates/stef-parser/src/location.rs new file mode 100644 index 0000000..a15dba1 --- /dev/null +++ b/crates/stef-parser/src/location.rs @@ -0,0 +1,26 @@ +use std::ops::Range; + +use winnow::{ + stream::{Checkpoint, Location, Offset, Stream}, + Located, +}; + +pub fn from_until( + mut input: Located, + start: Checkpoint<::Checkpoint>, + chars: [char; N], +) -> Range +where + I: Clone + Offset + Stream, +{ + input.reset(start); + + let start = input.location(); + + let end = chars + .into_iter() + .find_map(|target| input.offset_for(|c| c == target)) + .map(|pos| pos + 1); + + start..end.unwrap_or(start) +} diff --git a/crates/stef-parser/src/parser.rs b/crates/stef-parser/src/parser.rs new file mode 100644 index 0000000..a2c0490 --- /dev/null +++ b/crates/stef-parser/src/parser.rs @@ -0,0 +1,197 @@ +use winnow::{ + ascii::{multispace0, space0}, + combinator::{fail, peek, preceded, repeat, terminated}, + dispatch, + error::ParserError, + prelude::*, + stream::{AsChar, Stream, StreamIsPartial}, + token::any, + trace::trace, +}; + +pub use self::{ + aliases::{Cause as ParseAliasCause, ParseError as ParseAliasError}, + attributes::{Cause as ParseAttributeCause, ParseError as ParseAttributeError}, + comments::{Cause as ParseCommentCause, ParseError as ParseCommentError}, + consts::{Cause as ParseConstCause, ParseError as ParseConstError}, + enums::{Cause as ParseEnumCause, ParseError as ParseEnumError}, + fields::{Cause as ParseFieldsCause, ParseError as ParseFieldsError}, + generics::{Cause as ParseGenericsCause, ParseError as ParseGenericsError}, + ids::{Cause as ParseIdCause, ParseError as ParseIdError}, + imports::{Cause as ParseImportCause, ParseError as ParseImportError}, + literals::{Cause as ParseLiteralCause, ParseError as ParseLiteralError}, + modules::{Cause as ParseModuleCause, ParseError as ParseModuleError}, + structs::{Cause as ParseStructCause, ParseError as ParseStructError}, + types::{Cause as ParseTypeCause, ParseError as ParseTypeError}, +}; +use crate::{ + error::{ParseDefinitionError, ParseSchemaError}, + ext::ParserExt, + Definition, Schema, +}; + +mod aliases; +mod attributes; +mod consts; +mod enums; +mod fields; +mod generics; +mod imports; +mod literals; +mod modules; +mod structs; +mod types; + +type Input<'i> = winnow::Located<&'i str>; +type Result = winnow::PResult; + +pub(crate) fn parse_schema<'i>(input: &mut Input<'i>) -> Result, ParseSchemaError> { + trace( + "schema", + terminated( + repeat(0.., parse_definition.map_err(Into::into)), + multispace0, + ), + ) + .parse_next(input) + .map(|definitions| Schema { definitions }) +} + +fn parse_definition<'i>(input: &mut Input<'i>) -> Result, ParseDefinitionError> { + ( + ws(comments::parse.map_err(Into::into)), + ws(attributes::parse.map_err(Into::into)), + preceded( + space0, + dispatch! { + peek(any); + 'm' => modules::parse.map(Definition::Module).map_err(Into::into), + 's' => structs::parse.map(Definition::Struct).map_err(Into::into), + 'e' => enums::parse.map(Definition::Enum).map_err(Into::into), + 'c' => consts::parse.map(Definition::Const).map_err(Into::into), + 't' => aliases::parse.map(Definition::TypeAlias).map_err(Into::into), + 'u' => imports::parse.map(Definition::Import).map_err(Into::into), + _ => fail, + }, + ), + ) + .parse_next(input) + .map(|(comment, attributes, def)| def.with_comment(comment).with_attributes(attributes)) +} + +mod ids { + use std::ops::Range; + + use stef_derive::{ParserError, ParserErrorCause}; + use winnow::{ + ascii::dec_uint, combinator::preceded, error::ErrorKind, stream::Location, Parser, + }; + + use super::{Input, Result}; + use crate::{highlight, Id}; + + /// Encountered an invalid `@...` id declaration. + #[derive(Debug, ParserError)] + #[err( + msg("Failed to parse id declaration"), + code(stef::parse::id), + help("Expected id declaration in the form `{}`", highlight::sample("@..."),) + )] + #[rename(ParseIdError)] + pub struct ParseError { + /// Source location of the whole id. + #[err(label("In this declaration"))] + pub at: Range, + /// Specific cause of the error. + pub cause: Cause, + } + + /// Specific reason why a `@...` id declaration was invalid. + #[derive(Debug, ParserErrorCause)] + pub enum Cause { + /// Non-specific general parser error. + Parser(ErrorKind), + } + + pub(super) fn parse(input: &mut Input<'_>) -> Result { + preceded('@', dec_uint) + .parse_next(input) + .map(Id) + .map_err(|e| { + e.map(|e: ErrorKind| ParseError { + at: input.location()..input.location(), + cause: Cause::Parser(e), + }) + }) + } +} + +mod comments { + use std::ops::Range; + + use stef_derive::{ParserError, ParserErrorCause}; + use winnow::{ + ascii::space0, + combinator::{preceded, repeat, terminated}, + error::ErrorKind, + stream::Stream, + token::take_till0, + Parser, + }; + + use super::{Input, Result}; + use crate::{highlight, location, Comment}; + + /// Encountered an invalid `/// ...` comment declaration. + #[derive(Debug, ParserError)] + #[err( + msg("Failed to parse comment declaration"), + code(stef::parse::comment), + help( + "Expected comment declaration in the form `{}`", + highlight::sample("/// ..."), + ) + )] + #[rename(ParseCommentError)] + pub struct ParseError { + /// Source location of the whole comment. + #[err(label("In this declaration"))] + pub at: Range, + /// Specific cause of the error. + pub cause: Cause, + } + + /// Specific reason why a `/// ...` comment declaration was invalid. + #[derive(Debug, ParserErrorCause)] + pub enum Cause { + /// Non-specific general parser error. + Parser(ErrorKind), + } + + pub(super) fn parse<'i>(input: &mut Input<'i>) -> Result, ParseError> { + let start = input.checkpoint(); + + repeat( + 0.., + preceded((space0, "///", space0), terminated(take_till0('\n'), '\n')), + ) + .parse_next(input) + .map(Comment) + .map_err(|e| { + e.map(|cause| ParseError { + at: location::from_until(*input, start, ['\n']), + cause, + }) + }) + } +} + +#[inline] +fn ws>(inner: F) -> impl Parser +where + I: Stream + StreamIsPartial, + ::Token: AsChar + Clone, + F: Parser, +{ + trace("ws", preceded(multispace0, inner)) +} diff --git a/crates/stef-parser/src/parser/aliases.rs b/crates/stef-parser/src/parser/aliases.rs new file mode 100644 index 0000000..4a38001 --- /dev/null +++ b/crates/stef-parser/src/parser/aliases.rs @@ -0,0 +1,69 @@ +use std::ops::Range; + +use stef_derive::{ParserError, ParserErrorCause}; +use winnow::{ + ascii::{space0, space1}, + combinator::{cut_err, delimited, preceded}, + error::ErrorKind, + stream::Location, + Parser, +}; + +use super::{types, Input, ParserExt, Result}; +use crate::{highlight, Comment, TypeAlias}; + +/// Encountered an invalid `type` alias declaration. +#[derive(Debug, ParserError)] +#[err( + msg("Failed to parse type alias declaration"), + code(stef::parse::alias_def), + help( + "Expected type alias declaration in the form `{}`", + highlight::sample("type = ;"), + ) +)] +#[rename(ParseAliasError)] +pub struct ParseError { + /// Source location of the whole id. + #[err(label("In this declaration"))] + pub at: Range, + /// Specific cause of the error. + pub cause: Cause, +} + +/// Specific reason why a `type` alias declaration was invalid. +#[derive(Debug, ParserErrorCause)] +#[rename(ParseAliasCause)] +pub enum Cause { + /// Non-specific general parser error. + Parser(ErrorKind), + /// Invalid type declaration. + #[forward] + Type(types::ParseError), +} + +pub(super) fn parse<'i>(input: &mut Input<'i>) -> Result, ParseError> { + preceded( + ("type", space1), + cut_err(( + types::parse.map_err(Cause::from), + delimited( + (space0, '='), + preceded(space0, types::parse.map_err(Cause::from)), + (space0, ';'), + ), + )), + ) + .parse_next(input) + .map(|(alias, target)| TypeAlias { + comment: Comment::default(), + alias, + target, + }) + .map_err(|e| { + e.map(|cause| ParseError { + at: input.location()..input.location(), + cause, + }) + }) +} diff --git a/crates/stef-parser/src/parser/attributes.rs b/crates/stef-parser/src/parser/attributes.rs new file mode 100644 index 0000000..203d072 --- /dev/null +++ b/crates/stef-parser/src/parser/attributes.rs @@ -0,0 +1,126 @@ +use std::ops::Range; + +use stef_derive::{ParserError, ParserErrorCause}; +use winnow::{ + ascii::space0, + combinator::{alt, cut_err, fold_repeat, opt, preceded, separated1, terminated}, + error::ErrorKind, + stream::Location, + token::{one_of, take_while}, + Parser, +}; + +use super::{literals, ws, Input, ParserExt, Result}; +use crate::{highlight, Attribute, AttributeValue, Attributes, Literal}; + +/// Encountered an invalid `#[...]` attribute declaration. +#[derive(Debug, ParserError)] +#[err( + msg("Failed to parse struct declaration"), + code(stef::parse::struct_def), + help( + "Expected struct declaration in the form `{}`", + highlight::sample("struct {...}"), + ) +)] +#[rename(ParseAttributeError)] +pub struct ParseError { + /// Source location of the whole attribute. + #[err(label("In this declaration"))] + pub at: Range, + /// Specific cause of the error. + pub cause: Cause, +} + +/// Specific reason why a `#[...]` attribute declaration was invalid. +#[derive(Debug, ParserErrorCause)] +#[rename(ParseAttributeCause)] +pub enum Cause { + /// Non-specific general parser error. + Parser(ErrorKind), + /// Invalid literal for the attribute value. + #[forward] + Literal(literals::ParseError), +} + +pub(super) fn parse<'i>(input: &mut Input<'i>) -> Result, ParseError> { + let start = input.location(); + + fold_repeat( + 0.., + terminated(parse_attribute, '\n'), + Vec::new, + |mut acc, attrs| { + acc.extend(attrs); + acc + }, + ) + .parse_next(input) + .map(Attributes) + .map_err(|e| { + e.map(|cause| ParseError { + at: start..start, + cause, + }) + }) +} + +fn parse_attribute<'i>(input: &mut Input<'i>) -> Result>, Cause> { + preceded( + "#[", + cut_err(terminated( + terminated( + separated1( + ws((parse_name, parse_value)).map(|(name, value)| Attribute { name, value }), + ws(','), + ), + opt(','), + ), + ws(']'), + )), + ) + .parse_next(input) +} + +fn parse_name<'i>(input: &mut Input<'i>) -> Result<&'i str, Cause> { + ( + one_of('a'..='z'), + take_while(0.., ('a'..='z', '0'..='9', '_')), + ) + .recognize() + .parse_next(input) +} + +fn parse_value<'i>(input: &mut Input<'i>) -> Result, Cause> { + alt(( + parse_multi_value.map(AttributeValue::Multi), + parse_single_value.map(AttributeValue::Single), + parse_unit_value.map(|()| AttributeValue::Unit), + )) + .parse_next(input) +} + +fn parse_multi_value<'i>(input: &mut Input<'i>) -> Result>, Cause> { + preceded( + '(', + cut_err(terminated( + terminated( + separated1( + ws((parse_name, parse_value)).map(|(name, value)| Attribute { name, value }), + ws(','), + ), + opt(','), + ), + ws(')'), + )), + ) + .parse_next(input) +} + +fn parse_single_value(input: &mut Input<'_>) -> Result { + preceded((space0, '=', space0), literals::parse.map_err(Cause::from)).parse_next(input) +} + +fn parse_unit_value(input: &mut Input<'_>) -> Result<(), Cause> { + ().parse_next(input) +} diff --git a/crates/stef-parser/src/parser/consts.rs b/crates/stef-parser/src/parser/consts.rs new file mode 100644 index 0000000..adbd927 --- /dev/null +++ b/crates/stef-parser/src/parser/consts.rs @@ -0,0 +1,137 @@ +use std::ops::Range; + +use stef_derive::{ParserError, ParserErrorCause}; +use winnow::{ + ascii::{space0, space1}, + combinator::{cut_err, delimited, preceded, terminated}, + error::ErrorKind, + stream::{Location, Stream}, + token::{one_of, take_while}, + Parser, +}; + +use super::{literals, types, Input, ParserExt, Result}; +use crate::{highlight, location, Comment, Const}; + +/// Encountered an invalid `const` declaration. +#[derive(Debug, ParserError)] +#[err( + msg("Failed to parse const declaration"), + code(stef::parse::const_def), + help( + "Expected const declaration in the form `{}`", + highlight::sample("const : = ;"), + ) +)] +#[rename(ParseConstError)] +pub struct ParseError { + /// Source location of the whole const. + #[err(label("In this declaration"))] + pub at: Range, + /// Specific cause of the error. + pub cause: Cause, +} + +/// Specific reason why a `const` declaration was invalid. +#[derive(Debug, ParserErrorCause)] +#[rename(ParseConstCause)] +pub enum Cause { + /// Non-specific general parser error. + Parser(ErrorKind), + #[err( + msg("Unexpected character"), + code(stef::parse::const_def::char), + help("Expected a `{}` here", highlight::value(expected)) + )] + /// Encountered an unexpected character. + UnexpectedChar { + /// Source location of the character. + #[err(label("Problematic character"))] + at: usize, + /// The character that was expected instead. + expected: char, + }, + /// Defined name is not considered valid. + #[err( + msg("Invalid const name"), + code(stef::parse::const_def::invalid_name), + help( + "Const names must start with an uppercase letter ({}), followed by zero or more \ + uppercase alphanumeric characters or underscores ({})", + highlight::value("A-Z"), + highlight::value("A-Z, 0-9, _"), + ) + )] + InvalidName { + /// Source location of the character. + #[err(label("Problematic character"))] + at: usize, + }, + /// Invalid type declaration. + #[forward] + Type(types::ParseError), + /// Invalid const value literal. + #[forward] + Literal(literals::ParseError), +} + +pub(super) fn parse<'i>(input: &mut Input<'i>) -> Result, ParseError> { + let start = input.checkpoint(); + + preceded( + ("const", space1), + cut_err(( + terminated(parse_name, (':', space0)), + types::parse.map_err(Cause::from), + delimited( + (space0, '=', space0), + literals::parse.map_err(Cause::from), + ';'.map_err_loc(|at, ()| Cause::UnexpectedChar { at, expected: ';' }), + ), + )), + ) + .parse_next(input) + .map(|(name, ty, value)| Const { + comment: Comment::default(), + name, + ty, + value, + }) + .map_err(|e| { + e.map(|cause| ParseError { + at: location::from_until(*input, start, [';']), + cause, + }) + }) +} + +pub(super) fn parse_name<'i>(input: &mut Input<'i>) -> Result<&'i str, Cause> { + ( + one_of('A'..='Z'), + take_while(0.., ('A'..='Z', '0'..='9', '_')), + ) + .recognize() + .parse_next(input) + .map_err(|e| { + e.map(|()| Cause::InvalidName { + at: input.location(), + }) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn print_const() { + let err = ParseError { + at: (0..21), + cause: Cause::InvalidName { at: 6 }, + }; + println!( + "{:?}", + miette::Report::from(err).with_source_code("const vALUE: u32 = 1;") + ); + } +} diff --git a/crates/stef-parser/src/parser/enums.rs b/crates/stef-parser/src/parser/enums.rs new file mode 100644 index 0000000..ebe34a2 --- /dev/null +++ b/crates/stef-parser/src/parser/enums.rs @@ -0,0 +1,177 @@ +use std::ops::Range; + +use stef_derive::{ParserError, ParserErrorCause}; +use winnow::{ + ascii::{alphanumeric0, space0, space1}, + combinator::{cut_err, opt, preceded, separated1, terminated}, + error::ErrorKind, + stream::Location, + token::one_of, + Parser, +}; + +use super::{comments, fields, generics, ids, ws, Input, ParserExt, Result}; +use crate::{highlight, Attributes, Comment, Enum, Variant}; + +/// Encountered an invalid `enum` declaration. +#[derive(Debug, ParserError)] +#[err( + msg("Failed to parse enum declaration"), + code(stef::parse::enum_def), + help( + "Expected enum declaration in the form `{}`", + highlight::sample("enum {...}"), + ) +)] +#[rename(ParseEnumError)] +pub struct ParseError { + /// Source location of the whole enum. + #[err(label("In this declaration"))] + pub at: Range, + /// Specific cause of the error. + pub cause: Cause, +} + +/// Specific reason why a `enum` declaration was invalid. +#[derive(Debug, ParserErrorCause)] +#[rename(ParseEnumCause)] +pub enum Cause { + /// Non-specific general parser error. + Parser(ErrorKind), + /// Defined name is not considered valid. + #[err( + msg("Invalid enum name"), + code(stef::parse::enum_def::invalid_name), + help( + "Enum names must start with an uppercase letter ({}), followed by zero or more \ + alphanumeric characters ({})", + highlight::value("A-Z"), + highlight::value("A-Z, a-z, 0-9"), + ) + )] + InvalidName { + /// Source location of the character. + #[err(label("Problematic character"))] + at: usize, + }, + /// Defined variant name is not considered valid. + #[err( + msg("Invalid variant name"), + code(stef::parse::enum_def::invalid_name), + help( + "Variant names must start with an uppercase letter ({}), followed by zero or more \ + alphanumeric characters ({})", + highlight::value("A-Z"), + highlight::value("A-Z, a-z, 0-9"), + ) + )] + InvalidVariantName { + /// Source location of the character. + #[err(label("Problematic character"))] + at: usize, + }, + /// Invalid generic definition of the enum. + #[forward] + Generics(generics::ParseError), + /// Invalid field in a named variant. + #[forward] + Field(fields::ParseError), + /// Failed to parse the comments of a variant. + #[forward] + Comment(comments::ParseError), + /// Invalid variant identifier. + #[forward] + Id(ids::ParseError), +} + +pub(super) fn parse<'i>(input: &mut Input<'i>) -> Result, ParseError> { + preceded( + ("enum", space1), + cut_err(( + parse_name, + opt(generics::parse.map_err(Cause::from)).map(Option::unwrap_or_default), + preceded(space0, parse_variants), + )), + ) + .parse_next(input) + .map(|(name, generics, variants)| Enum { + comment: Comment::default(), + attributes: Attributes::default(), + name, + generics, + variants, + }) + .map_err(|e| { + e.map(|cause| ParseError { + at: input.location()..input.location(), + cause, + }) + }) +} + +pub(super) fn parse_name<'i>(input: &mut Input<'i>) -> Result<&'i str, Cause> { + (one_of('A'..='Z'), alphanumeric0) + .recognize() + .parse_next(input) + .map_err(|e| { + e.map(|()| Cause::InvalidName { + at: input.location(), + }) + }) +} + +fn parse_variants<'i>(input: &mut Input<'i>) -> Result>, Cause> { + preceded( + '{', + cut_err(terminated( + terminated(separated1(ws(parse_variant), ws(',')), opt(ws(','))), + ws('}'), + )), + ) + .parse_next(input) +} + +fn parse_variant<'i>(input: &mut Input<'i>) -> Result, Cause> { + ( + comments::parse.map_err(Cause::from), + preceded(space0, parse_variant_name), + preceded(space0, fields::parse.map_err(Cause::from)), + preceded(space0, ids::parse.map_err(Cause::from)), + ) + .parse_next(input) + .map(|(comment, name, fields, id)| Variant { + comment, + name, + fields, + id, + }) +} + +fn parse_variant_name<'i>(input: &mut Input<'i>) -> Result<&'i str, Cause> { + (one_of('A'..='Z'), alphanumeric0) + .recognize() + .parse_next(input) + .map_err(|e| { + e.map(|()| Cause::InvalidVariantName { + at: input.location(), + }) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn print_enum() { + let err = ParseError { + at: (0..31), + cause: Cause::InvalidName { at: 5 }, + }; + + println!( + "{:?}", + miette::Report::from(err).with_source_code("enum sample {\n Variant @1,\n}") + ); + } +} diff --git a/crates/stef-parser/src/parser/fields.rs b/crates/stef-parser/src/parser/fields.rs new file mode 100644 index 0000000..154e9ba --- /dev/null +++ b/crates/stef-parser/src/parser/fields.rs @@ -0,0 +1,152 @@ +use std::ops::Range; + +use stef_derive::{ParserError, ParserErrorCause}; +use winnow::{ + ascii::space0, + combinator::{cut_err, delimited, opt, peek, preceded, separated1, terminated}, + dispatch, + error::ErrorKind, + stream::{Location, Stream}, + token::{any, one_of, take_while}, + Parser, +}; + +use super::{comments, ids, types, ws, Input, ParserExt, Result}; +use crate::{highlight, location, Fields, NamedField, UnnamedField}; + +/// Encountered an invalid field declaration. +#[derive(Debug, ParserError)] +#[err( + msg("Failed to parse fields declaration"), + code(stef::parse::id), + help( + "Expected fields declaration in the form `{}`, `{}` or `{}`", + highlight::sample("{ , , ... }"), + highlight::sample("( , , ... )"), + highlight::sample("_nothing_"), + ) +)] +#[rename(ParseFieldsError)] +pub struct ParseError { + /// Source location of the whole id. + #[err(label("In this declaration"))] + pub at: Range, + /// Specific cause of the error. + pub cause: Cause, +} + +/// Specific reason why a fields declaration was invalid. +#[derive(Debug, ParserErrorCause)] +#[rename(ParseFieldsCause)] +pub enum Cause { + /// Non-specific general parser error. + Parser(ErrorKind), + /// Defined name is not considered valid. + #[err( + msg("Invalid field name"), + code(stef::parse::fields::named::invalid_name), + help( + "Field names must start with a lowercase letter ({}), followed by zero or more \ + lowercase alphanumeric characters or underscores ({})", + highlight::value("a-z"), + highlight::value("a-z, 0-9, _"), + ) + )] + InvalidName { + /// Source location of the character. + #[err(label("Problematic character"))] + at: usize, + }, + /// Invalid type declaration. + #[forward] + Type(types::ParseError), + /// Invalid field identifier. + #[forward] + Id(ids::ParseError), + /// Failed parsing field comments. + #[forward] + Comment(comments::ParseError), +} + +pub(super) fn parse<'i>(input: &mut Input<'i>) -> Result, ParseError> { + let start = input.checkpoint(); + + dispatch!( + peek(any); + '{' => parse_named.map(Fields::Named), + '(' => parse_unnamed.map(Fields::Unnamed), + _ => parse_unit.map(|()| Fields::Unit), + ) + .parse_next(input) + .map_err(|e| { + e.map(|cause| ParseError { + at: location::from_until(*input, start, [',', '\n']), + cause, + }) + }) +} + +fn parse_named<'i>(input: &mut Input<'i>) -> Result>, Cause> { + preceded( + '{', + cut_err(terminated( + terminated(separated1(parse_named_field, ws(',')), opt(ws(','))), + ws('}'), + )), + ) + .parse_next(input) +} + +fn parse_unnamed<'i>(input: &mut Input<'i>) -> Result>, Cause> { + preceded( + '(', + cut_err(terminated( + terminated(separated1(parse_unnamed_field, ws(',')), opt(ws(','))), + ws(')'), + )), + ) + .parse_next(input) +} + +fn parse_unit(input: &mut Input<'_>) -> Result<(), Cause> { + ().parse_next(input) +} + +fn parse_unnamed_field<'i>(input: &mut Input<'i>) -> Result, Cause> { + ( + ws(types::parse.map_err(Cause::from)), + ws(cut_err(ids::parse.map_err(Cause::from))), + ) + .parse_next(input) + .map(|(ty, id)| UnnamedField { ty, id }) +} + +fn parse_named_field<'i>(input: &mut Input<'i>) -> Result, Cause> { + ( + ws(comments::parse.map_err(Cause::from)), + delimited(space0, parse_field_name, ':'), + preceded(space0, types::parse.map_err(Cause::from)), + preceded(space0, ids::parse.map_err(Cause::from)), + ) + .parse_next(input) + .map(|(comment, name, ty, id)| NamedField { + comment, + name, + ty, + id, + }) +} + +fn parse_field_name<'i>(input: &mut Input<'i>) -> Result<&'i str, Cause> { + ( + one_of('a'..='z'), + take_while(0.., ('a'..='z', '0'..='9', '_')), + ) + .recognize() + .parse_next(input) + .map_err(|e| { + e.map(|()| Cause::InvalidName { + at: input.location(), + }) + }) +} diff --git a/crates/stef-parser/src/parser/generics.rs b/crates/stef-parser/src/parser/generics.rs new file mode 100644 index 0000000..ae32b6d --- /dev/null +++ b/crates/stef-parser/src/parser/generics.rs @@ -0,0 +1,74 @@ +use std::ops::Range; + +use stef_derive::{ParserError, ParserErrorCause}; +use winnow::{ + ascii::alphanumeric0, + combinator::{cut_err, preceded, separated1, terminated}, + error::ErrorKind, + stream::Location, + token::one_of, + Parser, +}; + +use super::{ws, Input, Result}; +use crate::{highlight, Generics}; + +/// Encountered an invalid `<...>` generics declaration. +#[derive(Debug, ParserError)] +#[err( + msg("Failed to parse generics declaration"), + code(stef::parse::generics), + help( + "Expected generics declaration in the form `{}`", + highlight::sample(""), + ) +)] +#[rename(ParseGenericsError)] +pub struct ParseError { + /// Source location of the whole comment. + #[err(label("In this declaration"))] + pub at: Range, + /// Specific cause of the error. + pub cause: Cause, +} + +/// Specific reason why a `<...>` generics declaration was invalid. +#[derive(Debug, ParserErrorCause)] +#[rename(ParseGenericsCause)] +pub enum Cause { + /// Non-specific general parser error. + Parser(ErrorKind), + /// Defined name is not considered valid. + #[err(msg("TODO!"), code(stef::parse::generics::invalid_name), help("TODO!"))] + InvalidName { + /// Source location of the character. + #[err(label("Problematic character"))] + at: usize, + }, +} + +pub(super) fn parse<'i>(input: &mut Input<'i>) -> Result, ParseError> { + preceded( + '<', + cut_err(terminated(separated1(ws(parse_name), ws(',')), ws('>'))), + ) + .parse_next(input) + .map(Generics) + .map_err(|e| { + e.map(|cause| ParseError { + at: input.location()..input.location(), + cause, + }) + }) +} + +fn parse_name<'i>(input: &mut Input<'i>) -> Result<&'i str, Cause> { + (one_of('A'..='Z'), alphanumeric0) + .recognize() + .parse_next(input) + .map_err(|e| { + e.map(|()| Cause::InvalidName { + at: input.location(), + }) + }) +} diff --git a/crates/stef-parser/src/parser/imports.rs b/crates/stef-parser/src/parser/imports.rs new file mode 100644 index 0000000..a80267f --- /dev/null +++ b/crates/stef-parser/src/parser/imports.rs @@ -0,0 +1,106 @@ +use std::ops::Range; + +use stef_derive::{ParserError, ParserErrorCause}; +use winnow::{ + ascii::space1, + combinator::{alt, cut_err, opt, preceded, separated1, terminated}, + error::ErrorKind, + stream::{Location, Stream}, + token::{one_of, take_while}, + Parser, +}; + +use super::{enums, structs, Input, ParserExt, Result}; +use crate::{highlight, location, Import}; + +/// Encountered an invalid `use` declaration. +#[derive(Debug, ParserError)] +#[err( + msg("Failed to parse use declaration"), + code(stef::parse::use_def), + help( + "Expected import declaration in the form `{}`", + highlight::sample("use ::;"), + ) +)] +#[rename(ParseImportError)] +pub struct ParseError { + /// Source location of the whole use statement. + #[err(label("In this declaration"))] + pub at: Range, + /// Specific cause of the error. + pub cause: Cause, +} + +/// Specific reason why a `use` declaration was invalid. +#[derive(Debug, ParserErrorCause)] +#[rename(ParseImportCause)] +pub enum Cause { + /// Non-specific general parser error. + Parser(ErrorKind), + /// Defined name is not considered valid. + #[err( + msg("Invalid segment name"), + code(stef::parse::import::segment::invalid_name), + help( + "Import path names must start with a lowercase letter ({}), followed by zero or more \ + lowercase alphanumeric characters or underscores ({})", + highlight::value("a-z"), + highlight::value("a-z, 0-9, _"), + ) + )] + InvalidSegmentName { + /// Source location of the character. + #[err(label("Problematic character"))] + at: usize, + }, + /// Invalid struct name. + #[forward] + StructName(structs::Cause), + /// Invalid enum name. + #[forward] + EnumName(enums::Cause), +} + +pub(super) fn parse<'i>(input: &mut Input<'i>) -> Result, ParseError> { + let start = input.checkpoint(); + + preceded( + ("use", space1), + cut_err(terminated( + ( + separated1(parse_segment, "::"), + opt(preceded( + "::", + alt(( + structs::parse_name.map_err(Cause::from), + enums::parse_name.map_err(Cause::from), + )), + )), + ), + ';', + )), + ) + .parse_next(input) + .map(|(segments, element)| Import { segments, element }) + .map_err(|e| { + e.map(|cause| ParseError { + at: location::from_until(*input, start, [';']), + cause, + }) + }) +} + +pub(super) fn parse_segment<'i>(input: &mut Input<'i>) -> Result<&'i str, Cause> { + ( + one_of('a'..='z'), + take_while(0.., ('a'..='z', '0'..='9', '_')), + ) + .recognize() + .parse_next(input) + .map_err(|e| { + e.map(|()| Cause::InvalidSegmentName { + at: input.location(), + }) + }) +} diff --git a/crates/stef-parser/src/parser/literals.rs b/crates/stef-parser/src/parser/literals.rs new file mode 100644 index 0000000..e093558 --- /dev/null +++ b/crates/stef-parser/src/parser/literals.rs @@ -0,0 +1,248 @@ +use std::ops::Range; + +use stef_derive::{ParserError, ParserErrorCause}; +use winnow::{ + ascii::{dec_uint, digit1, multispace1}, + combinator::{ + alt, cut_err, delimited, fail, fold_repeat, opt, peek, preceded, separated1, terminated, + }, + dispatch, + error::ErrorKind, + stream::Location, + token::{any, one_of, take_till1, take_while}, + Parser, +}; + +use super::{ws, Input, Result}; +use crate::{highlight, Literal}; + +/// Encountered an invalid literal declaration. +#[derive(Debug, ParserError)] +#[err( + msg("Failed to parse literal value"), + code(stef::parse::literal), + help( + "Expected literal value declaration in either of the forms:\n`{}` or `{}` for \ + booleans\n`{}` for numbers\n`{}` for floating point numbers\n`{}` for strings\nor `{}` \ + for bytes", + highlight::sample("true"), + highlight::sample("false"), + highlight::sample("1, 2, 3, ..."), + highlight::sample("1.2, 1.0e5, ..."), + highlight::sample("\"...\""), + highlight::sample("[...]"), + ) +)] +#[rename(ParseLiteralError)] +pub struct ParseError { + /// Source location of the whole literal. + #[err(label("In this declaration"))] + pub at: Range, + /// Specific cause of the error. + pub cause: Cause, +} + +/// Specific reason why a literal declaration was invalid. +#[derive(Debug, ParserErrorCause)] +#[rename(ParseLiteralCause)] +pub enum Cause { + /// Non-specific general parser error. + Parser(ErrorKind), + /// Found a reference value, which is not allowed. + #[err( + msg("Found a reference value"), + code(stef::parse::literal::reference), + help( + "References are not allowed for literals. Try removing the ampersand ({})", + highlight::value("&"), + ) + )] + FoundReference { + /// Source location of the character. + #[err(label("Problematic character"))] + at: usize, + }, + /// Encountered an invalid integer literal. + #[err( + msg("Invalid integer literal"), + code(stef::parse::literal::int), + help("Integers must only consist of digits ({})", highlight::value("0-9")) + )] + InvalidInt { + /// Source location of the character. + #[err(label("Problematic character"))] + at: usize, + }, + /// Failed to parse value as integer. + #[external] + ParseInt { + /// Source location of the character. + #[err(label("Problematic character"))] + at: usize, + /// Inner error that cause the failure. + cause: std::num::ParseIntError, + }, +} + +pub(super) fn parse(input: &mut Input<'_>) -> Result { + let start = input.location(); + + dispatch! { + peek(any); + 't' => parse_true.map(Literal::Bool), + 'f' => parse_false.map(Literal::Bool), + '+' | '-' | '0'..='9' => cut_err(alt(( + parse_float.map(Literal::Float), + parse_int.map(Literal::Int) + ))), + '"' => parse_string.map(Literal::String), + '[' => parse_bytes.map(Literal::Bytes), + '&' => fail, + _ => fail, + } + .parse_next(input) + .map_err(|e| { + e.map(|cause| ParseError { + at: start..start, + cause, + }) + }) +} + +fn parse_true(input: &mut Input<'_>) -> Result { + "true".value(true).parse_next(input) +} + +fn parse_false(input: &mut Input<'_>) -> Result { + "false".value(false).parse_next(input) +} + +fn parse_float(input: &mut Input<'_>) -> Result { + ( + opt(one_of(['+', '-'])), + (digit1, '.', digit1), + opt((one_of(['e', 'E']), opt(one_of(['+', '-'])), cut_err(digit1))), + ) + .recognize() + .parse_to() + .parse_next(input) +} + +fn parse_int(input: &mut Input<'_>) -> Result { + (opt(one_of(['+', '-'])), digit1) + .recognize() + .parse_to() + .parse_next(input) + .map_err(|e| { + e.map(|()| Cause::InvalidInt { + at: input.location(), + }) + }) +} + +fn parse_string(input: &mut Input<'_>) -> Result { + preceded( + '"', + cut_err(terminated( + fold_repeat(0.., parse_fragment, String::new, |mut acc, fragment| { + match fragment { + Fragment::Literal(s) => acc.push_str(s), + Fragment::EscapedChar(c) => acc.push(c), + Fragment::EscapedWhitespace => {} + } + acc + }), + '"', + )), + ) + .parse_next(input) +} + +enum Fragment<'a> { + Literal(&'a str), + EscapedChar(char), + EscapedWhitespace, +} + +fn parse_fragment<'i>(input: &mut Input<'i>) -> Result, Cause> { + alt(( + parse_string_literal.map(Fragment::Literal), + parse_string_escaped_char.map(Fragment::EscapedChar), + parse_string_escaped_whitespace.map(|_| Fragment::EscapedWhitespace), + )) + .parse_next(input) +} + +fn parse_string_literal<'i>(input: &mut Input<'i>) -> Result<&'i str, Cause> { + take_till1(['"', '\\']) + .verify(|s: &str| !s.is_empty()) + .parse_next(input) +} + +fn parse_string_escaped_char(input: &mut Input<'_>) -> Result { + preceded( + '\\', + dispatch!( + peek(any); + 'n' => 'n'.value('\n'), + 'r' => 'r'.value('\r'), + 't' => 't'.value('\t'), + 'b' => 'b'.value('\u{08}'), + 'f' => 'f'.value('\u{0c}'), + '\\' => '\\'.value('\\'), + '\0' => '\0'.value('\0'), + '/' => '/'.value('/'), + '"' => '"'.value('"'), + 'u' => parse_string_unicode, + _ => fail, + ), + ) + .parse_next(input) +} + +fn parse_string_unicode(input: &mut Input<'_>) -> Result { + preceded( + 'u', + cut_err(delimited( + '{', + take_while(1..=6, ('0'..='9', 'a'..='f', 'A'..='F')), + '}', + )), + ) + .try_map(|hex| u32::from_str_radix(hex, 16)) + .verify_map(std::char::from_u32) + .parse_next(input) +} + +fn parse_string_escaped_whitespace<'i>(input: &mut Input<'i>) -> Result<&'i str, Cause> { + preceded('\\', multispace1).parse_next(input) +} + +fn parse_bytes(input: &mut Input<'_>) -> Result, Cause> { + preceded( + '[', + cut_err(terminated( + separated1(ws(dec_uint::<_, u8, _>), ws(',')), + (opt(ws(',')), ws(']')), + )), + ) + .parse_next(input) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn error_found_reference() { + let err = ParseError { + at: (22..29), + cause: Cause::FoundReference { at: 22 }, + }; + + println!( + "{:?}", + miette::Report::from(err).with_source_code("const VALUE: string = &\"test\";") + ); + } +} diff --git a/crates/stef-parser/src/parser/modules.rs b/crates/stef-parser/src/parser/modules.rs new file mode 100644 index 0000000..a57d405 --- /dev/null +++ b/crates/stef-parser/src/parser/modules.rs @@ -0,0 +1,104 @@ +use std::ops::Range; + +use stef_derive::{ParserError, ParserErrorCause}; +use winnow::{ + ascii::{space0, space1}, + combinator::{cut_err, preceded, repeat, terminated}, + error::ErrorKind, + stream::{Location, Stream}, + token::{one_of, take_while}, + Parser, +}; + +use super::{parse_definition, ws, Input, ParserExt, Result}; +use crate::{error::ParseDefinitionError, highlight, location, Comment, Module}; + +/// Encountered an invalid `mod` declaration. +#[derive(Debug, ParserError)] +#[err( + msg("Failed to parse id declaration"), + code(stef::parse::mod_def), + help( + "Expected module declaration in the form `{}`", + highlight::sample("mod {...}"), + ) +)] +#[rename(ParseModuleError)] +pub struct ParseError { + /// Source location of the whole module. + #[err(label("In this declaration"))] + pub at: Range, + /// Specific cause of the error. + pub cause: Cause, +} + +/// Specific reason why a `mod` declaration was invalid. +#[derive(Debug, ParserErrorCause)] +#[rename(ParseModuleCause)] +pub enum Cause { + /// Non-specific general parser error. + Parser(ErrorKind), + /// Defined name is not considered valid. + #[err( + msg("Invalid module name"), + code(stef::parse::module::invalid_name), + help( + "Module names must start with a lowercase letter ({}), followed by zero or more \ + lowercase alphanumeric characters or underscores ({})", + highlight::value("a-z"), + highlight::value("a-z, 0-9, _"), + ) + )] + InvalidName { + /// Source location of the character. + #[err(label("Problematic character"))] + at: usize, + }, + /// Invalid definition of any element within the module. + #[forward] + Definition(Box), +} + +pub(super) fn parse<'i>(input: &mut Input<'i>) -> Result, ParseError> { + let start = input.checkpoint(); + + preceded( + ("mod", space1), + cut_err(( + parse_name, + preceded( + (space0, '{'), + terminated( + repeat(0.., ws(parse_definition.map_err(Cause::from))), + ws('}'), + ), + ), + )), + ) + .parse_next(input) + .map(|(name, definitions)| Module { + comment: Comment::default(), + name, + definitions, + }) + .map_err(|e| { + e.map(|cause| ParseError { + at: location::from_until(*input, start, ['}', '\n']), + cause, + }) + }) +} + +fn parse_name<'i>(input: &mut Input<'i>) -> Result<&'i str, Cause> { + ( + one_of('a'..='z'), + take_while(0.., ('a'..='z', '0'..='9', '_')), + ) + .recognize() + .parse_next(input) + .map_err(|e| { + e.map(|()| Cause::InvalidName { + at: input.location(), + }) + }) +} diff --git a/crates/stef-parser/src/parser/structs.rs b/crates/stef-parser/src/parser/structs.rs new file mode 100644 index 0000000..86d84d2 --- /dev/null +++ b/crates/stef-parser/src/parser/structs.rs @@ -0,0 +1,119 @@ +use std::ops::Range; + +use stef_derive::{ParserError, ParserErrorCause}; +use winnow::{ + ascii::{alphanumeric0, space0, space1}, + combinator::{cut_err, opt, preceded}, + error::ErrorKind, + stream::{Location, Stream}, + token::one_of, + Parser, +}; + +use super::{fields, generics, Input, ParserExt, Result}; +use crate::{highlight, location, Attributes, Comment, Struct}; + +/// Encountered an invalid `struct` declaration. +#[derive(Debug, ParserError)] +#[err( + msg("Failed to parse struct declaration"), + code(stef::parse::struct_def), + help( + "Expected struct declaration in the form `{}`", + highlight::sample("struct {...}"), + ) +)] +#[rename(ParseStructError)] +pub struct ParseError { + /// Source location of the whole struct. + #[err(label("In this declaration"))] + pub at: Range, + /// Specific cause of the error. + pub cause: Cause, +} + +/// Specific reason why a `struct` declaration was invalid. +#[derive(Debug, ParserErrorCause)] +#[rename(ParseStructCause)] +pub enum Cause { + /// Non-specific general parser error. + Parser(ErrorKind), + /// Defined name is not considered valid. + #[err( + msg("Invalid struct name"), + code(stef::parse::struct_def::invalid_name), + help( + "Struct names must start with an uppercase letter ({}), followed by zero or more \ + alphanumeric characters ({})", + highlight::value("A-Z"), + highlight::value("A-Z, a-z, 0-9"), + ) + )] + InvalidName { + /// Source location of the character. + #[err(label("Problematic character"))] + at: usize, + }, + /// Invalid sturct generics declaration. + #[forward] + Generics(super::generics::ParseError), + /// Invalid declaration of struct fields. + #[forward] + Fields(fields::ParseError), +} + +pub(super) fn parse<'i>(input: &mut Input<'i>) -> Result, ParseError> { + let start = input.checkpoint(); + + preceded( + ("struct", space1), + cut_err(( + parse_name, + opt(generics::parse.map_err(Cause::Generics)).map(Option::unwrap_or_default), + preceded(space0, fields::parse.map_err(Cause::Fields)), + )), + ) + .parse_next(input) + .map(|(name, generics, kind)| Struct { + comment: Comment::default(), + attributes: Attributes::default(), + name, + generics, + fields: kind, + }) + .map_err(|e| { + e.map(|cause| ParseError { + at: location::from_until(*input, start, ['}', '\n']), + cause, + }) + }) +} + +pub(super) fn parse_name<'i>(input: &mut Input<'i>) -> Result<&'i str, Cause> { + (one_of('A'..='Z'), alphanumeric0) + .recognize() + .parse_next(input) + .map_err(|e| { + e.map(|()| Cause::InvalidName { + at: input.location(), + }) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn print_struct() { + let err = ParseError { + at: (0..36), + cause: Cause::InvalidName { at: 7 }, + }; + + println!( + "{:?}", + miette::Report::from(err).with_source_code("struct sample {\n value: u32 @1,\n}") + ); + } +} diff --git a/crates/stef-parser/src/parser/types.rs b/crates/stef-parser/src/parser/types.rs new file mode 100644 index 0000000..cb0fca0 --- /dev/null +++ b/crates/stef-parser/src/parser/types.rs @@ -0,0 +1,178 @@ +use std::ops::Range; + +use stef_derive::{ParserError, ParserErrorCause}; +use winnow::{ + ascii::{alphanumeric0, dec_uint, space0}, + combinator::{ + alt, cut_err, fail, opt, preceded, separated1, separated_pair, success, terminated, + }, + dispatch, + error::ErrorKind, + stream::Location, + token::{one_of, tag, take_while}, + Parser, +}; + +use super::{imports, ws, Input, ParserExt, Result}; +use crate::{highlight, DataType, ExternalType}; + +/// Encountered an invalid type definition. +#[derive(Debug, ParserError)] +#[err( + msg("Failed to parse type definition"), + code(stef::parse::type_def), + help( + "Expected type definition in the form `{}`", + highlight::sample(""), + ) +)] +#[rename(ParseTypeError)] +pub struct ParseError { + /// Source location of the whole type definition. + #[err(label("In this declaration"))] + pub at: Range, + /// Specific cause of the error. + pub cause: Cause, +} + +/// Specific reason why a type definition was invalid. +#[derive(Debug, ParserErrorCause)] +#[rename(ParseTypeCause)] +pub enum Cause { + /// Non-specific general parser error. + Parser(ErrorKind), + /// Invalid type declaration. + #[forward] + Type(Box), + /// Invalid path segment. + #[forward] + Segment(Box), +} + +pub(super) fn parse<'i>(input: &mut Input<'i>) -> Result, ParseError> { + let start = input.location(); + + alt(( + parse_basic, + parse_generic, + parse_tuple, + parse_array, + parse_external.map(DataType::External), + )) + .parse_next(input) + .map_err(|e| { + e.map(|cause| ParseError { + at: start..input.location(), + cause, + }) + }) +} + +fn parse_basic<'i>(input: &mut Input<'i>) -> Result, Cause> { + alt(( + dispatch! { + take_while(2.., ('a'..='z', '0'..='9', '&')); + "bool" => success(DataType::Bool), + "u8" => success(DataType::U8), + "u16" => success(DataType::U16), + "u32" => success(DataType::U32), + "u64" => success(DataType::U64), + "u128" => success(DataType::U128), + "i8" => success(DataType::I8), + "i16" => success(DataType::I16), + "i32" => success(DataType::I32), + "i64" => success(DataType::I64), + "i128" => success(DataType::I128), + "f32" => success(DataType::F32), + "f64" => success(DataType::F64), + "string" => success(DataType::String), + "&string" => success(DataType::StringRef), + "bytes" => success(DataType::Bytes), + "&bytes" => success(DataType::BytesRef), + _ => fail, + }, + tag("box").value(DataType::BoxString), + tag("box").value(DataType::BoxBytes), + )) + .parse_next(input) +} + +fn parse_generic<'i>(input: &mut Input<'i>) -> Result, Cause> { + terminated( + dispatch! { + terminated(take_while(3.., ('a'..='z', '_')), '<'); + "vec" => cut_err(parse.map_err(Cause::from)) + .map(|t| DataType::Vec(Box::new(t))), + "hash_map" => cut_err(separated_pair( + parse.map_err(Cause::from), + (',', space0), + parse.map_err(Cause::from), + )) + .map(|kv| DataType::HashMap(Box::new(kv))), + "hash_set" => cut_err(parse.map_err(Cause::from)) + .map(|t| DataType::HashSet(Box::new(t))), + "option" => cut_err(parse.map_err(Cause::from)) + .map(|t| DataType::Option(Box::new(t))), + "non_zero" => cut_err(parse.map_err(Cause::from)) + .map(|t| DataType::NonZero(Box::new(t))), + _ => fail, + }, + '>', + ) + .parse_next(input) +} + +fn parse_tuple<'i>(input: &mut Input<'i>) -> Result, Cause> { + preceded( + '(', + cut_err(terminated( + separated1(ws(parse.map_err(Cause::from)), ws(',')), + ws(')'), + )), + ) + .parse_next(input) + .map(DataType::Tuple) +} + +fn parse_array<'i>(input: &mut Input<'i>) -> Result, Cause> { + preceded( + '[', + cut_err(terminated( + separated_pair(ws(parse.map_err(Cause::from)), ws(';'), ws(dec_uint)), + ws(']'), + )), + ) + .parse_next(input) + .map(|(t, size)| DataType::Array(Box::new(t), size)) +} + +fn parse_external<'i>(input: &mut Input<'i>) -> Result, Cause> { + ( + opt(terminated( + separated1(imports::parse_segment.map_err(Cause::from), "::"), + "::", + )) + .map(Option::unwrap_or_default), + parse_external_name, + opt(preceded( + '<', + cut_err(terminated( + separated1(ws(parse.map_err(Cause::from)), ws(',')), + ws('>'), + )), + )) + .map(Option::unwrap_or_default), + ) + .parse_next(input) + .map(|(path, name, generics)| ExternalType { + path, + name, + generics, + }) +} + +fn parse_external_name<'i>(input: &mut Input<'i>) -> Result<&'i str, Cause> { + (one_of('A'..='Z'), alphanumeric0) + .recognize() + .parse_next(input) +} diff --git a/crates/stef-parser/tests/inputs/alias-basic.stef b/crates/stef-parser/tests/inputs/alias-basic.stef new file mode 100644 index 0000000..9501f73 --- /dev/null +++ b/crates/stef-parser/tests/inputs/alias-basic.stef @@ -0,0 +1,2 @@ +/// Sample type alias. +type Sample = u32; diff --git a/crates/stef-parser/tests/inputs/attribute-multi.stef b/crates/stef-parser/tests/inputs/attribute-multi.stef new file mode 100644 index 0000000..c3291d4 --- /dev/null +++ b/crates/stef-parser/tests/inputs/attribute-multi.stef @@ -0,0 +1,2 @@ +#[validate(min = 1, max = 100)] +struct Sample diff --git a/crates/stef-parser/tests/inputs/attribute-single.stef b/crates/stef-parser/tests/inputs/attribute-single.stef new file mode 100644 index 0000000..ceeb552 --- /dev/null +++ b/crates/stef-parser/tests/inputs/attribute-single.stef @@ -0,0 +1,2 @@ +#[deprecated = "don't use"] +struct Sample diff --git a/crates/stef-parser/tests/inputs/attribute-unit.stef b/crates/stef-parser/tests/inputs/attribute-unit.stef new file mode 100644 index 0000000..f83141e --- /dev/null +++ b/crates/stef-parser/tests/inputs/attribute-unit.stef @@ -0,0 +1,2 @@ +#[deprecated] +struct Sample diff --git a/crates/stef-parser/tests/inputs/attributes-min-ws.stef b/crates/stef-parser/tests/inputs/attributes-min-ws.stef new file mode 100644 index 0000000..80e10b0 --- /dev/null +++ b/crates/stef-parser/tests/inputs/attributes-min-ws.stef @@ -0,0 +1,3 @@ +#[deprecated="don't use",compress] +#[validate(in_range(min=100,max=200),non_empty)] +struct Sample diff --git a/crates/stef-parser/tests/inputs/attributes.stef b/crates/stef-parser/tests/inputs/attributes.stef new file mode 100644 index 0000000..88c4429 --- /dev/null +++ b/crates/stef-parser/tests/inputs/attributes.stef @@ -0,0 +1,6 @@ +#[deprecated = "don't use", compress] +#[validate( + in_range(min = 100, max = 200), + non_empty, +)] +struct Sample diff --git a/crates/stef-parser/tests/inputs/const-basic.stef b/crates/stef-parser/tests/inputs/const-basic.stef new file mode 100644 index 0000000..75bf3aa --- /dev/null +++ b/crates/stef-parser/tests/inputs/const-basic.stef @@ -0,0 +1,6 @@ +const BOOL_TRUE: bool = true; +const BOOL_FALSE: bool = false; +const INT: u32 = 100; +const FLOAT: f64 = 5.5; +const STRING: string = "value"; +const BYTES: bytes = [1, 2, 3]; diff --git a/crates/stef-parser/tests/inputs/const-string.stef b/crates/stef-parser/tests/inputs/const-string.stef new file mode 100644 index 0000000..f6b4df4 --- /dev/null +++ b/crates/stef-parser/tests/inputs/const-string.stef @@ -0,0 +1,17 @@ +const SIMPLE: string = "value"; + +const NEWLINE_ESCAPE: string = "one \ + two \ + three\ +"; + +const ESCAPES: string = "escape basics \r\n \t \b \f \\ \"\ + hello\" \n\ + unicode \u{2764} \ + emoji ❤ \ +"; + +const MULTILINE: string = "a + b + c +"; diff --git a/crates/stef-parser/tests/inputs/enum-basic.stef b/crates/stef-parser/tests/inputs/enum-basic.stef new file mode 100644 index 0000000..d440ea9 --- /dev/null +++ b/crates/stef-parser/tests/inputs/enum-basic.stef @@ -0,0 +1,11 @@ +/// Sample enum. +enum Sample { + One @1, + /// Second variant + Two(u32 @1, u64 @2) @2, + Three { + field1: u32 @1, + /// Second field of third variant + field2: bool @2, + } @3, +} diff --git a/crates/stef-parser/tests/inputs/enum-generics.stef b/crates/stef-parser/tests/inputs/enum-generics.stef new file mode 100644 index 0000000..3f5fa80 --- /dev/null +++ b/crates/stef-parser/tests/inputs/enum-generics.stef @@ -0,0 +1,9 @@ +/// Enum with generics. +enum Sample { + One @1, + Two(A @1, B @2) @2, + Three { + field1: C @1, + field2: D @2, + } @3, +} diff --git a/crates/stef-parser/tests/inputs/enum-many-ws.stef b/crates/stef-parser/tests/inputs/enum-many-ws.stef new file mode 100644 index 0000000..4d07cc4 --- /dev/null +++ b/crates/stef-parser/tests/inputs/enum-many-ws.stef @@ -0,0 +1,17 @@ + + /// Sample enum. + enum Sample { + + One @1, + + Two ( u32 @1, u64 @2) @2, + + Three { + + field1: u32 @1, + + field2: bool @2, + + } @3, + + } diff --git a/crates/stef-parser/tests/inputs/enum-min-ws.stef b/crates/stef-parser/tests/inputs/enum-min-ws.stef new file mode 100644 index 0000000..fb67c9c --- /dev/null +++ b/crates/stef-parser/tests/inputs/enum-min-ws.stef @@ -0,0 +1 @@ +enum Sample{One@1,Two(u32@1,u64@2,T@3)@2,Three{field1:u32@1,field2:bool@2,field3:T@3}@3} diff --git a/crates/stef-parser/tests/inputs/import-basic.stef b/crates/stef-parser/tests/inputs/import-basic.stef new file mode 100644 index 0000000..769f606 --- /dev/null +++ b/crates/stef-parser/tests/inputs/import-basic.stef @@ -0,0 +1,2 @@ +use other::schema::Sample; +use second::submodule; diff --git a/crates/stef-parser/tests/inputs/invalid/alias_name.stef b/crates/stef-parser/tests/inputs/invalid/alias_name.stef new file mode 100644 index 0000000..3d0abc7 --- /dev/null +++ b/crates/stef-parser/tests/inputs/invalid/alias_name.stef @@ -0,0 +1 @@ +type sImple = Simple; diff --git a/crates/stef-parser/tests/inputs/invalid/const_literal.stef b/crates/stef-parser/tests/inputs/invalid/const_literal.stef new file mode 100644 index 0000000..9e588fc --- /dev/null +++ b/crates/stef-parser/tests/inputs/invalid/const_literal.stef @@ -0,0 +1 @@ +const VALUE: u32 = 1z; diff --git a/crates/stef-parser/tests/inputs/invalid/const_literal_bool.stef b/crates/stef-parser/tests/inputs/invalid/const_literal_bool.stef new file mode 100644 index 0000000..945e80f --- /dev/null +++ b/crates/stef-parser/tests/inputs/invalid/const_literal_bool.stef @@ -0,0 +1 @@ +const VALUE: bool = truze; diff --git a/crates/stef-parser/tests/inputs/invalid/const_literal_float.stef b/crates/stef-parser/tests/inputs/invalid/const_literal_float.stef new file mode 100644 index 0000000..62c0ec4 --- /dev/null +++ b/crates/stef-parser/tests/inputs/invalid/const_literal_float.stef @@ -0,0 +1 @@ +const VALUE: f64 = 1.0z; diff --git a/crates/stef-parser/tests/inputs/invalid/const_name.stef b/crates/stef-parser/tests/inputs/invalid/const_name.stef new file mode 100644 index 0000000..c7c50c4 --- /dev/null +++ b/crates/stef-parser/tests/inputs/invalid/const_name.stef @@ -0,0 +1 @@ +const vALUE: u32 = 1; diff --git a/crates/stef-parser/tests/inputs/invalid/enum_name.stef b/crates/stef-parser/tests/inputs/invalid/enum_name.stef new file mode 100644 index 0000000..3905496 --- /dev/null +++ b/crates/stef-parser/tests/inputs/invalid/enum_name.stef @@ -0,0 +1,3 @@ +enum sample { + One @1, +} diff --git a/crates/stef-parser/tests/inputs/invalid/field_name.stef b/crates/stef-parser/tests/inputs/invalid/field_name.stef new file mode 100644 index 0000000..f42e375 --- /dev/null +++ b/crates/stef-parser/tests/inputs/invalid/field_name.stef @@ -0,0 +1,3 @@ +struct Sample { + 1value: u32 @1, +} diff --git a/crates/stef-parser/tests/inputs/invalid/mod_name.stef b/crates/stef-parser/tests/inputs/invalid/mod_name.stef new file mode 100644 index 0000000..1ef8080 --- /dev/null +++ b/crates/stef-parser/tests/inputs/invalid/mod_name.stef @@ -0,0 +1 @@ +mod Sample {} diff --git a/crates/stef-parser/tests/inputs/invalid/struct_name.stef b/crates/stef-parser/tests/inputs/invalid/struct_name.stef new file mode 100644 index 0000000..857a31a --- /dev/null +++ b/crates/stef-parser/tests/inputs/invalid/struct_name.stef @@ -0,0 +1,3 @@ +struct sample { + value: u32 @1, +} diff --git a/crates/stef-parser/tests/inputs/module-basic.stef b/crates/stef-parser/tests/inputs/module-basic.stef new file mode 100644 index 0000000..3db5a89 --- /dev/null +++ b/crates/stef-parser/tests/inputs/module-basic.stef @@ -0,0 +1,12 @@ +mod a { + /// Inner module + mod b { + enum Sample { + One @1, + } + } + + struct Sample { + value: u32 @1, + } +} diff --git a/crates/stef-parser/tests/inputs/schema-basic.stef b/crates/stef-parser/tests/inputs/schema-basic.stef new file mode 100644 index 0000000..105f96b --- /dev/null +++ b/crates/stef-parser/tests/inputs/schema-basic.stef @@ -0,0 +1,15 @@ +/// Basic struct. +struct Sample { + a: u32 @1, + b: bool @2, +} + +/// Sample enum. +enum Sample { + One @1, + Two(u32 @1, u64 @2) @2, + Three { + field1: u32 @1, + field2: bool @2, + } @3, +} diff --git a/crates/stef-parser/tests/inputs/struct-basic.stef b/crates/stef-parser/tests/inputs/struct-basic.stef new file mode 100644 index 0000000..0950419 --- /dev/null +++ b/crates/stef-parser/tests/inputs/struct-basic.stef @@ -0,0 +1,6 @@ +/// Basic struct. +struct Sample { + a: u32 @1, + /// Second field + b: bool @2, +} diff --git a/crates/stef-parser/tests/inputs/struct-generics.stef b/crates/stef-parser/tests/inputs/struct-generics.stef new file mode 100644 index 0000000..8c13585 --- /dev/null +++ b/crates/stef-parser/tests/inputs/struct-generics.stef @@ -0,0 +1,5 @@ +/// Generic key-value pair. +struct KeyValue { + key: K @1, + value: V @2, +} diff --git a/crates/stef-parser/tests/inputs/struct-many-ws.stef b/crates/stef-parser/tests/inputs/struct-many-ws.stef new file mode 100644 index 0000000..16985c0 --- /dev/null +++ b/crates/stef-parser/tests/inputs/struct-many-ws.stef @@ -0,0 +1,11 @@ + + /// Some comment + struct Sample< + A, + B + > { + + a: u32 @1, + b: bool @2, + + } diff --git a/crates/stef-parser/tests/inputs/struct-min-ws.stef b/crates/stef-parser/tests/inputs/struct-min-ws.stef new file mode 100644 index 0000000..04e9972 --- /dev/null +++ b/crates/stef-parser/tests/inputs/struct-min-ws.stef @@ -0,0 +1 @@ +struct Sample{a:u32@1,b:bool@2,c:T@3} diff --git a/crates/stef-parser/tests/inputs/types-basic.stef b/crates/stef-parser/tests/inputs/types-basic.stef new file mode 100644 index 0000000..b057e38 --- /dev/null +++ b/crates/stef-parser/tests/inputs/types-basic.stef @@ -0,0 +1,23 @@ +struct Sample { + f01: bool @1, + f02: u8 @2, + f03: u16 @3, + f04: u32 @4, + f05: u64 @5, + f06: u128 @6, + f07: i8 @7, + f08: i16 @8, + f09: i32 @9, + f10: i64 @10, + f11: i128 @11, + f12: f32 @12, + f13: f64 @13, + f14: string @14, + f15: &string @15, + f16: bytes @16, + f17: &bytes @17, + f18: box @18, + f19: box @19, + f20: (u32, u32, u32) @20, + f21: [u32; 12] @21, +} diff --git a/crates/stef-parser/tests/inputs/types-generic.stef b/crates/stef-parser/tests/inputs/types-generic.stef new file mode 100644 index 0000000..b1f3484 --- /dev/null +++ b/crates/stef-parser/tests/inputs/types-generic.stef @@ -0,0 +1,7 @@ +struct Sample { + f1: vec @1, + f2: hash_map @2, + f3: hash_set @3, + f4: option @4, + f5: non_zero @5, +} diff --git a/crates/stef-parser/tests/inputs/types-nested.stef b/crates/stef-parser/tests/inputs/types-nested.stef new file mode 100644 index 0000000..fc811fa --- /dev/null +++ b/crates/stef-parser/tests/inputs/types-nested.stef @@ -0,0 +1,3 @@ +struct Sample { + value: vec>>>> @1, +} diff --git a/crates/stef-parser/tests/inputs/types-ref.stef b/crates/stef-parser/tests/inputs/types-ref.stef new file mode 100644 index 0000000..fc80ca5 --- /dev/null +++ b/crates/stef-parser/tests/inputs/types-ref.stef @@ -0,0 +1,4 @@ +struct Sample { + basic: Test123 @1, + with_generics: KeyValue @2, +} diff --git a/crates/stef-parser/tests/parser.rs b/crates/stef-parser/tests/parser.rs new file mode 100644 index 0000000..8038a02 --- /dev/null +++ b/crates/stef-parser/tests/parser.rs @@ -0,0 +1,56 @@ +use std::{ + fmt::{self, Display}, + fs, +}; + +use insta::{assert_snapshot, glob}; +use miette::{Diagnostic, MietteHandler, MietteHandlerOpts, ReportHandler}; +use stef_parser::Schema; + +#[test] +fn parse_schema() { + glob!("inputs/*.stef", |path| { + let input = fs::read_to_string(path).unwrap(); + let value = Schema::parse(input.as_str()).unwrap(); + + assert_snapshot!( + "parse", + format!("{value:#?}"), + stringify!(Schema::parse(input.as_str()).unwrap()) + ); + assert_snapshot!( + "print", + value.to_string(), + stringify!(Schema::parse(input.as_str()).unwrap()) + ); + }); +} + +#[test] +fn parse_invalid_schema() { + struct Wrapper<'a>(&'a MietteHandler, &'a dyn Diagnostic); + + impl<'a> Display for Wrapper<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.debug(self.1, f) + } + } + + let handler = MietteHandlerOpts::new() + .color(false) + .terminal_links(false) + .width(120) + .force_graphical(true) + .build(); + + glob!("inputs/invalid/*.stef", |path| { + let input = fs::read_to_string(path).unwrap(); + let value = Schema::parse(input.as_str()).unwrap_err(); + + insta::assert_snapshot!( + "error", + Wrapper(&handler, &*value).to_string(), + stringify!(Schema::parse(input.as_str()).unwrap_err()) + ); + }); +} diff --git a/crates/stef-parser/tests/snapshots/parser__error@alias_name.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@alias_name.stef.snap new file mode 100644 index 0000000..ed53f7f --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__error@alias_name.stef.snap @@ -0,0 +1,30 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap_err()" +input_file: crates/stef-parser/tests/inputs/invalid/alias_name.stef +--- +stef::parse::alias_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/struct.ParseAliasError.html) + + × Failed to parse type alias declaration + ├─▶ Failed to parse type definition + ╰─▶ error Verify + ╭──── + 1 │ type sImple = Simple; + · ▲ + · ╰── In this declaration + ╰──── + help: Expected type alias declaration in the form `❬B❭type = ;❬B❭` + +Error: stef::parse::type_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/struct.ParseTypeError.html) + + × Failed to parse type definition + ╰─▶ error Verify + ╭──── + 1 │ type sImple = Simple; + · ▲ + · ╰── In this declaration + ╰──── + help: Expected type definition in the form `❬B❭❬B❭` + +Error: × error Verify + diff --git a/crates/stef-parser/tests/snapshots/parser__error@const_literal.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@const_literal.stef.snap new file mode 100644 index 0000000..2cd1ecd --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__error@const_literal.stef.snap @@ -0,0 +1,26 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap_err()" +input_file: crates/stef-parser/tests/inputs/invalid/const_literal.stef +--- +stef::parse::const_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/struct.ParseConstError.html) + + × Failed to parse const declaration + ╰─▶ Unexpected character + ╭──── + 1 │ const VALUE: u32 = 1z; + · ───────────┬────────── + · ╰── In this declaration + ╰──── + help: Expected const declaration in the form `❬B❭const : = ;❬B❭` + +Error: stef::parse::const_def::char (https://docs.rs/stef-parser/0.1.0/stef_parser/error/enum.ParseConstCause.html#variant.UnexpectedChar) + + × Unexpected character + ╭──── + 1 │ const VALUE: u32 = 1z; + · ▲ + · ╰── Problematic character + ╰──── + help: Expected a `❬Y❭;❬Y❭` here + diff --git a/crates/stef-parser/tests/snapshots/parser__error@const_literal_bool.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@const_literal_bool.stef.snap new file mode 100644 index 0000000..659a2f9 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__error@const_literal_bool.stef.snap @@ -0,0 +1,35 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap_err()" +input_file: crates/stef-parser/tests/inputs/invalid/const_literal_bool.stef +--- +stef::parse::const_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/struct.ParseConstError.html) + + × Failed to parse const declaration + ├─▶ Failed to parse literal value + ╰─▶ error Tag + ╭──── + 1 │ const VALUE: bool = truze; + · ─────────────┬──────────── + · ╰── In this declaration + ╰──── + help: Expected const declaration in the form `❬B❭const : = ;❬B❭` + +Error: stef::parse::literal (https://docs.rs/stef-parser/0.1.0/stef_parser/error/struct.ParseLiteralError.html) + + × Failed to parse literal value + ╰─▶ error Tag + ╭──── + 1 │ const VALUE: bool = truze; + · ▲ + · ╰── In this declaration + ╰──── + help: Expected literal value declaration in either of the forms: + `❬B❭true❬B❭` or `❬B❭false❬B❭` for booleans + `❬B❭1, 2, 3, ...❬B❭` for numbers + `❬B❭1.2, 1.0e5, ...❬B❭` for floating point numbers + `❬B❭"..."❬B❭` for strings + or `❬B❭[...]❬B❭` for bytes + +Error: × error Tag + diff --git a/crates/stef-parser/tests/snapshots/parser__error@const_literal_float.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@const_literal_float.stef.snap new file mode 100644 index 0000000..806d505 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__error@const_literal_float.stef.snap @@ -0,0 +1,26 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap_err()" +input_file: crates/stef-parser/tests/inputs/invalid/const_literal_float.stef +--- +stef::parse::const_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/struct.ParseConstError.html) + + × Failed to parse const declaration + ╰─▶ Unexpected character + ╭──── + 1 │ const VALUE: f64 = 1.0z; + · ────────────┬─────────── + · ╰── In this declaration + ╰──── + help: Expected const declaration in the form `❬B❭const : = ;❬B❭` + +Error: stef::parse::const_def::char (https://docs.rs/stef-parser/0.1.0/stef_parser/error/enum.ParseConstCause.html#variant.UnexpectedChar) + + × Unexpected character + ╭──── + 1 │ const VALUE: f64 = 1.0z; + · ▲ + · ╰── Problematic character + ╰──── + help: Expected a `❬Y❭;❬Y❭` here + diff --git a/crates/stef-parser/tests/snapshots/parser__error@const_name.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@const_name.stef.snap new file mode 100644 index 0000000..3487930 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__error@const_name.stef.snap @@ -0,0 +1,27 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap_err()" +input_file: crates/stef-parser/tests/inputs/invalid/const_name.stef +--- +stef::parse::const_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/struct.ParseConstError.html) + + × Failed to parse const declaration + ╰─▶ Invalid const name + ╭──── + 1 │ const vALUE: u32 = 1; + · ──────────┬────────── + · ╰── In this declaration + ╰──── + help: Expected const declaration in the form `❬B❭const : = ;❬B❭` + +Error: stef::parse::const_def::invalid_name (https://docs.rs/stef-parser/0.1.0/stef_parser/error/enum.ParseConstCause.html#variant.InvalidName) + + × Invalid const name + ╭──── + 1 │ const vALUE: u32 = 1; + · ▲ + · ╰── Problematic character + ╰──── + help: Const names must start with an uppercase letter (❬Y❭A-Z❬Y❭), followed by zero or more uppercase alphanumeric + characters or underscores (❬Y❭A-Z, 0-9, _❬Y❭) + diff --git a/crates/stef-parser/tests/snapshots/parser__error@enum_name.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@enum_name.stef.snap new file mode 100644 index 0000000..c59ffde --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__error@enum_name.stef.snap @@ -0,0 +1,29 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap_err()" +input_file: crates/stef-parser/tests/inputs/invalid/enum_name.stef +--- +stef::parse::enum_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/struct.ParseEnumError.html) + + × Failed to parse enum declaration + ╰─▶ Invalid enum name + ╭─[1:1] + 1 │ enum sample { + · ▲ + · ╰── In this declaration + 2 │ One @1, + ╰──── + help: Expected enum declaration in the form `❬B❭enum {...}❬B❭` + +Error: stef::parse::enum_def::invalid_name (https://docs.rs/stef-parser/0.1.0/stef_parser/error/enum.ParseEnumCause.html#variant.InvalidName) + + × Invalid enum name + ╭─[1:1] + 1 │ enum sample { + · ▲ + · ╰── Problematic character + 2 │ One @1, + ╰──── + help: Enum names must start with an uppercase letter (❬Y❭A-Z❬Y❭), followed by zero or more alphanumeric characters + (❬Y❭A-Z, a-z, 0-9❬Y❭) + diff --git a/crates/stef-parser/tests/snapshots/parser__error@field_name.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@field_name.stef.snap new file mode 100644 index 0000000..f669bde --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__error@field_name.stef.snap @@ -0,0 +1,44 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap_err()" +input_file: crates/stef-parser/tests/inputs/invalid/field_name.stef +--- +stef::parse::struct_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/struct.ParseStructError.html) + + × Failed to parse struct declaration + ├─▶ Failed to parse fields declaration + ╰─▶ Invalid field name + ╭─[1:1] + 1 │ ╭─▶ struct Sample { + 2 │ │ 1value: u32 @1, + 3 │ ├─▶ } + · ╰──── In this declaration + ╰──── + help: Expected struct declaration in the form `❬B❭struct {...}❬B❭` + +Error: stef::parse::id (https://docs.rs/stef-parser/0.1.0/stef_parser/error/struct.ParseFieldsError.html) + + × Failed to parse fields declaration + ╰─▶ Invalid field name + ╭─[1:1] + 1 │ ╭─▶ struct Sample { + 2 │ ├─▶ 1value: u32 @1, + · ╰──── In this declaration + 3 │ } + ╰──── + help: Expected fields declaration in the form `❬B❭{ , , ... }❬B❭`, `❬B❭( , , ... ) + ❬B❭` or `❬B❭_nothing_❬B❭` + +Error: stef::parse::fields::named::invalid_name (https://docs.rs/stef-parser/0.1.0/stef_parser/error/enum.ParseFieldsCause.html#variant.InvalidName) + + × Invalid field name + ╭─[1:1] + 1 │ struct Sample { + 2 │ 1value: u32 @1, + · ▲ + · ╰── Problematic character + 3 │ } + ╰──── + help: Field names must start with a lowercase letter (❬Y❭a-z❬Y❭), followed by zero or more lowercase alphanumeric + characters or underscores (❬Y❭a-z, 0-9, _❬Y❭) + diff --git a/crates/stef-parser/tests/snapshots/parser__error@mod_name.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@mod_name.stef.snap new file mode 100644 index 0000000..bbc5743 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__error@mod_name.stef.snap @@ -0,0 +1,27 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap_err()" +input_file: crates/stef-parser/tests/inputs/invalid/mod_name.stef +--- +stef::parse::mod_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/struct.ParseModuleError.html) + + × Failed to parse id declaration + ╰─▶ Invalid module name + ╭──── + 1 │ mod Sample {} + · ──────┬────── + · ╰── In this declaration + ╰──── + help: Expected module declaration in the form `❬B❭mod {...}❬B❭` + +Error: stef::parse::module::invalid_name (https://docs.rs/stef-parser/0.1.0/stef_parser/error/enum.ParseModuleCause.html#variant.InvalidName) + + × Invalid module name + ╭──── + 1 │ mod Sample {} + · ▲ + · ╰── Problematic character + ╰──── + help: Module names must start with a lowercase letter (❬Y❭a-z❬Y❭), followed by zero or more lowercase alphanumeric + characters or underscores (❬Y❭a-z, 0-9, _❬Y❭) + diff --git a/crates/stef-parser/tests/snapshots/parser__error@struct_name.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@struct_name.stef.snap new file mode 100644 index 0000000..27872b1 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__error@struct_name.stef.snap @@ -0,0 +1,29 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap_err()" +input_file: crates/stef-parser/tests/inputs/invalid/struct_name.stef +--- +stef::parse::struct_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/struct.ParseStructError.html) + + × Failed to parse struct declaration + ╰─▶ Invalid struct name + ╭─[1:1] + 1 │ ╭─▶ struct sample { + 2 │ │ value: u32 @1, + 3 │ ├─▶ } + · ╰──── In this declaration + ╰──── + help: Expected struct declaration in the form `❬B❭struct {...}❬B❭` + +Error: stef::parse::struct_def::invalid_name (https://docs.rs/stef-parser/0.1.0/stef_parser/error/enum.ParseStructCause.html#variant.InvalidName) + + × Invalid struct name + ╭─[1:1] + 1 │ struct sample { + · ▲ + · ╰── Problematic character + 2 │ value: u32 @1, + ╰──── + help: Struct names must start with an uppercase letter (❬Y❭A-Z❬Y❭), followed by zero or more alphanumeric + characters (❬Y❭A-Z, a-z, 0-9❬Y❭) + diff --git a/crates/stef-parser/tests/snapshots/parser__parse@alias-basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@alias-basic.stef.snap new file mode 100644 index 0000000..22dc13c --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@alias-basic.stef.snap @@ -0,0 +1,26 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/alias-basic.stef +--- +Schema { + definitions: [ + TypeAlias( + TypeAlias { + comment: Comment( + [ + "Sample type alias.", + ], + ), + alias: External( + ExternalType { + path: [], + name: "Sample", + generics: [], + }, + ), + target: U32, + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@attribute-multi.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@attribute-multi.stef.snap new file mode 100644 index 0000000..5571063 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@attribute-multi.stef.snap @@ -0,0 +1,48 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/attribute-multi.stef +--- +Schema { + definitions: [ + Struct( + Struct { + comment: Comment( + [], + ), + attributes: Attributes( + [ + Attribute { + name: "validate", + value: Multi( + [ + Attribute { + name: "min", + value: Single( + Int( + 1, + ), + ), + }, + Attribute { + name: "max", + value: Single( + Int( + 100, + ), + ), + }, + ], + ), + }, + ], + ), + name: "Sample", + generics: Generics( + [], + ), + fields: Unit, + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@attribute-single.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@attribute-single.stef.snap new file mode 100644 index 0000000..0f6b34b --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@attribute-single.stef.snap @@ -0,0 +1,33 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/attribute-single.stef +--- +Schema { + definitions: [ + Struct( + Struct { + comment: Comment( + [], + ), + attributes: Attributes( + [ + Attribute { + name: "deprecated", + value: Single( + String( + "don't use", + ), + ), + }, + ], + ), + name: "Sample", + generics: Generics( + [], + ), + fields: Unit, + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@attribute-unit.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@attribute-unit.stef.snap new file mode 100644 index 0000000..de5a09c --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@attribute-unit.stef.snap @@ -0,0 +1,29 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/attribute-unit.stef +--- +Schema { + definitions: [ + Struct( + Struct { + comment: Comment( + [], + ), + attributes: Attributes( + [ + Attribute { + name: "deprecated", + value: Unit, + }, + ], + ), + name: "Sample", + generics: Generics( + [], + ), + fields: Unit, + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@attributes-min-ws.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@attributes-min-ws.stef.snap new file mode 100644 index 0000000..51ba7ae --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@attributes-min-ws.stef.snap @@ -0,0 +1,71 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/attributes-min-ws.stef +--- +Schema { + definitions: [ + Struct( + Struct { + comment: Comment( + [], + ), + attributes: Attributes( + [ + Attribute { + name: "deprecated", + value: Single( + String( + "don't use", + ), + ), + }, + Attribute { + name: "compress", + value: Unit, + }, + Attribute { + name: "validate", + value: Multi( + [ + Attribute { + name: "in_range", + value: Multi( + [ + Attribute { + name: "min", + value: Single( + Int( + 100, + ), + ), + }, + Attribute { + name: "max", + value: Single( + Int( + 200, + ), + ), + }, + ], + ), + }, + Attribute { + name: "non_empty", + value: Unit, + }, + ], + ), + }, + ], + ), + name: "Sample", + generics: Generics( + [], + ), + fields: Unit, + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@attributes.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@attributes.stef.snap new file mode 100644 index 0000000..072c2fb --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@attributes.stef.snap @@ -0,0 +1,71 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/attributes.stef +--- +Schema { + definitions: [ + Struct( + Struct { + comment: Comment( + [], + ), + attributes: Attributes( + [ + Attribute { + name: "deprecated", + value: Single( + String( + "don't use", + ), + ), + }, + Attribute { + name: "compress", + value: Unit, + }, + Attribute { + name: "validate", + value: Multi( + [ + Attribute { + name: "in_range", + value: Multi( + [ + Attribute { + name: "min", + value: Single( + Int( + 100, + ), + ), + }, + Attribute { + name: "max", + value: Single( + Int( + 200, + ), + ), + }, + ], + ), + }, + Attribute { + name: "non_empty", + value: Unit, + }, + ], + ), + }, + ], + ), + name: "Sample", + generics: Generics( + [], + ), + fields: Unit, + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@const-basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@const-basic.stef.snap new file mode 100644 index 0000000..bda0579 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@const-basic.stef.snap @@ -0,0 +1,85 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/const-basic.stef +--- +Schema { + definitions: [ + Const( + Const { + comment: Comment( + [], + ), + name: "BOOL_TRUE", + ty: Bool, + value: Bool( + true, + ), + }, + ), + Const( + Const { + comment: Comment( + [], + ), + name: "BOOL_FALSE", + ty: Bool, + value: Bool( + false, + ), + }, + ), + Const( + Const { + comment: Comment( + [], + ), + name: "INT", + ty: U32, + value: Int( + 100, + ), + }, + ), + Const( + Const { + comment: Comment( + [], + ), + name: "FLOAT", + ty: F64, + value: Float( + 5.5, + ), + }, + ), + Const( + Const { + comment: Comment( + [], + ), + name: "STRING", + ty: String, + value: String( + "value", + ), + }, + ), + Const( + Const { + comment: Comment( + [], + ), + name: "BYTES", + ty: Bytes, + value: Bytes( + [ + 1, + 2, + 3, + ], + ), + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@const-string.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@const-string.stef.snap new file mode 100644 index 0000000..94c1ab4 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@const-string.stef.snap @@ -0,0 +1,57 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/const-string.stef +--- +Schema { + definitions: [ + Const( + Const { + comment: Comment( + [], + ), + name: "SIMPLE", + ty: String, + value: String( + "value", + ), + }, + ), + Const( + Const { + comment: Comment( + [], + ), + name: "NEWLINE_ESCAPE", + ty: String, + value: String( + "one two three", + ), + }, + ), + Const( + Const { + comment: Comment( + [], + ), + name: "ESCAPES", + ty: String, + value: String( + "escape basics \r\n \t \u{8} \u{c} \\ \"hello\" \nunicode ❤ emoji ❤ ", + ), + }, + ), + Const( + Const { + comment: Comment( + [], + ), + name: "MULTILINE", + ty: String, + value: String( + "a\n b\n c\n", + ), + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@enum-basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@enum-basic.stef.snap new file mode 100644 index 0000000..26ea14a --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@enum-basic.stef.snap @@ -0,0 +1,99 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/enum-basic.stef +--- +Schema { + definitions: [ + Enum( + Enum { + comment: Comment( + [ + "Sample enum.", + ], + ), + attributes: Attributes( + [], + ), + name: "Sample", + generics: Generics( + [], + ), + variants: [ + Variant { + comment: Comment( + [], + ), + name: "One", + fields: Unit, + id: Id( + 1, + ), + }, + Variant { + comment: Comment( + [ + "Second variant", + ], + ), + name: "Two", + fields: Unnamed( + [ + UnnamedField { + ty: U32, + id: Id( + 1, + ), + }, + UnnamedField { + ty: U64, + id: Id( + 2, + ), + }, + ], + ), + id: Id( + 2, + ), + }, + Variant { + comment: Comment( + [], + ), + name: "Three", + fields: Named( + [ + NamedField { + comment: Comment( + [], + ), + name: "field1", + ty: U32, + id: Id( + 1, + ), + }, + NamedField { + comment: Comment( + [ + "Second field of third variant", + ], + ), + name: "field2", + ty: Bool, + id: Id( + 2, + ), + }, + ], + ), + id: Id( + 3, + ), + }, + ], + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@enum-generics.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@enum-generics.stef.snap new file mode 100644 index 0000000..7ae4cc1 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@enum-generics.stef.snap @@ -0,0 +1,124 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/enum-generics.stef +--- +Schema { + definitions: [ + Enum( + Enum { + comment: Comment( + [ + "Enum with generics.", + ], + ), + attributes: Attributes( + [], + ), + name: "Sample", + generics: Generics( + [ + "A", + "B", + "C", + "D", + ], + ), + variants: [ + Variant { + comment: Comment( + [], + ), + name: "One", + fields: Unit, + id: Id( + 1, + ), + }, + Variant { + comment: Comment( + [], + ), + name: "Two", + fields: Unnamed( + [ + UnnamedField { + ty: External( + ExternalType { + path: [], + name: "A", + generics: [], + }, + ), + id: Id( + 1, + ), + }, + UnnamedField { + ty: External( + ExternalType { + path: [], + name: "B", + generics: [], + }, + ), + id: Id( + 2, + ), + }, + ], + ), + id: Id( + 2, + ), + }, + Variant { + comment: Comment( + [], + ), + name: "Three", + fields: Named( + [ + NamedField { + comment: Comment( + [], + ), + name: "field1", + ty: External( + ExternalType { + path: [], + name: "C", + generics: [], + }, + ), + id: Id( + 1, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "field2", + ty: External( + ExternalType { + path: [], + name: "D", + generics: [], + }, + ), + id: Id( + 2, + ), + }, + ], + ), + id: Id( + 3, + ), + }, + ], + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@enum-many-ws.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@enum-many-ws.stef.snap new file mode 100644 index 0000000..beacc54 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@enum-many-ws.stef.snap @@ -0,0 +1,95 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/enum-many-ws.stef +--- +Schema { + definitions: [ + Enum( + Enum { + comment: Comment( + [ + "Sample enum.", + ], + ), + attributes: Attributes( + [], + ), + name: "Sample", + generics: Generics( + [], + ), + variants: [ + Variant { + comment: Comment( + [], + ), + name: "One", + fields: Unit, + id: Id( + 1, + ), + }, + Variant { + comment: Comment( + [], + ), + name: "Two", + fields: Unnamed( + [ + UnnamedField { + ty: U32, + id: Id( + 1, + ), + }, + UnnamedField { + ty: U64, + id: Id( + 2, + ), + }, + ], + ), + id: Id( + 2, + ), + }, + Variant { + comment: Comment( + [], + ), + name: "Three", + fields: Named( + [ + NamedField { + comment: Comment( + [], + ), + name: "field1", + ty: U32, + id: Id( + 1, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "field2", + ty: Bool, + id: Id( + 2, + ), + }, + ], + ), + id: Id( + 3, + ), + }, + ], + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@enum-min-ws.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@enum-min-ws.stef.snap new file mode 100644 index 0000000..6adc90a --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@enum-min-ws.stef.snap @@ -0,0 +1,123 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/enum-min-ws.stef +--- +Schema { + definitions: [ + Enum( + Enum { + comment: Comment( + [], + ), + attributes: Attributes( + [], + ), + name: "Sample", + generics: Generics( + [ + "T", + ], + ), + variants: [ + Variant { + comment: Comment( + [], + ), + name: "One", + fields: Unit, + id: Id( + 1, + ), + }, + Variant { + comment: Comment( + [], + ), + name: "Two", + fields: Unnamed( + [ + UnnamedField { + ty: U32, + id: Id( + 1, + ), + }, + UnnamedField { + ty: U64, + id: Id( + 2, + ), + }, + UnnamedField { + ty: External( + ExternalType { + path: [], + name: "T", + generics: [], + }, + ), + id: Id( + 3, + ), + }, + ], + ), + id: Id( + 2, + ), + }, + Variant { + comment: Comment( + [], + ), + name: "Three", + fields: Named( + [ + NamedField { + comment: Comment( + [], + ), + name: "field1", + ty: U32, + id: Id( + 1, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "field2", + ty: Bool, + id: Id( + 2, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "field3", + ty: External( + ExternalType { + path: [], + name: "T", + generics: [], + }, + ), + id: Id( + 3, + ), + }, + ], + ), + id: Id( + 3, + ), + }, + ], + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@import-basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@import-basic.stef.snap new file mode 100644 index 0000000..d0d5394 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@import-basic.stef.snap @@ -0,0 +1,29 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/import-basic.stef +--- +Schema { + definitions: [ + Import( + Import { + segments: [ + "other", + "schema", + ], + element: Some( + "Sample", + ), + }, + ), + Import( + Import { + segments: [ + "second", + "submodule", + ], + element: None, + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@module-basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@module-basic.stef.snap new file mode 100644 index 0000000..5494f57 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@module-basic.stef.snap @@ -0,0 +1,85 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/module-basic.stef +--- +Schema { + definitions: [ + Module( + Module { + comment: Comment( + [], + ), + name: "a", + definitions: [ + Module( + Module { + comment: Comment( + [ + "Inner module", + ], + ), + name: "b", + definitions: [ + Enum( + Enum { + comment: Comment( + [], + ), + attributes: Attributes( + [], + ), + name: "Sample", + generics: Generics( + [], + ), + variants: [ + Variant { + comment: Comment( + [], + ), + name: "One", + fields: Unit, + id: Id( + 1, + ), + }, + ], + }, + ), + ], + }, + ), + Struct( + Struct { + comment: Comment( + [], + ), + attributes: Attributes( + [], + ), + name: "Sample", + generics: Generics( + [], + ), + fields: Named( + [ + NamedField { + comment: Comment( + [], + ), + name: "value", + ty: U32, + id: Id( + 1, + ), + }, + ], + ), + }, + ), + ], + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@schema-basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@schema-basic.stef.snap new file mode 100644 index 0000000..b9b9f0b --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@schema-basic.stef.snap @@ -0,0 +1,135 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/schema-basic.stef +--- +Schema { + definitions: [ + Struct( + Struct { + comment: Comment( + [ + "Basic struct.", + ], + ), + attributes: Attributes( + [], + ), + name: "Sample", + generics: Generics( + [], + ), + fields: Named( + [ + NamedField { + comment: Comment( + [], + ), + name: "a", + ty: U32, + id: Id( + 1, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "b", + ty: Bool, + id: Id( + 2, + ), + }, + ], + ), + }, + ), + Enum( + Enum { + comment: Comment( + [ + "Sample enum.", + ], + ), + attributes: Attributes( + [], + ), + name: "Sample", + generics: Generics( + [], + ), + variants: [ + Variant { + comment: Comment( + [], + ), + name: "One", + fields: Unit, + id: Id( + 1, + ), + }, + Variant { + comment: Comment( + [], + ), + name: "Two", + fields: Unnamed( + [ + UnnamedField { + ty: U32, + id: Id( + 1, + ), + }, + UnnamedField { + ty: U64, + id: Id( + 2, + ), + }, + ], + ), + id: Id( + 2, + ), + }, + Variant { + comment: Comment( + [], + ), + name: "Three", + fields: Named( + [ + NamedField { + comment: Comment( + [], + ), + name: "field1", + ty: U32, + id: Id( + 1, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "field2", + ty: Bool, + id: Id( + 2, + ), + }, + ], + ), + id: Id( + 3, + ), + }, + ], + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@struct-basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@struct-basic.stef.snap new file mode 100644 index 0000000..a24dab6 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@struct-basic.stef.snap @@ -0,0 +1,51 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/struct-basic.stef +--- +Schema { + definitions: [ + Struct( + Struct { + comment: Comment( + [ + "Basic struct.", + ], + ), + attributes: Attributes( + [], + ), + name: "Sample", + generics: Generics( + [], + ), + fields: Named( + [ + NamedField { + comment: Comment( + [], + ), + name: "a", + ty: U32, + id: Id( + 1, + ), + }, + NamedField { + comment: Comment( + [ + "Second field", + ], + ), + name: "b", + ty: Bool, + id: Id( + 2, + ), + }, + ], + ), + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@struct-generics.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@struct-generics.stef.snap new file mode 100644 index 0000000..a678310 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@struct-generics.stef.snap @@ -0,0 +1,64 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/struct-generics.stef +--- +Schema { + definitions: [ + Struct( + Struct { + comment: Comment( + [ + "Generic key-value pair.", + ], + ), + attributes: Attributes( + [], + ), + name: "KeyValue", + generics: Generics( + [ + "K", + "V", + ], + ), + fields: Named( + [ + NamedField { + comment: Comment( + [], + ), + name: "key", + ty: External( + ExternalType { + path: [], + name: "K", + generics: [], + }, + ), + id: Id( + 1, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "value", + ty: External( + ExternalType { + path: [], + name: "V", + generics: [], + }, + ), + id: Id( + 2, + ), + }, + ], + ), + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@struct-many-ws.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@struct-many-ws.stef.snap new file mode 100644 index 0000000..03fdc7f --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@struct-many-ws.stef.snap @@ -0,0 +1,52 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/struct-many-ws.stef +--- +Schema { + definitions: [ + Struct( + Struct { + comment: Comment( + [ + "Some comment", + ], + ), + attributes: Attributes( + [], + ), + name: "Sample", + generics: Generics( + [ + "A", + "B", + ], + ), + fields: Named( + [ + NamedField { + comment: Comment( + [], + ), + name: "a", + ty: U32, + id: Id( + 1, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "b", + ty: Bool, + id: Id( + 2, + ), + }, + ], + ), + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@struct-min-ws.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@struct-min-ws.stef.snap new file mode 100644 index 0000000..54d132a --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@struct-min-ws.stef.snap @@ -0,0 +1,65 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/struct-min-ws.stef +--- +Schema { + definitions: [ + Struct( + Struct { + comment: Comment( + [], + ), + attributes: Attributes( + [], + ), + name: "Sample", + generics: Generics( + [ + "T", + ], + ), + fields: Named( + [ + NamedField { + comment: Comment( + [], + ), + name: "a", + ty: U32, + id: Id( + 1, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "b", + ty: Bool, + id: Id( + 2, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "c", + ty: External( + ExternalType { + path: [], + name: "T", + generics: [], + }, + ), + id: Id( + 3, + ), + }, + ], + ), + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@types-basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@types-basic.stef.snap new file mode 100644 index 0000000..7722ea9 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@types-basic.stef.snap @@ -0,0 +1,246 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/types-basic.stef +--- +Schema { + definitions: [ + Struct( + Struct { + comment: Comment( + [], + ), + attributes: Attributes( + [], + ), + name: "Sample", + generics: Generics( + [], + ), + fields: Named( + [ + NamedField { + comment: Comment( + [], + ), + name: "f01", + ty: Bool, + id: Id( + 1, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f02", + ty: U8, + id: Id( + 2, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f03", + ty: U16, + id: Id( + 3, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f04", + ty: U32, + id: Id( + 4, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f05", + ty: U64, + id: Id( + 5, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f06", + ty: U128, + id: Id( + 6, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f07", + ty: I8, + id: Id( + 7, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f08", + ty: I16, + id: Id( + 8, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f09", + ty: I32, + id: Id( + 9, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f10", + ty: I64, + id: Id( + 10, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f11", + ty: I128, + id: Id( + 11, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f12", + ty: F32, + id: Id( + 12, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f13", + ty: F64, + id: Id( + 13, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f14", + ty: String, + id: Id( + 14, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f15", + ty: StringRef, + id: Id( + 15, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f16", + ty: Bytes, + id: Id( + 16, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f17", + ty: BytesRef, + id: Id( + 17, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f18", + ty: BoxString, + id: Id( + 18, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f19", + ty: BoxBytes, + id: Id( + 19, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f20", + ty: Tuple( + [ + U32, + U32, + U32, + ], + ), + id: Id( + 20, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f21", + ty: Array( + U32, + 12, + ), + id: Id( + 21, + ), + }, + ], + ), + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@types-generic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@types-generic.stef.snap new file mode 100644 index 0000000..7d72727 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@types-generic.stef.snap @@ -0,0 +1,90 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/types-generic.stef +--- +Schema { + definitions: [ + Struct( + Struct { + comment: Comment( + [], + ), + attributes: Attributes( + [], + ), + name: "Sample", + generics: Generics( + [], + ), + fields: Named( + [ + NamedField { + comment: Comment( + [], + ), + name: "f1", + ty: Vec( + U32, + ), + id: Id( + 1, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f2", + ty: HashMap( + ( + U32, + String, + ), + ), + id: Id( + 2, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f3", + ty: HashSet( + U32, + ), + id: Id( + 3, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f4", + ty: Option( + U32, + ), + id: Id( + 4, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "f5", + ty: NonZero( + U32, + ), + id: Id( + 5, + ), + }, + ], + ), + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@types-nested.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@types-nested.stef.snap new file mode 100644 index 0000000..93443cf --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@types-nested.stef.snap @@ -0,0 +1,48 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/types-nested.stef +--- +Schema { + definitions: [ + Struct( + Struct { + comment: Comment( + [], + ), + attributes: Attributes( + [], + ), + name: "Sample", + generics: Generics( + [], + ), + fields: Named( + [ + NamedField { + comment: Comment( + [], + ), + name: "value", + ty: Vec( + Option( + NonZero( + HashMap( + ( + I64, + BoxString, + ), + ), + ), + ), + ), + id: Id( + 1, + ), + }, + ], + ), + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__parse@types-ref.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@types-ref.stef.snap new file mode 100644 index 0000000..7f2414a --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__parse@types-ref.stef.snap @@ -0,0 +1,62 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/types-ref.stef +--- +Schema { + definitions: [ + Struct( + Struct { + comment: Comment( + [], + ), + attributes: Attributes( + [], + ), + name: "Sample", + generics: Generics( + [], + ), + fields: Named( + [ + NamedField { + comment: Comment( + [], + ), + name: "basic", + ty: External( + ExternalType { + path: [], + name: "Test123", + generics: [], + }, + ), + id: Id( + 1, + ), + }, + NamedField { + comment: Comment( + [], + ), + name: "with_generics", + ty: External( + ExternalType { + path: [], + name: "KeyValue", + generics: [ + U32, + Bool, + ], + }, + ), + id: Id( + 2, + ), + }, + ], + ), + }, + ), + ], +} diff --git a/crates/stef-parser/tests/snapshots/parser__print@alias-basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@alias-basic.stef.snap new file mode 100644 index 0000000..dc0ecf6 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@alias-basic.stef.snap @@ -0,0 +1,8 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/alias-basic.stef +--- +/// Sample type alias. +type Sample = u32; + diff --git a/crates/stef-parser/tests/snapshots/parser__print@attribute-multi.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@attribute-multi.stef.snap new file mode 100644 index 0000000..d7616ab --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@attribute-multi.stef.snap @@ -0,0 +1,9 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/attribute-multi.stef +--- +#[validate(min = 1, max = 100)] +struct Sample + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@attribute-single.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@attribute-single.stef.snap new file mode 100644 index 0000000..d1d7e1a --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@attribute-single.stef.snap @@ -0,0 +1,9 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/attribute-single.stef +--- +#[deprecated = "don't use"] +struct Sample + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@attribute-unit.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@attribute-unit.stef.snap new file mode 100644 index 0000000..7943c33 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@attribute-unit.stef.snap @@ -0,0 +1,9 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/attribute-unit.stef +--- +#[deprecated] +struct Sample + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@attributes-min-ws.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@attributes-min-ws.stef.snap new file mode 100644 index 0000000..b2ef182 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@attributes-min-ws.stef.snap @@ -0,0 +1,9 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/attributes-min-ws.stef +--- +#[deprecated = "don't use", compress, validate(in_range(min = 100, max = 200), non_empty)] +struct Sample + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@attributes.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@attributes.stef.snap new file mode 100644 index 0000000..f066a05 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@attributes.stef.snap @@ -0,0 +1,9 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/attributes.stef +--- +#[deprecated = "don't use", compress, validate(in_range(min = 100, max = 200), non_empty)] +struct Sample + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@const-basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@const-basic.stef.snap new file mode 100644 index 0000000..cbc3f55 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@const-basic.stef.snap @@ -0,0 +1,12 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/const-basic.stef +--- +const BOOL_TRUE: bool = true; +const BOOL_FALSE: bool = false; +const INT: u32 = 100; +const FLOAT: f64 = 5.5; +const STRING: string = "value"; +const BYTES: bytes = [1, 2, 3]; + diff --git a/crates/stef-parser/tests/snapshots/parser__print@const-string.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@const-string.stef.snap new file mode 100644 index 0000000..7093104 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@const-string.stef.snap @@ -0,0 +1,10 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/const-string.stef +--- +const SIMPLE: string = "value"; +const NEWLINE_ESCAPE: string = "one two three"; +const ESCAPES: string = "escape basics \r\n \t \u{8} \u{c} \\ \"hello\" \nunicode ❤ emoji ❤ "; +const MULTILINE: string = "a\n b\n c\n"; + diff --git a/crates/stef-parser/tests/snapshots/parser__print@enum-basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@enum-basic.stef.snap new file mode 100644 index 0000000..9b792ed --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@enum-basic.stef.snap @@ -0,0 +1,18 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/enum-basic.stef +--- +/// Sample enum. +enum Sample { + One @1, + /// Second variant + Two(u32 @1, u64 @2) @2, + Three { + field1: u32 @1, + /// Second field of third variant + field2: bool @2, + } @3, +} + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@enum-generics.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@enum-generics.stef.snap new file mode 100644 index 0000000..0f824a6 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@enum-generics.stef.snap @@ -0,0 +1,16 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/enum-generics.stef +--- +/// Enum with generics. +enum Sample { + One @1, + Two(A @1, B @2) @2, + Three { + field1: C @1, + field2: D @2, + } @3, +} + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@enum-many-ws.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@enum-many-ws.stef.snap new file mode 100644 index 0000000..1d30b39 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@enum-many-ws.stef.snap @@ -0,0 +1,16 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/enum-many-ws.stef +--- +/// Sample enum. +enum Sample { + One @1, + Two(u32 @1, u64 @2) @2, + Three { + field1: u32 @1, + field2: bool @2, + } @3, +} + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@enum-min-ws.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@enum-min-ws.stef.snap new file mode 100644 index 0000000..b4a965a --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@enum-min-ws.stef.snap @@ -0,0 +1,16 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/enum-min-ws.stef +--- +enum Sample { + One @1, + Two(u32 @1, u64 @2, T @3) @2, + Three { + field1: u32 @1, + field2: bool @2, + field3: T @3, + } @3, +} + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@import-basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@import-basic.stef.snap new file mode 100644 index 0000000..6b824c6 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@import-basic.stef.snap @@ -0,0 +1,8 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/import-basic.stef +--- +use other::schema::Sample; +use second::submodule; + diff --git a/crates/stef-parser/tests/snapshots/parser__print@module-basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@module-basic.stef.snap new file mode 100644 index 0000000..0a2b69f --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@module-basic.stef.snap @@ -0,0 +1,19 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/module-basic.stef +--- +mod a { + /// Inner module + mod b { + enum Sample { + One @1, + } + } + + struct Sample { + value: u32 @1, + } +} + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@schema-basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@schema-basic.stef.snap new file mode 100644 index 0000000..e5d3005 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@schema-basic.stef.snap @@ -0,0 +1,22 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/schema-basic.stef +--- +/// Basic struct. +struct Sample { + a: u32 @1, + b: bool @2, +} + +/// Sample enum. +enum Sample { + One @1, + Two(u32 @1, u64 @2) @2, + Three { + field1: u32 @1, + field2: bool @2, + } @3, +} + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@struct-basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@struct-basic.stef.snap new file mode 100644 index 0000000..28e6590 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@struct-basic.stef.snap @@ -0,0 +1,13 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/struct-basic.stef +--- +/// Basic struct. +struct Sample { + a: u32 @1, + /// Second field + b: bool @2, +} + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@struct-generics.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@struct-generics.stef.snap new file mode 100644 index 0000000..09ea6fe --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@struct-generics.stef.snap @@ -0,0 +1,12 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/struct-generics.stef +--- +/// Generic key-value pair. +struct KeyValue { + key: K @1, + value: V @2, +} + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@struct-many-ws.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@struct-many-ws.stef.snap new file mode 100644 index 0000000..2d163bc --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@struct-many-ws.stef.snap @@ -0,0 +1,12 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/struct-many-ws.stef +--- +/// Some comment +struct Sample { + a: u32 @1, + b: bool @2, +} + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@struct-min-ws.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@struct-min-ws.stef.snap new file mode 100644 index 0000000..9098f11 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@struct-min-ws.stef.snap @@ -0,0 +1,12 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/struct-min-ws.stef +--- +struct Sample { + a: u32 @1, + b: bool @2, + c: T @3, +} + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@types-basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@types-basic.stef.snap new file mode 100644 index 0000000..37a4737 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@types-basic.stef.snap @@ -0,0 +1,30 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/types-basic.stef +--- +struct Sample { + f01: bool @1, + f02: u8 @2, + f03: u16 @3, + f04: u32 @4, + f05: u64 @5, + f06: u128 @6, + f07: i8 @7, + f08: i16 @8, + f09: i32 @9, + f10: i64 @10, + f11: i128 @11, + f12: f32 @12, + f13: f64 @13, + f14: string @14, + f15: &string @15, + f16: bytes @16, + f17: &bytes @17, + f18: box @18, + f19: box @19, + f20: (u32, u32, u32) @20, + f21: [u32; 12] @21, +} + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@types-generic.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@types-generic.stef.snap new file mode 100644 index 0000000..eb1e829 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@types-generic.stef.snap @@ -0,0 +1,14 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/types-generic.stef +--- +struct Sample { + f1: vec @1, + f2: hash_map @2, + f3: hash_set @3, + f4: option @4, + f5: non_zero @5, +} + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@types-nested.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@types-nested.stef.snap new file mode 100644 index 0000000..06efc0a --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@types-nested.stef.snap @@ -0,0 +1,10 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/types-nested.stef +--- +struct Sample { + value: vec>>>> @1, +} + + diff --git a/crates/stef-parser/tests/snapshots/parser__print@types-ref.stef.snap b/crates/stef-parser/tests/snapshots/parser__print@types-ref.stef.snap new file mode 100644 index 0000000..8fac8d2 --- /dev/null +++ b/crates/stef-parser/tests/snapshots/parser__print@types-ref.stef.snap @@ -0,0 +1,11 @@ +--- +source: crates/stef-parser/tests/parser.rs +expression: "Schema :: parse(input.as_str()).unwrap()" +input_file: crates/stef-parser/tests/inputs/types-ref.stef +--- +struct Sample { + basic: Test123 @1, + with_generics: KeyValue @2, +} + + diff --git a/crates/stef/Cargo.toml b/crates/stef/Cargo.toml new file mode 100644 index 0000000..6b4606c --- /dev/null +++ b/crates/stef/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "stef" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +readme.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +bytes = "1.4.0" +paste = "1.0.14" +thiserror = "1.0.47" diff --git a/crates/stef/src/lib.rs b/crates/stef/src/lib.rs new file mode 100644 index 0000000..b3a108e --- /dev/null +++ b/crates/stef/src/lib.rs @@ -0,0 +1,403 @@ +use std::{ + collections::{HashMap, HashSet}, + num::{ + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroU128, NonZeroU16, + NonZeroU32, NonZeroU64, NonZeroU8, + }, +}; + +use bytes::BufMut; + +pub mod varint; + +pub fn encode_bool(w: &mut impl BufMut, value: bool) { + w.put_u8(value.into()); +} + +pub fn encode_u8(w: &mut impl BufMut, value: u8) { + w.put_u8(value); +} + +pub fn encode_i8(w: &mut impl BufMut, value: i8) { + w.put_i8(value); +} +macro_rules! encode_int { + ($ty:ty) => { + paste::paste! { + pub fn [](w: &mut impl BufMut, value: $ty) { + let (buf, len) = varint::[](value); + w.put(&buf[..len]); + } + } + }; + ($($ty:ty),+ $(,)?) => { + $(encode_int!($ty);)+ + } +} + +encode_int!(u16, u32, u64, u128); +encode_int!(i16, i32, i64, i128); + +pub fn encode_f32(w: &mut impl BufMut, value: f32) { + w.put_f32(value); +} + +pub fn encode_f64(w: &mut impl BufMut, value: f64) { + w.put_f64(value); +} + +pub fn write_id(w: &mut impl BufMut, id: u32) { + let (buf, len) = varint::encode_u32(id); + w.put(&buf[..len]); +} + +pub fn write_discr(w: &mut impl BufMut, discr: u32) { + let (buf, len) = varint::encode_u32(discr); + + w.put(&buf[..len]); +} + +pub fn write_string(w: &mut impl BufMut, value: &str) { + write_bytes(w, value.as_bytes()); +} + +pub fn write_bytes(w: &mut impl BufMut, value: &[u8]) { + let (buf, len) = varint::encode_u64(value.len() as u64); + + w.put(&buf[..len]); + w.put(value) +} + +pub trait Encode { + fn encode(&self, w: &mut impl BufMut); +} + +pub fn write_vec(w: &mut impl BufMut, vec: &Vec) { + encode_u64(w, vec.len() as u64); + + for value in vec { + value.encode(w); + } +} + +pub fn write_hash_map(w: &mut impl BufMut, map: &HashMap) { + encode_u64(w, map.len() as u64); + + for (key, value) in map { + key.encode(w); + value.encode(w); + } +} + +pub fn write_hash_set(w: &mut impl BufMut, set: &HashSet) { + encode_u64(w, set.len() as u64); + + for value in set { + value.encode(w); + } +} + +pub fn write_option(w: &mut impl BufMut, option: &Option) { + if let Some(value) = option { + value.encode(w); + } +} + +// TODO: NonZero + +pub fn write_array(w: &mut impl BufMut, array: &[T; N]) { + encode_u64(w, array.len() as u64); + + for value in array { + value.encode(w); + } +} + +pub fn write_tuple1(w: &mut impl BufMut, tuple: &(T0,)) +where + T0: Encode, +{ + tuple.0.encode(w); +} + +pub fn write_tuple2(w: &mut impl BufMut, tuple: &(T0, T1)) +where + T0: Encode, + T1: Encode, +{ + tuple.0.encode(w); + tuple.1.encode(w); +} + +pub fn write_tuple3(w: &mut impl BufMut, tuple: &(T0, T1, T2)) +where + T0: Encode, + T1: Encode, + T2: Encode, +{ + tuple.0.encode(w); + tuple.1.encode(w); + tuple.2.encode(w); +} + +pub fn write_tuple4(w: &mut impl BufMut, tuple: &(T0, T1, T2, T3)) +where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, +{ + tuple.0.encode(w); + tuple.1.encode(w); + tuple.2.encode(w); + tuple.3.encode(w); +} + +pub fn write_tuple5(w: &mut impl BufMut, tuple: &(T0, T1, T2, T3, T4)) +where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, + T4: Encode, +{ + tuple.0.encode(w); + tuple.1.encode(w); + tuple.2.encode(w); + tuple.3.encode(w); + tuple.4.encode(w); +} + +pub fn write_tuple6(w: &mut impl BufMut, tuple: &(T0, T1, T2, T3, T4, T5)) +where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, + T4: Encode, + T5: Encode, +{ + tuple.0.encode(w); + tuple.1.encode(w); + tuple.2.encode(w); + tuple.3.encode(w); + tuple.4.encode(w); + tuple.5.encode(w); +} + +pub fn write_tuple7( + w: &mut impl BufMut, + tuple: &(T0, T1, T2, T3, T4, T5, T6), +) where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, + T4: Encode, + T5: Encode, + T6: Encode, +{ + tuple.0.encode(w); + tuple.1.encode(w); + tuple.2.encode(w); + tuple.3.encode(w); + tuple.4.encode(w); + tuple.5.encode(w); + tuple.6.encode(w); +} + +pub fn write_tuple8( + w: &mut impl BufMut, + tuple: &(T0, T1, T2, T3, T4, T5, T6, T7), +) where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, + T4: Encode, + T5: Encode, + T6: Encode, + T7: Encode, +{ + tuple.0.encode(w); + tuple.1.encode(w); + tuple.2.encode(w); + tuple.3.encode(w); + tuple.4.encode(w); + tuple.5.encode(w); + tuple.6.encode(w); + tuple.7.encode(w); +} + +pub fn write_tuple9( + w: &mut impl BufMut, + tuple: &(T0, T1, T2, T3, T4, T5, T6, T7, T8), +) where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, + T4: Encode, + T5: Encode, + T6: Encode, + T7: Encode, + T8: Encode, +{ + tuple.0.encode(w); + tuple.1.encode(w); + tuple.2.encode(w); + tuple.3.encode(w); + tuple.4.encode(w); + tuple.5.encode(w); + tuple.6.encode(w); + tuple.7.encode(w); + tuple.8.encode(w); +} + +pub fn write_tuple10( + w: &mut impl BufMut, + tuple: &(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9), +) where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, + T4: Encode, + T5: Encode, + T6: Encode, + T7: Encode, + T8: Encode, + T9: Encode, +{ + tuple.0.encode(w); + tuple.1.encode(w); + tuple.2.encode(w); + tuple.3.encode(w); + tuple.4.encode(w); + tuple.5.encode(w); + tuple.6.encode(w); + tuple.7.encode(w); + tuple.8.encode(w); + tuple.9.encode(w); +} + +pub fn write_tuple11( + w: &mut impl BufMut, + tuple: &(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10), +) where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, + T4: Encode, + T5: Encode, + T6: Encode, + T7: Encode, + T8: Encode, + T9: Encode, + T10: Encode, +{ + tuple.0.encode(w); + tuple.1.encode(w); + tuple.2.encode(w); + tuple.3.encode(w); + tuple.4.encode(w); + tuple.5.encode(w); + tuple.6.encode(w); + tuple.7.encode(w); + tuple.8.encode(w); + tuple.9.encode(w); + tuple.10.encode(w); +} + +#[allow(clippy::type_complexity)] +pub fn write_tuple12( + w: &mut impl BufMut, + tuple: &(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), +) where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, + T4: Encode, + T5: Encode, + T6: Encode, + T7: Encode, + T8: Encode, + T9: Encode, + T10: Encode, + T11: Encode, +{ + tuple.0.encode(w); + tuple.1.encode(w); + tuple.2.encode(w); + tuple.3.encode(w); + tuple.4.encode(w); + tuple.5.encode(w); + tuple.6.encode(w); + tuple.7.encode(w); + tuple.8.encode(w); + tuple.9.encode(w); + tuple.10.encode(w); + tuple.11.encode(w); +} + +impl Encode for NonZeroU8 { + fn encode(&self, w: &mut impl BufMut) { + encode_u8(w, self.get()); + } +} + +impl Encode for NonZeroU16 { + fn encode(&self, w: &mut impl BufMut) { + encode_u16(w, self.get()); + } +} + +impl Encode for NonZeroU32 { + fn encode(&self, w: &mut impl BufMut) { + encode_u32(w, self.get()); + } +} + +impl Encode for NonZeroU64 { + fn encode(&self, w: &mut impl BufMut) { + encode_u64(w, self.get()); + } +} + +impl Encode for NonZeroU128 { + fn encode(&self, w: &mut impl BufMut) { + encode_u128(w, self.get()); + } +} + +impl Encode for NonZeroI8 { + fn encode(&self, w: &mut impl BufMut) { + encode_i8(w, self.get()); + } +} + +impl Encode for NonZeroI16 { + fn encode(&self, w: &mut impl BufMut) { + encode_i16(w, self.get()); + } +} + +impl Encode for NonZeroI32 { + fn encode(&self, w: &mut impl BufMut) { + encode_i32(w, self.get()); + } +} + +impl Encode for NonZeroI64 { + fn encode(&self, w: &mut impl BufMut) { + encode_i64(w, self.get()); + } +} + +impl Encode for NonZeroI128 { + fn encode(&self, w: &mut impl BufMut) { + encode_i128(w, self.get()); + } +} diff --git a/crates/stef/src/varint.rs b/crates/stef/src/varint.rs new file mode 100644 index 0000000..b4c6799 --- /dev/null +++ b/crates/stef/src/varint.rs @@ -0,0 +1,92 @@ +use thiserror::Error; + +macro_rules! zigzag { + ($from:ty, $to:ty) => { + paste::paste! { + #[doc = "Use the _ZigZag_ scheme to encode an `" $from "` as `" $to "`."] + #[inline] + fn [](value: $from) -> $to { + ((value << 1) ^ (value >> ($from::BITS - 1))) as $to + } + + #[doc = "Convert a _ZigZag_ encoded `" $from "` back to its original data."] + #[inline] + fn [](value: $to) -> $from { + ((value >> 1) as $from) ^ (-((value & 0b1) as $from)) + } + } + }; + ($($from:ty => $to:ty),+ $(,)?) => { + $(zigzag!($from, $to);)+ + } +} + +zigzag!( + i16 => u16, + i32 => u32, + i64 => u64, + i128 => u128, +); + +/// Calculate the maximum amount of bytes that an integer might require to be encoded as _varint_. +#[inline] +pub(crate) const fn max_size() -> usize { + (std::mem::size_of::() * 8 + 7) / 7 +} + +macro_rules! varint { + ($ty:ty, $signed:ty) => { + paste::paste! { + #[inline] + pub fn [](mut value: $ty) -> ([u8; max_size::<$ty>()], usize) { + let mut buf = [0; max_size::<$ty>()]; + + for (i, b) in buf.iter_mut().enumerate() { + *b = (value & 0xff) as u8; + if value < 128 { + return (buf, i + 1); + } + + *b |= 0x80; + value >>= 7; + } + + debug_assert_eq!(value, 0); + (buf, buf.len()) + } + + #[inline] + pub fn [](buf: &[u8]) -> Result<($ty, &[u8]), DecodeIntError> { + let mut value = 0; + for (i, b) in buf.iter().copied().enumerate().take(max_size::<$ty>()) { + value |= ((b & 0x7f) as $ty) << (7 * i); + + if b & 0x80 == 0 { + return Ok((value, &buf[i + 1..])); + } + } + + Err(DecodeIntError) + } + + #[inline] + pub fn [](value: $signed) -> ([u8; max_size::<$ty>()], usize) { + []([](value)) + } + + #[inline] + pub fn [](buf: &[u8]) -> Result<($signed, &[u8]), DecodeIntError> { + [](buf).map(|(v, b)| ([](v), b)) + } + } + }; + ($(($ty:ty, $signed:ty)),+ $(,)?) => { + $(varint!($ty, $signed);)+ + } +} + +varint!((u16, i16), (u32, i32), (u64, i64), (u128, i128)); + +#[derive(Debug, Error)] +#[error("input was lacking a final marker for the end of the integer data")] +pub struct DecodeIntError; diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..a3e3e5e --- /dev/null +++ b/deny.toml @@ -0,0 +1,16 @@ +all-features = true + +[licenses] +allow-osi-fsf-free = "both" +exceptions = [ + { allow = ["Unicode-DFS-2016"], name = "unicode-ident" }, +] + +[bans] +skip = [ + { name = "terminal_size", version = "0.1" }, +] +skip-tree = [ + { name = "rustix", version = "0.37", depth = 2 }, + { name = "windows-sys", version = "0.45", depth = 3 }, +] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..2f10164 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,7 @@ +comment_width = 100 +format_strings = true +group_imports = "StdExternalCrate" +imports_granularity = "Crate" +reorder_impl_items = true +use_field_init_shorthand = true +wrap_comments = true diff --git a/vscode-extension/.gitignore b/vscode-extension/.gitignore new file mode 100644 index 0000000..0d59039 --- /dev/null +++ b/vscode-extension/.gitignore @@ -0,0 +1,3 @@ +/node_modules +/syntaxes/*.json +*.vsix diff --git a/vscode-extension/.vscodeignore b/vscode-extension/.vscodeignore new file mode 100644 index 0000000..f16182d --- /dev/null +++ b/vscode-extension/.vscodeignore @@ -0,0 +1,3 @@ +syntaxes/*.yaml +.gitignore +pnpm-lock.yaml diff --git a/vscode-extension/LICENSE.md b/vscode-extension/LICENSE.md new file mode 120000 index 0000000..7eabdb1 --- /dev/null +++ b/vscode-extension/LICENSE.md @@ -0,0 +1 @@ +../LICENSE.md \ No newline at end of file diff --git a/vscode-extension/language-configuration.json b/vscode-extension/language-configuration.json new file mode 100644 index 0000000..a341d1e --- /dev/null +++ b/vscode-extension/language-configuration.json @@ -0,0 +1,30 @@ +{ + "comments": { + // symbol used for single line comment. Remove this entry if your language does not support line comments + "lineComment": "///", + // symbols used for start and end a block comment. Remove this entry if your language does not support block comments + // "blockComment": [ "/*", "*/" ] + }, + // symbols used as brackets + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + // symbols that are auto closed when typing + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + // ["\"", "\""], + // ["'", "'"] + ], + // symbols that can be used to surround a selection + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + // ["\"", "\""], + // ["'", "'"] + ] +} diff --git a/vscode-extension/package.json b/vscode-extension/package.json new file mode 100644 index 0000000..fdec7d9 --- /dev/null +++ b/vscode-extension/package.json @@ -0,0 +1,51 @@ +{ + "name": "stef", + "displayName": "STEF", + "description": "", + "version": "0.1.0", + "license": "MIT", + "repository": { + "url": "https://github.com/dnaka91/stef.git" + }, + "engines": { + "node": ">=20", + "pnpm": ">=8", + "vscode": "^1.81.0" + }, + "categories": [ + "Programming Languages" + ], + "contributes": { + "languages": [ + { + "id": "stef", + "aliases": [ + "STEF", + "stef" + ], + "extensions": [ + ".stef" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "stef", + "scopeName": "source.stef", + "path": "./syntaxes/stef.tmLanguage.json" + } + ] + }, + "vsce": { + "dependencies": false + }, + "scripts": { + "vscode:prepublish": "js-yaml syntaxes/stef.tmLanguage.yaml > syntaxes/stef.tmLanguage.json", + "package": "vsce package" + }, + "devDependencies": { + "@vscode/vsce": "^2.21.0", + "js-yaml": "^4.1.0" + } +} diff --git a/vscode-extension/pnpm-lock.yaml b/vscode-extension/pnpm-lock.yaml new file mode 100644 index 0000000..2d424ae --- /dev/null +++ b/vscode-extension/pnpm-lock.yaml @@ -0,0 +1,772 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +devDependencies: + '@vscode/vsce': + specifier: ^2.21.0 + version: 2.21.0 + js-yaml: + specifier: ^4.1.0 + version: 4.1.0 + +packages: + + /@vscode/vsce@2.21.0: + resolution: {integrity: sha512-KuxYqScqUY/duJbkj9eE2tN2X/WJoGAy54hHtxT3ZBkM6IzrOg7H7CXGUPBxNlmqku2w/cAjOUSrgIHlzz0mbA==} + engines: {node: '>= 14'} + hasBin: true + dependencies: + azure-devops-node-api: 11.2.0 + chalk: 2.4.2 + cheerio: 1.0.0-rc.12 + commander: 6.2.1 + glob: 7.2.3 + hosted-git-info: 4.1.0 + jsonc-parser: 3.2.0 + leven: 3.1.0 + markdown-it: 12.3.2 + mime: 1.6.0 + minimatch: 3.1.2 + parse-semver: 1.1.1 + read: 1.0.7 + semver: 7.5.4 + tmp: 0.2.1 + typed-rest-client: 1.8.11 + url-join: 4.0.1 + xml2js: 0.5.0 + yauzl: 2.10.0 + yazl: 2.5.1 + optionalDependencies: + keytar: 7.9.0 + dev: true + + /ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: true + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /azure-devops-node-api@11.2.0: + resolution: {integrity: sha512-XdiGPhrpaT5J8wdERRKs5g8E0Zy1pvOYTli7z9E8nmOn3YGp4FhtjhrOyFmX/8veWCwdI69mCHKJw6l+4J/bHA==} + dependencies: + tunnel: 0.0.6 + typed-rest-client: 1.8.11 + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + requiresBuild: true + dev: true + optional: true + + /bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + requiresBuild: true + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: true + optional: true + + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + dev: true + + /buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + requiresBuild: true + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: true + optional: true + + /call-bind@1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.2.1 + dev: true + + /chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: true + + /cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + dependencies: + boolbase: 1.0.0 + css-select: 5.1.0 + css-what: 6.1.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + dev: true + + /cheerio@1.0.0-rc.12: + resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} + engines: {node: '>= 6'} + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.1.0 + htmlparser2: 8.0.2 + parse5: 7.1.2 + parse5-htmlparser2-tree-adapter: 7.0.0 + dev: true + + /chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + requiresBuild: true + dev: true + optional: true + + /color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: true + + /color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: true + + /commander@6.2.1: + resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} + engines: {node: '>= 6'} + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.1.0 + nth-check: 2.1.1 + dev: true + + /css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + dev: true + + /decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + requiresBuild: true + dependencies: + mimic-response: 3.1.0 + dev: true + optional: true + + /deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + requiresBuild: true + dev: true + optional: true + + /detect-libc@2.0.2: + resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} + engines: {node: '>=8'} + requiresBuild: true + dev: true + optional: true + + /dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + dev: true + + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: true + + /domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: true + + /domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dev: true + + /end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + requiresBuild: true + dependencies: + once: 1.4.0 + dev: true + optional: true + + /entities@2.1.0: + resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==} + dev: true + + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + dev: true + + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: true + + /expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + requiresBuild: true + dev: true + optional: true + + /fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + dependencies: + pend: 1.2.0 + dev: true + + /fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + requiresBuild: true + dev: true + optional: true + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /function-bind@1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: true + + /get-intrinsic@1.2.1: + resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-proto: 1.0.1 + has-symbols: 1.0.3 + dev: true + + /github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + requiresBuild: true + dev: true + optional: true + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + dev: true + + /has-proto@1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + dev: true + + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: true + + /has@1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + dev: true + + /hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + dependencies: + lru-cache: 6.0.0 + dev: true + + /htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + entities: 4.5.0 + dev: true + + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + requiresBuild: true + dev: true + optional: true + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true + + /ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + requiresBuild: true + dev: true + optional: true + + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /jsonc-parser@3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: true + + /keytar@7.9.0: + resolution: {integrity: sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==} + requiresBuild: true + dependencies: + node-addon-api: 4.3.0 + prebuild-install: 7.1.1 + dev: true + optional: true + + /leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + dev: true + + /linkify-it@3.0.3: + resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==} + dependencies: + uc.micro: 1.0.6 + dev: true + + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + dev: true + + /markdown-it@12.3.2: + resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==} + hasBin: true + dependencies: + argparse: 2.0.1 + entities: 2.1.0 + linkify-it: 3.0.3 + mdurl: 1.0.1 + uc.micro: 1.0.6 + dev: true + + /mdurl@1.0.1: + resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} + dev: true + + /mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + requiresBuild: true + dev: true + optional: true + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + requiresBuild: true + dev: true + optional: true + + /mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + requiresBuild: true + dev: true + optional: true + + /mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + dev: true + + /napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + requiresBuild: true + dev: true + optional: true + + /node-abi@3.47.0: + resolution: {integrity: sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==} + engines: {node: '>=10'} + requiresBuild: true + dependencies: + semver: 7.5.4 + dev: true + optional: true + + /node-addon-api@4.3.0: + resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==} + requiresBuild: true + dev: true + optional: true + + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 + dev: true + + /object-inspect@1.12.3: + resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + dev: true + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /parse-semver@1.1.1: + resolution: {integrity: sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==} + dependencies: + semver: 5.7.2 + dev: true + + /parse5-htmlparser2-tree-adapter@7.0.0: + resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} + dependencies: + domhandler: 5.0.3 + parse5: 7.1.2 + dev: true + + /parse5@7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + dependencies: + entities: 4.5.0 + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + dev: true + + /prebuild-install@7.1.1: + resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} + engines: {node: '>=10'} + hasBin: true + requiresBuild: true + dependencies: + detect-libc: 2.0.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 3.47.0 + pump: 3.0.0 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.1 + tunnel-agent: 0.6.0 + dev: true + optional: true + + /pump@3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + requiresBuild: true + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + dev: true + optional: true + + /qs@6.11.2: + resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: true + + /rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + requiresBuild: true + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + dev: true + optional: true + + /read@1.0.7: + resolution: {integrity: sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==} + engines: {node: '>=0.8'} + dependencies: + mute-stream: 0.0.8 + dev: true + + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + requiresBuild: true + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: true + optional: true + + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + requiresBuild: true + dev: true + optional: true + + /sax@1.2.4: + resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} + dev: true + + /semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + dev: true + + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: true + + /side-channel@1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + object-inspect: 1.12.3 + dev: true + + /simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + requiresBuild: true + dev: true + optional: true + + /simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + requiresBuild: true + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + dev: true + optional: true + + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + requiresBuild: true + dependencies: + safe-buffer: 5.2.1 + dev: true + optional: true + + /strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + requiresBuild: true + dev: true + optional: true + + /supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: true + + /tar-fs@2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + requiresBuild: true + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.0 + tar-stream: 2.2.0 + dev: true + optional: true + + /tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + requiresBuild: true + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: true + optional: true + + /tmp@0.2.1: + resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} + engines: {node: '>=8.17.0'} + dependencies: + rimraf: 3.0.2 + dev: true + + /tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + requiresBuild: true + dependencies: + safe-buffer: 5.2.1 + dev: true + optional: true + + /tunnel@0.0.6: + resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} + engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + dev: true + + /typed-rest-client@1.8.11: + resolution: {integrity: sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==} + dependencies: + qs: 6.11.2 + tunnel: 0.0.6 + underscore: 1.13.6 + dev: true + + /uc.micro@1.0.6: + resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} + dev: true + + /underscore@1.13.6: + resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==} + dev: true + + /url-join@4.0.1: + resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} + dev: true + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + requiresBuild: true + dev: true + optional: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true + + /xml2js@0.5.0: + resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} + engines: {node: '>=4.0.0'} + dependencies: + sax: 1.2.4 + xmlbuilder: 11.0.1 + dev: true + + /xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + dev: true + + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: true + + /yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + dev: true + + /yazl@2.5.1: + resolution: {integrity: sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==} + dependencies: + buffer-crc32: 0.2.13 + dev: true diff --git a/vscode-extension/syntaxes/stef.tmLanguage.yaml b/vscode-extension/syntaxes/stef.tmLanguage.yaml new file mode 100644 index 0000000..a8c68fb --- /dev/null +++ b/vscode-extension/syntaxes/stef.tmLanguage.yaml @@ -0,0 +1,183 @@ +$schema: "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json" +name: "STEF Schema" +scopeName: source.stef +fileTypes: [stef] + +patterns: + - include: "#comments" + - include: "#mod" + - include: "#struct" + - include: "#enum" + +repository: + comments: + patterns: + - name: comment.line.documentation.stef comment.line.triple-slash.stef + begin: /// + end: $\n? + + attributes: + patterns: + - name: meta.preprocessor.stef + begin: '#\[' + end: $\]\n? + + mod: + begin: (mod)\s+([a-z][a-z0-9_]*)\s*(?=\{) + beginCaptures: + "1": { name: keyword.declaration.mod.stef } + "2": { name: entity.name.class.mod.stef } + end: (?<=\}) + patterns: + - include: "#comments" + - include: "$self" + - include: "#struct" + - include: "#enum" + + struct: + begin: (struct)\s+([A-Z][A-Za-z0-9_]*)\s*(<.+>)?\s*(?=\{) + beginCaptures: + "1": { name: keyword.declaration.struct.stef storage.type.struct.stef } + "2": { name: entity.name.class.struct.stef } + "3": { patterns: [include: "#generics"] } + end: (?<=\}) + patterns: + - include: "#comments" + - include: "#fieldsNamed" + - include: "#fieldsUnnamed" + + enum: + begin: (enum)\s+([A-Z][A-Za-z0-9_]*)\s*(?=\{) + beginCaptures: + "1": { name: keyword.declaration.enum.stef storage.type.enum.stef } + "2": { name: entity.name.class.enum.stef } + end: (?<=\}) + patterns: + - include: "#comments" + - include: "#variants" + variants: + patterns: + - begin: ([A-Z][A-Za-z0-9_]*)\s*(?=\{) + beginCaptures: + "1": { name: entity.name.class.enum.variant.named.stef } + end: (?<=\})\s*(@\d+)(,)? + endCaptures: + "1": { name: constant.numeric.stef } + "2": { name: punctuation.comma.stef } + patterns: + - include: "#comments" + - include: "#fieldsNamed" + - begin: ([A-Z][A-Za-z0-9_]*)\s*(?=\() + beginCaptures: + "1": { name: entity.name.class.enum.variant.unnamed.stef } + end: (?<=\))\s*(@\d+)(,)? + endCaptures: + "1": { name: constant.numeric.stef } + "2": { name: punctuation.comma.stef } + patterns: + - include: "#comments" + - include: "#fieldsUnnamed" + - match: ([A-Z][A-Za-z0-9_]*)\s*(@\d+)(,)? + captures: + "1": { name: entity.name.class.enum.variant.unit.stef } + "2": { name: constant.numeric.stef } + "3": { name: punctuation.comma.stef } + + fieldsNamed: + begin: \{ + beginCaptures: + "0": { name: punctuation.brackets.curly.open } + end: \} + endCaptures: + "0": { name: punctuation.brackets.curly.close } + patterns: + - include: "#comments" + - include: "#fieldNamed" + fieldNamed: + patterns: + - match: \s*([a-z][a-z0-9_]*)\s*(:)\s*([A-Za-z0-9_&<>\[\]\(\);, ]+)\s*(@\d+)(,)? + captures: + "1": { name: variable.language.stef variable.other.stef } + "2": { name: punctuation.colon.stef } + "3": { patterns: [include: "#type"] } + "4": { name: constant.numeric.stef } + "5": { name: punctuation.comma.stef } + fieldsUnnamed: + begin: \( + beginCaptures: + "0": { name: punctuation.brackets.round.open } + end: \) + endCaptures: + "0": { name: punctuation.brackets.round.close } + patterns: + - include: "#comments" + - include: "#fieldUnnamed" + fieldUnnamed: + patterns: + - match: ([A-Za-z0-9_&<>\[\]\(\);, ]+)\s*(@\d+)(,)? + captures: + "1": { name: storage.type.stef } + "2": { name: constant.numeric.stef } + "3": { name: punctuation.comma.stef } + + generics: + begin: "<" + beginCaptures: + "0": { name: punctuation.brackets.angle.open } + end: ">" + endCaptures: + "0": { name: punctuation.brackets.angle.close } + patterns: + - match: ([A-Za-z0-9_&\[\]\(\)<>;, ]+?)\s*(,)? + captures: + "1": { name: storage.type.stef } + "2": { name: punctuation.comma.stef } + + type: + patterns: + - name: storage.type.builtin.stef + match: (bool|[iu](?:8|16|32|64|128)|f(?:32|64)|&?(?:string|bytes)|box<(?:string|bytes)>) + - name: storage.type.builtin.tuple.stef + begin: \( + beginCaptures: + "0": { name: punctuation.brackets.round.open } + end: \) + endCaptures: + "0": { name: punctuation.brackets.round.close } + patterns: + - include: "#type" + - name: punctuation.comma.stef + match: "," + - name: storage.type.builtin.array.stef + match: (\[)([^;]+)\s*(;)\s*(\d+)(\]) + captures: + "1": { name: punctuation.brackets.square.open } + "2": { patterns: [include: "#type"] } + "3": { name: punctuation.semicolon.stef } + "4": { name: constant.numeric.stef } + "5": { name: punctuation.brackets.square.close } + - name: storage.type.builtin.vec.stef + match: vec(<.+>) + captures: + "1": { patterns: [include: "#generics"] } + - name: storage.type.builtin.hash_map.stef + match: hash_map(.+,.+>) + captures: + "1": { patterns: [include: "#generics"] } + - name: storage.type.builtin.hash_set.stef + match: hash_set(<.+>) + captures: + "1": { patterns: [include: "#generics"] } + - name: storage.type.builtin.option.stef + match: option(<.+>) + captures: + "1": { patterns: [include: "#generics"] } + - name: storage.type.builtin.non_zero.stef + match: non_zero(<.+>) + captures: + "1": { patterns: [include: "#generics"] } + + id: + patterns: + - name: constant.numeric.stef + match: '@\d+'