From 37bffe9ebe23d6d36885a6b6017f8df4e355135a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABlle=20MARTIN?= <maelle.martin@proton.me> Date: Tue, 20 Aug 2024 18:21:12 +0200 Subject: [PATCH] SCRIPT: Rework the codegen and passes + Cleanup --- README.md | 10 +- rsc/licence/MPL-2.0 | 347 ++++++++++++++ src/Rust/Cargo.lock | 106 ++--- src/Rust/Cargo.toml | 21 +- src/Rust/vvs_ass/Cargo.toml | 7 +- src/Rust/vvs_ass/src/styles.rs | 2 +- src/Rust/vvs_cli/Cargo.toml | 31 +- src/Rust/vvs_cli/src/args.rs | 2 +- src/Rust/vvs_cli/src/config.rs | 82 ++-- src/Rust/vvs_cli/src/main.rs | 157 +++---- src/Rust/vvs_codegen/Cargo.toml | 23 +- src/Rust/vvs_codegen/build.rs | 3 - src/Rust/vvs_codegen/src/context.rs | 105 ----- src/Rust/vvs_codegen/src/error.rs | 15 + src/Rust/vvs_codegen/src/graph.rs | 2 + src/Rust/vvs_codegen/src/lib.rs | 30 +- src/Rust/vvs_codegen/src/lowerer/calls.rs | 108 ----- src/Rust/vvs_codegen/src/lowerer/cast.rs | 329 ------------- src/Rust/vvs_codegen/src/lowerer/constant.rs | 59 --- src/Rust/vvs_codegen/src/lowerer/drops.rs | 109 ----- .../vvs_codegen/src/lowerer/expression.rs | 347 -------------- src/Rust/vvs_codegen/src/lowerer/function.rs | 55 --- .../vvs_codegen/src/lowerer/instruction.rs | 431 ----------------- src/Rust/vvs_codegen/src/lowerer/mod.rs | 266 +++-------- src/Rust/vvs_codegen/src/lowerer/types.rs | 59 +-- src/Rust/vvs_codegen/src/value.rs | 100 ---- src/Rust/vvs_font/build.rs | 44 +- src/Rust/vvs_lang/Cargo.toml | 21 - src/Rust/vvs_lang/src/ast/constant.rs | 134 ------ src/Rust/vvs_lang/src/ast/expression.rs | 441 ------------------ src/Rust/vvs_lang/src/ast/extension.rs | 11 - src/Rust/vvs_lang/src/ast/function.rs | 36 -- src/Rust/vvs_lang/src/ast/identifier.rs | 57 --- src/Rust/vvs_lang/src/ast/import.rs | 77 --- src/Rust/vvs_lang/src/ast/instruction.rs | 187 -------- src/Rust/vvs_lang/src/ast/maybe_const_expr.rs | 26 -- src/Rust/vvs_lang/src/ast/mod.rs | 64 --- src/Rust/vvs_lang/src/ast/module.rs | 201 -------- src/Rust/vvs_lang/src/ast/options.rs | 64 --- src/Rust/vvs_lang/src/ast/pattern.rs | 39 -- src/Rust/vvs_lang/src/ast/program.rs | 140 ------ src/Rust/vvs_lang/src/ast/span.rs | 76 --- src/Rust/vvs_lang/src/ast/string.rs | 28 -- src/Rust/vvs_lang/src/ast/type_context.rs | 180 ------- src/Rust/vvs_lang/src/ast/typed.rs | 17 - src/Rust/vvs_lang/src/ast/types.rs | 226 --------- src/Rust/vvs_lang/src/ast/variable.rs | 119 ----- src/Rust/vvs_lang/src/ast/variant.rs | 192 -------- src/Rust/vvs_lang/src/ast/visibility.rs | 77 --- src/Rust/vvs_lang/src/lib.rs | 4 - src/Rust/vvs_lib/Cargo.toml | 3 +- src/Rust/vvs_llvm/Cargo.toml | 19 +- src/Rust/vvs_llvm/src/bindings/mod.rs | 16 +- src/Rust/vvs_llvm/src/bindings/module.rs | 8 + src/Rust/vvs_llvm/src/bindings/types.rs | 28 +- src/Rust/vvs_llvm/src/lib.rs | 4 +- src/Rust/vvs_parser/Cargo.toml | 9 +- src/Rust/vvs_parser/samples/tag.vvs | 2 +- src/Rust/vvs_parser/src/ast/mod.rs | 84 +++- src/Rust/vvs_parser/src/ast/options.rs | 2 +- .../src/ast/parsers/structs/result.rs | 16 +- src/Rust/vvs_parser/src/lib.rs | 7 +- src/Rust/vvs_parser/src/tokenizer/lexer.rs | 3 + src/Rust/vvs_parser/src/vivy/error_report.rs | 7 + .../vvs_parser/src/vivy/frontend_pipeline.rs | 150 +++--- src/Rust/vvs_parser/src/vivy/main_program.rs | 21 +- src/Rust/vvs_parser/src/vivy/mod.rs | 4 +- .../src/vivy/passes/opts_transform.rs | 5 +- .../src/vivy/passes/stmt_checker.rs | 28 +- .../src/vivy/passes/type_checker.rs | 278 +++++++---- src/Rust/vvs_parser/src/vivy/search_path.rs | 31 +- src/Rust/vvs_parser/src/vivy/symbol_table.rs | 28 +- src/Rust/vvs_parser_derive/Cargo.toml | 6 +- src/Rust/vvs_procmacro/src/vvrt/gen.rs | 61 +-- src/Rust/vvs_runtime/Cargo.toml | 15 +- src/Rust/vvs_runtime/src/jit.rs | 35 +- src/Rust/vvs_runtime_types/Cargo.toml | 19 +- src/Rust/vvs_runtime_types/src/mangle.rs | 15 - src/Rust/vvs_runtime_types/src/vvll.rs | 40 +- src/Rust/vvs_shortstring/Cargo.toml | 17 + .../src/lib.rs} | 0 src/Rust/vvs_utils/src/xdg/config.rs | 249 +++------- src/Rust/vvs_utils/src/xdg/mod.rs | 14 +- src/Rust/vvs_utils/src/xdg/traits.rs | 7 + 84 files changed, 1353 insertions(+), 5145 deletions(-) create mode 100644 rsc/licence/MPL-2.0 delete mode 100644 src/Rust/vvs_codegen/build.rs delete mode 100644 src/Rust/vvs_codegen/src/context.rs create mode 100644 src/Rust/vvs_codegen/src/error.rs delete mode 100644 src/Rust/vvs_codegen/src/lowerer/calls.rs delete mode 100644 src/Rust/vvs_codegen/src/lowerer/cast.rs delete mode 100644 src/Rust/vvs_codegen/src/lowerer/constant.rs delete mode 100644 src/Rust/vvs_codegen/src/lowerer/drops.rs delete mode 100644 src/Rust/vvs_codegen/src/lowerer/expression.rs delete mode 100644 src/Rust/vvs_codegen/src/lowerer/function.rs delete mode 100644 src/Rust/vvs_codegen/src/lowerer/instruction.rs delete mode 100644 src/Rust/vvs_codegen/src/value.rs delete mode 100644 src/Rust/vvs_lang/Cargo.toml delete mode 100644 src/Rust/vvs_lang/src/ast/constant.rs delete mode 100644 src/Rust/vvs_lang/src/ast/expression.rs delete mode 100644 src/Rust/vvs_lang/src/ast/extension.rs delete mode 100644 src/Rust/vvs_lang/src/ast/function.rs delete mode 100644 src/Rust/vvs_lang/src/ast/identifier.rs delete mode 100644 src/Rust/vvs_lang/src/ast/import.rs delete mode 100644 src/Rust/vvs_lang/src/ast/instruction.rs delete mode 100644 src/Rust/vvs_lang/src/ast/maybe_const_expr.rs delete mode 100644 src/Rust/vvs_lang/src/ast/mod.rs delete mode 100644 src/Rust/vvs_lang/src/ast/module.rs delete mode 100644 src/Rust/vvs_lang/src/ast/options.rs delete mode 100644 src/Rust/vvs_lang/src/ast/pattern.rs delete mode 100644 src/Rust/vvs_lang/src/ast/program.rs delete mode 100644 src/Rust/vvs_lang/src/ast/span.rs delete mode 100644 src/Rust/vvs_lang/src/ast/string.rs delete mode 100644 src/Rust/vvs_lang/src/ast/type_context.rs delete mode 100644 src/Rust/vvs_lang/src/ast/typed.rs delete mode 100644 src/Rust/vvs_lang/src/ast/types.rs delete mode 100644 src/Rust/vvs_lang/src/ast/variable.rs delete mode 100644 src/Rust/vvs_lang/src/ast/variant.rs delete mode 100644 src/Rust/vvs_lang/src/ast/visibility.rs delete mode 100644 src/Rust/vvs_lang/src/lib.rs create mode 100644 src/Rust/vvs_shortstring/Cargo.toml rename src/Rust/{vvs_parser/src/short_string.rs => vvs_shortstring/src/lib.rs} (100%) create mode 100644 src/Rust/vvs_utils/src/xdg/traits.rs diff --git a/README.md b/README.md index ac7fa9ed..8aaa7e44 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ - C++ compiler with [C++17 support](https://en.cppreference.com/w/cpp/17) - [mpv](https://mpv.io/) development library - [Qt6](https://www.qt.io/) development library: QtCore, QtWidgets, QtOpenGL, QtOpenGLWidgets. -- The AV libraries in e recent version: libavutil libavcodec libavformat +- The AV libraries in a recent version: libavutil libavcodec libavformat - [cbindgen](https://github.com/mozilla/cbindgen): `cargo install --force cbindgen` - [cargo-insta](https://insta.rs/docs/cli/): `cargo install --force cargo-insta` - Some unix utils, the `jq` binary, the `bash` shell. @@ -18,7 +18,7 @@ Simply use cmake to build in another folder of the source folder: ```bash # Prepare, compile and install -cmake -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_INSTALL_PREFIX=$HOME/.local -GNinja +cmake -Bbuild -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_INSTALL_PREFIX=$HOME/.local -GNinja ninja -Cbuild ninja -Cbuild install ``` @@ -50,8 +50,8 @@ cargo depgraph --build-deps --dedup-transitive-deps | dot -Tpdf | zathura - ## Licence - The C++ part of this software is licenced under [the LGPL v3.0 or latter](/LICENSE) -- The Rust part (Vivy Script) is under the [MIT license](/src/Rust/LICENSE.txt) +- The most of the [Rust part](/src/Rust) (Vivy Script) is under the [MIT license](/src/Rust/LICENSE.txt) - The NotoSans fonts are distributed using the [OFL licence](/rsc/licence/OFL-1.1) -- Some parts of the Rust folder (vvs_parser, vvs_parser_derive) are under the - [MPL-2.0](/src/Rust/full_moon/LICENSE.txt) +- Some parts of the Rust folder ([vvs_parser](/src/Rust/vvs_parser), [vvs_parser_derive](/src/Rust/vvs_parser_derive)) + are under the [MPL 2.0](/src/Rust/full_moon/LICENSE.txt) and was originally written by *Kampfkarren <kampfkarren@gmail.com>* diff --git a/rsc/licence/MPL-2.0 b/rsc/licence/MPL-2.0 new file mode 100644 index 00000000..2b8bbd45 --- /dev/null +++ b/rsc/licence/MPL-2.0 @@ -0,0 +1,347 @@ +Mozilla Public License Version 2.0 +================================== + +### 1. Definitions + +**1.1. “Contributorâ€** + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +**1.2. “Contributor Versionâ€** + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +**1.3. “Contributionâ€** + means Covered Software of a particular Contributor. + +**1.4. “Covered Softwareâ€** + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +**1.5. “Incompatible With Secondary Licensesâ€** + means + +* **(a)** that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or +* **(b)** that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +**1.6. “Executable Formâ€** + means any form of the work other than Source Code Form. + +**1.7. “Larger Workâ€** + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +**1.8. “Licenseâ€** + means this document. + +**1.9. “Licensableâ€** + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +**1.10. “Modificationsâ€** + means any of the following: + +* **(a)** any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or +* **(b)** any new file in Source Code Form that contains any Covered + Software. + +**1.11. “Patent Claims†of a Contributor** + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +**1.12. “Secondary Licenseâ€** + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +**1.13. “Source Code Formâ€** + means the form of the work preferred for making modifications. + +**1.14. “You†(or “Yourâ€)** + means an individual or a legal entity exercising rights under this + License. For legal entities, “You†includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, “control†means **(a)** the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or **(b)** ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +### 2. License Grants and Conditions + +#### 2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +* **(a)** under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and +* **(b)** under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +#### 2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +#### 2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +* **(a)** for any code that a Contributor has removed from Covered Software; + or +* **(b)** for infringements caused by: **(i)** Your and any other third party's + modifications of Covered Software, or **(ii)** the combination of its + Contributions with other software (except as part of its Contributor + Version); or +* **(c)** under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +#### 2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +#### 2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +#### 2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +#### 2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +### 3. Responsibilities + +#### 3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +#### 3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +* **(a)** such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +* **(b)** You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +#### 3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +#### 3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +#### 3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +### 4. Inability to Comply Due to Statute or Regulation + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: **(a)** comply with +the terms of this License to the maximum extent possible; and **(b)** +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +### 5. Termination + +**5.1.** The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated **(a)** provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and **(b)** on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +**5.2.** If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +**5.3.** In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +### 6. Disclaimer of Warranty + +> Covered Software is provided under this License on an “as is†+> basis, without warranty of any kind, either expressed, implied, or +> statutory, including, without limitation, warranties that the +> Covered Software is free of defects, merchantable, fit for a +> particular purpose or non-infringing. The entire risk as to the +> quality and performance of the Covered Software is with You. +> Should any Covered Software prove defective in any respect, You +> (not any Contributor) assume the cost of any necessary servicing, +> repair, or correction. This disclaimer of warranty constitutes an +> essential part of this License. No use of any Covered Software is +> authorized under this License except under this disclaimer. + +### 7. Limitation of Liability + +> Under no circumstances and under no legal theory, whether tort +> (including negligence), contract, or otherwise, shall any +> Contributor, or anyone who distributes Covered Software as +> permitted above, be liable to You for any direct, indirect, +> special, incidental, or consequential damages of any character +> including, without limitation, damages for lost profits, loss of +> goodwill, work stoppage, computer failure or malfunction, or any +> and all other commercial damages or losses, even if such party +> shall have been informed of the possibility of such damages. This +> limitation of liability shall not apply to liability for death or +> personal injury resulting from such party's negligence to the +> extent applicable law prohibits such limitation. Some +> jurisdictions do not allow the exclusion or limitation of +> incidental or consequential damages, so this exclusion and +> limitation may not apply to You. + +### 8. Litigation + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +### 9. Miscellaneous + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +### 10. Versions of the License + +#### 10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +#### 10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +#### 10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +#### 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +## Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +## Exhibit B - “Incompatible With Secondary Licenses†Notice + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/src/Rust/Cargo.lock b/src/Rust/Cargo.lock index e4d015d1..08c11d2e 100644 --- a/src/Rust/Cargo.lock +++ b/src/Rust/Cargo.lock @@ -160,16 +160,19 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.74", + "syn 2.0.75", "tempfile", "toml", ] [[package]] name = "cc" -version = "1.1.10" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" +checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -206,9 +209,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.15" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ "clap_builder", "clap_derive", @@ -229,9 +232,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.16" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c677cd0126f3026d8b093fa29eae5d812fde5c05bc66dbb29d0374eea95113a" +checksum = "1ee158892bd7ce77aa15c208abbdb73e155d191c287a659b57abd5adb92feb03" dependencies = [ "clap", ] @@ -245,7 +248,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -395,7 +398,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", "unicode-xid", ] @@ -480,9 +483,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "indexmap" @@ -511,9 +514,9 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ "hermit-abi", "libc", @@ -558,9 +561,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "linked-hash-map" @@ -779,29 +782,29 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.207" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.207" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] name = "serde_json" -version = "1.0.124" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" dependencies = [ "indexmap", "itoa", @@ -819,6 +822,12 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "similar" version = "2.6.0" @@ -853,9 +862,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.74" +version = "2.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" dependencies = [ "proc-macro2", "quote", @@ -911,7 +920,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1027,6 +1036,7 @@ dependencies = [ "thiserror", "toml", "vvs_ass", + "vvs_codegen", "vvs_font", "vvs_llvm", "vvs_parser", @@ -1038,15 +1048,13 @@ dependencies = [ name = "vvs_codegen" version = "0.5.0" dependencies = [ - "anyhow", "hashbrown", "log", - "serde", - "serde_json", - "vvs_lang", + "thiserror", "vvs_llvm", + "vvs_parser", "vvs_runtime_types", - "vvs_utils", + "vvs_shortstring", ] [[package]] @@ -1059,23 +1067,6 @@ dependencies = [ "ttf-parser", ] -[[package]] -name = "vvs_lang" -version = "0.5.0" -dependencies = [ - "anyhow", - "derive_more", - "hashbrown", - "log", - "paste", - "regex", - "serde", - "thiserror", - "toml", - "vvs_parser", - "vvs_utils", -] - [[package]] name = "vvs_lib" version = "0.5.0" @@ -1096,6 +1087,8 @@ dependencies = [ "llvm-sys", "log", "paste", + "thiserror", + "vvs_shortstring", ] [[package]] @@ -1117,6 +1110,7 @@ dependencies = [ "smol_str", "termcolor", "vvs_parser_derive", + "vvs_shortstring", ] [[package]] @@ -1136,7 +1130,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] [[package]] @@ -1147,6 +1141,7 @@ dependencies = [ "log", "serde", "serde_json", + "vvs_ass", "vvs_llvm", "vvs_runtime_types", "vvs_utils", @@ -1163,12 +1158,19 @@ dependencies = [ "serde_json", "unicode-segmentation", "vvs_ass", - "vvs_lang", "vvs_llvm", "vvs_procmacro", "vvs_utils", ] +[[package]] +name = "vvs_shortstring" +version = "0.5.0" +dependencies = [ + "serde", + "smol_str", +] + [[package]] name = "vvs_utils" version = "0.5.0" @@ -1211,7 +1213,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", "wasm-bindgen-shared", ] @@ -1233,7 +1235,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1437,5 +1439,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn 2.0.75", ] diff --git a/src/Rust/Cargo.toml b/src/Rust/Cargo.toml index 840c7a05..6d16464d 100644 --- a/src/Rust/Cargo.toml +++ b/src/Rust/Cargo.toml @@ -4,17 +4,18 @@ members = ["vvs_*"] [workspace.package] -version = "0.5.0" -authors = [ - "Maëlle Martin <maelle.martin@proton.me>", - "Kampfkarren <kampfkarren@gmail.com>", -] +version = "0.5.0" description = "The V5 of the Vivy Script utility to manipulate in an easy way ASS files" edition = "2021" license = "MIT" rust-version = "1.76" +authors = [ + "Maëlle Martin <maelle.martin@proton.me>", +] + + [profile.release] debug = false opt-level = 3 @@ -37,11 +38,11 @@ vvs_procmacro = { path = "vvs_procmacro" } vvs_utils = { path = "vvs_utils" } vvs_font = { path = "vvs_font" } vvs_ass = { path = "vvs_ass" } -vvs_lang = { path = "vvs_lang" } vvs_runtime = { path = "vvs_runtime" } -vvs_codegen = { path = "vvs_codegen" } vvs_llvm = { path = "vvs_llvm" } vvs_runtime_types = { path = "vvs_runtime_types" } +vvs_shortstring = { path = "vvs_shortstring" } +vvs_codegen = { path = "vvs_codegen" } # Utils bytecount = "*" @@ -60,6 +61,8 @@ bitflags = { version = "*", default-features = false } # Parsing unicode-segmentation = "*" +codespan = "*" +codespan-reporting = "*" regex = { version = "*", default-features = false, features = ["std"] } # SerDe @@ -99,7 +102,5 @@ clap = { version = "*", default-features = false, features = [ ] } # FOR FULL-MOON TESTS -codespan = "*" -codespan-reporting = "*" -criterion = "*" +criterion = "*" insta = { version = "*", features = ["glob", "yaml"] } diff --git a/src/Rust/vvs_ass/Cargo.toml b/src/Rust/vvs_ass/Cargo.toml index 7200e2fc..43d28806 100644 --- a/src/Rust/vvs_ass/Cargo.toml +++ b/src/Rust/vvs_ass/Cargo.toml @@ -7,10 +7,9 @@ edition.workspace = true license.workspace = true [dependencies] -vvs_procmacro.workspace = true -vvs_utils.workspace = true -vvs_font.workspace = true - +vvs_procmacro.workspace = true +vvs_utils.workspace = true +vvs_font.workspace = true unicode-segmentation.workspace = true thiserror.workspace = true anyhow.workspace = true diff --git a/src/Rust/vvs_ass/src/styles.rs b/src/Rust/vvs_ass/src/styles.rs index 612e64df..dd0e5d75 100644 --- a/src/Rust/vvs_ass/src/styles.rs +++ b/src/Rust/vvs_ass/src/styles.rs @@ -126,7 +126,7 @@ impl ASSStyle { impl Default for ASSStyle { fn default() -> Self { let font = vvs_font::embeded_fonts() - .first() + .next() .expect("we should at least bundle one font"); let font = vvs_font::Font::try_from(font.1).expect("the font we bundle is not valid"); let font_name = font.name().expect("the font we bundle doesn't have a valid name"); diff --git a/src/Rust/vvs_cli/Cargo.toml b/src/Rust/vvs_cli/Cargo.toml index 36de30fc..9b915097 100644 --- a/src/Rust/vvs_cli/Cargo.toml +++ b/src/Rust/vvs_cli/Cargo.toml @@ -1,33 +1,32 @@ [package] -name = "vvs_cli" +name = "vvs_cli" +description = "The CLI for VVS (VVCC)" + version.workspace = true authors.workspace = true edition.workspace = true license.workspace = true -description = "The CLI for VVS (VVCC)" [[bin]] name = "vvcc" path = "src/main.rs" [dependencies] -vvs_ass.workspace = true -vvs_font.workspace = true -vvs_utils.workspace = true -vvs_parser.workspace = true -vvs_runtime.workspace = true -# vvs_codegen.workspace = true - -thiserror.workspace = true -anyhow.workspace = true -serde.workspace = true -toml.workspace = true -log.workspace = true - +vvs_ass.workspace = true +vvs_font.workspace = true +vvs_utils.workspace = true +vvs_parser.workspace = true +vvs_runtime.workspace = true +vvs_codegen.workspace = true +thiserror.workspace = true +anyhow.workspace = true +serde.workspace = true +toml.workspace = true +log.workspace = true clap_mangen.workspace = true clap_complete.workspace = true clap.workspace = true [build-dependencies] -anyhow.workspace = true +anyhow.workspace = true vvs_llvm = { workspace = true, features = ["link"] } diff --git a/src/Rust/vvs_cli/src/args.rs b/src/Rust/vvs_cli/src/args.rs index 9f75870f..08e8ea60 100644 --- a/src/Rust/vvs_cli/src/args.rs +++ b/src/Rust/vvs_cli/src/args.rs @@ -56,7 +56,7 @@ pub struct Args { , action = clap::ArgAction::Append , id = "include" )] - pub include_folders: Vec<PathBuf>, + pub includes: Vec<PathBuf>, /// Shows informations about a script. /// diff --git a/src/Rust/vvs_cli/src/config.rs b/src/Rust/vvs_cli/src/config.rs index 0222b83d..fd5718aa 100644 --- a/src/Rust/vvs_cli/src/config.rs +++ b/src/Rust/vvs_cli/src/config.rs @@ -2,7 +2,9 @@ use crate::args::Args; use clap_complete::Shell; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use thiserror::Error; use vvs_utils::*; +use xdg::XDGConfigFileSerde; #[derive(Debug, Serialize, Deserialize)] pub struct ConfigKaraMaker { @@ -14,8 +16,7 @@ pub struct ConfigKaraMaker { #[derive(Debug, Default, Serialize, Deserialize)] pub struct ConfigFile { - #[serde(rename = "includes")] - include_folders: Vec<PathBuf>, + includes: Vec<PathBuf>, karamaker: ConfigKaraMaker, } @@ -36,7 +37,28 @@ pub struct Config { pub verbose: u8, // Merge of the two - pub include_folders: Vec<PathBuf>, + pub includes: Vec<PathBuf>, +} + +#[derive(Debug, Error)] +pub enum ConfigFileError { + #[error("deserialization error: {0}")] + Deserialize(toml::de::Error), + + #[error("serialization error: {0}")] + Serialize(toml::ser::Error), +} + +impl XDGConfigFileSerde for ConfigFile { + type Error = ConfigFileError; + + fn deserialize(input: &str) -> Result<Self, Self::Error> { + toml::from_str(input).map_err(ConfigFileError::Deserialize) + } + + fn serialize(&self) -> Result<String, Self::Error> { + toml::to_string_pretty(self).map_err(ConfigFileError::Serialize) + } } macro_rules! merge { @@ -65,47 +87,43 @@ impl Extend<ConfigFile> for ConfigFile { iter.into_iter().for_each(|other| { let (km, self_km) = (other.karamaker, &mut self.karamaker); merge! { - append other => self, { include_folders }; + append other => self, { includes }; override km => self_km, { kara_maker }; } }); } } -impl Extend<ConfigFile> for Config { - fn extend<T: IntoIterator<Item = ConfigFile>>(&mut self, iter: T) { - iter.into_iter().for_each(|cfg| { - let km = cfg.karamaker; - merge! { - append cfg => self, { include_folders }; - override km => self, { kara_maker }; - } - }); +impl Config { + /// Extends the config from config files. + pub fn with_config_files(self, iter: impl IntoIterator<Item = ConfigFile>) -> Self { + iter.into_iter().fold(self, |this, cfg| this.with_config_file(cfg)) } -} -impl Extend<Args> for Config { - fn extend<T: IntoIterator<Item = Args>>(&mut self, iter: T) { - iter.into_iter().for_each(|args| { - merge! { - append args => self, { include_folders }; - set_if_not args => self, { script, ass_file, options, shell, font_info }; - max args => self, { verbose }; - override args => self, { info, manpage }; - } - }); + /// Extends the config from config file. + pub fn with_config_file(mut self, cfg: ConfigFile) -> Self { + merge! { + append cfg => self, { includes }; + override cfg.karamaker => self, { kara_maker }; + } + self } -} -impl ConfigFile { - #[inline] - pub fn serialize(&self) -> Result<String, Box<dyn std::error::Error>> { - Ok(toml::to_string_pretty(self).map_err(Box::new)?) + /// Extends the config from command line arguments. + pub fn with_args(mut self, args: Args) -> Self { + merge! { + append args => self, { includes }; + set_if_not args => self, { script, ass_file, options, shell, font_info }; + max args => self, { verbose }; + override args => self, { info, manpage }; + } + self } - #[inline] - pub fn deserialize(input: String) -> Result<Self, Box<dyn std::error::Error>> { - Ok(toml::from_str(&input).map_err(Box::new)?) + /// Apply the logger setting and returns self. + pub fn apply_logger(self) -> Self { + crate::logger::level(self.verbose); + self } } diff --git a/src/Rust/vvs_cli/src/main.rs b/src/Rust/vvs_cli/src/main.rs index 8c6cbf0b..154af093 100644 --- a/src/Rust/vvs_cli/src/main.rs +++ b/src/Rust/vvs_cli/src/main.rs @@ -1,22 +1,20 @@ //! The VivyScript cli -use crate::args::Args; -use anyhow::{anyhow, Context, Result}; -use clap::CommandFactory; -use std::{fs, io}; -use vvs_ass::{ass_container_from_file, ASSContainer}; +use anyhow::{Context as _, Result}; +use clap::CommandFactory as _; +use std::{fs, io, path::Path}; use vvs_cli::{ - args, + args::Args, config::{Config, ConfigFile}, logger, }; +use vvs_codegen::lowerer; use vvs_parser::prelude::*; -use vvs_runtime::{types::VVRTTable, JIT}; use vvs_utils::xdg::*; -fn print_face_info(name: &str, font: &[u8]) -> Result<()> { - let font = vvs_font::Font::try_from(font).with_context(|| format!("failed to parse font {name}"))?; - +fn print_font(name: impl AsRef<Path>, font: &[u8]) -> Result<()> { + let name = name.as_ref(); + let font = vvs_font::Font::try_from(font).with_context(|| format!("failed to parse font {}", name.display()))?; let font_types = font .font_types() .into_iter() @@ -35,99 +33,72 @@ fn print_face_info(name: &str, font: &[u8]) -> Result<()> { fn main() -> Result<()> { logger::init(None).context("failed to init logger")?; - let mut config = Config::default(); - config.extend([ - XDGConfig::<ConfigFile, XDGConfigMergedSilent>::new("vvcc", ConfigFile::deserialize) - .file("vvcc.toml") - .read_or_default(ConfigFile::serialize), - ]); - config.extend([<Args as clap::Parser>::parse()]); - let Config { - script, - ass_file: ass_file_path, - options: options_path, - info, - manpage, - shell, - font_info, - verbose, - include_folders: _, - kara_maker, - } = config; - logger::level(verbose); + let Config { script, ass_file, options, info, manpage, shell, font_info, includes, .. } = Config::default() + .with_config_file( + XDGConfig::<ConfigFile, XDGConfigMergedSilent>::new("vvcc") + .file("vvcc.toml") + .read_or_default(), + ) + .with_args(<Args as clap::Parser>::parse()) + .apply_logger(); + + // Handle auxiliary stuff if manpage { - log::debug!(target: "vvcc", "generate the manpage for vvcc"); return clap_mangen::Man::new(Args::command()) .render(&mut io::stdout()) .context("failed to render manpage for vvcc"); - } - - if let Some(shell) = shell { - log::debug!(target: "vvcc", "generate shell completion script for shell {shell}"); - let mut command = Args::command(); - let command_name = command.get_name().to_string(); - clap_complete::generate(shell, &mut command, command_name, &mut io::stdout()); + } else if let Some(shell) = shell { + clap_complete::generate(shell, &mut Args::command(), Args::command().get_name().to_string(), &mut io::stdout()); return Ok(()); - } - - if let Some(font_info) = font_info { + } else if let Some(font_info) = font_info { return match font_info { - Some(path) => print_face_info( - &path.to_string_lossy(), - &fs::read(&path).with_context(|| format!("failed to read font: {}", path.to_string_lossy()))?, - ), - None => vvs_font::embeded_fonts() - .iter() - .try_for_each(|(n, f)| print_face_info(n, f)), + Some(p) => print_font(&p, &fs::read(&p).with_context(|| format!("failed to read font: {}", p.display()))?), + None => vvs_font::embeded_fonts().try_for_each(|(n, f)| print_font(n, f)), }; } - match script { - None => Args::command() - .print_help() - .context("failed to print help message for vvcc"), - - Some(script) => { - let script = SourceCode::from_path(script)?; - log::info!("run as `{kara_maker}` script `{}`", script.name()); - if let Some(ref options) = options_path { - log::info!("use option file `{}`", options.display()) - } - - let output = FrontendPipeline::new(&script) - .passes(&[]) - .options(options_path.as_ref()) - .context("failed to parse option file")? - .run() - .context("failed to parse script files")?; - - if info { - match ass_file_path { - Some(ref path) => println!("ass file ........... {}", path.display()), - None => println!("ass file ........... ø"), - } - match options_path { - Some(path) => println!("options file ....... {}", path.display()), - None => println!("options file ....... ø"), - } - println!("{output}"); - } - - // let FrontendOutput { ast, imports, main, options, library } = output; - - let ass: ASSContainer<VVRTTable> = ass_container_from_file( - ass_file_path.ok_or_else(|| anyhow!("no subtitle file to run the script `{}` on", script.name()))?, - ) - .context("failed to parse the subtitle file")?; - - // let jit = JIT::new()?; - // let module = unsafe { LowererBuilder::new(script.name(), jit.ctx()) }.build().lower(); - // unsafe { jit.add_module(module)? }; - - log::error!("use the ASS container and insert it into the JIT: {ass:#?}"); - - unimplemented!() + // Parse, build, execute the script. + let Some(script) = script else { + return Ok(Args::command().print_help()?); + }; + + let output = FrontendPipeline::new(&SourceCode::from_path(&script)?) + .passes(&[]) + .search( + &SearchPath::from_iter(includes) + .with_script_folder(script.parent().context("expected the script to have a parent folder")?), + ) + .options(options.as_ref()) + .context("failed to parse option file")? + .run() + .context("failed to parse script files")?; + + if info { + match ass_file { + Some(ref path) => println!("ass file ........... {}", path.display()), + None => println!("ass file ........... ø"), } + match options { + Some(path) => println!("options file ....... {}", path.display()), + None => println!("options file ....... ø"), + } + println!("{output}"); } + + let jit = vvs_runtime::JIT::new()?; + jit.with_module( + lowerer::Lowerer::new(&jit.ctx(), &output.main.name)? + .declare_globals(output.declared_functions())? + .lower_module(&output.main.name, output.ast)? + .lower_modules(output.imports)? + .generate_main(output.main)? + .finished(), + )? + .run_on_file( + vvs_ass::ass_container_from_file(ass_file.context("no subtitle file to run the script on")?) + .context("failed to parse the subtitle file")?, + )?; + + Ok(()) } diff --git a/src/Rust/vvs_codegen/Cargo.toml b/src/Rust/vvs_codegen/Cargo.toml index 8e67079a..4b083963 100644 --- a/src/Rust/vvs_codegen/Cargo.toml +++ b/src/Rust/vvs_codegen/Cargo.toml @@ -1,24 +1,21 @@ [package] -name = "vvs_codegen" +name = "vvs_codegen" +description = "Code generation from Lua to LLVM-IR" + version.workspace = true authors.workspace = true edition.workspace = true license.workspace = true -description = "The codegen into LLVM for Vivy Script" [dependencies] -log.workspace = true -serde.workspace = true -serde_json.workspace = true -anyhow.workspace = true -hashbrown.workspace = true - +log.workspace = true +hashbrown.workspace = true +thiserror.workspace = true +vvs_shortstring.workspace = true +vvs_parser.workspace = true vvs_runtime_types.workspace = true -vvs_utils.workspace = true -vvs_lang.workspace = true vvs_llvm = { workspace = true, features = ["bindings"] } -[build-dependencies] -vvs_llvm = { workspace = true, features = ["link"] } -anyhow.workspace = true +[dev-dependencies] +vvs_llvm = { workspace = true, features = ["bindings", "link"] } diff --git a/src/Rust/vvs_codegen/build.rs b/src/Rust/vvs_codegen/build.rs deleted file mode 100644 index a0148072..00000000 --- a/src/Rust/vvs_codegen/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() -> anyhow::Result<()> { - vvs_llvm::build::llvm_link() -} diff --git a/src/Rust/vvs_codegen/src/context.rs b/src/Rust/vvs_codegen/src/context.rs deleted file mode 100644 index 9ba934ef..00000000 --- a/src/Rust/vvs_codegen/src/context.rs +++ /dev/null @@ -1,105 +0,0 @@ -use crate::Value; -use hashbrown::HashMap; -use vvs_llvm::sys::*; -use vvs_runtime_types::vvll::LLVMTypeIsManaged; - -/// Just a hashmap used to store values in scope. -#[derive(Default, Clone)] -pub(crate) struct ValueContext { - /// The values in context. - values: HashMap<Box<str>, Value>, - - /// The values that are to be droped, in the order of their définition! They must be dropped in - /// the reverse order in which they are stored! - to_drop: Vec<Value>, - - /// The number of anon values present in this context. - anon_count: u64, -} - -impl ValueContext { - /// Create a new sub-context. We clone the available values but not the destructors to run as - /// we don't want to run them too early. - pub fn sub_context(&self) -> Self { - let Self { values, anon_count: i, .. } = self; - Self { values: values.clone(), anon_count: *i, to_drop: vec![] } - } - - /// Register a new value in the context. We don't give a shit about its name, see if we need a - /// destructor for this value. - pub fn register_anon(&mut self, value: Value) { - if let Some(name) = value.as_str() { - let name = format!(".anon.{}:{}", self.anon_count, name); - self.anon_count += 1; - self.register(name, value) - } - } - - /// Clone the context and register a new value into it. Used to reduce a bit calls to clone - /// everywhere... - pub fn to_register(&self, name: impl AsRef<str>, value: Value) -> Self { - let mut this = self.clone(); - this.register(name, value); - this - } - - /// Clone the context and register a new value into it. Used to reduce a bit calls to clone - /// everywhere... - pub fn to_register_anon(&self, value: Value) -> Self { - let mut this = self.clone(); - this.register_anon(value); - this - } - - /// Register a new value in the context. - pub fn register(&mut self, name: impl AsRef<str>, value: Value) { - fn inner(this: &mut ValueContext, name: &str, value: Value) { - log::error!("take into account destructors for {name}"); - if Value::Nil == value { - panic!("can't register nil value `{name}`") - } else if this.values.insert(name.into(), value).is_some() { - panic!("multiple definitions of `{name}`") - } - } - inner(self, name.as_ref(), value) - } - - /// Register a multiple values in the context. - pub fn register_all(&mut self, iter: impl IntoIterator<Item = (impl AsRef<str>, Value)>) { - iter.into_iter().for_each(|(name, value)| self.register(name, value)); - } - - /// Get the values to drop in this context in the order of their definition. Also remove them - /// from this context. - pub fn get_values_to_drop(&mut self) -> Vec<Value> { - std::mem::take(&mut self.to_drop) - } - - /// Forget a value to drop by its llvm value ref. This way we can also forget anon values. - pub fn forget_by_llvm_value(&mut self, value: LLVMValueRef) { - use Value::*; - if let Some(idx) = self.to_drop.iter().enumerate().find_map(|(idx, val)| { - matches!(*val, Const(_, val) | Alloca(_, _, val) | Function(_, _, val) if val == value).then_some(idx) - }) { - self.to_drop.remove(idx); - } - } - - /// Add values to drop from another context to extend their lifetime. We can't fail that thing - /// because we ignore values that are re-registered. - pub fn add_values_to_drop(&mut self, mut values: Vec<Value>) { - values.retain(|v| { - v.llvm_value() - .is_some_and(|v| self.to_drop.iter().any(|u| u.llvm_value().is_some_and(|u| u == v))) - && v.llvm_type().is_some_and(|t| unsafe { LLVMTypeIsManaged(t) }) - }); - self.to_drop.append(&mut values); - } - - /// Get the value by its name in the context, or panic! - pub fn get_value(&self, name: impl AsRef<str>) -> &Value { - self.values - .get(name.as_ref()) - .unwrap_or_else(|| panic!("failed to find value `{}`", name.as_ref())) - } -} diff --git a/src/Rust/vvs_codegen/src/error.rs b/src/Rust/vvs_codegen/src/error.rs new file mode 100644 index 00000000..f77595c9 --- /dev/null +++ b/src/Rust/vvs_codegen/src/error.rs @@ -0,0 +1,15 @@ +use std::ffi::NulError; +use thiserror::Error; +use vvs_shortstring::ShortString; + +#[derive(Debug, Error)] +pub enum CodegenError { + #[error("redefinition of function: {0}.{1}")] + RedefinedFunction(ShortString, ShortString), + + #[error("nul byte in string: {0}")] + NulError(NulError), + + #[error("{0}")] + Error(ShortString), +} diff --git a/src/Rust/vvs_codegen/src/graph.rs b/src/Rust/vvs_codegen/src/graph.rs index 51388796..aa73009f 100644 --- a/src/Rust/vvs_codegen/src/graph.rs +++ b/src/Rust/vvs_codegen/src/graph.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + //! Small utility to store a basic block graph of a function to compute dominance of values. use hashbrown::HashSet; diff --git a/src/Rust/vvs_codegen/src/lib.rs b/src/Rust/vvs_codegen/src/lib.rs index 6da3d4ee..57d8d548 100644 --- a/src/Rust/vvs_codegen/src/lib.rs +++ b/src/Rust/vvs_codegen/src/lib.rs @@ -1,29 +1,7 @@ -//! The code used to lower the Vivy Script AST into LLVM code. +mod error; -mod context; -mod graph; -// mod lowerer; -mod value; +pub(crate) mod graph; -// Exports in this crate. -use crate::{context::*, graph::*, value::*}; +pub mod lowerer; -// Re-exports. -// pub use lowerer::{Lowerer, LowererBuilder}; - -type LLVMLowerContext = (vvs_llvm::sys::LLVMModuleRef, vvs_llvm::sys::LLVMContextRef, vvs_llvm::sys::LLVMBuilderRef); - -/// The number of temp / unnamed values. -static mut TEMP_NAME_COUNTER: u64 = 0; - -#[macro_export] -macro_rules! cstr { - ($str: literal, $($arg: expr),+ $(,)?) => { - format!(concat!($str, "\0"), $($arg),+).as_ptr() as *const i8 - }; - - () => {{ - $crate::TEMP_NAME_COUNTER += 1; - format!(".temp.{}\0", $crate::TEMP_NAME_COUNTER).as_ptr() as *const i8 - }}; -} +pub use error::CodegenError; diff --git a/src/Rust/vvs_codegen/src/lowerer/calls.rs b/src/Rust/vvs_codegen/src/lowerer/calls.rs deleted file mode 100644 index b9d0be84..00000000 --- a/src/Rust/vvs_codegen/src/lowerer/calls.rs +++ /dev/null @@ -1,108 +0,0 @@ -use crate::lowerer::*; -use vvs_lang::ast::*; -use vvs_llvm::*; - -/// Lower a single instruction. -pub(super) struct CallLowerer<'a> { - llvm: LLVMLowerContext, - ctx: &'a ValueContext, -} - -impl<'a> CallLowerer<'a> { - pub(super) fn new(llvm: LLVMLowerContext, ctx: &'a ValueContext) -> Self { - Self { llvm, ctx } - } - - pub(super) unsafe fn call_intrinsic<const N: usize>( - name: &'static str, - (m, _, b): LLVMLowerContext, - ret: LLVMTypeRef, - mut args: [LLVMValueRef; N], - ) -> LLVMValueRef { - let id = LLVMLookupIntrinsicID(name.as_ptr() as *const _, name.len()); - let mut args_ty: Vec<_> = args.iter().map(|val| LLVMTypeOf(*val)).collect(); - let (args_ty, args_len, args) = (args_ty.as_mut_ptr(), args.len().try_into().unwrap(), args.as_mut_ptr()); - let (functy, func) = ( - LLVMFunctionType(ret, args_ty, args_len, 0), - LLVMGetIntrinsicDeclaration(m, id, args_ty, args_len as usize), - ); - LLVMBuildCall2(b, functy, func, args, args_len, cstr!()) - } - - pub(super) fn lower_method_invok( - self, - obj: &ASTExpr, - func: impl AsRef<str>, - args: &[ASTExpr], - ) -> (ValueContext, Value) { - let Self { llvm: llvm @ (_, c, b), ctx } = self; - - let (ctx, object) = ExpressionLowerer::with_assumed_type(llvm, obj, ctx).lower(); - let (oty, obj) = (object.get_ast_type(), object.loaded(c, b)); - - let (aargs, rty, lty, lfn) = match ctx.get_value(VVRTSymbol::method(oty, func.as_ref()).unwrap().to_string()) { - Value::Function(ASTType::Function(aargs, rty), lty, lfn) if args.len() == aargs.len() => { - (aargs.clone(), rty.clone(), *lty, *lfn) - } - val => unreachable!("unexpected not a function value: {val:?}"), - }; - - let mut largs = Vec::with_capacity(args.len()); - let largc = 1 + args.len() as u32; - largs.push(obj); - let ctx = args.iter().zip(aargs).fold(ctx, |ctx, (arg, ty)| { - let (ctx, arg) = ExpressionLowerer::with_expected_type(llvm, arg, &ty, &ctx).lower(); - largs.push(arg.loaded(c, b)); - ctx - }); - - match *rty { - ASTType::Nil => unsafe { - LLVMBuildCall2(b, lty, lfn, largs.as_mut_ptr(), largc, c"".as_ptr()); - (ctx, Value::Nil) - }, - ty => unsafe { - let ret_ty = TypeLowerer::new(c, &ty).lower(); - let ptr = LLVMBuildAlloca(b, ret_ty, cstr!()); - let val = LLVMBuildCall2(b, lty, lfn, largs.as_mut_ptr(), largc, cstr!()); - LLVMBuildStore(b, val, ptr); - (ctx, Value::Alloca(ty, ret_ty, ptr)) - }, - } - } - - pub(super) fn lower_func_call(self, func: &ASTExpr, args: &[ASTExpr]) -> (ValueContext, Value) { - let Self { llvm: llvm @ (_, c, b), ctx } = self; - - let (ctx, aty, rty_ast, lty, lfn) = - match ExpressionLowerer::with_expected_type(llvm, func, &ASTType::Nil, ctx).lower() { - (ctx, Value::Function(ASTType::Function(aty, rty), lty, lfn)) if args.len() == aty.len() => { - (ctx, aty, rty, lty, lfn) - } - (_, what) => unreachable!("unexpected not-a-function-thingy: {what:?}"), - }; - - let mut largs = Vec::with_capacity(args.len()); - let args_len = args.len().try_into().expect("too many arguments"); - let mut ctx = args.iter().zip(aty).fold(ctx, |ctx, (arg, ty)| { - let (ctx, expr) = ExpressionLowerer::with_expected_type(llvm, arg, &ty, &ctx).lower(); - largs.push(expr.loaded(c, b)); - ctx - }); - - match unsafe { LLVMGetReturnType(lty) } { - rty if rty == unsafe { LLVMVoidTypeInContext(c) } => unsafe { - LLVMBuildCall2(b, lty, lfn, largs.as_mut_ptr(), args_len, c"".as_ptr()); - (ctx, Value::Nil) - }, - rty => unsafe { - let val = LLVMBuildCall2(b, lty, lfn, largs.as_mut_ptr(), args_len, cstr!()); - let ptr = LLVMBuildAlloca(b, rty, c".ignored".as_ptr()); - LLVMBuildStore(b, val, ptr); - let value = Value::Alloca(*rty_ast, rty, ptr); - ctx.register_anon(value.clone()); - (ctx, value) - }, - } - } -} diff --git a/src/Rust/vvs_codegen/src/lowerer/cast.rs b/src/Rust/vvs_codegen/src/lowerer/cast.rs deleted file mode 100644 index 4d818f3a..00000000 --- a/src/Rust/vvs_codegen/src/lowerer/cast.rs +++ /dev/null @@ -1,329 +0,0 @@ -use crate::lowerer::*; -use vvs_lang::ast::*; -use vvs_llvm::*; -use vvs_runtime_types::{types::*, vvll::LLVMExported}; - -pub(super) struct CastLowerer<'a> { - llvm: LLVMLowerContext, - - source: &'a Value, - from_type: &'a ASTType, - as_type: &'a ASTType, - - ctx: &'a ValueContext, -} - -/// Represents the value that was casted by the [CastLowerer] lowerer. -#[derive(Debug)] -pub(super) enum CastedValue { - /// A value can be [CastedValue::Unchanged] which means that we did nothing to it. you don't - /// need to register it again to trop it as it's nothing new... - Unchanged { value: Value }, - - /// A value can be [CastedValue::Dynamic] which means that we generated code to try to convert - /// the thing at runtime. The first element is the flag which is set to true if we did succeed, - /// false otherwise. This is an `i8` and must be checked at runtime. Because we created a - /// value, we muste register it to be dropped latter if needed. - Dynamic { flag: LLVMValueRef, value: Value }, - - /// A value can be [CastedValue::Static], which means that we did generate the code to cast the - /// thing and that we will always succeed. We still need to register this value to be dropped - /// if necessary... - Static { value: Value }, -} - -impl<'a> CastLowerer<'a> { - /// Create a new [CastLowerer] from an AST value. - pub fn from_ast(llvm: LLVMLowerContext, source: &'a ASTVar, as_type: &'a ASTType, ctx: &'a ValueContext) -> Self { - Self::from_llvm(llvm, ctx.get_value(source.name()), as_type, ctx) - } - - /// Create a new [CastLowerer] from a LLVM value. - pub fn from_llvm(llvm: LLVMLowerContext, source: &'a Value, as_type: &'a ASTType, ctx: &'a ValueContext) -> Self { - Self { llvm, source, as_type, from_type: source.get_ast_type(), ctx } - } - - /// Tells if the types are the same and we don't need to do the casting. - fn is_same_type(from: &ASTType, to: &ASTType) -> bool { - (to == from) - || (to.is_table() && from.is_table()) - || (to.is_sequence() && from.is_sequence()) - || (to.is_variant() && from.is_variant()) - } - - /// Lower in a static context, meaning that we assume that the casting process must succeed and - /// must not require a runtime check. - pub fn lower_static(self) -> CastedValue { - use ASTType::*; - let Self { llvm: llvm @ (_, c, b), source, from_type, as_type, ctx } = self; - - if matches!(as_type, Nil) { - return CastedValue::Unchanged { value: ConstantLowerer::new(c, &ASTConst::Nil).lower() }; - } else if Self::is_same_type(from_type, as_type) { - return CastedValue::Unchanged { value: source.clone() }; - } - - let loaded = source.loaded(c, b); - let store_into_ptr = |ty: ASTType, value: LLVMValueRef| unsafe { - let ptr = LLVMBuildAlloca(b, LLVMTypeOf(value), cstr!()); - LLVMBuildStore(b, value, ptr); - CastedValue::Static { value: Value::Alloca(ty, LLVMTypeOf(value), ptr) } - }; - - macro_rules! build_into { - ($ty: ident: $conv: ident) => { - unsafe { - let llvm_type = TypeLowerer::new(c, &$ty).lower(); - let ptr = LLVMBuildAlloca(b, llvm_type, cstr!()); - LLVMBuildStore(b, $conv(b, loaded, llvm_type, cstr!()), ptr); - CastedValue::Static { value: Value::Alloca($ty, llvm_type, ptr) } - } - }; - } - - match from_type { - Nil => match as_type { - Integer => CastedValue::Static { - value: Value::Const(Integer, unsafe { - LLVMConstInt(TypeLowerer::new(c, &Integer).lower(), Default::default(), 0) - }), - }, - Floating => CastedValue::Static { - value: Value::Const(Floating, unsafe { - LLVMConstReal(TypeLowerer::new(c, &Floating).lower(), Default::default()) - }), - }, - Boolean => CastedValue::Static { - value: Value::Const(Boolean, unsafe { - LLVMConstInt(TypeLowerer::new(c, &Boolean).lower(), Default::default(), 0) - }), - }, - ty if ty.is_table() => store_into_ptr(AnyTable, ctx.get_value("VVRTTable_new").call(b, [])), - ty if ty.is_sequence() => store_into_ptr(AnySequence, ctx.get_value("VVRTSeq_new").call(b, [])), - Any => store_into_ptr(Any, ctx.get_value("VVRTAny_nil").call(b, [])), - as_type => panic!("can't convert `{from_type} into {as_type}`"), - }, - - Integer => match as_type { - Integer => CastedValue::Unchanged { value: source.clone() }, - Boolean => build_into! { Boolean: LLVMBuildIntCast }, - Floating => build_into! { Floating: LLVMBuildSIToFP }, - Any => store_into_ptr(Any, ctx.get_value("VVRTAny_from_integer").call(b, [loaded])), - as_type => panic!("can't convert `{from_type} into {as_type}`"), - }, - - Floating => match as_type { - Floating => CastedValue::Unchanged { value: source.clone() }, - Integer => build_into! { Integer: LLVMBuildFPToSI }, - Boolean => build_into! { Boolean: LLVMBuildFPToSI }, - Any => store_into_ptr(Any, ctx.get_value("VVRTAny_from_floating").call(b, [loaded])), - as_type => panic!("can't convert `{from_type} into {as_type}`"), - }, - - Boolean => match as_type { - Boolean => CastedValue::Unchanged { value: source.clone() }, - Integer => build_into! { Integer: LLVMBuildIntCast }, - Floating => build_into! { Floating: LLVMBuildSIToFP }, - Any => store_into_ptr(Any, ctx.get_value("VVRTAny_from_boolean").call(b, [loaded])), - as_type => panic!("can't convert `{from_type} into {as_type}`"), - }, - - ty if ty.is_sequence() => match as_type { - Any => store_into_ptr(Any, ctx.get_value("VVRTAny_from_seq").call(b, [source.loaded(c, b)])), - as_type => panic!("can't convert `{from_type} into {as_type}`"), - }, - - Tuple(tuple) => match as_type { - Tuple(as_tuple) if tuple.len() == as_tuple.len() => unsafe { - let as_llvm = TypeLowerer::new(c, as_type).lower(); - let from_tuple = TypeLowerer::new(c, from_type).lower(); - let ptr = LLVMBuildAlloca(b, as_llvm, cstr!()); - - tuple.iter().enumerate().for_each(|(idx, from)| { - // Get the value that we must coerce. - let from_ptr = LLVMBuildStructGEP2(b, from_tuple, loaded, idx as u32, cstr!()); - let from_val = Value::Alloca(from.clone(), TypeLowerer::new(c, from).lower(), from_ptr); - - // Because of how we define tuples, we don't have to register the temp - // values here, they should only be integers, floats or booleans. - let value = match CastLowerer::from_llvm(llvm, &from_val, as_type, ctx).lower_static() { - CastedValue::Unchanged { value } | CastedValue::Static { value } => value.loaded(c, b), - casted => unreachable!("should have statically casted, got: {casted:?}"), - }; - let mut idxs = [ - LLVMConstInt(LLVMInt32TypeInContext(c), 0, 0), - LLVMConstInt(LLVMInt32TypeInContext(c), idx as u64, 0), - ]; - let ptr = LLVMBuildGEP2(b, as_llvm, ptr, idxs.as_mut_ptr(), idxs.len() as u32, cstr!()); - LLVMBuildStore(b, value, ptr); - }); - - CastedValue::Static { value: Value::Alloca(as_type.clone(), as_llvm, ptr) } - }, - - ty if ty.is_numeric() && tuple.len() == 1 => unsafe { - let from_val = Value::Alloca( - tuple[0].clone(), - TypeLowerer::new(c, &tuple[0]).lower(), - LLVMBuildStructGEP2(b, TypeLowerer::new(c, from_type).lower(), loaded, 0, cstr!()), - ); - CastLowerer::from_llvm(llvm, &from_val, ty, ctx).lower_static() - }, - - ty if ty.is_table() => unsafe { - let table = ctx.get_value("VVRTTable_new").call(b, []); - let from_tuple = TypeLowerer::new(c, from_type).lower(); - - tuple.iter().enumerate().for_each(|(idx, from)| { - let from_ptr = LLVMBuildStructGEP2(b, from_tuple, loaded, idx as u32, cstr!()); - let from_val = Value::Alloca(from.clone(), TypeLowerer::new(c, from).lower(), from_ptr); - - // Here we don't need to clone because we know that all types whithing a - // tuple are trivially copiable. - let value = match CastLowerer::from_llvm(llvm, &from_val, &Any, ctx).lower_static() { - CastedValue::Unchanged { value } | CastedValue::Static { value } => value.loaded(c, b), - casted => unreachable!("should have statically casted, got: {casted:?}"), - }; - - let key = idx.to_string(); - let (key, len) = ( - LLVMConstStringInContext(c, key.as_ptr() as *const _, key.len() as u32, 1), - LLVMConstInt(LLVMInt32TypeInContext(c), key.len() as u64, 0), - ); - ctx.get_value("VVRTTable_insert_from_raw_key") - .call(b, [table, key, len, value]); - }); - - let ptr = LLVMBuildAlloca(b, VVRTTable::llvm_type(c), cstr!()); - LLVMBuildStore(b, table, ptr); - CastedValue::Static { value: Value::Alloca(AnyTable, VVRTTable::llvm_type(c), ptr) } - }, - - ty if ty.is_sequence() => unsafe { - let seq = ctx.get_value("VVRTSeq_new").call(b, []); - let from_tuple = TypeLowerer::new(c, from_type).lower(); - - tuple.iter().enumerate().for_each(|(idx, from)| { - let from_ptr = LLVMBuildStructGEP2(b, from_tuple, loaded, idx as u32, cstr!()); - let from_val = Value::Alloca(from.clone(), TypeLowerer::new(c, from).lower(), from_ptr); - - // Here we don't need to clone because we know that all types whithing a - // tuple are trivially copiable. - let value = match CastLowerer::from_llvm(llvm, &from_val, &Any, ctx).lower_static() { - CastedValue::Unchanged { value } | CastedValue::Static { value } => value.loaded(c, b), - casted => unreachable!("should have statically casted, got: {casted:?}"), - }; - - ctx.get_value("VVRTSeq_push").call(b, [seq, value]); - }); - - let ptr = LLVMBuildAlloca(b, VVRTSeq::llvm_type(c), cstr!()); - LLVMBuildStore(b, seq, ptr); - CastedValue::Static { value: Value::Alloca(AnySequence, VVRTSeq::llvm_type(c), ptr) } - }, - - as_type => panic!("can't convert `{from_type} into {as_type}`"), - }, - - ty if ty.is_variant() => match as_type { - as_type if as_type.is_table() => todo!(), - String => store_into_ptr(String, ctx.get_value("VVRTVariant_to_string").call(b, [loaded])), - Any => store_into_ptr(Any, ctx.get_value("VVRTAny_from_variant").call(b, [loaded])), - as_type => panic!("can't convert `{from_type} into {as_type}`"), - }, - - // Forbiden casts or Dynamic casts - ty => panic!("can't cast a value of type `{ty}` or this type need a runtime check to do the cast"), - } - } - - /// Try to do the lowering at runtime, if it failed we guarenty that the value will be nil and - /// that no values will need to be dropped. In case of success we calling function must - /// register the value in the context. - pub fn lower_dynamic(self) -> CastedValue { - use ASTType::*; - let Self { llvm: (_, c, b), source, from_type, as_type, ctx } = self; - - if matches!(as_type, Nil) && !matches!(from_type, ASTType::Any) { - return CastedValue::Unchanged { value: ConstantLowerer::new(c, &ASTConst::Nil).lower() }; - } else if Self::is_same_type(from_type, as_type) { - return CastedValue::Unchanged { value: source.clone() }; - } - - let loaded = source.loaded(c, b); - let store_into_ptr = |ty: ASTType, value: LLVMValueRef| unsafe { - let ptr = LLVMBuildAlloca(b, LLVMTypeOf(value), cstr!()); - LLVMBuildStore(b, value, ptr); - Value::Alloca(ty, LLVMTypeOf(value), ptr) - }; - let any_is_ty = |ty: VVRTType| { - ctx.get_value("VVRTAny_is_ty") - .call(b, [loaded, unsafe { ty.as_llvm_const(c) }]) - }; - - match from_type { - ty if ty.is_table() => match as_type { - Any => CastedValue::Static { - value: store_into_ptr(Any, ctx.get_value("VVRTAny_from_table").call(b, [loaded])), - }, - ty if ty.is_variant() => todo!(), - ty if ty.is_sequence() => todo!(), - as_type => panic!("can't convert `{from_type} into {as_type}`"), - }, - - // Get value out of any. - Any => match as_type { - Nil => CastedValue::Dynamic { - flag: any_is_ty(VVRTType::Nil), - value: ConstantLowerer::new(c, &ASTConst::Nil).lower(), - }, - Integer => CastedValue::Dynamic { - flag: any_is_ty(VVRTType::Number), - value: store_into_ptr(Integer, ctx.get_value("VVRTAny_maybe_as_integer").call(b, [loaded])), - }, - Boolean => CastedValue::Dynamic { - flag: any_is_ty(VVRTType::Number), - value: store_into_ptr(Boolean, ctx.get_value("VVRTAny_maybe_as_boolean").call(b, [loaded])), - }, - Floating => CastedValue::Dynamic { - flag: any_is_ty(VVRTType::Number), - value: store_into_ptr(Floating, ctx.get_value("VVRTAny_maybe_as_floating").call(b, [loaded])), - }, - String => { - let val = ctx.get_value("VVRTAny_maybe_as_string").call(b, [loaded]); - let val = ctx.get_value("VVRTString_clone").call(b, [val]); - CastedValue::Dynamic { flag: any_is_ty(VVRTType::String), value: store_into_ptr(String, val) } - } - Syllabe => { - let val = ctx.get_value("VVRTAny_maybe_as_syllabe").call(b, [loaded]); - let val = ctx.get_value("VVRTSyllabe_clone").call(b, [val]); - CastedValue::Dynamic { flag: any_is_ty(VVRTType::ASSSyllabe), value: store_into_ptr(Syllabe, val) } - } - Line => { - let val = ctx.get_value("VVRTAny_maybe_as_line").call(b, [loaded]); - let val = ctx.get_value("VVRTLine_clone").call(b, [val]); - CastedValue::Dynamic { flag: any_is_ty(VVRTType::ASSLine), value: store_into_ptr(Line, val) } - } - ty if ty.is_variant() => { - let val = ctx.get_value("VVRTAny_maybe_as_variant").call(b, [loaded]); - let val = ctx.get_value("VVRTVariant_clone").call(b, [val]); - CastedValue::Dynamic { flag: any_is_ty(VVRTType::Variant), value: store_into_ptr(Variant, val) } - } - ty if ty.is_table() => { - let val = ctx.get_value("VVRTAny_maybe_as_table").call(b, [loaded]); - let val = ctx.get_value("VVRTTable_clone").call(b, [val]); - CastedValue::Dynamic { flag: any_is_ty(VVRTType::Table), value: store_into_ptr(AnyTable, val) } - } - ty if ty.is_sequence() => { - let val = ctx.get_value("VVRTAny_maybe_as_seq").call(b, [loaded]); - let val = ctx.get_value("VVRTSeq_clone").call(b, [val]); - CastedValue::Dynamic { flag: any_is_ty(VVRTType::Seq), value: store_into_ptr(AnySequence, val) } - } - as_type => panic!("can't convert `{from_type} into {as_type}`"), - }, - - // Don't need to perform a runtime check to do the cast, use the lower function - _ => self.lower_static(), - } - } -} diff --git a/src/Rust/vvs_codegen/src/lowerer/constant.rs b/src/Rust/vvs_codegen/src/lowerer/constant.rs deleted file mode 100644 index e8a67e17..00000000 --- a/src/Rust/vvs_codegen/src/lowerer/constant.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::Value; -use std::mem; -use vvs_lang::ast::*; -use vvs_llvm::*; - -/// Lower a single constant. We only need the LLVM context what should be the type... -pub(crate) struct ConstantLowerer<'a> { - /// The context. - c: LLVMContextRef, - - /// We expression we want to lower. - constant: &'a ASTConst, -} - -impl<'a> ConstantLowerer<'a> { - /// Create a new constant expression lowerer. - pub fn new(c: LLVMContextRef, constant: &'a ASTConst) -> Self { - Self { c, constant } - } - - /// Create a sub-lowerer, for constants that need a recursive descent. - fn sub_lowerer(&self, constant: &'a ASTConst) -> Self { - Self { c: self.c, constant } - } - - /// Lower the constant. - pub fn lower(self) -> Value { - let Self { c, constant } = self; - let ty = constant.get_const_type(); - use {ASTConst::*, Value::*}; - unsafe { - match constant { - ASTConst::Nil => Const(ty, LLVMConstPointerNull(LLVMInt64TypeInContext(c))), - False => Const(ty, LLVMConstInt(LLVMInt1TypeInContext(c), 0, 0)), - True => Const(ty, LLVMConstInt(LLVMInt1TypeInContext(c), 1, 0)), - Floating(f) => Const(ty, LLVMConstReal(LLVMFloatTypeInContext(c), *f as f64)), - String(s) => { - let (ptr, len) = (s.as_bytes().as_ptr() as *const _, s.len().try_into().unwrap()); - Const(ty, LLVMConstString(ptr, len, 1)) - } - Integer(i) => { - let int = mem::transmute::<i64, u64>(*i as i64); - Const(ty, LLVMConstInt(LLVMInt32TypeInContext(c), int, 1)) - } - Tuple(inner) => { - let i = inner.iter().map(|cnst| match self.sub_lowerer(cnst).lower() { - Value::Const(_, val) => val, - val => unreachable!("expected a constant, got {val:?}"), - }); - let mut i = i.collect::<Vec<_>>(); - let (ptr, len) = (i.as_mut_ptr(), i.len().try_into().unwrap()); - Const(ty, LLVMConstStructInContext(c, ptr, len, 0)) - } - Color(_) => panic!("need to implement colors"), - Table(_) => panic!("need to implement tables"), - } - } - } -} diff --git a/src/Rust/vvs_codegen/src/lowerer/drops.rs b/src/Rust/vvs_codegen/src/lowerer/drops.rs deleted file mode 100644 index 9b556da8..00000000 --- a/src/Rust/vvs_codegen/src/lowerer/drops.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::{value::Value, Graph, ValueContext}; -use std::collections::BTreeSet; -use vvs_llvm::sys::*; - -/// Drop all the values in the correct order at the end of a given basic block. We take into -/// account any terminator of said basic block. -pub(super) struct DropLowerer<'a> { - /// The context. - c: LLVMContextRef, - - /// Our own builder. - b: LLVMBuilderRef, - - /// The values to drop, they are in the order of their declaration. We drop them in the reverse - /// order! - values: &'a [Value], - - /// The function from which the values must be dropped. - func: LLVMValueRef, - - /// The basic block to use to drop things. We will create our own builder to do the thing, so - /// we don't need others things to pass their builder. - bb: LLVMBasicBlockRef, - - /// The value context. - ctx: &'a ValueContext, -} - -impl<'a> DropLowerer<'a> { - /// Create a new lowerer to drop the specified values from a given function at the end of a - /// given basic block. - /// - /// # Safety - /// All the values must have been declared inside the passed the function. The basic block must - /// be from the passed function. - pub unsafe fn new( - c: LLVMContextRef, - func: LLVMValueRef, - values: &'a [Value], - bb: LLVMBasicBlockRef, - ctx: &'a ValueContext, - ) -> Self { - let b = unsafe { LLVMCreateBuilderInContext(c) }; - Self { c, b, values, func, bb, ctx } - } - - pub fn lower(self) { - use LLVMOpcode::*; - let Self { c, b, values, bb, func, ctx } = self; - - // Position the builder correcly. - - unsafe { - match LLVMGetLastInstruction(bb) { - last if last.is_null() => LLVMPositionBuilderAtEnd(b, bb), - last => match LLVMGetInstructionOpcode(last) { - LLVMRet | LLVMBr | LLVMSwitch | LLVMIndirectBr | LLVMCallBr => LLVMPositionBuilderBefore(b, last), - _ => LLVMPositionBuilderAtEnd(b, bb), - }, - }; - }; - - // Get the dominance graph to filter the values that we must really drop here! - - let bbs_iter = unsafe { LLVMFunctionIter::new(func) }; - let bbs = Graph::with_capacity(bbs_iter.count()).add_nodes(bbs_iter); - let bbs = bbs_iter.fold(bbs, |bbs, bb| unsafe { - match LLVMBasicBlockIter::new(bb).last() { - Some(instr) => match LLVMGetInstructionOpcode(instr) { - LLVMBr => match LLVMGetNumOperands(instr) { - 1 => bbs.add_edge(bb, LLVMValueAsBasicBlock(LLVMGetOperand(instr, 0))), - 3 => bbs - .add_edge(bb, LLVMValueAsBasicBlock(LLVMGetOperand(instr, 1))) - .add_edge(bb, LLVMValueAsBasicBlock(LLVMGetOperand(instr, 2))), - n => unreachable!("got br instruction with {n} operands"), - }, - LLVMSwitch => todo!("handle switch, got {} operands", LLVMGetNumOperands(instr)), - LLVMIndirectBr => todo!("handle indirectbr, got {} operands", LLVMGetNumOperands(instr)), - _ => bbs, - }, - None => bbs, - } - }); - - // Filter the values that we must really delete and call the destructors. We can destroy - // the values in any order because things are reference-counted. Also, just drop the values - - let declared_values: BTreeSet<LLVMValueRef> = unsafe { - bbs.any_path_content(LLVMGetEntryBasicBlock(func), bb) - .into_iter() - .flat_map(|bb| LLVMBasicBlockIter::new(bb)) - .collect() - }; - - values - .iter() - .filter(|val| val.llvm_value().map_or(false, |val| declared_values.contains(&val))) - .flat_map(|val| val.get_dropper_name().map(|drop| (val, drop))) - .for_each(|(val, drop)| { - ctx.get_value(drop).call(b, [val.loaded(c, b)]); - }); - } -} - -impl<'a> Drop for DropLowerer<'a> { - fn drop(&mut self) { - unsafe { LLVMDisposeBuilder(self.b) }; - } -} diff --git a/src/Rust/vvs_codegen/src/lowerer/expression.rs b/src/Rust/vvs_codegen/src/lowerer/expression.rs deleted file mode 100644 index d3a82d2e..00000000 --- a/src/Rust/vvs_codegen/src/lowerer/expression.rs +++ /dev/null @@ -1,347 +0,0 @@ -use crate::{lowerer::*, Value, ValueContext}; -use vvs_lang::ast::*; -use vvs_llvm::sys::{LLVMIntPredicate::*, LLVMRealPredicate::*, *}; -use vvs_runtime_types::{ - types::{VVRTString, VVRTTable}, - vvll::LLVMExported, -}; -use vvs_utils::either; - -/// Lower a single expression. -pub(super) struct ExpressionLowerer<'a> { - llvm: LLVMLowerContext, - - /// We expression we want to lower. - expr: &'a ASTExpr, - - /// The expected type, to see if we need to generate coertion. - as_type: Option<&'a ASTType>, - - /// The value context, this stores available already-lowered LLVM values. - ctx: &'a ValueContext, -} - -impl<'a> ExpressionLowerer<'a> { - /// Create a new expression lowerer. - pub(super) fn with_assumed_type(llvm: LLVMLowerContext, expr: &'a ASTExpr, ctx: &'a ValueContext) -> Self { - Self { llvm, expr, as_type: None, ctx } - } - - /// Create a new expression lowerer with an expected type for the lowered expression. If needed - /// the lower process will call [CastLowerer]. - pub(super) fn with_expected_type( - llvm: LLVMLowerContext, - expr: &'a ASTExpr, - as_type: &'a ASTType, - ctx: &'a ValueContext, - ) -> Self { - Self { llvm, expr, as_type: Some(as_type), ctx } - } - - /// Overwrite the used [ValueContext] used to do the lowering. - pub(super) fn with_ctx(mut self, ctx: &'a ValueContext) -> Self { - self.ctx = ctx; - self - } - - /// Set the type that is expected by the expression, calling the [CastLowerer] if needed! - pub(super) fn into_type(mut self, ty: &'a ASTType) -> Self { - self.as_type = Some(ty); - self - } - - /// Create a new sub-expression lowerer. Used for recusive descent to lower complexe - /// expressions, thus private. - fn sub_lowerer(&self, expr: &'a ASTExpr) -> Self { - let Self { llvm, ctx, .. } = *self; - Self { llvm, expr, as_type: None, ctx } - } - - /// Lower an equality. - unsafe fn lower_equality( - llvm @ (_, c, b): LLVMLowerContext, - ctx: &ValueContext, - (l, lty): (LLVMValueRef, &ASTType), - (r, rty): (LLVMValueRef, &ASTType), - ) -> LLVMValueRef { - use ASTType::*; - match lty { - Nil => LLVMConstInt(LLVMInt1TypeInContext(c), 1, 0), - Boolean | Integer => LLVMBuildICmp(b, LLVMIntEQ, l, r, cstr!()), - Floating => { - let flt = LLVMFloatTypeInContext(c); - let diff = LLVMBuildFSub(b, l, r, c"".as_ptr()); - let diff = CallLowerer::call_intrinsic("llvm.fabs.f32", llvm, flt, [diff]); - LLVMBuildFCmp(b, LLVMRealOLE, diff, LLVMConstReal(flt, 10e-5), c"".as_ptr()) - } - - Tuple(lt) => match rty { - Tuple(rt) if lt.len() == rt.len() => lt.iter().zip(rt).enumerate().fold( - LLVMConstInt(LLVMInt1TypeInContext(c), 1, 0), - |acc, (idx, (lt, rt))| { - let idx = idx.try_into().expect("too many elements"); - let l = LLVMBuildStructGEP2(b, LLVMTypeOf(l), l, idx, cstr!()); - let r = LLVMBuildStructGEP2(b, LLVMTypeOf(r), r, idx, cstr!()); - let ret = Self::lower_equality(llvm, ctx, (l, lt), (r, rt)); - LLVMBuildAnd(b, ret, acc, cstr!()) - }, - ), - _ => LLVMConstInt(LLVMInt1TypeInContext(c), 0, 0), - }, - - Any => ctx.get_value("VVRTAny_eq").call(b, [l, r]), - Line => ctx.get_value("VVRTLine_eq").call(b, [l, r]), - String => ctx.get_value("VVRTString_eq").call(b, [l, r]), - Syllabe => ctx.get_value("VVRTSyllabe_eq").call(b, [l, r]), - ty if ty.is_sequence() => ctx.get_value("VVRTSeq_eq").call(b, [l, r]), - ty if ty.is_variant() => ctx.get_value("VVRTVariant_eq").call(b, [l, r]), - ty if ty.is_table() => ctx.get_value("VVRTTable_eq").call(b, [l, r]), - - _ => panic!("can't apply `{:?}` to expressions of type `{lty}` and `{rty}`", ASTBinop::CmpEQ), - } - } - - /// Lower the value into LLVM-IR at the position specified by the builder passed at - /// construction time. If any value is created it will be added in the returned [ValueContext]. - pub(super) fn lower(self) -> (ValueContext, Value) { - let Self { llvm: llvm @ (_, c, b), expr: ASTExpr { content, .. }, as_type, ctx } = self; - let (bln, int, flt) = (unsafe { LLVMInt1TypeInContext(c) }, unsafe { LLVMInt32TypeInContext(c) }, unsafe { - LLVMFloatTypeInContext(c) - }); - let (false_, true_, zero_, one_) = ( - unsafe { LLVMConstInt(bln, 0, 0) }, - unsafe { LLVMConstInt(bln, 1, 0) }, - unsafe { LLVMConstInt(int, 0, 0) }, - unsafe { LLVMConstInt(int, 1, 0) }, - ); - let store_scalar = |ctx: ValueContext, val: LLVMValueRef, ty: LLVMTypeRef, ast: ASTType| unsafe { - let ptr = LLVMBuildAlloca(b, ty, cstr!()); - LLVMBuildStore(b, val, ptr); - (ctx, Value::Alloca(ast, ty, ptr)) - }; - - match content { - ASTExprVariant::Var(var) => (ctx.clone(), ctx.get_value(var.name()).clone()), - - ASTExprVariant::Const(value) => { - let value = ConstantLowerer::new(c, value).lower(); - match as_type { - Some(expected_type) if expected_type != value.get_ast_type() => { - match CastLowerer::from_llvm(llvm, &value, expected_type, ctx).lower_static() { - CastedValue::Unchanged { value } => (ctx.clone(), value), - CastedValue::Static { value } => (ctx.to_register_anon(value.clone()), value), - CastedValue::Dynamic { .. } => unreachable!(), - } - } - _ => (ctx.clone(), value), - } - } - - ASTExprVariant::Tuple(tuple) => unsafe { - let (mut lowered_tuple, mut types, mut tuple_ty) = - (Vec::with_capacity(tuple.len()), Vec::with_capacity(tuple.len()), Vec::with_capacity(tuple.len())); - - let ctx = tuple.iter().fold(self.ctx.clone(), |ctx, item| { - let (ctx, expr) = self.sub_lowerer(item).with_ctx(&ctx).lower(); - let (ast_type, expr) = (expr.get_ast_type(), expr.loaded(c, b)); - lowered_tuple.push(expr); - types.push(LLVMTypeOf(expr)); - tuple_ty.push(ast_type.clone()); - ctx - }); - - let tuple_len = types.len().try_into().expect("too many elements"); - let types = LLVMStructTypeInContext(c, types.as_mut_ptr(), tuple_len, 0); - let tuple_ptr = LLVMBuildAlloca(b, types, c"".as_ptr()); - - for (idx, val) in lowered_tuple.into_iter().enumerate() { - let idx = idx.try_into().expect("too many elements"); - let ptr = LLVMBuildStructGEP2(b, types, tuple_ptr, idx, c"".as_ptr()); - LLVMBuildStore(b, val, ptr); - } - - (ctx, Value::Alloca(ASTType::Tuple(tuple_ty), types, tuple_ptr)) - }, - - ASTExprVariant::Table(table) => unsafe { - let ty = VVRTTable::llvm_type(c); - let ptr = LLVMBuildAlloca(b, ty, c"".as_ptr()); - LLVMBuildStore(b, ctx.get_value("VVRTTable_new").call(b, []), ptr); - let mut ctx = table.iter().fold(self.ctx.clone(), |_, (key, value)| { - // Static key! The table will create it's own string. - let key_len: u32 = key.len().try_into().expect("too many elements"); - let key = LLVMConstStringInContext(c, key.as_ptr() as *const _, key_len, 1); - let key_len = LLVMConstInt(LLVMInt32TypeInContext(c), key_len as u64, 0); - - // Lower the value and cast it into an Any. - let (mut ctx, value) = self.sub_lowerer(value).into_type(&ASTType::Any).lower(); - let value = value.loaded(c, b); - ctx.forget_by_llvm_value(value); - - // Insert the value with the correct key. - ctx.get_value("VVRTTable_insert_from_raw_key") - .call(b, [ptr, key, key_len, value]); - ctx - }); - let value = Value::Alloca(ASTType::AnyTable, ty, ptr); - ctx.register_anon(value.clone()); - (ctx, value) - }, - - ASTExprVariant::FuncCall(func, args) => { - let (ctx, value) = CallLowerer::new(llvm, self.ctx).lower_func_call(func, args); - (ctx.to_register_anon(value.clone()), value) - } - ASTExprVariant::MethodInvok(obj, method, args) => { - let (ctx, value) = CallLowerer::new(llvm, self.ctx).lower_method_invok(obj, method, args); - (ctx.to_register_anon(value.clone()), value) - } - - ASTExprVariant::Unop(op, inner) => unsafe { - use vvs_lang::ast::{ASTType::*, ASTUnop::*}; - - let (ctx, inner) = match as_type { - Some(ty) => self.sub_lowerer(inner).into_type(ty).lower(), - None => self.sub_lowerer(inner).lower(), - }; - let (ty, inner) = (inner.get_ast_type(), inner.loaded(c, b)); - let call_len = |ctx: ValueContext, func: &str| { - let value = ctx.get_value(func).call(b, [inner]); - store_scalar(ctx, value, int, Integer) - }; - - match op { - LogicNot | BitNot | Neg if matches!(ty, Boolean) => { - let cmp = LLVMBuildICmp(b, LLVMIntEQ, inner, false_, cstr!()); - let res = LLVMBuildSelect(b, cmp, true_, false_, cstr!()); - store_scalar(ctx, res, bln, Boolean) - } - - LogicNot if matches!(ty, Integer) => { - let cmp = LLVMBuildICmp(b, LLVMIntEQ, inner, zero_, cstr!()); - let res = LLVMBuildSelect(b, cmp, one_, zero_, cstr!()); - store_scalar(ctx, res, int, Integer) - } - - BitNot if matches!(ty, Integer) => { - let res = CallLowerer::call_intrinsic("llvm.bitreverse.i32", llvm, int, [inner]); - store_scalar(ctx, res, int, Integer) - } - - #[rustfmt::skip] Neg if matches!(ty, Integer ) => store_scalar(ctx, LLVMBuildNeg (b, inner, cstr!()), int, Integer ), - #[rustfmt::skip] Neg if matches!(ty, Floating) => store_scalar(ctx, LLVMBuildFNeg(b, inner, cstr!()), flt, Floating), - - #[rustfmt::skip] Len => match ty { - Line => call_len(ctx, "VVRTLine_len"), - String => call_len(ctx, "VVRTString_len"), - Syllabe => call_len(ctx, "VVRTSyllabe_len"), - ty if ty.is_sequence() => call_len(ctx, "VVRTSeq_len"), - ty if ty.is_table() => call_len(ctx, "VVRTTable_len"), - Tuple(len) => { - let len = TryInto::<i32>::try_into(len.len()).expect("too many elements") as u64; - (ctx, Value::Const(Integer, LLVMConstInt(int, len, 0))) - } - ty => panic!("can't apply `{op:?}` to expression of type `{ty}`"), - }, - - op => panic!("can't apply `{op:?}` to expression of type `{ty}`"), - } - }, - - ASTExprVariant::Binop(left, op, right) => unsafe { - use vvs_lang::ast::{ASTBinop::*, ASTType::*}; - - let (ctx, left) = match as_type { - Some(ty) => self.sub_lowerer(left).into_type(ty).lower(), - None => self.sub_lowerer(left).lower(), - }; - let lty = left.get_ast_type(); - let (ctx, right) = self - .sub_lowerer(right) - .into_type(either!(*op == Power => &Integer; lty)) - .with_ctx(&ctx) - .lower(); - let (l, r, rty) = (left.loaded(c, b), right.loaded(c, b), right.get_ast_type()); - let (is_int, is_flt, is_bln) = - (matches!(lty, Integer), matches!(lty, Floating), matches!(lty, Boolean)); - - let (res, ty, ast) = match op { - Power if is_int => todo!(), - Power if is_flt => todo!(), - - #[rustfmt::skip] Mul if is_int => (LLVMBuildMul (b, l, r, cstr!()), int, Integer), - #[rustfmt::skip] Div if is_int => (LLVMBuildSDiv(b, l, r, cstr!()), int, Integer), - #[rustfmt::skip] Add if is_int => (LLVMBuildAdd (b, l, r, cstr!()), int, Integer), - #[rustfmt::skip] Sub if is_int => (LLVMBuildSub (b, l, r, cstr!()), int, Integer), - - #[rustfmt::skip] Mul if is_flt => (LLVMBuildFMul(b, l, r, cstr!()), flt, Floating), - #[rustfmt::skip] Div if is_flt => (LLVMBuildFDiv(b, l, r, cstr!()), flt, Floating), - #[rustfmt::skip] Add if is_flt => (LLVMBuildFAdd(b, l, r, cstr!()), flt, Floating), - #[rustfmt::skip] Sub if is_flt => (LLVMBuildFSub(b, l, r, cstr!()), flt, Floating), - - #[rustfmt::skip] Mod if is_int => (LLVMBuildSRem(b, l, r, cstr!()), int, Integer ), - #[rustfmt::skip] Mod if is_flt => (LLVMBuildFRem(b, l, r, cstr!()), flt, Floating), - - #[rustfmt::skip] StrCat if matches!(lty, String) => { - (ctx.get_value("VVRTString_cat").call(b, [l, r]), VVRTString::llvm_type(c), String) - }, - - #[rustfmt::skip] CmpLE if is_int => (LLVMBuildICmp(b, LLVMIntSLE, l, r, cstr!()), bln, Boolean), - #[rustfmt::skip] CmpLT if is_int => (LLVMBuildICmp(b, LLVMIntSLT, l, r, cstr!()), bln, Boolean), - #[rustfmt::skip] CmpGE if is_int => (LLVMBuildICmp(b, LLVMIntSGE, l, r, cstr!()), bln, Boolean), - #[rustfmt::skip] CmpGT if is_int => (LLVMBuildICmp(b, LLVMIntSGT, l, r, cstr!()), bln, Boolean), - - #[rustfmt::skip] CmpLE if is_flt => (LLVMBuildFCmp(b, LLVMRealOLE, l, r, cstr!()), flt, Floating), - #[rustfmt::skip] CmpLT if is_flt => (LLVMBuildFCmp(b, LLVMRealOLT, l, r, cstr!()), flt, Floating), - #[rustfmt::skip] CmpGE if is_flt => (LLVMBuildFCmp(b, LLVMRealOGE, l, r, cstr!()), flt, Floating), - #[rustfmt::skip] CmpGT if is_flt => (LLVMBuildFCmp(b, LLVMRealOGT, l, r, cstr!()), flt, Floating), - - #[rustfmt::skip] BitAnd | LogicAnd if is_bln => (LLVMBuildAnd(b, l, r, cstr!()), bln, Boolean), - #[rustfmt::skip] BitXor | LogicXor if is_bln => (LLVMBuildXor(b, l, r, cstr!()), bln, Boolean), - #[rustfmt::skip] BitOr | LogicOr if is_bln => (LLVMBuildOr (b, l, r, cstr!()), bln, Boolean), - - #[rustfmt::skip] BitAnd if is_int => (LLVMBuildAnd(b, l, r, cstr!()), int, Integer), - #[rustfmt::skip] BitXor if is_int => (LLVMBuildXor(b, l, r, cstr!()), int, Integer), - #[rustfmt::skip] BitOr if is_int => (LLVMBuildOr (b, l, r, cstr!()), int, Integer), - #[rustfmt::skip] LogicAnd if is_int => (LLVMBuildICmp(b, LLVMIntEQ, LLVMBuildAnd(b, l, r, cstr!()), one_, cstr!()), bln, Boolean), - #[rustfmt::skip] LogicXor if is_int => (LLVMBuildICmp(b, LLVMIntEQ, LLVMBuildXor(b, l, r, cstr!()), one_, cstr!()), bln, Boolean), - #[rustfmt::skip] LogicOr if is_int => (LLVMBuildICmp(b, LLVMIntEQ, LLVMBuildOr (b, l, r, cstr!()), one_, cstr!()), bln, Boolean), - - #[rustfmt::skip] BitShiftLeft if is_int => (LLVMBuildShl (b, l, r, cstr!()), int, Integer), - #[rustfmt::skip] BitShiftRight if is_int => (LLVMBuildAShr(b, l, r, cstr!()), int, Integer), - - CmpEQ => (Self::lower_equality(llvm, &ctx, (l, lty), (r, rty)), bln, Boolean), - CmpNE => { - let not = Self::lower_equality(llvm, &ctx, (l, lty), (r, rty)); - (LLVMBuildNot(b, not, cstr!()), bln, Boolean) - } - - op => panic!("can't apply `{op:?}` to expressions of type `{lty}` and `{rty}`"), - }; - store_scalar(ctx, res, ty, ast) - }, - - ASTExprVariant::Suffixed(table, _fields) => { - let (_ctx, _table) = self.sub_lowerer(table).lower(); - todo!() - } - - ASTExprVariant::Color(variant) | ASTExprVariant::Movement(variant) => { - let mut arguments = Vec::with_capacity(variant.args_len()); - let _ctx = variant.args().iter().fold(ctx.clone(), |ctx, expr| { - let (ctx, expr) = self.sub_lowerer(expr).with_ctx(&ctx).lower(); - arguments.push(expr); - ctx - }); - let _variant = variant.variant(); - - todo!() - } - - ASTExprVariant::Default(_) => todo!(), - - // See later for clozures - ASTExprVariant::FuncBind(_, _) => unreachable!(), - } - } -} diff --git a/src/Rust/vvs_codegen/src/lowerer/function.rs b/src/Rust/vvs_codegen/src/lowerer/function.rs deleted file mode 100644 index fd8ab087..00000000 --- a/src/Rust/vvs_codegen/src/lowerer/function.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::{lowerer::*, ValueContext}; -use vvs_lang::ast::*; -use vvs_llvm::*; - -pub(super) struct FunctionLowerer<'a> { - llvm: LLVMLowerContext, - - /// The global context, we store a reference, then we will add it the arguments of the - /// function. - ctx: &'a ValueContext, - - /// The llvm function. - value: LLVMValueRef, - - /// The vivy function. - function: &'a ASTFunction, -} - -impl<'a> FunctionLowerer<'a> { - pub fn new(llvm: LLVMLowerContext, ctx: &'a ValueContext, v: LLVMValueRef, f: &'a ASTFunction) -> Self { - Self { llvm, ctx, value: v, function: f } - } - - pub fn lower(self) { - let Self { llvm: llvm @ (_, c, b), value, function, .. } = self; - let ASTFunction { arguments, content, returns, .. } = function; - let mut ctx = self.ctx.clone(); - - let mut ctx = unsafe { - LLVMPositionBuilderAtEnd(b, LLVMAppendBasicBlockInContext(c, value, c"entry".as_ptr())); - for (idx, arg) in arguments.iter().enumerate() { - let ty = arg - .get_specified_type() - .expect("the type should be specified at this point"); - let llvm_ty = TypeLowerer::new(c, ty).lower(); - let arg_name = CString::new(arg.name().as_ref()).expect("invalid name"); - let arg_param = LLVMGetParam(value, idx.try_into().expect("too many arguments")); - let ptr = LLVMBuildAlloca(b, llvm_ty, arg_name.as_ptr()); - LLVMBuildStore(b, arg_param, ptr); - ctx.register(arg.name(), Value::Alloca(ty.clone(), llvm_ty, ptr)); - } - - InstructionLowerer::new(llvm, value, content, returns.clone(), ctx).lower() - }; - - let to_drop = ctx.get_values_to_drop(); - unsafe { LLVMFunctionIter::new(value) } - .filter(|&bb| unsafe { - LLVMBasicBlockIter::new(bb) - .last() - .map_or(false, |instr| LLVMGetInstructionOpcode(instr) == LLVMOpcode::LLVMRet) - }) - .for_each(|bb| unsafe { DropLowerer::new(c, value, &to_drop, bb, &ctx) }.lower()); - } -} diff --git a/src/Rust/vvs_codegen/src/lowerer/instruction.rs b/src/Rust/vvs_codegen/src/lowerer/instruction.rs deleted file mode 100644 index d731bfd9..00000000 --- a/src/Rust/vvs_codegen/src/lowerer/instruction.rs +++ /dev/null @@ -1,431 +0,0 @@ -use crate::lowerer::*; -use std::sync::OnceLock; -use vvs_lang::{anon_expression, anon_instruction, ast::*}; -use vvs_llvm::*; -use vvs_utils::either; - -#[derive(Clone, Copy)] -struct BBLoop { - body_entry: LLVMBasicBlockRef, - loop_exit: LLVMBasicBlockRef, -} - -/// Lower a single instruction. -pub(super) struct InstructionLowerer<'a> { - llvm: LLVMLowerContext, - - /// The function containing the instruction to lower. - func: LLVMValueRef, - - /// Remeber the last loop to be able to break or continue. - last_loop: Option<BBLoop>, - - /// The instruction we want to lower. - instr: &'a ASTInstr, - - /// The return type of the function, to see if we need to generetate coertion. - func_return_type: ASTType, - - /// The value context, this stores available already-lowered LLVM values. - ctx: ValueContext, -} - -impl<'a> InstructionLowerer<'a> { - /// Create a new expression lowerer. - pub(super) fn new( - llvm: LLVMLowerContext, - func: LLVMValueRef, - instruction: &'a ASTInstr, - func_return_type: ASTType, - ctx: ValueContext, - ) -> Self { - let last_loop = Default::default(); - Self { llvm, func, instr: instruction, func_return_type, ctx, last_loop } - } - - /// Create a new sub-expression lowerer. Used for recusive descent to lower complexe - /// expressions, thus private. - fn sub_lowerer(&self, instruction: &'a ASTInstr) -> Self { - let Self { llvm, func, last_loop, .. } = *self; - let func_return_type = self.func_return_type.clone(); - let ctx = self.ctx.clone(); - Self { llvm, instr: instruction, func, func_return_type, ctx, last_loop } - } - - /// Short-hand to create an expression lowerer by passing some arguments correctly. - fn expr(&'a self, expr: &'a ASTExpr) -> ExpressionLowerer<'a> { - ExpressionLowerer::with_assumed_type(self.llvm, expr, &self.ctx) - } - - /// Get a new sub-expression lowerer. We need to know the exit block of this scope so that we - /// can build the calls to drop for this scope. - fn scoped_lowerer(&mut self, exit_bb: LLVMBasicBlockRef, cb: impl FnOnce(Self) -> ValueContext) { - static mut NOOP: OnceLock<ASTInstr> = OnceLock::new(); - - // Save context. - let pre_ctx = self.ctx.clone(); - self.ctx = pre_ctx.sub_context(); - - // Build an empty instruction to give as parent lowerer in scope. - let Self { llvm: llvm @ (_, c, _), func, last_loop, .. } = *self; - let instruction = unsafe { NOOP.get_or_init(|| anon_instruction!(Noop)) }; - let func_return_type = self.func_return_type.clone(); - let ctx = self.ctx.sub_context(); - let this = Self { llvm, func, last_loop, instr: instruction, func_return_type, ctx }; - - // Drop what was declared in the scope - let mut ctx = cb(this); - unsafe { DropLowerer::new(c, func, &ctx.get_values_to_drop(), exit_bb, &ctx) }.lower(); - - // Restore context. - self.ctx = pre_ctx; - } - - /// Set the last loop to lower a given instruction. - fn with_loop(mut self, r#loop: BBLoop) -> Self { - self.last_loop = Some(r#loop); - self - } - - /// Set the value context. - fn with_ctx(mut self, ctx: ValueContext) -> Self { - self.ctx = ctx; - self - } - - pub(super) fn lower(mut self) -> ValueContext { - let Self { llvm: llvm @ (_, c, b), func, instr: ASTInstr { content, span }, .. } = self; - match content { - // Nop - ASTInstrVariant::Noop => self.ctx, - - // A simple list of instructions - ASTInstrVariant::Block(instructions) => unsafe { - let exit = LLVMAppendBasicBlockInContext(c, self.func, c".block".as_ptr()); - self.scoped_lowerer(exit, |this| { - instructions - .iter() - .fold(this.ctx.clone(), |ctx, instruction| this.sub_lowerer(instruction).with_ctx(ctx).lower()) - }); - LLVMBuildBr(b, exit); - LLVMPositionBuilderAtEnd(b, exit); - self.ctx - }, - - // Breaks/Continue - ASTInstrVariant::Break | ASTInstrVariant::Continue if self.last_loop.is_none() => { - panic!("try to lower break/continue while not being inside a loop") - } - ASTInstrVariant::Break => unsafe { - LLVMBuildBr(b, self.last_loop.expect("internal error").loop_exit); - self.ctx - }, - ASTInstrVariant::Continue => unsafe { - LLVMBuildBr(b, self.last_loop.expect("internal error").body_entry); - self.ctx - }, - - // Return from function - ASTInstrVariant::Return(expr) => unsafe { - let (ctx, value) = self.expr(expr).lower(); - match value { - Value::Nil => LLVMBuildRetVoid(b), - Value::Function(..) => panic!("error at `{span}`: can't return a function for now"), - value => LLVMBuildRet(b, value.loaded(c, b)), - }; - ctx - }, - - // Assignation - ASTInstrVariant::Assign(dests, srcs) if dests.len() != srcs.len() => { - unreachable!("internal error: verifier failed for assignations at `{span}`") - } - ASTInstrVariant::Assign(dests, srcs) => dests.iter().zip(srcs).fold(self.ctx, |ctx, (dest, src)| { - let (dest, dest_ty) = Self::compute_dest_location(c, b, &ctx, dest); - let (ctx, src) = ExpressionLowerer::with_expected_type(llvm, src, dest_ty, &ctx).lower(); - Self::store_into(c, b, dest, &src); - ctx - }), - - // Declarations - ASTInstrVariant::Decl(dests, srcs) if dests.len() != srcs.len() => { - unreachable!("internal error: verifier failed for declarations at `{span}`") - } - ASTInstrVariant::Decl(dests, srcs) => { - let mut declarations = Vec::with_capacity(dests.len()); - let ctx = dests.iter().zip(srcs).fold(self.ctx.clone(), |ctx, (dest, src)| { - let dest_ast_ty = dest.get_specified_type().expect("internal error"); - let dest_llvm_ty = TypeLowerer::new(c, dest_ast_ty).lower(); - let val = unsafe { LLVMBuildAlloca(b, dest_llvm_ty, cstr!("{}", dest.name())) }; - let (ctx, expr) = self.expr(src).with_ctx(&ctx).into_type(dest_ast_ty).lower(); - Self::store_into(c, b, val, &expr); - declarations.push((dest.name(), Value::Alloca(dest_ast_ty.clone(), dest_llvm_ty, val))); - ctx - }); - declarations.into_iter().fold(ctx, |mut ctx, (name, val)| { - ctx.register(name, val); - ctx - }) - } - - // Call a function, discard the result - ASTInstrVariant::FuncCall(func, args) => { - let (ctx, value) = CallLowerer::new(llvm, &self.ctx).lower_func_call(func, args); - ctx.to_register_anon(value) - } - ASTInstrVariant::MethodInvok(obj, method, args) => { - let (ctx, value) = CallLowerer::new(llvm, &self.ctx).lower_method_invok(obj, method, args); - ctx.to_register_anon(value) - } - - // If/Else/Elseif blocks - ASTInstrVariant::Cond { if_blocks, else_block } => unsafe { - let exit = LLVMAppendBasicBlockInContext(c, self.func, c".if.exit".as_ptr()); - let pre_ctx = self.ctx.clone(); - let true_cond = ASTCondition::BooleanTrue(anon_expression!(Const(ASTConst::True))); - - let get_bb = move |idx: usize, what: &str| { - LLVMAppendBasicBlockInContext(c, self.func, cstr!(".if.{}.{}", idx, what)) - }; - let mut bbs: HashMap<usize, LLVMBasicBlockRef> = Default::default(); - let mut get_bb_cond = move |idx| *bbs.entry(idx).or_insert_with(move || get_bb(idx, "condition")); - - /// Use a macro to not write the same code twice by forgeting something... - macro_rules! build_cond_block { - (($if_num: expr, $if_cond: expr, $if_inner: expr), $else_bb: expr, $exit: expr) => {{ - let (if_bb_body, if_bb_body_exit) = (get_bb($if_num, "body"), get_bb($if_num, "body.exit")); - self.scoped_lowerer(if_bb_body_exit, |mut scoped| { - // CONDITION: We don't need to drop things even if we didn't succeded, - // add the new ctx to lower the conditional body. The head - // BB of the conditional is cached by it's number. - LLVMPositionBuilderAtEnd(b, get_bb_cond($if_num)); - let ctx = scoped.lower_condition($if_cond, if_bb_body, $else_bb); - - // BODY: We need to drop things after the exit of the loop, we also - // drop the conditional stuff - LLVMPositionBuilderAtEnd(b, if_bb_body); - let ctx = scoped.sub_lowerer($if_inner).with_ctx(ctx).lower(); - LLVMBuildBr(b, if_bb_body_exit); - LLVMPositionBuilderAtEnd(b, if_bb_body_exit); - LLVMBuildBr(b, $exit); - - // WARN: scoped.ctx -> contains also the temporary values! We must drop - // all of those if we succeded or not. - DropLowerer::new(c, func, &scoped.ctx.get_values_to_drop(), $else_bb, &ctx).lower(); - DropLowerer::new(c, func, &scoped.ctx.get_values_to_drop(), if_bb_body_exit, &ctx).lower(); - - ctx - }) - }}; - } - - let else_block = else_block.as_ref().map(|instr| (&true_cond, instr.as_ref())); - let if_blocks = if_blocks.iter().map(|(cond, instr): &_| (cond, instr)); - let blocks = if_blocks.chain(else_block).enumerate().collect::<Vec<(_, (&_, &_))>>(); - assert!(!blocks.is_empty(), "empty conditionals not allowed"); - - // IF -> Need to jump to the first condition checker - LLVMBuildBr(b, get_bb_cond(0)); - - // Build IF, for ELSE -> will be handled at next iteration - blocks.windows(2).for_each(|slice| match slice { - [(if_num, (if_cond, if_inner)), (else_num, _)] => { - build_cond_block!((*if_num, if_cond, if_inner), get_bb_cond(*else_num), exit) - } - _ => unreachable!(), - }); - - // ELSE / IF in the case where we have only one conditional -> Handle last item - let (else_num, (else_cond, else_inner)) = blocks.last().expect("internal error"); - build_cond_block!((*else_num, else_cond, else_inner), exit, exit); - - LLVMPositionBuilderAtEnd(b, exit); - pre_ctx - }, - - // While loops - ASTInstrVariant::WhileDo(condition, inner) => unsafe { - let (cond, exit, body, body_exit) = ( - LLVMAppendBasicBlockInContext(c, self.func, c".while.cond".as_ptr()), - LLVMAppendBasicBlockInContext(c, self.func, c".while.exit".as_ptr()), - LLVMAppendBasicBlockInContext(c, self.func, c".while.body".as_ptr()), - LLVMAppendBasicBlockInContext(c, self.func, c".while.body.exit".as_ptr()), - ); - - self.scoped_lowerer(body_exit, |mut scoped| { - // For a WhileDo the condition binding variables are only visible in the body - // of the loop. Here we ensure variables are only available inside the loop. - LLVMBuildBr(b, cond); - LLVMPositionBuilderAtEnd(b, cond); - let ctx = scoped.lower_condition(condition, body, exit); - - LLVMPositionBuilderAtEnd(b, body); - let ctx = scoped - .sub_lowerer(inner) - .with_ctx(ctx) - .with_loop(BBLoop { body_entry: body, loop_exit: exit }) - .lower(); - - // Drop what was - LLVMBuildBr(b, body_exit); - LLVMPositionBuilderAtEnd(b, body_exit); - DropLowerer::new(c, func, &scoped.ctx.get_values_to_drop(), body_exit, &ctx).lower(); - LLVMBuildBr(b, cond); - ctx - }); - - LLVMPositionBuilderAtEnd(b, exit); - self.ctx - }, - - // For loop - ASTInstrVariant::ForLoop { var, lower: l, upper: u, step, body: inner } => unsafe { - let (cond, exit, body, body_exit) = ( - LLVMAppendBasicBlockInContext(c, self.func, c".forloop.cond".as_ptr()), - LLVMAppendBasicBlockInContext(c, self.func, c".forloop.exit".as_ptr()), - LLVMAppendBasicBlockInContext(c, self.func, c".forloop.body".as_ptr()), - LLVMAppendBasicBlockInContext(c, self.func, c".forloop.body.exit".as_ptr()), - ); - - let (ctx, lower) = self.expr(l).into_type(&ASTType::Integer).lower(); - let (mut ctx, upper) = self.expr(u).with_ctx(&ctx).into_type(&ASTType::Integer).lower(); - let (lower, upper) = (lower.loaded(c, b), upper.loaded(c, b)); - - // Setup the index - let t_i32 = LLVMInt32TypeInContext(c); - let step = step.unwrap_or(1) as i64; - assert!(step > 0, "the for-loop step must be strictly positive"); - let step = LLVMConstInt(t_i32, std::mem::transmute_copy(&step), 0); - - let index = LLVMBuildAlloca(b, t_i32, cstr!(".index.{}", var)); - ctx.register(var.name(), Value::Alloca(ASTType::Integer, t_i32, index)); - LLVMBuildStore(b, lower, index); - - // Build the condition check - LLVMBuildBr(b, cond); - LLVMPositionBuilderAtEnd(b, cond); - const PREDICATE: LLVMIntPredicate = LLVMIntPredicate::LLVMIntSLT; - let loaded = LLVMBuildLoad2(b, t_i32, index, cstr!(".index.{}.loaded", var)); - let cont = LLVMBuildICmp(b, PREDICATE, loaded, upper, cstr!(".index.{}.flag", var)); - LLVMBuildCondBr(b, cont, body, exit); - - // Lower the body, we need a scope for that. - self.scoped_lowerer(body_exit, |scoped| { - LLVMPositionBuilderAtEnd(b, body); - let ctx = scoped - .sub_lowerer(inner) - .with_loop(BBLoop { body_entry: body, loop_exit: exit }) - .lower(); - let loaded = LLVMBuildLoad2(b, t_i32, index, cstr!(".index.{}.loaded", var)); - let loaded = LLVMBuildAdd(b, loaded, step, cstr!(".index.{}.incremented", var)); - LLVMBuildStore(b, loaded, index); - LLVMBuildBr(b, body_exit); - LLVMPositionBuilderAtEnd(b, body_exit); - LLVMBuildBr(b, cond); - ctx - }); - - // End of loop - LLVMPositionBuilderAtEnd(b, exit); - ctx - }, - ASTInstrVariant::ForInto { .. } => unsafe { - let (_cond, _exit, _body, _body_exit) = ( - LLVMAppendBasicBlockInContext(c, self.func, c".foreach.cond".as_ptr()), - LLVMAppendBasicBlockInContext(c, self.func, c".foreach.exit".as_ptr()), - LLVMAppendBasicBlockInContext(c, self.func, c".foreach.body".as_ptr()), - LLVMAppendBasicBlockInContext(c, self.func, c".foreach.body.exit".as_ptr()), - ); - todo!() - }, - } - } - - /// Store the content of a value into a location in memory. - /// - /// TODO: Do that in the Value enum - fn store_into(c: LLVMContextRef, b: LLVMBuilderRef, dest: LLVMValueRef, src: &Value) { - match src { - Value::Function(_, _, _) => panic!("we don't handle function pointers for now"), - Value::Const(_, v) => unsafe { LLVMBuildStore(b, *v, dest) }, - Value::Nil => match ConstantLowerer::new(c, &ASTConst::Nil).lower() { - Value::Const(_, v) => unsafe { LLVMBuildStore(b, v, dest) }, - _ => unreachable!(), - }, - Value::Alloca(_, ty, v) => unsafe { - let v = LLVMBuildLoad2(b, *ty, *v, cstr!()); - LLVMBuildStore(b, v, dest) - }, - }; - } - - /// Get the address and the type of a variable to store into it. - fn compute_dest_location<'b>( - _c: LLVMContextRef, - _b: LLVMBuilderRef, - _ctx: &'b ValueContext, - _expr: &'b ASTExpr, - ) -> (LLVMValueRef, &'b ASTType) { - todo!() - } - - /// Lower the condition. Returns the new context with the added binded values if needed. If the - /// condition is not checked we don't need to drop anything from the returned value context. - /// This means that we must drop the returned context only on the then condition path. - /// - /// If things where allocated to check the condition, then we add them to the current context, - /// but not to the returned context! This means that may the condition succeed or not, you must - /// drop the context of the calling lowerer at the end of the `then` and `else` paths. - /// - /// Most of the time the context is already scopped, so it shouldn't be too difficult trop - /// things at the right moment. - fn lower_condition( - &mut self, - condition: &ASTCondition, - bb_then: LLVMBasicBlockRef, - bb_else: LLVMBasicBlockRef, - ) -> ValueContext { - let Self { llvm: llvm @ (_, c, b), .. } = *self; - match condition { - ASTCondition::BooleanTrue(cond) => unsafe { - let (mut ctx, cond) = self.expr(cond).into_type(&ASTType::Boolean).lower(); - LLVMBuildCondBr(b, cond.loaded(c, b), bb_then, bb_else); - - let ret_ctx = self.ctx.clone(); - self.ctx.add_values_to_drop(ctx.get_values_to_drop()); - ret_ctx - }, - - ASTCondition::CoercibleInto { new, as_type, source: src } - if src - .get_specified_type() - .expect("should be setted") - .coercible_to(as_type) => - unsafe { - let llvm_true = LLVMConstInt(LLVMInt1TypeInContext(c), 1, 0); - let (register, flag, src) = match CastLowerer::from_ast(llvm, src, as_type, &self.ctx).lower_dynamic() { - CastedValue::Dynamic { flag, value } => (true, flag, value), - CastedValue::Static { value } => (true, llvm_true, value), - CastedValue::Unchanged { value } => (false, llvm_true, value), - }; - LLVMBuildCondBr(b, flag, bb_then, bb_else); - - LLVMPositionBuilderAtEnd(b, bb_then); - let ty = TypeLowerer::new(c, as_type).lower(); - let dest = LLVMBuildAlloca(b, ty, cstr!(".{}.alloca", new)); - LLVMBuildStore(b, src.loaded(c, b), dest); - - either!(!register => self.ctx.clone(); - self.ctx.to_register(new.name(), Value::Alloca(as_type.clone(), ty, dest)) - ) - }, - - ASTCondition::CoercibleInto { .. } => unsafe { - LLVMBuildBr(b, bb_else); - self.ctx.clone() - }, - - ASTCondition::BindVariant { .. } => todo!(), - } - } -} diff --git a/src/Rust/vvs_codegen/src/lowerer/mod.rs b/src/Rust/vvs_codegen/src/lowerer/mod.rs index 2de61638..53e5e2d3 100644 --- a/src/Rust/vvs_codegen/src/lowerer/mod.rs +++ b/src/Rust/vvs_codegen/src/lowerer/mod.rs @@ -1,221 +1,83 @@ -mod calls; -mod cast; -mod constant; -mod drops; -mod expression; -mod function; -mod instruction; +//! Contains structs to lower from Lua code to LLVM-IR + mod types; -use crate::{ - cstr, - lowerer::{calls::*, cast::*, drops::*, expression::*, function::*, instruction::*, types::*}, - value::Value, - LLVMLowerContext, ValueContext, -}; -use hashbrown::HashMap; -use std::ffi::{CStr, CString}; -use vvs_lang::ast::*; -use vvs_llvm::*; +use crate::{lowerer::types::TypeLowerer, CodegenError}; +use vvs_llvm::prelude::*; +use vvs_parser::prelude::{ast, MainProgram}; use vvs_runtime_types::{VVRTSymbol, VVRTSymbolType}; -// Re-export the constant lowerer because it can be usefull in other parts of the codegen. -pub(crate) use self::constant::ConstantLowerer; - -pub struct Lowerer<'a> { - llvm: LLVMLowerContext, - - functions: Vec<(ASTString, ASTString, &'a ASTFunction)>, - globals: HashMap<(ASTString, ASTString), &'a ASTConst>, - - ctx: ValueContext, +pub struct Lowerer<'ctx, 'this> +where + 'ctx: 'this, +{ + context: &'this Context<'ctx>, + module: Module<'this>, } -pub struct LowererBuilder<'a> { - name: CString, - c: LLVMContextRef, - m: Option<LLVMModuleRef>, - b: LLVMBuilderRef, - programs: Option<&'a ASTProgram>, - modules: Vec<&'a ASTModule>, -} - -impl<'a> LowererBuilder<'a> { - /// Create a new lowerer builder. We need a name and the context from the JIT thing. - /// - /// # Safety - /// This function is unsafe because we use the LLVM context pointer passed from anywhere. This - /// pointer must be not null and be a valid LLVM context. - pub unsafe fn new(name: impl AsRef<str>, c: LLVMContextRef) -> Self { - Self { - c, - name: CString::new(name.as_ref()).expect("invalid module name"), - b: LLVMCreateBuilderInContext(c), - m: Default::default(), - modules: Default::default(), - programs: Default::default(), - } +impl<'ctx, 'this> Lowerer<'ctx, 'this> +where + 'ctx: 'this, +{ + /// Create a new lowerer. We will lower things into a module. + pub fn new(context: &'this Context<'ctx>, module: impl AsRef<str>) -> Result<Self, CodegenError> { + let _ = vvs_llvm::init::initialize_llvm(); + Ok(Self { context, module: context.create_module(module.as_ref()).map_err(CodegenError::NulError)? }) } - /// Add a complete program to lower. If any other program was present, overwrite it. - pub fn program(mut self, program: &'a ASTProgram) -> Self { - self.programs = Some(program); - self + pub fn declare_globals<'a>( + self, + iter: impl IntoIterator<Item = (impl AsRef<str>, impl AsRef<str>, Vec<&'a ast::TypeInfo>, &'a ast::TypeInfo)>, + ) -> Result<Self, CodegenError> { + iter.into_iter() + .try_fold(self, |this, (module, name, args, ret)| this.declare_global(module, name, args.as_slice(), ret)) } - /// Add a module to lower to the list of things to lower. - pub fn module(mut self, module: &'a ASTModule) -> Self { - self.modules.push(module); - self + pub fn declare_global( + mut self, + module_name: impl AsRef<str>, + name: impl AsRef<str>, + arguments: &[&ast::TypeInfo], + returns: &ast::TypeInfo, + ) -> Result<Self, CodegenError> { + let Self { context, ref mut module, .. } = self; + let (module_name, name) = (module_name.as_ref(), name.as_ref()); + match module.get_function(&format!("{module_name}.{name}")).err() { + Some(LLVMError::Undefined(_)) => module + .add_function( + VVRTSymbol::new(VVRTSymbolType::Function, module_name, name) + .map_err(|err| CodegenError::Error(format!("{err}").into()))? + .to_string(), + FunctionType::from(( + TypeLowerer::new(context).lower_slice(arguments), + TypeLowerer::new(context).lower(returns), + )), + ) + .map_err(CodegenError::NulError)?, + Some(LLVMError::NulError(err)) => return Err(CodegenError::NulError(err)), + None => return Err(CodegenError::RedefinedFunction(module_name.into(), name.into())), + }; + Ok(self) } - /// Build the lowerer from this builder. - pub fn build(self) -> Lowerer<'a> { - let Self { c, m, b, programs, modules, name } = self; - - // Get all the modules that will be lowered inside the final LLVM module. By doing this we - // have a similar thing to LTO for Vivy Script. - - let init = HashMap::<ASTString, &'a ASTModule>::default(); - let modules = modules - .into_iter() - .chain(programs.iter().flat_map(|prog| prog.modules())) - .fold(init, |mut map, module| match map.entry(module.name()) { - hashbrown::hash_map::Entry::Occupied(_) => map, - hashbrown::hash_map::Entry::Vacant(entry) => { - entry.insert(module); - map - } - }); - - // Get the globals that needs to be lowered / needs to be known by the lowerer. We first - // collect the setted options from the program as those will overwrite values from the - // modules, then we get what is defined as default in the module. To ensure we doesn't - // overwrite things, we only insert in the final hashmap if the thing was not already - // defined in the hashmap. Other than setted overwriting defaults, there should not be any - // other collisions. - - let init: HashMap<(ASTString, ASTString), &'a ASTConst> = programs - .iter() - .flat_map(|prog| { - prog.setted_options() - .iter() - .map(|((m, n), v)| ((m.clone(), n.clone()), v)) - }) - .collect(); - let globals = modules.iter().flat_map(|(mname, m)| { - let hdl = |(var, val): (&'a ASTVar, &'a _)| (mname.clone(), var.name(), val); - m.consts(ASTVisibilityRule::any()).map(hdl).chain(m.options().map(hdl)) - }); - let globals = globals.fold(init, |mut globals, (mn, n, val)| match globals.entry((mn, n)) { - hashbrown::hash_map::Entry::Occupied(_) => globals, - hashbrown::hash_map::Entry::Vacant(entry) => { - entry.insert(val); - globals - } - }); - - // Get the functions that are available in all the module to lower them. Should not be any - // collision. - - let functions = modules.iter().flat_map(|(mname, m)| { - m.functions(ASTVisibilityRule::any()) - .map(|(fname, f)| (mname.clone(), fname, f)) - }); - let functions = functions.collect(); - - // Now we can create the lowerer with the specified LLVM module or by creating a new one. - - let m = m.unwrap_or_else(|| unsafe { LLVMModuleCreateWithNameInContext(name.as_ptr(), c) }); - let mut ctx = ValueContext::default(); - ctx.register_all( - unsafe { vvs_runtime_types::vvll::LLVMRegisterExportedIntoModule(c, m) } - .into_iter() - .map(|(name, asttype, val, ty)| (name, Value::Function(asttype, ty, val))), - ); - - Lowerer { llvm: (m, c, b), functions, globals, ctx } + pub fn lower_modules( + self, + iter: impl IntoIterator<Item = (impl AsRef<str>, ast::Ast)>, + ) -> Result<Self, CodegenError> { + iter.into_iter() + .try_fold(self, |this, (name, ast)| this.lower_module(name, ast)) } -} - -impl<'a> Lowerer<'a> { - /// Lower the content of the programs and modules, then returns the LLVM module containing - /// everything... - pub fn lower(self) -> LLVMModuleRef { - let Self { llvm: llvm @ (m, c, b), functions, globals, mut ctx } = self; - - // Register all the globals in the context, they will be directly inlined in the lowering - // process. - - ctx.register_all( - globals - .into_iter() - .map(|((m, n), cnst)| (format!("{m}.{n}"), ConstantLowerer::new(c, cnst).lower())), - ); - - // Lower all the functions. First declare the things and register them with the mangled - // name and the unmagnled name. As we collect the iterator we ensure that all the functions - // are declared before we generate them. Doing so allows us to use recursion and we don't - // need to find a good order to define-use the functions like in C. When we latter lower - // the content of the functions we try to find the first error and return it. - - functions.into_iter().for_each(|(module, name, function)| { - // Declare the thing - let mangled = VVRTSymbol::new(VVRTSymbolType::Function, &module, &name) - .expect("invalid function names") - .mangle(); - let ast_function_type = function.function_type(); - let ty = TypeLowerer::new(c, &ast_function_type).lower(); - let value = unsafe { LLVMAddFunction(m, mangled.as_ptr(), ty) }; - // Register it. When we collect the iterator. - let to_register = Value::Function(ast_function_type, ty, value); - ctx.register(mangled.to_str().expect("invalid function names"), to_register.clone()); - ctx.register(format!("{module}.{name}"), to_register); - - // We will generate it latter - FunctionLowerer::new(llvm, &ctx, value, function).lower(); - }); - - // Cleanup and optimize the code. - - unsafe { - LLVMDisposeBuilder(b); - Self::verify_and_apply_optimizations(m, 3) - } + pub fn lower_module(self, name: impl AsRef<str>, ast: ast::Ast) -> Result<Self, CodegenError> { + todo!() } - /// Run the optimization passes on a module. - unsafe fn verify_and_apply_optimizations(m: LLVMModuleRef, lvl: u8) -> LLVMModuleRef { - let mut cmsg = std::ptr::null_mut(); - if 0 != LLVMVerifyModule(m, LLVMVerifierFailureAction::LLVMPrintMessageAction, &mut cmsg) { - let msg = CStr::from_ptr(cmsg).to_string_lossy().to_string(); - LLVMDisposeMessage(cmsg); - panic!("invalid module: {msg}"); - } - - let pbo = LLVMCreatePassBuilderOptions(); - let pm = LLVMCreatePassManager(); - let tm = LLVMCreateTargetMachine( - LLVMGetTargetFromName(LLVMGetTarget(m)), - LLVMGetDefaultTargetTriple(), - LLVMGetHostCPUName(), - LLVMGetHostCPUFeatures(), - LLVMCodeGenOptLevel::LLVMCodeGenLevelAggressive, - LLVMRelocMode::LLVMRelocPIC, - LLVMCodeModel::LLVMCodeModelDefault, - ); - - let err = LLVMRunPasses(m, cstr!("default<O{}>", lvl), tm, pbo); - if !err.is_null() { - let msg = CStr::from_ptr(LLVMGetErrorMessage(err)).to_string_lossy().to_string(); - LLVMConsumeError(err); - panic!("failed to run optimizer with level O{lvl} on module: {msg}") - } + pub fn generate_main(self, main: MainProgram) -> Result<Self, CodegenError> { + todo!() + } - LLVMDisposePassBuilderOptions(pbo); - LLVMDisposePassManager(pm); - LLVMDisposeTargetMachine(tm); - m + /// Finished the lowering process and get the module out of the builder. + pub fn finished(self) -> Module<'this> { + self.module } } diff --git a/src/Rust/vvs_codegen/src/lowerer/types.rs b/src/Rust/vvs_codegen/src/lowerer/types.rs index 5e1b4748..6d721119 100644 --- a/src/Rust/vvs_codegen/src/lowerer/types.rs +++ b/src/Rust/vvs_codegen/src/lowerer/types.rs @@ -1,57 +1,24 @@ -use vvs_lang::ast::*; -use vvs_llvm::*; -use vvs_runtime_types::{types::*, vvll::LLVMExported}; +use vvs_llvm::prelude::*; +use vvs_parser::prelude::ast; -pub(super) struct TypeLowerer<'a> { - c: LLVMContextRef, - vivy_type: &'a ASTType, +pub(crate) struct TypeLowerer<'a> { + context: &'a Context<'a>, } impl<'a> TypeLowerer<'a> { - pub(super) fn new(c: LLVMContextRef, vivy_type: &'a ASTType) -> Self { - Self { c, vivy_type } + pub fn new(context: &'a Context) -> Self { + Self { context } } - fn sub_lowerer(&self, ty: &'a ASTType) -> Self { - Self { c: self.c, vivy_type: ty } + pub fn lower(&self, ty: &ast::TypeInfo) -> Type { + todo!() } - pub(super) fn lower(self) -> LLVMTypeRef { - let Self { c, vivy_type } = self; - match vivy_type { - // Easy stuff... - ASTType::Nil => unsafe { LLVMVoidTypeInContext(c) }, - ASTType::Integer => unsafe { LLVMInt32TypeInContext(c) }, - ASTType::Floating => unsafe { LLVMFloatTypeInContext(c) }, - ASTType::Boolean => unsafe { LLVMInt1TypeInContext(c) }, - - // Functions - ASTType::Function(args, returns) => unsafe { - let mut args = args.iter().map(|ty| self.sub_lowerer(ty).lower()).collect::<Vec<_>>(); - LLVMFunctionType( - self.sub_lowerer(returns).lower(), - args.as_mut_ptr(), - args.len().try_into().expect("too many arguments"), - 0, - ) - }, - - // The tuple - ASTType::Tuple(inner) => unsafe { - let mut inner = inner.iter().map(|ty| self.sub_lowerer(ty).lower()).collect::<Vec<_>>(); - LLVMStructType(inner.as_mut_ptr(), inner.len().try_into().expect("too many elements"), 0) - }, - - // Special structs, query the runtime for that... - ASTType::Any => unsafe { VVRTAny::llvm_type(c) }, - ASTType::String => unsafe { VVRTString::llvm_type(c) }, - ASTType::Syllabe => unsafe { VVRTSyllabe::llvm_type(c) }, - ASTType::Line => unsafe { VVRTLine::llvm_type(c) }, - ty if ty.is_table() => unsafe { VVRTTable::llvm_type(c) }, - ty if ty.is_variant() => unsafe { VVRTVariant::llvm_type(c) }, - ty if ty.is_sequence() => unsafe { VVRTSeq::llvm_type(c) }, + pub fn lower_array<const N: usize>(&self, tys: [ast::TypeInfo; N]) -> [Type<'a>; N] { + todo!() + } - _ => unreachable!(), - } + pub fn lower_slice(&self, tys: &[&ast::TypeInfo]) -> Vec<Type<'a>> { + todo!() } } diff --git a/src/Rust/vvs_codegen/src/value.rs b/src/Rust/vvs_codegen/src/value.rs deleted file mode 100644 index 8ad8ed92..00000000 --- a/src/Rust/vvs_codegen/src/value.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::{cstr /*lowerer*/}; -use vvs_lang::ast::{ASTConst, ASTType, Typed}; -use vvs_llvm::sys::*; - -#[derive(Debug, Default, Clone, PartialEq, Eq)] -pub(crate) enum Value { - /// Just the Nil/Null/None/nullptr value. - #[default] - Nil, - - /// A constant. - Const(ASTType, LLVMValueRef), - - /// A pointer to the stack plus the underlying type. - Alloca(ASTType, LLVMTypeRef, LLVMValueRef), - - /// A function with its function type. - Function(ASTType, LLVMTypeRef, LLVMValueRef), -} - -impl Value { - /// Try to get the name of the value. Only [Value::Nil] have no name. - pub fn as_str(&self) -> Option<&str> { - use Value::*; - match *self { - Nil => None, - Function(.., val) | Const(_, val) | Alloca(.., val) => unsafe { - let mut len = 0; - let str = LLVMGetValueName2(val, &mut len) as *const _; - Some(std::str::from_utf8_unchecked(std::slice::from_raw_parts(str, len))) - }, - } - } - - pub fn llvm_type(&self) -> Option<LLVMTypeRef> { - match self { - Value::Const(..) | Value::Nil => None, - Value::Alloca(_, t, _) | Value::Function(_, t, _) => Some(*t), - } - } - - pub fn llvm_value(&self) -> Option<LLVMValueRef> { - match self { - Value::Nil => None, - Value::Const(_, v) | Value::Alloca(_, _, v) | Value::Function(_, _, v) => Some(*v), - } - } - - pub fn loaded(&self, c: LLVMContextRef, b: LLVMBuilderRef) -> LLVMValueRef { - match self { - Value::Nil => todo!(), // lowerer::ConstantLowerer::new(c, &ASTConst::Nil).lower().loaded(c, b), - Value::Const(_, val) => *val, - Value::Alloca(_, ty, ptr) => unsafe { LLVMBuildLoad2(b, *ty, *ptr, cstr!()) }, - Value::Function(_, _, func_ptr) => *func_ptr, - } - } - - /// Get the name of the dropper function, if any! - pub fn get_dropper_name(&self) -> Option<&'static str> { - match self.get_ast_type() { - ty if ty.is_variant() => Some("VVRTVariant_drop"), - ty if ty.is_table() => Some("VVRTTable_drop"), - ty if ty.is_sequence() => Some("VVRTSeq_drop"), - ASTType::Syllabe => Some("VVRTSyllabe_drop"), - ASTType::String => Some("VVRTString_drop"), - ASTType::Line => Some("VVRTLine_drop"), - ASTType::Any => Some("VVRTAny_drop"), - - ASTType::Function(_, _) => todo!("handle functions and clozures..."), - _ => None, - } - } - - /// Call a function with some values. Note that this function won't create memory manage points - /// for the passed values (see types that are managed and the ones that are not.) - pub fn call<const N: usize>(&self, b: LLVMBuilderRef, mut args: [LLVMValueRef; N]) -> LLVMValueRef { - match self { - Value::Function(_, func_ty, func_val) => unsafe { - let args_ptr = args.as_mut_ptr(); - let args_len = args.len().try_into().expect("too much arguments"); - LLVMBuildCall2(b, *func_ty, *func_val, args_ptr, args_len, c"".as_ptr()) - }, - val => panic!("can't call a non-function value: {val:?}"), - } - } - - pub fn get_ast_type(&self) -> &ASTType { - const NIL: ASTType = ASTType::Nil; - match self { - Value::Nil => &NIL, - Value::Const(ty, _) | Value::Alloca(ty, _, _) | Value::Function(ty, _, _) => ty, - } - } -} - -impl Typed for Value { - fn get_type(&self, _: &vvs_lang::ast::ASTTypeContext) -> ASTType { - self.get_ast_type().clone() - } -} diff --git a/src/Rust/vvs_font/build.rs b/src/Rust/vvs_font/build.rs index 1ae42238..1dce12b8 100644 --- a/src/Rust/vvs_font/build.rs +++ b/src/Rust/vvs_font/build.rs @@ -24,38 +24,36 @@ fn main() { let fonts = fs::read_dir(&font_dir) .expect("failed to read the font folder") .filter_map(Result::ok) + .filter(|file| file.file_type().map(|ft| ft.is_file()).unwrap_or_default()) .filter(|file| { - file.file_type().map(|ft| ft.is_file()).unwrap_or_default() && { - let path = file.path(); - path.file_name() - .map(|f| !EXCLUDED_FONTS.iter().any(|pat| f.to_string_lossy().starts_with(pat))) - .unwrap_or_default() - && path.extension().map(|e| e == "ttf").unwrap_or_default() - } + let path = file.path(); + path.file_name() + .map(|f| !EXCLUDED_FONTS.iter().any(|pat| f.to_string_lossy().starts_with(pat))) + .unwrap_or_default() + && path.extension().map(|e| e == "ttf").unwrap_or_default() }) .map(|file| { - let (path, file_name) = (file.path(), file.file_name()); - let path = path.to_string_lossy(); - let name = file_name.to_string_lossy(); - let name = name.rsplit_once('.').unwrap().0; - format!("({name:?}, include_bytes!({path:?}))") + format!( + "{{ const FONT: &[u8] = include_bytes!({path:?}); ({name:?}, FONT) }}", + path = file.path().display(), + name = file.file_name().to_string_lossy().rsplit_once('.').unwrap().0 + ) }) .collect::<Vec<_>>() .join(",\n"); - // Generate - let src_content = format!( - r#" -pub const fn embeded_fonts() -> &'static [(&'static str, &'static [u8])] {{ - &[ {fonts} ] + fs::write( + out_dir.join("generated_font_utils.rs"), + format!( + r#" +pub fn embeded_fonts() -> impl Iterator<Item = (&'static str, &'static [u8])> {{ + [ {fonts} ].into_iter() }} - "# - ); + "# + ), + ) + .expect("failed to write generated source file"); - // Write - fs::write(out_dir.join("generated_font_utils.rs"), src_content).expect("failed to write generated source file"); - - // Rerun rerun_directory(&font_dir); println!("cargo:rerun-if-changed=build.rs"); } diff --git a/src/Rust/vvs_lang/Cargo.toml b/src/Rust/vvs_lang/Cargo.toml deleted file mode 100644 index 538d8c5f..00000000 --- a/src/Rust/vvs_lang/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "vvs_lang" -description = "Vivy Script Language" -version.workspace = true -authors.workspace = true -edition.workspace = true -license.workspace = true - -[dependencies] -thiserror.workspace = true -paste.workspace = true -serde.workspace = true -hashbrown.workspace = true -log.workspace = true -regex.workspace = true -anyhow.workspace = true -toml.workspace = true -derive_more.workspace = true - -vvs_utils.workspace = true -vvs_parser.workspace = true diff --git a/src/Rust/vvs_lang/src/ast/constant.rs b/src/Rust/vvs_lang/src/ast/constant.rs deleted file mode 100644 index 22af5e6b..00000000 --- a/src/Rust/vvs_lang/src/ast/constant.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::ast::*; -use anyhow::{anyhow, ensure, Context}; - -/// A constant expr. -#[derive(Debug, PartialEq, Clone)] -pub enum ASTConst { - Nil, - False, - True, - Tuple(Vec<ASTConst>), - Color(ASTVariant<ASTConst>), - String(ASTString), - Integer(ASTInteger), - Floating(ASTFloating), - Table(ASTTable<ASTConst>), -} - -/// Get the type for a const table, returns the more specialized type ([ASTType::UniformTable], etc) -fn get_const_table_type(table: &ASTTable<ASTConst>) -> ASTType { - let integer_keys = table.keys().all(|key| key.parse::<i64>().is_ok()); - let mut uniform_values = table.values().map(|val| val.get_const_type()).collect::<Vec<_>>(); - let uniform_values = uniform_values - .pop() - .map(|ty| uniform_values.into_iter().all(|a| ty.eq(&a)).then_some(ty)); - match (integer_keys, uniform_values) { - (true, Some(Some(ty))) => ASTType::Sequence(Box::new(ty)), - _ => ASTType::Table( - table - .iter() - .map(|(key, value)| (key.clone(), value.get_const_type())) - .collect(), - ), - } -} - -impl Typed for ASTConst { - #[inline] - fn get_type(&self, _: &ASTTypeContext) -> ASTType { - self.get_const_type() - } -} - -impl std::fmt::Display for ASTConst { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ASTConst::Nil => f.write_str("nil"), - ASTConst::False => f.write_str("false"), - ASTConst::True => f.write_str("true"), - ASTConst::Color(ASTVariant { variant, args }) => write!(f, "color#{variant}{args:?}"), - ASTConst::String(str) => write!(f, "{str:?}"), - ASTConst::Integer(int) => write!(f, "{int}"), - ASTConst::Floating(flt) => write!(f, "{flt}"), - ASTConst::Table(table) => write_ast_table(f, table), - ASTConst::Tuple(tuple) => write_ast_tuple(f, tuple), - } - } -} - -impl MaybeConstExpr for ASTConst { - #[inline] - fn is_const_expr(&self) -> bool { - true - } - - #[inline] - fn eval_const_expr(&self) -> Option<ASTConst> { - Some(self.clone()) - } -} - -impl ASTConst { - #[inline] - pub fn get_const_type(&self) -> ASTType { - match self { - ASTConst::Nil => ASTType::Nil, - ASTConst::True | ASTConst::False => ASTType::Boolean, - ASTConst::Color(..) => ASTType::Color, - ASTConst::String(_) => ASTType::String, - ASTConst::Integer(_) => ASTType::Integer, - ASTConst::Floating(_) => ASTType::Floating, - ASTConst::Tuple(inner) => ASTType::Tuple(inner.iter().map(|expr| expr.get_const_type()).collect()), - ASTConst::Table(content) => get_const_table_type(content), - } - } - - /// Try to create an [ASTConst] from a [toml::Value]. We verify that the value is valid, i.e. - /// we don't have nested tables and such. - /// - /// NOTE: For now we can't construct colors and/or movements from toml values... - pub fn from_toml_value(cache: &ASTStringCacheHandle, value: toml::Value) -> anyhow::Result<Self> { - use toml::Value as TomlValue; - - fn table_from_toml_value( - cache: &ASTStringCacheHandle, - mut values: impl Iterator<Item = (ASTString, TomlValue)>, - ) -> anyhow::Result<ASTConst> { - let (lower, upper) = values.size_hint(); - let table = ASTTable::with_capacity(upper.unwrap_or(lower)); - let table = values.try_fold(table, |mut table, (key, value)| { - let value = ASTConst::from_toml_value(cache, value)?; - ensure!(!matches!(value, ASTConst::Table(_)), "forbiden nested tables"); - table - .insert(key, value) - .map(|_| Err(anyhow!("redefinition of a value in the table"))) - .unwrap_or(Ok(table)) - })?; - Ok(ASTConst::Table(table)) - } - - match value { - TomlValue::Datetime(date) => Ok(ASTConst::String(cache.get(date.to_string()))), - TomlValue::String(str) => Ok(ASTConst::String(cache.get(str))), - - TomlValue::Float(num) => Ok(ASTConst::Floating(num as f32)), - TomlValue::Integer(num) => Ok(ASTConst::Integer(num.try_into().context("integer overflow")?)), - TomlValue::Boolean(true) => Ok(ASTConst::True), - TomlValue::Boolean(false) => Ok(ASTConst::False), - - TomlValue::Array(values) => { - ensure!(values.len() < i32::MAX as usize, "too many elements in array"); - let values = values - .into_iter() - .enumerate() - .map(|(k, v)| (cache.get(k.to_string()), v)); - table_from_toml_value(cache, values) - } - TomlValue::Table(values) => { - ensure!(values.len() < i32::MAX as usize, "too many elements in table"); - table_from_toml_value(cache, values.into_iter().map(|(k, v)| (cache.get(k), v))) - } - } - } -} diff --git a/src/Rust/vvs_lang/src/ast/expression.rs b/src/Rust/vvs_lang/src/ast/expression.rs deleted file mode 100644 index 79d482fb..00000000 --- a/src/Rust/vvs_lang/src/ast/expression.rs +++ /dev/null @@ -1,441 +0,0 @@ -use crate::ast::*; -use std::ops::{Deref, DerefMut}; - -#[macro_export] -macro_rules! anon_expression { - (const $variant: ident $($args: tt)?) => { $crate::anon_expression!(Const (ASTConst::$variant $($args)?)) }; - (box const $variant: ident $($args: tt)?) => { Box::new($crate::anon_expression!(Const (ASTConst::$variant $($args)?))) }; - (box $variant: ident $($args: tt)?) => { Box::new($crate::anon_expression!($variant $($args)?)) }; - - ($variant: ident $($args: tt)?) => { - ASTExpr { - span: Default::default(), - content: ASTExprVariant::$variant $($args)?, - } - }; -} - -#[macro_export] -macro_rules! expression { - ($span: expr, $variant: ident $($args: tt)?) => { - ASTExpr { - span: $span.into(), - content: ASTExprVariant::$variant $($args)?, - } - }; -} - -/// Binops, sorted by precedence. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ASTBinop { - /// Assoc: right - Power, - - Mul, - Div, - Mod, - Add, - Sub, - - /// Assoc: right - StrCat, - - CmpLE, - CmpLT, - CmpGE, - CmpGT, - CmpEQ, - CmpNE, - - LogicAnd, - LogicXor, - LogicOr, - - BitAnd, - BitXor, - BitOr, - BitShiftLeft, - BitShiftRight, -} - -/// Unops. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ASTUnop { - LogicNot, - BitNot, - Len, - Neg, -} - -/// Expressions. For the partial equality we skip the span field to be able to test efficiently the -/// parsing. -#[derive(Debug, Clone, Default)] -pub struct ASTExpr { - pub content: ASTExprVariant, - pub span: ASTSpan, -} - -/// Expressions. -#[derive(Debug, PartialEq, Clone)] -pub enum ASTExprVariant { - /// A table, like in Lua - Table(ASTTable<ASTExpr>), - - /// A binary operation, like in most of languages. - Binop(Box<ASTExpr>, ASTBinop, Box<ASTExpr>), - - /// A unary operation, like in most of languages. - Unop(ASTUnop, Box<ASTExpr>), - - /// A call to a function. - FuncCall(Box<ASTExpr>, Vec<ASTExpr>), - - /// A function where the first arguments are binded. - FuncBind(Box<ASTExpr>, Vec<ASTExpr>), - - /// Invocation of a method. To call a function from a table, we don't use the - /// [ASTExprVariant::MethodInvok] thing, but a [ASTExprVariant::FuncCall] where the first - /// element is the dereferencing of the table. Here the name of the method must be known and be - /// like: `{ty}::{method}`. - MethodInvok(Box<ASTExpr>, ASTString, Vec<ASTExpr>), - - /// We access fields from an expression. - Suffixed(Box<ASTExpr>, Vec<ASTField>), - - /// A non-constant expression color. - Color(ASTVariant<ASTExpr>), - - /// Represents a movement for a line or syllabe. We don't have a constant variant for this - /// representation because movements are very dynamic (it depends on the resolution of the - /// video...) and thus doesn't make much sense to have a constant movement. - /// - /// NOTE: See if other people agree to that, there can be some arguments for having a constant - /// movement or an optional one. - Movement(ASTVariant<ASTExpr>), - - /// A tuple, Lua don't have ones but because we won't be stack based we can't do the multiple - /// return thing without a tuple... - Tuple(Vec<ASTExpr>), - - /// A variable load. - Var(ASTVar), - - /// A constant expression. - Const(ASTConst), - - /// The default value for a type. - Default(ASTType), -} - -/// Fields indexes can be expressions or identifiers -#[derive(Debug, PartialEq, Clone)] -pub enum ASTField { - /// We index by a string or an integer... - Expr(ASTSpan, ASTExprVariant), - - /// A name field. - Identifier(ASTSpan, ASTString), -} - -impl PartialEq for ASTExpr { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.content == other.content - } -} - -/// Get the type for a table, returns the more specialized type ([ASTType::UniformTable], etc) -fn get_table_type<T: Typed>(table: &ASTTable<T>, ctx: &ASTTypeContext) -> ASTType { - let integer_keys = table.keys().all(|key| key.parse::<i64>().is_ok()); - let mut uniform_values = table.values().map(|val| val.get_type(ctx)).collect::<Vec<_>>(); - let uniform_values = uniform_values - .pop() - .map(|ty| uniform_values.into_iter().all(|a| ty.eq(&a)).then_some(ty)); - match (integer_keys, uniform_values) { - (true, Some(Some(ty))) => ASTType::Sequence(Box::new(ty)), - _ => ASTType::Table( - table - .iter() - .map(|(key, value)| (key.clone(), value.get_type(ctx))) - .collect(), - ), - } -} - -fn get_table_deref_type(ctx: &ASTTypeContext, table: ASTType, fields: &[ASTField]) -> ASTType { - let (first, tail) = match fields { - [] => return table, - [first, tail @ ..] => (first, tail), - }; - - match table { - ASTType::Tuple(_) => todo!(), - - ASTType::AnyTable => ASTType::Any, - ASTType::Table(_) => todo!(), - ASTType::UniformTable(_) => todo!(), - - ty @ ASTType::Syllabe | ty @ ASTType::Line => match first { - ASTField::Expr(..) => ASTType::Nil, - ASTField::Identifier(_, field) => { - let ty = crate::ast::get_field_extensions(ctx, ty) - .find_map(|(key, ty)| key.eq(&field.as_ref()).then_some(ty.clone())) - .unwrap_or(ASTType::Nil); - get_table_deref_type(ctx, ty, tail) - } - }, - - _ => ASTType::Nil, - } -} - -impl Typed for ASTExprVariant { - fn get_type(&self, ctx: &ASTTypeContext) -> ASTType { - use crate::ast::{ASTBinop::*, ASTUnop::*}; - match self { - ASTExprVariant::FuncBind(_, _) => todo!(), - - ASTExprVariant::MethodInvok(_, _, _) => todo!(), - - ASTExprVariant::FuncCall(_, _) => todo!(), - - ASTExprVariant::Default(ty) => ty.clone(), - ASTExprVariant::Color(_) => ASTType::Color, - ASTExprVariant::Movement(_) => ASTType::Movement, - ASTExprVariant::Const(r#const) => r#const.get_type(ctx), - - ASTExprVariant::Var(var) => match var.get_specified_type() { - Some(ty) => ty.clone(), - None => ctx.get(var), - }, - - ASTExprVariant::Tuple(tuple) => ASTType::Tuple(tuple.iter().map(|expr| expr.get_type(ctx)).collect()), - ASTExprVariant::Table(table) => get_table_type(table, ctx), - ASTExprVariant::Suffixed(expr, fields) => get_table_deref_type(ctx, expr.get_type(ctx), fields), - - ASTExprVariant::Unop(op, inner) => { - use ASTType::*; - match (op, inner.get_type(ctx)) { - (BitNot | LogicNot, ty @ Integer | ty @ Boolean) => ty, - (Neg, ty @ Floating | ty @ Integer | ty @ Boolean) => ty, - ( - Len, - String | Tuple(_) | Syllabe | Line | AnyTable | UniformTable(_) | AnySequence | Table(_) - | Sequence(_), - ) => Integer, - _ => Nil, - } - } - - ASTExprVariant::Binop(left, op, right) => { - use ASTType::*; - let (left, right) = (left.get_type(ctx), right.get_type(ctx)); - match op { - Power => match (left, right) { - (ty @ Integer | ty @ Floating, Integer) => ty, - _ => Nil, - }, - - Mul | Div | Add | Sub => match (left, right) { - (ty @ Integer, Integer) | (ty @ Floating, Floating) => ty, - _ => Nil, - }, - - Mod => match (left, right) { - (Integer, Integer) => Integer, - _ => Nil, - }, - - StrCat => match (left, right) { - (String, String) => String, - _ => Nil, - }, - - CmpLE | CmpLT | CmpGE | CmpGT => match (left, right) { - (Integer, Integer) | (Floating, Floating) => Boolean, - _ => Nil, - }, - - CmpEQ | CmpNE => match right.coercible_to(&left) { - true => Boolean, - false => Nil, - }, - - LogicAnd | LogicXor | LogicOr => match (left, right) { - (Boolean, Boolean) => Boolean, - _ => Nil, - }, - - BitAnd | BitXor | BitOr => match (left, right) { - (Integer, Integer) => Integer, - (Boolean, Boolean) => Boolean, - _ => Nil, - }, - - BitShiftLeft | BitShiftRight => match (left, right) { - (Integer, Integer) => Integer, - _ => Nil, - }, - } - } - } - } -} - -impl Deref for ASTExpr { - type Target = ASTExprVariant; - #[inline] - fn deref(&self) -> &Self::Target { - &self.content - } -} - -impl DerefMut for ASTExpr { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.content - } -} - -impl VariantName for ASTExpr { - #[inline] - fn variant_name(&self) -> &'static str { - self.content.variant_name() - } -} - -impl VariantName for ASTExprVariant { - #[inline] - fn variant_name(&self) -> &'static str { - match self { - ASTExprVariant::Movement(_) => "movement", - ASTExprVariant::Color(_) => "color", - ASTExprVariant::Table(_) => "table", - ASTExprVariant::Binop(_, _, _) => "binary operation", - ASTExprVariant::Unop(_, _) => "unary operation", - ASTExprVariant::FuncCall(_, _) => "function call", - ASTExprVariant::FuncBind(_, _) => "function bind", - ASTExprVariant::MethodInvok(_, _, _) => "method invokation", - ASTExprVariant::Suffixed(_, _) => "prefixed expression", - ASTExprVariant::Tuple(_) => "tuple", - ASTExprVariant::Var(_) => "variable", - ASTExprVariant::Const(_) => "constant", - ASTExprVariant::Default(_) => "default", - } - } -} - -impl ASTField { - #[inline] - pub fn span(&self) -> ASTSpan { - match self { - ASTField::Expr(span, _) | ASTField::Identifier(span, _) => *span, - } - } -} - -impl MaybeConstExpr for ASTExprVariant { - fn is_const_expr(&self) -> bool { - use ASTExprVariant::*; - match self { - // We don't do constant method invokation for now. - FuncCall(_, _) | MethodInvok(_, _, _) | FuncBind(_, _) => false, - - // Depends on the content. - Binop(l, _, r) => l.is_const_expr() && r.is_const_expr(), - Unop(_, inner) => inner.is_const_expr(), - Tuple(inner) => inner.iter().all(|expr| expr.is_const_expr()), - Color(variant) | Movement(variant) => variant.is_const_expr(), - Suffixed(table, suffixes) => table.is_const_expr() && suffixes.iter().all(|field| field.is_const_expr()), - Default(ty) => ty.is_const_constructible(), - - // Well, it depend, for now we will say that it's not compile time evaluable. - Table(_) => false, - Var(_) => false, - - // Obviously - Const(_) => true, - } - } - - fn eval_const_expr(&self) -> Option<ASTConst> { - use ASTExprVariant::*; - match self { - Const(r#const) => Some(r#const.clone()), - - Binop(_, _, _) => todo!(), - Unop(_, _) => todo!(), - - Default(ty) if ty.is_const_constructible() => Some(match ty { - ASTType::Nil | ASTType::Any => ASTConst::Nil, - ASTType::Integer => ASTConst::Integer(0), - ASTType::Floating => ASTConst::Floating(0.), - ASTType::Boolean => ASTConst::False, - _ => unreachable!(), - }), - - Color(ASTVariant { variant, args }) => Some(ASTConst::Color(ASTVariant { - variant: variant.clone(), - args: args - .iter() - .map(|expr| expr.eval_const_expr().ok_or(())) - .collect::<Result<Vec<_>, _>>() - .ok()?, - })), - - Tuple(inner) => Some(ASTConst::Tuple( - inner - .iter() - .map(|expr| expr.eval_const_expr().ok_or(())) - .collect::<Result<Vec<_>, _>>() - .ok()?, - )), - - Suffixed(_, _) - | FuncCall(_, _) - | FuncBind(_, _) - | MethodInvok(_, _, _) - | Table(_) - | Movement(_) - | Default(_) - | Var(_) => None, - } - } -} - -impl Typed for ASTField { - #[inline] - fn get_type(&self, ctx: &ASTTypeContext) -> ASTType { - match self { - ASTField::Expr(_, expr) => expr.get_type(ctx), - ASTField::Identifier(sp, name) => { - let var = ASTVar::new(ctx.strings()).span(sp).name(name.clone()).build(); - ctx.get(&var) - } - } - } -} - -impl MaybeConstExpr for ASTField { - #[inline] - fn is_const_expr(&self) -> bool { - match self { - ASTField::Expr(_, expr) => expr.is_const_expr(), - ASTField::Identifier(_, _) => false, - } - } - - #[inline] - fn eval_const_expr(&self) -> Option<ASTConst> { - match self { - ASTField::Identifier(..) => None, - ASTField::Expr(_, expr) => expr.eval_const_expr(), - } - } -} - -impl Default for ASTExprVariant { - fn default() -> Self { - ASTExprVariant::Const(ASTConst::Nil) - } -} diff --git a/src/Rust/vvs_lang/src/ast/extension.rs b/src/Rust/vvs_lang/src/ast/extension.rs deleted file mode 100644 index 3cf13f38..00000000 --- a/src/Rust/vvs_lang/src/ast/extension.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::ast::{ASTType, ASTTypeContext}; - -#[inline] -pub fn get_field_extensions(_: &ASTTypeContext, ass_type: ASTType) -> impl Iterator<Item = &(&'static str, ASTType)> { - log::error!("todo: add user options for each item"); - match ass_type { - ASTType::Syllabe => [("start", ASTType::Integer), ("finish", ASTType::Integer)].iter(), - ASTType::Line => [("start", ASTType::Integer), ("finish", ASTType::Integer)].iter(), - _ => unreachable!(), - } -} diff --git a/src/Rust/vvs_lang/src/ast/function.rs b/src/Rust/vvs_lang/src/ast/function.rs deleted file mode 100644 index 6c20bc5b..00000000 --- a/src/Rust/vvs_lang/src/ast/function.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::ast::*; - -/// A function. Can also represent a job. -#[derive(Debug, Clone)] -pub struct ASTFunction { - pub name: ASTString, - pub returns: ASTType, - pub arguments: Vec<ASTVar>, - pub content: ASTInstr, - - pub span: ASTSpan, -} - -/// The signature of a callable thing in Vivy Script. -pub type ASTFunctionSignature = (Vec<ASTType>, ASTType); - -impl ASTFunction { - /// Get the signature of a function. - #[inline] - pub fn signature(&self) -> ASTFunctionSignature { - let args = self - .arguments - .iter() - .flat_map(ASTVar::get_specified_type) - .cloned() - .collect(); - (args, self.returns.clone()) - } - - /// Get the type of this function, as a function tpe. - #[inline] - pub fn function_type(&self) -> ASTType { - let (args, returns) = self.signature(); - ASTType::Function(args, Box::new(returns)) - } -} diff --git a/src/Rust/vvs_lang/src/ast/identifier.rs b/src/Rust/vvs_lang/src/ast/identifier.rs deleted file mode 100644 index 4cc9e144..00000000 --- a/src/Rust/vvs_lang/src/ast/identifier.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::ast::*; - -#[derive(Debug, Default, PartialEq, Eq, Clone)] -pub enum ASTIdentifier { - /// We ignore the value, like in ocaml and rust, this is the '_'. - #[default] - Joker, - - /// A normal identifier, one that can be used by the user. - String(ASTString), - - /// If an identifier begins by '_' and is followed other things then it is reserved by the - /// parser, the generator, and other things. Those identifiers can't be used by a user and they - /// should never see one. - Reserved(ASTString), -} - -impl std::fmt::Display for ASTIdentifier { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.as_ref()) - } -} - -impl AsRef<str> for ASTIdentifier { - fn as_ref(&self) -> &str { - use ASTIdentifier::*; - match self { - Joker => "_", - String(str) | Reserved(str) => str.as_ref(), - } - } -} - -impl ASTIdentifier { - /// Create a new identifier. The passed string will be trimed. If the string is empty or - /// contains non ascii-alphanumeric-underscored characters then an error will be raised. - pub fn new(cache: ASTStringCacheHandle, identifier: impl AsRef<str>) -> Result<Self, String> { - let identifier = identifier.as_ref().trim(); - match identifier - .char_indices() - .find(|(_, c)| '_'.ne(c) && !c.is_ascii_alphanumeric()) - { - Some((idx, char)) => Err(format!("found invalid character '{char}' in identifier at index {idx}")), - None => { - if identifier.is_empty() { - Err("an identifier can't be empty".to_string()) - } else if identifier.starts_with('_') && identifier.len() == 1 { - Ok(Self::Joker) - } else if identifier.starts_with('_') { - Ok(Self::Reserved(cache.get(identifier.trim_start_matches('_')))) - } else { - Ok(Self::Reserved(cache.get(identifier))) - } - } - } - } -} diff --git a/src/Rust/vvs_lang/src/ast/import.rs b/src/Rust/vvs_lang/src/ast/import.rs deleted file mode 100644 index f538506d..00000000 --- a/src/Rust/vvs_lang/src/ast/import.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::ast::{ASTModule, ASTModulePtr, ASTString}; -use hashbrown::HashMap; -use std::{ - cell::RefCell, - path::{Path, PathBuf}, - rc::Rc, -}; - -/// Structure used to resolve module imports. -/// -/// By default, includes the current working dir. -#[derive(Debug)] -pub struct ImportResolver { - path: Vec<PathBuf>, - cache: Rc<RefCell<HashMap<ASTString, ASTModulePtr>>>, -} - -pub(crate) enum ResolveResult { - Path(PathBuf), - Module(ASTModulePtr), -} - -impl FromIterator<PathBuf> for ImportResolver { - #[inline] - fn from_iter<T: IntoIterator<Item = PathBuf>>(iter: T) -> Self { - Self { path: iter.into_iter().collect(), cache: Default::default() } - } -} - -impl Default for ImportResolver { - /// Get the default import resolver, always includes the current working dir... - #[inline] - fn default() -> Self { - Self { path: vec![std::env::current_dir().expect("failed to get the CWD")], cache: Default::default() } - } -} - -impl ImportResolver { - /// Create a new empty resolver. Note that it's different from the default resolver that uses - /// the current working directory (CWD, PWD, etc...) - #[inline] - pub fn empty() -> Self { - Self { path: vec![], cache: Default::default() } - } - - pub(crate) fn resolve(&self, module: impl AsRef<str>) -> Option<ResolveResult> { - let find = |prefix| Self::get_file_path(prefix, module.as_ref()).map(ResolveResult::Path); - self.cache - .borrow() - .get(module.as_ref()) - .cloned() - .map(ResolveResult::Module) - .or_else(|| self.path.iter().find_map(find)) - } - - #[inline] - pub(crate) fn cache_module(&self, name: ASTString, module: ASTModulePtr) { - if let Some(module) = self.cache.borrow_mut().insert(name, module) { - log::error!("re-cache module `{}`", ASTModule::get(&module).name()) - } - } - - fn get_file_path(prefix: impl AsRef<Path>, module: impl AsRef<str>) -> Option<PathBuf> { - let mut file = prefix.as_ref().join(module.as_ref()); - file.set_extension("vvl"); - file.canonicalize() - .map_err(|err| match err.kind() { - std::io::ErrorKind::NotFound => (), - err => log::error!( - "failed to lookup for `{}` in `{}`: {err}", - module.as_ref(), - prefix.as_ref().to_string_lossy() - ), - }) - .ok() - } -} diff --git a/src/Rust/vvs_lang/src/ast/instruction.rs b/src/Rust/vvs_lang/src/ast/instruction.rs deleted file mode 100644 index 57e64ffa..00000000 --- a/src/Rust/vvs_lang/src/ast/instruction.rs +++ /dev/null @@ -1,187 +0,0 @@ -use crate::ast::*; -use std::ops::{Deref, DerefMut}; - -#[macro_export] -macro_rules! anon_instruction { - ($variant: ident $($args: tt)?) => { - ASTInstr { - span: Default::default(), - content: ASTInstrVariant::$variant $($args)?, - } - }; -} - -#[macro_export] -macro_rules! instruction { - ($span: expr, $variant: ident $($args: tt)?) => { - ASTInstr { - span: $span.into(), - content: ASTInstrVariant::$variant $($args)?, - } - }; -} - -/// Instructions. -#[derive(Debug, Clone)] -pub struct ASTInstr { - pub content: ASTInstrVariant, - pub span: ASTSpan, -} - -/// Instructions. -#[derive(Debug, PartialEq, Clone)] -pub enum ASTInstrVariant { - /// Declare variables with something inside. - Decl(Vec<ASTVar>, Vec<ASTExpr>), - - /// Assign into variables. - Assign(Vec<ASTExpr>, Vec<ASTExpr>), - - /// A function call. - FuncCall(Box<ASTExpr>, Vec<ASTExpr>), - - /// A method invokation. - MethodInvok(Box<ASTExpr>, ASTString, Vec<ASTExpr>), - - /// Begin a block. - Block(Vec<ASTInstr>), - - /// A WhileDo instruction. - WhileDo(ASTCondition, Box<ASTInstr>), - - /// Conditionals, contract the elseid blocks. - Cond { if_blocks: Vec<(ASTCondition, ASTInstr)>, else_block: Option<Box<ASTInstr>> }, - - /// For loop, the classic one: - /// ```vvs - /// for elem = 1, 3, 1 do print(elem) end - /// ``` - ForLoop { var: ASTVar, lower: ASTExpr, upper: ASTExpr, step: Option<ASTInteger>, body: Box<ASTInstr> }, - - /// For loop with an iterable expression (table): - /// ```vvs - /// for elem in { 1, 2, 3 } do print(elem) end - /// for elem in ( 1, 2, 3 ) do print(elem) end - /// for elem in my_table do print(elem) end - /// ``` - ForInto { var: ASTVar, list: ASTExpr, body: Box<ASTInstr> }, - - /// Just a noop. - Noop, - - /// Final thing: break from block. - Break, - - /// Final thing: continue to next iteration. - Continue, - - /// Final thing: return something. - Return(ASTExpr), -} - -impl PartialEq for ASTInstr { - fn eq(&self, other: &Self) -> bool { - self.content == other.content - } -} - -fn get_instruction_slice_type(ctx: &ASTTypeContext, block: &[ASTInstr]) -> ASTType { - let Some((last, instructions)) = block.split_last() else { - return ASTType::Nil; - }; - let coerce_type = |from: ASTType, var: &ASTVar| { - let (span, ty, name) = (var.span(), var.get_specified_type().unwrap_or(&from), var.name()); - let strings = ctx.strings(); - let var = ASTVar::new(strings).span(span).name(name.clone()); - if from.coercible_to(ty) { - var.with_type(from).build() - } else { - log::error!(target: "cc", "{span}; try to assign value of type `{from}` into `{name}: {ty}`"); - var.with_type(ASTType::Nil).build() - } - }; - let fold_declaration = |mut ctx, (var, from): (&ASTVar, &ASTExpr)| { - let var = coerce_type(from.get_type(&ctx), var); - let (_, var_ty) = (var.span(), var.get_type(&ctx)); - ctx.declare(var, var_ty); - ctx - }; - let fold_on_declaration = |ctx, instruction: &ASTInstr| match &instruction.content { - ASTInstrVariant::Decl(vars, froms) => vars.iter().zip(froms.iter()).fold(ctx, fold_declaration), - _ => ctx, - }; - last.get_type(&instructions.iter().fold(ctx.for_scope(), fold_on_declaration)) -} - -impl Typed for ASTInstrVariant { - fn get_type(&self, ctx: &ASTTypeContext) -> ASTType { - match self { - ASTInstrVariant::Decl(_, _) - | ASTInstrVariant::Noop - | ASTInstrVariant::Assign(_, _) - | ASTInstrVariant::Break - | ASTInstrVariant::Continue => ASTType::Nil, - - ASTInstrVariant::WhileDo(_, _) | ASTInstrVariant::ForLoop { .. } | ASTInstrVariant::ForInto { .. } => { - ASTType::Nil - } - - ASTInstrVariant::Return(expr) => expr.get_type(ctx), - ASTInstrVariant::Block(block) => get_instruction_slice_type(ctx, block), - ASTInstrVariant::Cond { if_blocks, else_block } => { - let mut types = else_block - .as_ref() - .map(|i| i.get_type(ctx)) - .into_iter() - .chain(if_blocks.iter().map(|(_, i)| i.get_type(ctx))); - types - .next() - .and_then(|ty| types.all(|other| ty.eq(&other)).then_some(ty)) - .unwrap_or_default() - } - - ASTInstrVariant::MethodInvok(_, _, _) | ASTInstrVariant::FuncCall(_, _) => todo!("implement clozures"), - } - } -} - -impl Deref for ASTInstr { - type Target = ASTInstrVariant; - - fn deref(&self) -> &Self::Target { - &self.content - } -} - -impl DerefMut for ASTInstr { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.content - } -} - -impl VariantName for ASTInstr { - fn variant_name(&self) -> &'static str { - self.content.variant_name() - } -} - -impl VariantName for ASTInstrVariant { - fn variant_name(&self) -> &'static str { - match self { - ASTInstrVariant::Decl(_, _) => "variable declaration", - ASTInstrVariant::Assign(_, _) => "variable assignation", - ASTInstrVariant::FuncCall(_, _) => "function call", - ASTInstrVariant::MethodInvok(_, _, _) => "method invokation", - ASTInstrVariant::Block(_) => "instruction block", - ASTInstrVariant::WhileDo(_, _) => "while loop", - ASTInstrVariant::Cond { .. } => "conditionals", - ASTInstrVariant::ForLoop { step: Some(_), .. } => "for loop with step", - ASTInstrVariant::ForLoop { step: None, .. } => "for loop", - ASTInstrVariant::ForInto { .. } => "for into loop", - ASTInstrVariant::Noop => "noop", - ASTInstrVariant::Break => "break statement", - ASTInstrVariant::Continue => "continue statement", - ASTInstrVariant::Return(_) => "return statement", - } - } -} diff --git a/src/Rust/vvs_lang/src/ast/maybe_const_expr.rs b/src/Rust/vvs_lang/src/ast/maybe_const_expr.rs deleted file mode 100644 index d0e05e45..00000000 --- a/src/Rust/vvs_lang/src/ast/maybe_const_expr.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::ast::*; -use std::ops::Deref; - -/// Trait for objects that are typed and may be values known at compile time. -/// -/// As long a a type can be dereferenced into a [MaybeConstExpr] thing, it is [MaybeConstExpr]. -pub trait MaybeConstExpr: Typed { - /// Tells whether the thing is a constant expression or not. - fn is_const_expr(&self) -> bool; - - /// Evaluate the expression, if it is indeed a constant expression then returns the result, - /// otherwise return [None]. - fn eval_const_expr(&self) -> Option<ASTConst>; -} - -impl<S: MaybeConstExpr, T: Deref<Target = S>> MaybeConstExpr for T { - #[inline] - fn is_const_expr(&self) -> bool { - self.deref().is_const_expr() - } - - #[inline] - fn eval_const_expr(&self) -> Option<ASTConst> { - self.deref().eval_const_expr() - } -} diff --git a/src/Rust/vvs_lang/src/ast/mod.rs b/src/Rust/vvs_lang/src/ast/mod.rs deleted file mode 100644 index 8da5d32a..00000000 --- a/src/Rust/vvs_lang/src/ast/mod.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! Module used to store all the definition of the AST used for parsing VVS/VVL files. - -mod constant; -mod expression; -mod extension; -mod function; -mod identifier; -mod import; -mod instruction; -mod maybe_const_expr; -mod module; -mod options; -mod pattern; -mod program; -mod span; -mod string; -mod type_context; -mod typed; -mod types; -mod variable; -mod variant; -mod visibility; - -use hashbrown::HashMap; -use std::rc::Rc; - -pub use self::{ - constant::*, expression::*, extension::*, function::*, identifier::*, import::*, instruction::*, - maybe_const_expr::*, module::*, options::*, pattern::*, program::*, span::*, string::*, type_context::*, typed::*, - types::*, variable::*, variant::*, visibility::*, -}; - -pub type ASTString = Rc<str>; -pub type ASTFloating = f32; -pub type ASTInteger = i32; -pub type ASTTable<T> = HashMap<ASTString, T>; - -pub fn write_ast_table<T: std::fmt::Display>(f: &mut std::fmt::Formatter<'_>, table: &ASTTable<T>) -> std::fmt::Result { - f.write_str("{")?; - if let Some((key, value)) = table.iter().next() { - write!(f, " {key} = {value}")?; - } - for (key, value) in table.iter().skip(1) { - write!(f, ", {key} = {value}")?; - } - f.write_str(" }") -} - -pub fn write_ast_tuple<T: std::fmt::Display>(f: &mut std::fmt::Formatter<'_>, tuple: &[T]) -> std::fmt::Result { - f.write_str("(")?; - if let Some(value) = tuple.iter().next() { - write!(f, " {value}")?; - } - for value in tuple.iter().skip(1) { - write!(f, ", {value}")?; - } - f.write_str(" )") -} - -/// Trait that allows to get a variant name out of an enum or a struct containing an enum. -pub(crate) trait VariantName { - /// Get the name out of the value. - fn variant_name(&self) -> &'static str; -} diff --git a/src/Rust/vvs_lang/src/ast/module.rs b/src/Rust/vvs_lang/src/ast/module.rs deleted file mode 100644 index d7eba73a..00000000 --- a/src/Rust/vvs_lang/src/ast/module.rs +++ /dev/null @@ -1,201 +0,0 @@ -use crate::ast::*; -use hashbrown::{HashMap, HashSet}; -use std::{ - cell::UnsafeCell, - rc::{Rc, Weak}, -}; - -/// A VVL module of a Vivy Script -#[derive(Debug, Clone)] -pub struct ASTModule { - /// The name of the module, should be a valid identifier. - name: ASTString, - - /// Checks if we want to put it into the type context or not... - variants: HashMap<ASTString, ASTVariantRules>, - - functions: HashMap<ASTString, (ASTVisibility, ASTFunction)>, - jobs: HashMap<ASTString, (ASTVisibility, ASTFunction)>, - consts: HashMap<ASTString, (ASTVisibility, ASTVar, ASTConst)>, - options: HashMap<ASTString, (ASTVar, ASTConst)>, - imports: HashMap<ASTString, (ASTSpan, ASTModulePtr)>, - - /// Contains all the symbols available in the module, to be sure that options, constants, - /// functions and jobs doesn't enter in collision. - symbols: HashSet<ASTString>, - - /// Caching for strings, identified by their hash and reuse the same memory location to reduce - /// memory footprint. Use the .finish function from the hasher to get the key. - strings: ASTStringCacheHandle, - - /// Type context intern to the module. This is self-referencing. Under the hood we use a weak - /// pointer and a lazy initialize thingy for the type context. - tyctx: ASTTypeContext, -} - -pub type ASTModulePtr = Rc<UnsafeCell<ASTModule>>; -pub type ASTModuleWeakPtr = Weak<UnsafeCell<ASTModule>>; - -impl ASTModule { - /// Create a new empty module. - #[inline] - pub fn new(name: ASTString, strings: ASTStringCacheHandle) -> ASTModulePtr { - Rc::new_cyclic(|ptr| { - let colors_rules = ASTVariantRules::new_colors(strings.clone()); - let movement_rules = ASTVariantRules::new_movements(strings.clone()); - UnsafeCell::new(Self { - name, - strings, - tyctx: ASTTypeContext::new(ptr.clone()), - variants: HashMap::from_iter([ - (colors_rules.name(), colors_rules), - (movement_rules.name(), movement_rules), - ]), - symbols: Default::default(), - functions: Default::default(), - jobs: Default::default(), - consts: Default::default(), - options: Default::default(), - imports: Default::default(), - }) - }) - } - - /// Get all the variant rules. - #[inline] - pub fn all_variant_rules(&self) -> &HashMap<ASTString, ASTVariantRules> { - &self.variants - } - - /// Get a set of rule for a specified variant if it exists in a mutable way. We only expose - /// this function to this crate because it's the only place where we want to be able to mutate - /// the module. - #[inline] - pub(crate) fn variant_rules_mut(&mut self, rule: impl AsRef<str>) -> Option<&mut ASTVariantRules> { - self.variants.get_mut(rule.as_ref()) - } - - /// Get the type context in a constant way. - #[inline] - pub fn tyctx(&self) -> &ASTTypeContext { - &self.tyctx - } - - /// Get the type context in a mutable way. Only possible if we have a mutable reference to the - /// module anyway. If there are multiple strong references to the module it won't be correct... - #[inline] - pub fn tyctx_mut(&mut self) -> &mut ASTTypeContext { - &mut self.tyctx - } - - /// Get the name of the module. - #[inline] - pub fn name(&self) -> ASTString { - self.name.clone() - } - - /// Get a specific option if it exists. - #[inline] - pub fn option(&self, option: impl AsRef<str>) -> Option<&(ASTVar, ASTConst)> { - self.options.get(option.as_ref()) - } - - /// Get a job by its name, returns [None] if the job was not defined. - #[inline] - pub fn job(&self, name: impl AsRef<str>) -> Option<&ASTFunction> { - self.jobs.get(name.as_ref()).map(|(_, job)| job) - } - - /// Get a function by its name, returns [None] if the function was not defined. - #[inline] - pub fn function(&self, name: impl AsRef<str>) -> Option<&ASTFunction> { - self.functions.get(name.as_ref()).map(|(_, function)| function) - } - - /// Get all the options... All the options are public by design so no visibility rule is - /// necessary to query them. - #[inline] - pub fn options(&self) -> impl Iterator<Item = (&ASTVar, &ASTConst)> { - self.options.values().map(|(a, b): &(_, _)| (a, b)) - } - - /// Get all the constants with the specified visibility rule. - #[inline] - pub fn consts(&self, rule: ASTVisibilityRule) -> impl Iterator<Item = (&ASTVar, &ASTConst)> { - self.consts - .values() - .filter_map(move |(v, var, value)| (rule.allows(*v)).then_some((var, value))) - } - - /// Get all the functions from the module with the specified visibility rule. - #[inline] - pub fn functions(&self, rule: ASTVisibilityRule) -> impl Iterator<Item = (ASTString, &ASTFunction)> { - self.functions - .values() - .filter_map(move |(v, f)| rule.allows(*v).then_some((f.name.clone(), f))) - } - - /// Get all the jobs from the module with the specified visibility rule. - #[inline] - pub fn jobs(&self, rule: ASTVisibilityRule) -> impl Iterator<Item = (ASTString, &ASTFunction)> { - self.jobs - .values() - .filter_map(move |(v, f)| rule.allows(*v).then_some((f.name.clone(), f))) - } - - /// Get all the callable items from the module with the specified visibility rule. This - /// includes jobs and functions. - pub(crate) fn callables(&self, rule: ASTVisibilityRule) -> impl Iterator<Item = (ASTString, &ASTFunction)> { - self.functions(rule).chain(self.jobs(rule)) - } - - /// Get all the jobs from a module in a mutable way, with the specified visibility rule. - pub(crate) fn jobs_mut(&mut self, rule: ASTVisibilityRule) -> impl Iterator<Item = (ASTString, &mut ASTFunction)> { - self.jobs - .values_mut() - .filter_map(move |(v, f)| rule.allows(*v).then_some((f.name.clone(), f))) - } - - /// Get all the fonctions from a module in a mutable way, with the specified visibility rule. - pub(crate) fn functions_mut( - &mut self, - rule: ASTVisibilityRule, - ) -> impl Iterator<Item = (ASTString, &mut ASTFunction)> { - self.functions - .values_mut() - .filter_map(move |(v, f)| rule.allows(*v).then_some((f.name.clone(), f))) - } - - /// Get the imported modules, required by the current module. - #[inline] - pub fn imports(&self) -> impl Iterator<Item = &ASTModule> + '_ { - self.imports.iter().map(|(_, (_, module))| ASTModule::get(module)) - } - - /// Get the import location of a module, for debug purpose. - #[inline] - pub fn get_import_location(&self, import: &ASTString) -> Option<ASTSpan> { - self.imports - .iter() - .find_map(|(module, (span, _))| import.eq(module).then_some(*span)) - } - - /// Get a handle to the string cache. - #[inline] - pub fn strings(&self) -> &ASTStringCacheHandle { - &self.strings - } - - /// Get a const reference to the module. As long as you don't use the [ASTModule::get_mut] - /// function, this one should be Ok. - #[inline] - pub(crate) fn get(this: &ASTModulePtr) -> &ASTModule { - unsafe { &*this.as_ref().get() } - } - - /// Get a mutable reference to the module... Unsafe! - #[inline] - pub(crate) fn get_mut(this: &ASTModulePtr) -> *mut ASTModule { - this.as_ref().get() - } -} diff --git a/src/Rust/vvs_lang/src/ast/options.rs b/src/Rust/vvs_lang/src/ast/options.rs deleted file mode 100644 index 13d45a6f..00000000 --- a/src/Rust/vvs_lang/src/ast/options.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::ast::*; -use anyhow::{bail, Context}; -use hashbrown::HashMap; - -/// Options for a program, represent the thing that is red from a file. -pub struct OptionTable { - modules: HashMap<ASTString, ASTTable<ASTConst>>, -} - -impl OptionTable { - pub fn new(cache: &ASTStringCacheHandle, values: toml::Table) -> anyhow::Result<Self> { - let modules = HashMap::with_capacity(values.len()); - let modules = values.into_iter().try_fold(modules, |mut modules, (name, values)| { - use hashbrown::hash_map::EntryRef::*; - let toml::Value::Table(values) = values else { - bail!("expected a table for `{name}`") - }; - let table = modules - .entry(cache.get(&name)) - .or_insert(ASTTable::with_capacity(values.len())); - values - .into_iter() - .try_for_each(|(option, value)| match table.entry_ref(option.as_str()) { - Occupied(_) => bail!("redefinition of option `{option}` in module `{name}`"), - Vacant(entry) => { - entry.insert(ASTConst::from_toml_value(cache, value)?); - Ok(()) - } - })?; - if table.is_empty() { - modules.remove(name.as_str()); - } - Ok(modules) - }); - Ok(Self { modules: modules.context("invalid toml file")? }) - } -} - -impl Iterator for OptionTable { - type Item = ((ASTString, ASTString), ASTConst); - - fn next(&mut self) -> Option<Self::Item> { - let Self { modules } = self; - let module = modules.keys().next()?.clone(); - - let values = match modules.get_mut(&module) { - Some(values) => values, - None => { - modules.remove(&module); - return None; - } - }; - - let (option, value) = match values.keys().next().cloned() { - Some(option) => values.remove_entry(&option)?, - None => { - modules.remove(&module); - return None; - } - }; - - Some(((module, option), value)) - } -} diff --git a/src/Rust/vvs_lang/src/ast/pattern.rs b/src/Rust/vvs_lang/src/ast/pattern.rs deleted file mode 100644 index 5b3707d4..00000000 --- a/src/Rust/vvs_lang/src/ast/pattern.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! In this file we declare things needed to do the pattern matching or boolean check for -//! conditionals. -//! -//! Note that we can only pattern match variants. - -use crate::ast::*; - -/// A condition, two expression must be equal, or should we try to match a thing. -#[derive(Debug, PartialEq, Clone)] -pub enum ASTCondition { - /// Usual if check. - BooleanTrue(ASTExpr), - - /// Does the expression can be pattern-matched or not. This is only for variants. - BindVariant { as_type: ASTType, variant: ASTString, pattern: Vec<ASTPatternElement>, source: ASTVar }, - - /// Tells whever a variable is coercible into a type or not. - CoercibleInto { new: ASTVar, as_type: ASTType, source: ASTVar }, -} - -/// When we match a pattern, we can do conditionals on the value of elements, or we can bind -/// variables that will be visible in the if/elseif/if-let statement. -#[derive(Debug, PartialEq, Clone)] -pub enum ASTPatternElement { - /// Must be equal to this thing. - Const(ASTConst), - - /// Expose this field of the struct in the block if the struct matched. - Var(ASTVar), -} - -impl ASTPatternElement { - pub fn span(&self) -> Option<ASTSpan> { - match self { - ASTPatternElement::Const(_) => None, - ASTPatternElement::Var(var) => Some(var.span()), - } - } -} diff --git a/src/Rust/vvs_lang/src/ast/program.rs b/src/Rust/vvs_lang/src/ast/program.rs deleted file mode 100644 index 67088e1e..00000000 --- a/src/Rust/vvs_lang/src/ast/program.rs +++ /dev/null @@ -1,140 +0,0 @@ -use crate::ast::*; -use hashbrown::HashMap; - -/// The first element of the tuple is the destination variable, the second is the tuple to describe -/// the job picked from a module, the last is the list of variables to use as input for this job. -pub type ProgramOperation = (ASTSpan, ASTString, (ASTString, ASTString), Option<Vec<ASTConst>>, Vec<ASTString>); - -/// The main VVS file/program. -#[derive(Debug)] -pub struct ASTProgram { - /// The list of all the modules used in the program, with their import location. - modules: HashMap<ASTString, (ASTSpan, ASTModulePtr)>, - - /// The setted options, from the VVS file. - setted: Vec<((ASTString, ASTString), ASTConst)>, - - /// The operations to do in order. - operations: Vec<ProgramOperation>, - - /// What the program writes. - writes: (ASTSpan, Vec<ASTString>), - - /// Initial variable. - initial_var: ASTString, - - /// Caching for strings, identified by theyr hash and reuse the same memory location to reduce - /// memory footprint. Use the .finish function from the hasher to get the key. - strings: ASTStringCacheHandle, -} - -impl ASTProgram { - pub fn new(strings: ASTStringCacheHandle) -> Self { - Self { - initial_var: strings.get("INIT"), - setted: Default::default(), - writes: Default::default(), - modules: Default::default(), - operations: Default::default(), - strings, - } - } - - /// Iterate over all the modules in the program. - #[inline] - pub fn modules(&self) -> impl Iterator<Item = &ASTModule> + '_ { - self.modules.values().map(|(_, module)| ASTModule::get(module)) - } - - /// Get a handle to the string cache. - #[inline] - pub fn strings(&self) -> &ASTStringCacheHandle { - &self.strings - } - - /// Get a const reference to a module. - pub fn module(&self, name: impl AsRef<str>) -> Option<&ASTModule> { - self.modules - .get(name.as_ref()) - .map(|(_, module)| ASTModule::get(module)) - } - - /// Get the pointer to a module. Returns [None] if the module was not present in the program. - /// - /// # Safety - /// With this function we create the possibility to make multiple mutable references to the - /// same module down the line, so we mark it as an unsafe operation for now... - pub(crate) unsafe fn module_ptr(&self, name: impl AsRef<str>) -> Option<ASTModulePtr> { - self.modules.get(name.as_ref()).map(|(_, ptr)| ptr.clone()) - } - - /// Get all the available options for this program, with their default value. - pub fn available_options(&self) -> impl Iterator<Item = (ASTString, &ASTVar, &ASTConst)> { - self.modules() - .flat_map(|module| module.options().map(|(var, def)| (module.name(), var, def))) - } - - /// Get all the setted options from the program. - #[inline] - pub fn setted_options(&self) -> &[((ASTString, ASTString), ASTConst)] { - self.setted.as_ref() - } - - /// Get the value for an option. For the resolution: we first take the value from the setted - /// list before the default value. Note that if we set an option that was never declared we - /// don't return it here, it should have raised a warning and should not be used in the code - /// anyway. - pub fn option(&self, module: &ASTString, name: &ASTString) -> Option<ASTConst> { - let default = self - .module(module)? - .options() - .find_map(|(var, val)| var.name().eq(name).then_some(val))? - .clone(); - let setted = self - .setted - .iter() - .find_map(|((mm, var), val)| (*module == *mm && *name == *var).then_some(val)); - Some(setted.cloned().unwrap_or(default)) - } - - /// Get the operations and the variables to write. Note that no check is done here. - pub fn as_operations(&self) -> (ASTString, Vec<ProgramOperation>, Vec<ASTString>) { - (self.initial_var.clone(), self.operations.clone(), self.writes.1.clone()) - } - - #[inline] - pub(crate) fn writes_span(&self) -> ASTSpan { - self.writes.0 - } - - /// Set an option in the [Program], if the option was already set or was not declared in a - /// [Module] we raise a warning. - pub fn set_option(mut self, module: ASTString, name: ASTString, value: ASTConst) -> Self { - if self - .module(&module) - .map(|module| module.options().any(|(var, _)| var.name().eq(&name))) - .unwrap_or_default() - { - log::warn!(target: "cc", ";set option {module}.{name} which was not declared"); - } else if self.setted.iter().any(|((mm, nn), _)| *mm == module && name == *nn) { - log::warn!(target: "cc", ";re-set option {module}.{name}"); - } - self.setted.push(((module, name), value)); - self - } - - /// Import a module into the program. - #[inline] - pub(crate) fn import_module(mut self, name: ASTString, span: ASTSpan, module: ASTModulePtr) -> Self { - if let Some((_, module)) = self.modules.insert(name, (span, module)) { - log::warn!(target: "cc", ";re-import of module `{}`", ASTModule::get(&module).name()) - } - self - } - - /// Use options from a toml file to set things in the program and simplify a bit said - /// program... - pub fn with_options(self, _options: OptionTable) -> Self { - self - } -} diff --git a/src/Rust/vvs_lang/src/ast/span.rs b/src/Rust/vvs_lang/src/ast/span.rs deleted file mode 100644 index 512ad50d..00000000 --- a/src/Rust/vvs_lang/src/ast/span.rs +++ /dev/null @@ -1,76 +0,0 @@ -/// A span, without the borrowed string. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] -pub struct ASTSpan { - line: u64, - column: u64, - offset: u64, -} - -impl std::fmt::Display for ASTSpan { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Self { line, column, .. } = &self; - write!(f, "+{line}:{column}") - } -} - -impl ASTSpan { - /// Create the span out of a line and column and an offset. The lines and - /// columns begeins at 1. The offset begins at 0. - #[inline] - pub fn new(line: u64, column: u64, offset: u64) -> Self { - assert!(line >= 1); - assert!(column >= 1); - Self { line, column, offset } - } - - /// Get the column of the span. The column starts at 1 - #[inline] - pub fn line(&self) -> u64 { - self.line - } - - /// Get the column of the span. The column starts at 1 - #[inline] - pub fn column(&self) -> u64 { - self.column - } - - /// Get the offset of the span. - #[inline] - pub fn offset(&self) -> u64 { - self.offset - } - - /// Merge two spans. For now we just take the first one (the one with the - /// lesser offset, i.e. the minimal one). In the future we will update the - /// length field (when it's added to the structure...) - #[inline] - pub fn merge(s1: Self, s2: Self) -> Self { - if PartialOrd::gt(&s1, &s2) { - s2 - } else { - s1 - } - } -} - -impl From<&ASTSpan> for ASTSpan { - #[inline] - fn from(value: &ASTSpan) -> Self { - *value - } -} - -impl From<&mut ASTSpan> for ASTSpan { - #[inline] - fn from(value: &mut ASTSpan) -> Self { - *value - } -} - -impl PartialOrd for ASTSpan { - #[inline] - fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { - PartialOrd::partial_cmp(&self.offset, &other.offset) - } -} diff --git a/src/Rust/vvs_lang/src/ast/string.rs b/src/Rust/vvs_lang/src/ast/string.rs deleted file mode 100644 index acec81a7..00000000 --- a/src/Rust/vvs_lang/src/ast/string.rs +++ /dev/null @@ -1,28 +0,0 @@ -use super::ASTString; -use hashbrown::HashMap; -use std::{cell::RefCell, collections::hash_map::DefaultHasher, hash::Hasher, rc::Rc}; - -/// Used when iterating into the module with mut access, we might need to access the string -/// cache... -#[derive(Debug, Clone, Default)] -pub struct ASTStringCacheHandle { - strings: Rc<RefCell<HashMap<u64, ASTString>>>, -} - -impl ASTStringCacheHandle { - /// Get the id of a string. - fn get_id(str: impl AsRef<str>) -> u64 { - let mut hasher = DefaultHasher::new(); - hasher.write(str.as_ref().as_bytes()); - hasher.finish() - } - - /// Get or create a string in the cache. - pub fn get(&self, str: impl AsRef<str>) -> ASTString { - self.strings - .borrow_mut() - .entry(Self::get_id(str.as_ref())) - .or_insert_with(|| ASTString::from(str.as_ref())) - .clone() - } -} diff --git a/src/Rust/vvs_lang/src/ast/type_context.rs b/src/Rust/vvs_lang/src/ast/type_context.rs deleted file mode 100644 index 829dcc0b..00000000 --- a/src/Rust/vvs_lang/src/ast/type_context.rs +++ /dev/null @@ -1,180 +0,0 @@ -use crate::ast::*; -use core::cell::OnceCell; - -/// Match any item, private or public. -const RULE_ANY: ASTVisibilityRule = ASTVisibilityRule::AtLeast(ASTVisibility::Private); -const RULE_PUB: ASTVisibilityRule = ASTVisibilityRule::Only(ASTVisibility::Public); - -/// The ast context for storing types. Any use of the module must be delayed because when we create -/// the type context for a module, we do it with a cyclic thingy, so the [Rc] is not already -/// constructed (no strong reference), so all we have is a [std::rc::Weak]. -#[derive(Debug, Clone)] -pub struct ASTTypeContext { - module: ASTModuleWeakPtr, - content: OnceCell<ASTTypeContextContent>, -} - -/// The content of the [ASTTypeContext] must be lazyly initialized because of cyclic references -/// with the module and it's type context. -type ASTTypeContextContent = ( - HashMap<ASTString, ASTType>, // Variables - HashMap<ASTString, ASTVariantRules>, // Variants - ASTStringCacheHandle, // String cache - Vec<ASTString>, // Name of imported modules -); - -impl ASTTypeContext { - fn init_content(&self) -> ASTTypeContextContent { - let ptr = self.module.upgrade().expect("no more strong references to module..."); - let module = ASTModule::get(&ptr); - - // We need to import things from the current module and from the imported modules. But for - // imported modules we must prepend the constant/option/callable name by the module name - // with a dot, to call those things like in lua. - let modules = [(None, module)] - .into_iter() - .chain(module.imports().map(|module| (Some(module.name()), module))); - - let scope = modules.flat_map(|(mname, module)| { - let rule = if mname.is_some() { RULE_PUB } else { RULE_ANY }; - let (mname1, mname2, mname3) = (mname.clone(), mname.clone(), mname); - - let options = module.options().map(move |(var, _)| { - let name = mname1 - .as_ref() - .map(|n| module.strings().get(format!("{n}.{}", var.name()))) - .unwrap_or_else(|| var.name()); - (name, var.get_specified_type_or_nil()) - }); - - let consts = module.consts(rule).map(move |(var, _)| { - let name = mname2 - .as_ref() - .map(|n| module.strings().get(format!("{n}.{}", var.name()))) - .unwrap_or_else(|| var.name()); - (name, var.get_specified_type_or_nil()) - }); - - let funcs = module.callables(rule).map(move |(name, func)| { - let name = mname3 - .as_ref() - .map(|n| module.strings().get(format!("{n}.{name}"))) - .unwrap_or(name); - - let arguments = func.arguments.iter().map(|arg| arg.get_specified_type_or_nil()); - let func_type = ASTType::Function(arguments.collect(), Box::new(func.returns.clone())); - - (name, func_type) - }); - - options.chain(consts).chain(funcs) - }); - - let strings = module.strings().clone(); - let variants = module.all_variant_rules().clone(); - let imports = module.imports().map(|module| module.name()).collect(); - (scope.collect(), variants, strings, imports) - } - - fn content(&self) -> &ASTTypeContextContent { - self.content.get_or_init(|| self.init_content()) - } - - fn content_mut(&mut self) -> &mut ASTTypeContextContent { - self.content(); - self.content.get_mut().expect("should be initialized") - } - - #[inline] - pub fn new(module: ASTModuleWeakPtr) -> Self { - Self { module, content: OnceCell::new() } - } - - /// Try to map rules to a pattern. Returns the matching [Some] variables with their destination - /// types or [None] if the matching is not possible to do. Because all rules must be kown at - /// compile time we can now statically if a variant mapping is correct or not! - pub fn variant_try_map_rule( - &self, - rule: impl AsRef<str>, - to: &[ASTPatternElement], - ) -> Option<Vec<(ASTVar, ASTType)>> { - // Get the rule-set. - let rule = self.content().1.get(rule.as_ref())?; - - // The first thing must be a constant string or identifier with the name of the sub-rule. - use ASTPatternElement::*; - let (rule, content) = match to { - [Const(ASTConst::String(sub_rule)), content @ ..] => (rule.rule(sub_rule)?, content), - [Var(var), content @ ..] => { - debug_assert!( - var.get_specified_type().is_none(), - "got a specified type for `{var}`, expected just an identifier" - ); - (rule.rule(var.name())?, content) - } - _ => return None, - }; - - // Be sure that we capture everything in that rule. We don't have the `..` like in rust... - if rule.len() != content.len() { - return None; - } - - Some( - // Do the matching for the sub-rule. - rule.iter().zip(content).map(|(rule, pat)| match pat { - // If a constant is provided we must ensure that the types are compatible! - Const(pat) => rule.coercible_to(&pat.get_const_type()).then_some(None).ok_or(()), - // If a variable is declared then we will return it. If the variable has a - // specified type, we must ensure that the element in the variant can be coerced - // into this specified type. - Var(var) => match var.get_specified_type() { - Some(ty) => rule.coercible_to(ty).then(|| Some((var.clone(), rule.clone()))).ok_or(()), - None => Ok(Some((var.clone(), rule.clone()))), - }, - }) - // Get ride of any errors because of impossible mappings. - .collect::<Result<Vec<Option<(ASTVar, ASTType)>>, ()>>().ok()? - // We collect the successful bindings into a vector, we don't care when a constant - // mapped with an element of the variant. - .into_iter().flatten().collect(), - ) - } - - /// Get a set of rule for a specified variant if it exists. - pub fn variant_complies_with_rule<T: Typed>(&self, rule: impl AsRef<str>, variant: &ASTVariant<T>) -> bool { - let rules = self.content().1.get(rule.as_ref()); - rules.map_or(false, |rules| rules.complies(self, variant)) - } - - /// Create a new context, don't clone as we want to do other things... - #[inline] - pub fn for_scope(&self) -> Self { - self.clone() - } - - /// Get the type of a variable by its name. - pub fn get(&self, name: &ASTVar) -> ASTType { - let (scope, ..) = self.content(); - scope.get(name.name().as_ref()).unwrap().clone() - } - - /// Declare a new variable. Returns an error on redefinition. - pub fn declare(&mut self, var: ASTVar, ty: ASTType) { - if var.is_joker() { - log::debug!("can't declare the joker as a variable at {}", var.span()); - } else if self.content_mut().0.insert(var.name(), ty).is_some() { - panic!() - } - } - - pub fn has_import(&self, import: impl AsRef<str>) -> bool { - self.content().3.iter().any(|scoped| scoped.as_ref() == import.as_ref()) - } - - /// Get the string cache out of the module pointer stored in the context. - #[inline] - pub fn strings(&self) -> &ASTStringCacheHandle { - &self.content().2 - } -} diff --git a/src/Rust/vvs_lang/src/ast/typed.rs b/src/Rust/vvs_lang/src/ast/typed.rs deleted file mode 100644 index d2f8b537..00000000 --- a/src/Rust/vvs_lang/src/ast/typed.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::ast::*; -use std::ops::Deref; - -/// Trait for objects that are types. -/// -/// As long a a type can be dereferenced into a [Typed] thing, it is [Typed]. -pub trait Typed { - /// Get the type of the object. - fn get_type(&self, ctx: &ASTTypeContext) -> ASTType; -} - -impl<S: Typed, T: Deref<Target = S>> Typed for T { - #[inline] - fn get_type(&self, ctx: &ASTTypeContext) -> ASTType { - self.deref().get_type(ctx) - } -} diff --git a/src/Rust/vvs_lang/src/ast/types.rs b/src/Rust/vvs_lang/src/ast/types.rs deleted file mode 100644 index cb231531..00000000 --- a/src/Rust/vvs_lang/src/ast/types.rs +++ /dev/null @@ -1,226 +0,0 @@ -use crate::ast::*; - -/// Types. Every object that can be manipulated by the user has a type. We have base types and -/// collection types and special ASS types. -/// -/// Some coercion is possible between collection types... -#[derive(Debug, PartialEq, Eq, Clone, Default)] -pub enum ASTType { - /// Null, unit, nullptr, etc. This is the default type - #[default] - Nil, - - /// An integer - Integer, - - /// A floating point number - Floating, - - /// A string. - String, - - /// A boolean, true/false. - Boolean, - - /// A color - Color, - - /// A movement of a syllabe or line. - Movement, - - /// A variant, can be a color or a movement. - Variant, - - /// A tuple - Tuple(Vec<ASTType>), - - /// Special ASS type, a syllabe. A simple syllabe. - Syllabe, - - /// Special ASS type, a line. It's a collection of [ASTType::Syllabe] with extra steps, can - /// iterate over it. - Line, - - /// A table where the content is not really defined. Because we don't know the keys we can't - /// iterate over it. - AnyTable, - - /// A table where everything inside is defined, a struct. We can't iterate over it but we - /// statically know the keys of the table. - Table(ASTTable<ASTType>), - - /// A uniform table, where all the values has the same types, like a sequence but which string - /// keys. - UniformTable(Box<ASTType>), - - /// A sequence where the content is not really defined, like the any table but with sequences. - AnySequence, - - /// A sequence where every value has the same type. We can iterate over it, like a uniform - /// table, but with integer and continuous keys. - Sequence(Box<ASTType>), - - /// A function type, needs the arguments and the return value. - Function(Vec<ASTType>, Box<ASTType>), - - /// A type that is not known at compile time, the user must check its type with a switch case. - Any, -} - -impl ASTType { - /// Try to see if a type can be pattern matched and if it's the case, returns [Some] bindings - /// to add them to the type context, otherwise return [None]. - pub fn try_match(&self, mctx: &ASTTypeContext, to: &[ASTPatternElement]) -> Option<Vec<(ASTVar, ASTType)>> { - self.get_variant_rule() - .and_then(|rule| mctx.variant_try_map_rule(rule, to)) - } - - /// Tells wether the type is buildable in a const context or not. - #[inline] - pub fn is_const_constructible(&self) -> bool { - matches!(self, ASTType::Nil | ASTType::Tuple(_) | ASTType::Any) - || self.is_numeric() - || self.is_table() - || self.is_sequence() - } - - /// Tells wether the type is numeric or not. - #[inline] - pub fn is_numeric(&self) -> bool { - matches!(self, ASTType::Boolean | ASTType::Integer | ASTType::Floating) - } - - /// Tells whether the type is any sort of sequence or not. - #[inline] - pub fn is_sequence(&self) -> bool { - matches!(self, ASTType::Sequence(_) | ASTType::AnySequence) - } - - /// Tells whether the type is any sort of table or not. - #[inline] - pub fn is_table(&self) -> bool { - matches!(self, ASTType::UniformTable(_) | ASTType::Table(_) | ASTType::AnyTable) - } - - /// Tells whether the type is any sort of variant or not. - #[inline] - pub fn is_variant(&self) -> bool { - matches!(self, ASTType::Variant | ASTType::Color | ASTType::Movement) - } - - /// Get the name of the rules to follow for this variant if it's really a variant. - #[inline] - pub fn get_variant_rule(&self) -> Option<&'static str> { - match self { - ASTType::Color => Some("color"), - ASTType::Movement => Some("movement"), - _ => None, - } - } - - /// Tells whether a type is trivially copiable, i.e. this type won't be managed. - #[inline] - pub fn is_trivialy_copiable(&self) -> bool { - use ASTType::*; - matches!(self, Nil | Integer | Floating | Boolean | Tuple(_)) - } - - pub fn coercible_to(&self, to: &ASTType) -> bool { - use ASTType::*; - match (self, to) { - // Equals... - (x, y) - if (x == y) - || (x.is_table() && matches!(y, AnyTable)) - || (x.is_sequence() && matches!(y, AnySequence)) - || (x.is_variant() && y.is_variant()) => - { - true - } - - // Nil things - (_, Nil) | (Nil, Integer | Floating | Boolean) => true, - (Nil, to) => to.is_table() || to.is_sequence(), - - // Integer things - (Integer, Floating | Boolean) | (Boolean, Integer | Floating) | (Floating, Integer) => true, - - // Sequence / ASS things - (String, Sequence(ty)) | (Sequence(ty), String) => matches!(&**ty, String), - (Sequence(x), Line) | (Line, Sequence(x)) => matches!(&**x, Syllabe), - (Syllabe, Sequence(x)) => matches!(&**x, String), - - // Table / Sequence things - (from, AnyTable) => from.is_sequence() || from.is_table() || from.is_variant(), - (Sequence(to), UniformTable(from)) | (UniformTable(from), Sequence(to)) => from.coercible_to(to), - (Table(table), Sequence(ty)) => { - let integer_keys = table.keys().all(|key| key.parse::<i64>().is_ok()); - let mut uniform_values = table.values().collect::<Vec<_>>(); - let uniform_values = uniform_values - .pop() - .map(|ty| uniform_values.into_iter().all(|a| ty.eq(a)).then_some(ty)); - integer_keys && matches!(uniform_values, Some(Some(table_ty)) if table_ty.eq(ty)) - } - - _ => false, - } - } -} - -impl Typed for ASTType { - #[inline] - fn get_type(&self, _: &ASTTypeContext) -> ASTType { - self.clone() - } -} - -impl std::fmt::Display for ASTType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ASTType::Nil => f.write_str("nil"), - ASTType::Any => f.write_str("any"), - - ASTType::Integer => f.write_str("integer"), - ASTType::Floating => f.write_str("float"), - ASTType::String => f.write_str("string"), - ASTType::Boolean => f.write_str("boolean"), - ASTType::Color => f.write_str("color"), - ASTType::Variant => f.write_str("variant"), - ASTType::Movement => f.write_str("movement"), - - ASTType::Function(args, ret) => { - f.write_str("function (")?; - if let Some(ty) = args.iter().next() { - write!(f, " {ty}")?; - } - for ty in args.iter().skip(1) { - write!(f, ", {ty}")?; - } - write!(f, " ) -> {ret}") - } - - ASTType::Tuple(tuple) => { - f.write_str("(")?; - if let Some(ty) = tuple.iter().next() { - write!(f, " {ty}")?; - } - for ty in tuple.iter().skip(1) { - write!(f, ", {ty}")?; - } - f.write_str(" )") - } - - ASTType::Line => f.write_str("line"), - ASTType::Syllabe => f.write_str("syllabe"), - ASTType::AnySequence => f.write_str("sequence { any }"), - ASTType::Sequence(ty) => write!(f, "sequence {{ {ty} }}"), - - ASTType::AnyTable => f.write_str("table { any }"), - ASTType::UniformTable(inner) => write!(f, "table {{ {inner} }}"), - ASTType::Table(table) => { - f.write_str("table ")?; - write_ast_table(f, table) - } - } - } -} diff --git a/src/Rust/vvs_lang/src/ast/variable.rs b/src/Rust/vvs_lang/src/ast/variable.rs deleted file mode 100644 index 3cb53833..00000000 --- a/src/Rust/vvs_lang/src/ast/variable.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::ast::*; -use std::sync::atomic::{AtomicU64, Ordering}; - -/// Variable thing. Have a name, a where-it-is-defined span and optionally a type. Having to no -/// type means that the type was not already found. -/// -/// To get the type of a variable, it is either present in the context type hashmap, then we check -/// the specified type, if all failed we return the [ASTType::Nil] type. -#[derive(Debug, Clone)] -pub struct ASTVar(ASTSpan, ASTString, Option<ASTType>); - -#[derive(Debug, Clone)] -pub struct ASTVarBuilder<'a> { - span: ASTSpan, - cache: &'a ASTStringCacheHandle, - name: Option<ASTString>, - specified_ty: Option<ASTType>, -} - -impl<'a> ASTVarBuilder<'a> { - pub fn build(self) -> ASTVar { - let name = self.name.unwrap_or_else(|| { - static ANON: AtomicU64 = AtomicU64::new(0); - self.cache.get(format!("_{}", ANON.fetch_add(1, Ordering::SeqCst))) - }); - ASTVar(self.span, name, self.specified_ty) - } - - pub fn span(mut self, span: impl Into<ASTSpan>) -> Self { - self.span = span.into(); - self - } - - pub fn with_type(mut self, ty: ASTType) -> Self { - self.specified_ty = Some(ty); - self - } - - pub fn name_from(mut self, name: impl AsRef<str>) -> Self { - self.name = Some(self.cache.get(name.as_ref())); - self - } - - pub fn name(mut self, name: ASTString) -> Self { - self.name = Some(name); - self - } -} - -impl PartialEq for ASTVar { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.1 == other.1 && self.2 == other.2 - } -} - -impl ASTVar { - #[inline] - #[allow(clippy::new_ret_no_self)] - pub fn new(cache: &ASTStringCacheHandle) -> ASTVarBuilder { - ASTVarBuilder { span: Default::default(), cache, name: None, specified_ty: None } - } - - #[inline] - pub fn span(&self) -> ASTSpan { - self.0 - } - - /// Get the specified type if any. - #[inline] - pub fn get_specified_type(&self) -> Option<&ASTType> { - self.2.as_ref() - } - - /// Get the specified type. If no type is specified then we say that we return [ASTType::Nil]. - #[inline] - pub fn get_specified_type_or_nil(&self) -> ASTType { - self.get_specified_type().cloned().unwrap_or_default() - } - - /// Set the specified type. - #[inline] - pub fn set_specified_type(&mut self, ty: ASTType) { - self.2 = Some(ty); - } - - /// Get the name. - #[inline] - pub fn name(&self) -> ASTString { - self.1.clone() - } - - /// Tells wether the variable is the joker or not. We can assign into the joker, in this case - /// we yeet the result, but we can never read it. - pub fn is_joker(&self) -> bool { - self.1.as_ref().eq("_") - } -} - -impl From<&ASTVar> for ASTSpan { - #[inline] - fn from(value: &ASTVar) -> Self { - value.span() - } -} - -impl std::fmt::Display for ASTVar { - #[inline] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.1.as_ref()) - } -} - -impl Typed for ASTVar { - #[inline] - fn get_type(&self, ctx: &ASTTypeContext) -> ASTType { - ctx.get(self) - } -} diff --git a/src/Rust/vvs_lang/src/ast/variant.rs b/src/Rust/vvs_lang/src/ast/variant.rs deleted file mode 100644 index d6e6da7f..00000000 --- a/src/Rust/vvs_lang/src/ast/variant.rs +++ /dev/null @@ -1,192 +0,0 @@ -use crate::ast::*; - -/// A trait to describes types that are variants. Can be used to handle colors and movements in a -/// generic way. -/// -/// Represents a color in the AST. A color can be: -/// - `#(rgb 0, 0, 0)` for black -/// - `#(green)` for green -/// - etc... -/// -/// Represents a movement in the AST, a movement can be: -/// - `#[pos 500, 500]` -/// - `#[cmove 0, 0, 100, 50, 30]` -/// - etc... -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ASTVariant<T: Typed> { - pub(crate) variant: ASTString, - pub(crate) args: Vec<T>, -} - -pub(crate) struct ASTFmtColor<'a, T: Typed>(&'a ASTVariant<T>, &'a ASTTypeContext); -pub(crate) struct ASTFmtMovement<'a, T: Typed>(&'a ASTVariant<T>, &'a ASTTypeContext); - -impl<T: Typed> ASTVariant<T> { - #[inline] - pub fn variant(&self) -> ASTString { - self.variant.clone() - } - - #[inline] - pub fn args(&self) -> &[T] { - &self.args - } - - #[inline] - pub fn args_mut(&mut self) -> &mut [T] { - &mut self.args - } - - #[inline] - pub fn args_len(&self) -> usize { - self.args.len() - } -} - -impl ASTVariant<ASTExpr> { - #[inline] - pub fn is_const_expr(&self) -> bool { - self.args.iter().all(|expr| expr.is_const_expr()) - } -} - -impl ASTVariant<ASTConst> { - #[inline] - pub fn is_const_expr(&self) -> bool { - true - } -} - -impl<'a, T: Typed> ASTFmtColor<'a, T> { - pub(crate) fn new(variant: &'a ASTVariant<T>, ctx: &'a ASTTypeContext) -> Self { - Self(variant, ctx) - } -} - -impl<'a, T: Typed> ASTFmtMovement<'a, T> { - pub(crate) fn new(variant: &'a ASTVariant<T>, ctx: &'a ASTTypeContext) -> Self { - Self(variant, ctx) - } -} - -impl<'a, T: Typed> std::fmt::Display for ASTFmtColor<'a, T> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Self(ASTVariant { variant, args }, ctx) = self; - write!(f, "#({variant}")?; - if let Some(arg) = args.iter().next() { - write!(f, " {}", arg.get_type(ctx))?; - } - for arg in args.iter().skip(1) { - write!(f, ", {}", arg.get_type(ctx))?; - } - f.write_str(" )") - } -} - -impl<'a, T: Typed> std::fmt::Display for ASTFmtMovement<'a, T> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let Self(ASTVariant { variant, args }, ctx) = self; - write!(f, "#[{variant}")?; - if let Some(arg) = args.iter().next() { - write!(f, " {}", arg.get_type(ctx))?; - } - for arg in args.iter().skip(1) { - write!(f, ", {}", arg.get_type(ctx))?; - } - f.write_str(" ]") - } -} - -/// Describes a set of rules for a variant. -#[derive(Debug, Clone)] -pub struct ASTVariantRules { - family: ASTString, - rules: HashMap<ASTString, Vec<ASTType>>, -} - -macro_rules! rule { - ($cache: expr, $name: literal) => { ($cache.get($name), vec![]) }; - ($cache: expr, $name: literal -> [$($arg: ident),*]) => { ($cache.get($name), vec![$(ASTType::$arg,)*]) }; -} - -impl ASTVariantRules { - /// Default empty ruleset. - #[inline] - pub fn new(name: ASTString) -> Self { - Self { family: name, rules: Default::default() } - } - - /// Get the name of the family of variants. - #[inline] - pub fn name(&self) -> ASTString { - self.family.clone() - } - - /// Get all the rules of this family. - #[inline] - pub fn rules(&self) -> impl Iterator<Item = (&ASTString, &Vec<ASTType>)> { - self.rules.iter() - } - - /// Get a specific rule if present. - #[inline] - pub fn rule(&self, variant: impl AsRef<str>) -> Option<&[ASTType]> { - self.rules.get(variant.as_ref()).map(|vec| vec.as_ref()) - } - - /// Create a new rule set for colors, the default one. - #[inline] - pub fn new_colors(cache: ASTStringCacheHandle) -> Self { - Self { - family: cache.get("color"), - rules: HashMap::from_iter([ - rule!(cache, "rgb" -> [Integer, Integer, Integer]), - rule!(cache, "rgba" -> [Integer, Integer, Integer, Integer]), - rule!(cache, "black"), - rule!(cache, "white"), - rule!(cache, "grey"), - rule!(cache, "red"), - rule!(cache, "green"), - rule!(cache, "blue"), - ]), - } - } - - /// Create a new rule set for movements, the default one. - #[inline] - pub fn new_movements(cache: ASTStringCacheHandle) -> Self { - Self { - family: cache.get("movement"), - rules: HashMap::from_iter([ - rule![cache, "implicit"], - rule!(cache, "fixed" -> [Integer, Integer]), - rule!(cache, "linear" -> [Integer, Integer, Integer, Integer]), - rule!(cache, "accelerated" -> [Integer, Integer, Integer, Integer, Floating]), - ]), - } - } - - /// Insert a new rule in the ruleset, returns [true] if the rule was inserted successfully, - /// [false] otherwise. - #[must_use] - #[inline] - pub fn insert_rule(&mut self, name: ASTString, types: Vec<ASTType>) -> bool { - self.rules.insert(name, types).is_none() - } - - /// Returns wether a variant complies with the set of allowed one. - #[must_use] - #[inline] - pub fn complies<T: Typed>(&self, ctx: &ASTTypeContext, variant: &ASTVariant<T>) -> bool { - match self.rules.get(&variant.variant) { - None => false, - Some(rule) => { - (rule.len() == variant.args.len()) - && rule - .iter() - .zip(variant.args.iter().map(|arg| arg.get_type(ctx))) - .all(|(rule, arg)| arg.coercible_to(rule)) - } - } - } -} diff --git a/src/Rust/vvs_lang/src/ast/visibility.rs b/src/Rust/vvs_lang/src/ast/visibility.rs deleted file mode 100644 index 5d5c88f5..00000000 --- a/src/Rust/vvs_lang/src/ast/visibility.rs +++ /dev/null @@ -1,77 +0,0 @@ -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum ASTVisibility { - #[default] - Private, - Public, -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum ASTVisibilityRule { - Only(ASTVisibility), - AtLeast(ASTVisibility), -} - -impl ASTVisibilityRule { - /// Checks if the rule allows to show an item with the specified visibility [ASTVisibility]. - /// Used to factorize the functions in [crate::ast::ASTModule] and [crate::ast::ASTProgram]. - #[inline] - pub fn allows(&self, vis: ASTVisibility) -> bool { - match self { - ASTVisibilityRule::Only(this) => vis.eq(this), - ASTVisibilityRule::AtLeast(this) => PartialOrd::le(this, &vis), - } - } - - /// Allows all the visibilities. - #[inline] - pub fn any() -> Self { - Self::AtLeast(ASTVisibility::Private) - } -} - -impl Default for ASTVisibilityRule { - fn default() -> Self { - Self::Only(ASTVisibility::Private) - } -} - -impl ASTVisibility { - #[inline] - pub fn as_str(&self) -> &str { - match self { - ASTVisibility::Private => "private", - ASTVisibility::Public => "public", - } - } -} - -impl AsRef<str> for ASTVisibility { - #[inline] - fn as_ref(&self) -> &str { - self.as_str() - } -} - -#[test] -fn test_visibility() { - assert!(ASTVisibility::Private < ASTVisibility::Public); -} - -#[test] -fn test_visibility_rules() { - use ASTVisibility::*; - use ASTVisibilityRule::*; - - assert!(Only(Private).allows(Private)); - assert!(!Only(Private).allows(Public)); - assert!(Only(Public).allows(Public)); - assert!(!Only(Public).allows(Private)); - - assert!(AtLeast(Private).allows(Public)); - assert!(AtLeast(Private).allows(Private)); - assert!(AtLeast(Public).allows(Public)); - assert!(!AtLeast(Public).allows(Private)); - - assert!(ASTVisibilityRule::any().allows(Public)); - assert!(ASTVisibilityRule::any().allows(Private)); -} diff --git a/src/Rust/vvs_lang/src/lib.rs b/src/Rust/vvs_lang/src/lib.rs deleted file mode 100644 index 97947b62..00000000 --- a/src/Rust/vvs_lang/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -#![allow(dead_code)] - -// Old code, will be purged -pub mod ast; diff --git a/src/Rust/vvs_lib/Cargo.toml b/src/Rust/vvs_lib/Cargo.toml index 0285744a..0a15308f 100644 --- a/src/Rust/vvs_lib/Cargo.toml +++ b/src/Rust/vvs_lib/Cargo.toml @@ -15,8 +15,7 @@ log.workspace = true serde.workspace = true hashbrown.workspace = true serde_json.workspace = true - -vvs_ass.workspace = true +vvs_ass.workspace = true [build-dependencies] cbindgen.workspace = true diff --git a/src/Rust/vvs_llvm/Cargo.toml b/src/Rust/vvs_llvm/Cargo.toml index 41ce7c7a..3bde3081 100644 --- a/src/Rust/vvs_llvm/Cargo.toml +++ b/src/Rust/vvs_llvm/Cargo.toml @@ -1,15 +1,18 @@ [package] -name = "vvs_llvm" -description = "Link against and re-export llvm things" +name = "vvs_llvm" +description = "Link against and re-export llvm things" + version.workspace = true authors.workspace = true edition.workspace = true license.workspace = true [dependencies] -anyhow.workspace = true -paste.workspace = true -log.workspace = true +anyhow.workspace = true +thiserror.workspace = true +paste.workspace = true +log.workspace = true +vvs_shortstring.workspace = true llvm-sys = { version = "181", features = [ "strict-versioning", # Be strict about versions, we do want the lattest thing "no-llvm-linking", # We do the linking ourself because of problems when using spack: https://github.com/spack/spack/discussions/36192 @@ -17,7 +20,7 @@ llvm-sys = { version = "181", features = [ ] } [features] -sys = [] -link = [] -init = [] +sys = [] +link = [] +init = [] bindings = ["init"] diff --git a/src/Rust/vvs_llvm/src/bindings/mod.rs b/src/Rust/vvs_llvm/src/bindings/mod.rs index 3fed3a15..31f1a390 100644 --- a/src/Rust/vvs_llvm/src/bindings/mod.rs +++ b/src/Rust/vvs_llvm/src/bindings/mod.rs @@ -28,7 +28,7 @@ macro_rules! declare { /// # Safety /// Must be used from functions of the [crate::bindings::context::Context] to be sure that the /// usage is correct... - pub(super) unsafe fn from_ptr(inner: $llvm $($(, $field: $ty)*)?) -> Self { + pub unsafe fn from_ptr(inner: $llvm $($(, $field: $ty)*)?) -> Self { Self { inner $($(, $field)*)?, marker: core::marker::PhantomData @@ -36,7 +36,7 @@ macro_rules! declare { } /// Get the inner pointer. - pub(super) fn as_ptr(&self) -> $llvm { + pub fn as_ptr(&self) -> $llvm { self.inner } @@ -48,6 +48,18 @@ macro_rules! declare { } use declare; +/// An error to describes what can go wrong here. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Got an error because a nul byte was present in a stirng. + #[error("{0}")] + NulError(#[from] std::ffi::NulError), + + /// A value with a given name was not present. + #[error("undefined item with name '{0}'")] + Undefined(vvs_shortstring::ShortString), +} + const fn cstr(content: &[u8]) -> &std::ffi::CStr { match std::ffi::CStr::from_bytes_with_nul(content) { Ok(string) => string, diff --git a/src/Rust/vvs_llvm/src/bindings/module.rs b/src/Rust/vvs_llvm/src/bindings/module.rs index 4205010b..b11a16c5 100644 --- a/src/Rust/vvs_llvm/src/bindings/module.rs +++ b/src/Rust/vvs_llvm/src/bindings/module.rs @@ -19,6 +19,14 @@ impl Module<'_> { Ok(unsafe { FunctionDeclaration::from_ptr(function) }) } + + /// Get a function out of the a module as a declaration. + pub fn get_function(&self, name: impl AsRef<str>) -> Result<FunctionDeclaration, LLVMError> { + match unsafe { LLVMGetNamedFunction(self.as_ptr(), CString::new(name.as_ref())?.as_ptr()) } { + ptr if ptr.is_null() => Err(LLVMError::Undefined(name.as_ref().into())), + ptr => Ok(unsafe { FunctionDeclaration::from_ptr(ptr) }), + } + } } impl Drop for Module<'_> { diff --git a/src/Rust/vvs_llvm/src/bindings/types.rs b/src/Rust/vvs_llvm/src/bindings/types.rs index 5969d4c9..33303aee 100644 --- a/src/Rust/vvs_llvm/src/bindings/types.rs +++ b/src/Rust/vvs_llvm/src/bindings/types.rs @@ -4,13 +4,6 @@ crate::bindings::declare! { const LLVMTypeRef as Type<'a> } crate::bindings::declare! { const LLVMTypeRef as FunctionType<'a> } impl FunctionType<'_> { - /// Create a new function type. - pub fn new<const N: usize>(args: [Type; N], returns: Type) -> Self { - unsafe { - Self::from_ptr(LLVMFunctionType(returns.as_ptr(), args.map(|ty| ty.as_ptr()).as_mut_ptr(), N as u32, 0)) - } - } - /// Get the returned type of the function. pub fn returns(&self) -> Type { unsafe { Type::from_ptr(LLVMGetReturnType(self.as_ptr())) } @@ -31,6 +24,27 @@ impl FunctionType<'_> { } } +impl<'a, const N: usize> From<([Type<'a>; N], Type<'a>)> for FunctionType<'a> { + fn from((args, returns): ([Type<'a>; N], Type<'a>)) -> Self { + let (returns, mut args) = (returns.as_ptr(), args.map(|ty| ty.as_ptr())); + unsafe { Self::from_ptr(LLVMFunctionType(returns, args.as_mut_ptr(), N as u32, 0)) } + } +} + +impl<'a> From<(Vec<Type<'a>>, Type<'a>)> for FunctionType<'a> { + fn from((mut args, returns): (Vec<Type<'a>>, Type<'a>)) -> Self { + Self::from((args.as_mut_slice(), returns)) + } +} + +impl<'a> From<(&mut [Type<'a>], Type<'a>)> for FunctionType<'a> { + fn from((args, returns): (&mut [Type<'a>], Type<'a>)) -> Self { + let count = args.len() as u32; + let (returns, mut args) = (returns.as_ptr(), args.iter().map(|ty| ty.as_ptr()).collect::<Vec<_>>()); + unsafe { Self::from_ptr(LLVMFunctionType(returns, args.as_mut_ptr(), count, 0)) } + } +} + macro_rules! type_is { ($llvm:ident as $name:ident) => { paste::paste! { diff --git a/src/Rust/vvs_llvm/src/lib.rs b/src/Rust/vvs_llvm/src/lib.rs index e2f7b5f4..9a3d9dc0 100644 --- a/src/Rust/vvs_llvm/src/lib.rs +++ b/src/Rust/vvs_llvm/src/lib.rs @@ -78,7 +78,9 @@ pub mod sys { #[cfg(feature = "bindings")] pub mod prelude { - pub use crate::bindings::{basic_block::*, builder::*, context::*, function::*, module::*, types::*, value::*}; + pub use crate::bindings::{ + basic_block::*, builder::*, context::*, function::*, module::*, types::*, value::*, Error as LLVMError, + }; } #[cfg(feature = "bindings")] diff --git a/src/Rust/vvs_parser/Cargo.toml b/src/Rust/vvs_parser/Cargo.toml index f078adec..0239b3d0 100644 --- a/src/Rust/vvs_parser/Cargo.toml +++ b/src/Rust/vvs_parser/Cargo.toml @@ -4,13 +4,16 @@ license = "MPL-2.0" description = "A lossless Lua parser hacked to parse VVS" version.workspace = true -authors.workspace = true edition.workspace = true +authors = [ + "Maëlle Martin <maelle.martin@proton.me>", + "Kampfkarren <kampfkarren@gmail.com>", +] [dependencies] -vvs_parser_derive.workspace = true - +vvs_parser_derive.workspace = true +vvs_shortstring.workspace = true bytecount.workspace = true cfg-if.workspace = true derive_more.workspace = true diff --git a/src/Rust/vvs_parser/samples/tag.vvs b/src/Rust/vvs_parser/samples/tag.vvs index cf625d30..3850e9b9 100644 --- a/src/Rust/vvs_parser/samples/tag.vvs +++ b/src/Rust/vvs_parser/samples/tag.vvs @@ -12,7 +12,7 @@ end --- Returns only lines with the specified displacement. -job line_modulo(every: number, ls: lines): line -- Note that jobs may only returns `line` or `syllabe` +job line_modulo(every: number, disp: number, ls: lines): line -- Note that jobs may only returns `line` or `syllabe` for i,l in ls do if (i % every) == disp then yield l diff --git a/src/Rust/vvs_parser/src/ast/mod.rs b/src/Rust/vvs_parser/src/ast/mod.rs index 5ccfe1e9..eb11dd04 100644 --- a/src/Rust/vvs_parser/src/ast/mod.rs +++ b/src/Rust/vvs_parser/src/ast/mod.rs @@ -167,10 +167,13 @@ pub enum Field { ExpressionKey { /// The `[...]` part of `[expression] = value` brackets: ContainedSpan, + /// The `expression` part of `[expression] = value` key: Expression, + /// The `=` part of `[expression] = value` equal: TokenReference, + /// The `value` part of `[expression] = value` value: Expression, }, @@ -180,8 +183,10 @@ pub enum Field { NameKey { /// The `name` part of `name = value` key: TokenReference, + /// The `=` part of `name = value` equal: TokenReference, + /// The `value` part of `name = value` value: Expression, }, @@ -191,6 +196,22 @@ pub enum Field { NoKey(Expression), } +impl From<Field> for Expression { + fn from(value: Field) -> Self { + match value { + Field::ExpressionKey { value, .. } | Field::NameKey { value, .. } | Field::NoKey(value) => value, + } + } +} + +impl<'a> From<&'a Field> for &'a Expression { + fn from(value: &'a Field) -> Self { + match value { + Field::ExpressionKey { value, .. } | Field::NameKey { value, .. } | Field::NoKey(value) => value, + } + } +} + /// A table being constructed, such as `{ 1, 2, 3 }` or `{ a = 1 }` #[derive(Clone, Debug, Display, PartialEq, Node, Visit, Deserialize, Serialize)] #[display("{}{fields}{}", braces.tokens().0, braces.tokens().1)] @@ -495,9 +516,11 @@ pub enum FunctionArgs { /// The `1, 2, 3` part of `1, 2, 3` arguments: Punctuated<Expression>, }, + /// Used when a function is called in the form of `call "foobar"` #[display("{_0}")] String(TokenReference), + /// Used when a function is called in the form of `call { 1, 2, 3 }` #[display("{_0}")] TableConstructor(TableConstructor), @@ -507,10 +530,30 @@ impl FunctionArgs { pub(crate) fn empty() -> Self { FunctionArgs::Parentheses { parentheses: ContainedSpan::new(TokenReference::basic_symbol("("), TokenReference::basic_symbol(")")), - arguments: Punctuated::new(), } } + + /// Like [FunctionArgs::into_arguments], but without consuming the [FunctionArgs]. + pub fn as_arguments(&self) -> Vec<Cow<'_, Field>> { + match self { + FunctionArgs::String(string) => vec![Cow::Owned(Field::NoKey(Expression::String(string.clone())))], + FunctionArgs::TableConstructor(table) => table.fields().iter().map(Cow::Borrowed).collect(), + FunctionArgs::Parentheses { arguments, .. } => { + arguments.iter().cloned().map(Field::NoKey).map(Cow::Owned).collect() + } + } + } + + /// Turn the arguments from a function call into a list of fields that are used to call the + /// function… + pub fn into_arguments(self) -> Vec<Field> { + match self { + FunctionArgs::Parentheses { arguments, .. } => arguments.into_iter().map(Field::NoKey).collect(), + FunctionArgs::TableConstructor(table) => table.fields.into_iter().collect(), + FunctionArgs::String(string) => vec![Field::NoKey(Expression::String(string))], + } + } } /// A numeric for loop, such as `for index = 1, 10 do end` @@ -2492,7 +2535,7 @@ mod tests { } /// Any type, such as `string`, `boolean?`, etc. -#[derive(Clone, Debug, Display, IsVariant, PartialEq, Node, Deserialize, Serialize)] +#[derive(Clone, Debug, Display, IsVariant, Node, Deserialize, Serialize)] #[non_exhaustive] pub enum TypeInfo { /// A shorthand type annotating the structure of an array: { number } @@ -2608,6 +2651,29 @@ impl DefaultRef for TypeInfo { } } +impl PartialEq for TypeInfo { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Basic(l0), Self::Basic(r0)) => l0.token_type() == r0.token_type(), + + (Self::Array { type_info: lty, .. }, Self::Array { type_info: rty, .. }) + | (Self::Optional { base: lty, .. }, Self::Optional { base: rty, .. }) => lty == rty, + + (Self::Table { fields: l, .. }, Self::Table { fields: r, .. }) => { + l.iter().zip(r.iter()).all(|(l, r)| PartialEq::eq(l, r)) + } + + (Self::Tuple { types: l, .. }, Self::Tuple { types: r, .. }) => { + l.iter().zip(r.iter()).all(|(l, r)| PartialEq::eq(l, r)) + } + + (Self::Callback { .. }, Self::Callback { .. }) => unimplemented!("need to code the callback type check"), + + _ => false, + } + } +} + /// A type field used within table types. /// The `foo: number` in `{ foo: number }`. #[derive(Clone, Debug, Display, PartialEq, Node, Visit, Deserialize, Serialize)] @@ -2906,6 +2972,20 @@ impl CompoundOp { unreachable!("converting an unknown token into a compound operator") } } + + /// The operator must be applied to numerical values. + pub fn is_numerical_op(&self) -> bool { + matches!( + self, + Self::PlusEqual(_) + | Self::MinusEqual(_) + | Self::StarEqual(_) + | Self::SlashEqual(_) + | Self::DoubleSlashEqual(_) + | Self::PercentEqual(_) + | Self::CaretEqual(_) + ) + } } /// A Compound Assignment statement, such as `x += 1` or `x -= 1` diff --git a/src/Rust/vvs_parser/src/ast/options.rs b/src/Rust/vvs_parser/src/ast/options.rs index 228af95d..bf7a4562 100644 --- a/src/Rust/vvs_parser/src/ast/options.rs +++ b/src/Rust/vvs_parser/src/ast/options.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; /// [module] /// option = some Lua/VivyScript expression /// ``` -#[derive(Default, Serialize, Deserialize)] +#[derive(Default, Clone, Serialize, Deserialize)] pub struct OptionTable { modules: Vec<(ShortString, Vec<(ShortString, Expression)>)>, } diff --git a/src/Rust/vvs_parser/src/ast/parsers/structs/result.rs b/src/Rust/vvs_parser/src/ast/parsers/structs/result.rs index 8231bb82..67e75aae 100644 --- a/src/Rust/vvs_parser/src/ast/parsers/structs/result.rs +++ b/src/Rust/vvs_parser/src/ast/parsers/structs/result.rs @@ -1,13 +1,15 @@ #[derive(Debug)] pub enum ParserResult<T> { - // This doesn't necessarily mean that there were no errors, - // because this can sometimes be a recovered value. + // This doesn't necessarily mean that there were no errors, because this can sometimes be a + // recovered value. Value(T), - // Couldn't get any sort of value, but the lexer has moved. - // This should always come with an error. + // Couldn't get any sort of value, but the lexer has moved. This should always come with an + // error. LexerMoved, + // The thing was not found, this can be an error or just that another parser should be used at + // this point. NotFound, } @@ -28,11 +30,7 @@ impl<T> ParserResult<T> { } pub fn unwrap(self) -> T { - match self { - ParserResult::Value(value) => value, - ParserResult::LexerMoved => panic!("unwrap() called when value was LexerMoved"), - ParserResult::NotFound => panic!("unwrap() called when value was NotFound"), - } + self.expect("unwrap() called when value was LexerMoved or NotFound") } pub fn unwrap_or(self, default: T) -> T { diff --git a/src/Rust/vvs_parser/src/lib.rs b/src/Rust/vvs_parser/src/lib.rs index 63d28204..1fbe49d1 100644 --- a/src/Rust/vvs_parser/src/lib.rs +++ b/src/Rust/vvs_parser/src/lib.rs @@ -9,7 +9,6 @@ mod ast; mod error; mod node; mod private; -mod short_string; mod tokenizer; mod traits; mod util; @@ -19,13 +18,15 @@ mod vivy; #[cfg(test)] mod tests; -use crate::{error::Error, short_string::ShortString}; +use crate::error::Error; +use vvs_shortstring::ShortString; /// Crates depending upon [vss_parser] may import the prelude. pub mod prelude { /// Re-export everything that is linked to the [Ast]. pub mod ast { - pub use crate::{ast::*, node::*, short_string::ShortString, tokenizer::*, visitors::*}; + pub use crate::{ast::*, node::*, tokenizer::*, visitors::*}; + pub use vvs_shortstring::ShortString; } /// Parsers, get the raw representation of things without transforming or checking the diff --git a/src/Rust/vvs_parser/src/tokenizer/lexer.rs b/src/Rust/vvs_parser/src/tokenizer/lexer.rs index 7ce1b9b5..e03d816d 100644 --- a/src/Rust/vvs_parser/src/tokenizer/lexer.rs +++ b/src/Rust/vvs_parser/src/tokenizer/lexer.rs @@ -53,6 +53,7 @@ impl Lexer { } /// Returns a vector of all tokens left in the source string. + #[allow(dead_code)] pub fn collect(self) -> LexerResult<Vec<Token>> { let mut tokens = Vec::new(); let mut lexer = self; @@ -733,6 +734,7 @@ impl<T: std::fmt::Debug> LexerResult<T> { } /// Unwraps the result, panicking if it is not [`LexerResult::Ok`]. + #[allow(dead_code)] pub fn unwrap(self) -> T { match self { Self::Ok(value) => value, @@ -749,6 +751,7 @@ impl<T: std::fmt::Debug> LexerResult<T> { } /// Returns the errors, if there was any. + #[allow(dead_code)] pub fn errors(self) -> Vec<TokenizerError> { match self { Self::Recovered(_, errors) => errors, diff --git a/src/Rust/vvs_parser/src/vivy/error_report.rs b/src/Rust/vvs_parser/src/vivy/error_report.rs index 1ddc23d0..b6b051bc 100644 --- a/src/Rust/vvs_parser/src/vivy/error_report.rs +++ b/src/Rust/vvs_parser/src/vivy/error_report.rs @@ -113,6 +113,13 @@ impl ErrorReport<'_> { ErrorReport::new().add_errors(name, source, [error.into()]).report(); } + /// Reports multiple errors + pub fn errors(name: &'_ str, source: &'_ str, errors: impl IntoIterator<Item = impl Into<Error>>) { + ErrorReport::new() + .add_errors(name, source, errors.into_iter().map(Into::into)) + .report(); + } + /// Reports a single warning pub fn warning(name: &'_ str, source: &'_ str, warning: impl Into<Error>) { ErrorReport::new().add_warnings(name, source, [warning.into()]).report(); diff --git a/src/Rust/vvs_parser/src/vivy/frontend_pipeline.rs b/src/Rust/vvs_parser/src/vivy/frontend_pipeline.rs index ee70bd1e..57c0d4ba 100644 --- a/src/Rust/vvs_parser/src/vivy/frontend_pipeline.rs +++ b/src/Rust/vvs_parser/src/vivy/frontend_pipeline.rs @@ -4,7 +4,7 @@ use crate::{ ast::*, tokenizer::TokenReference, - traits::VecExtension as _, + traits::{DefaultRef, VecExtension as _}, vivy::{ error_report::ErrorReport, library::Library, @@ -18,7 +18,6 @@ use crate::{ use derive_more::Display; use hashbrown::{HashMap, HashSet}; use std::{borrow::Cow, cell::RefCell, fmt, fs, mem, path::Path, rc::Rc}; -use symbol_table::SymbolTableLocked; /// Process a program. pub struct FrontendPipeline<'a> { @@ -31,9 +30,6 @@ pub struct FrontendPipeline<'a> { /// List of imported modules. imports: HashMap<ShortString, Rc<(Ast, Cow<'static, str>)>>, - /// List of imported symbol tables. - symbols: Vec<Rc<SymbolTable<'a, TypeInfo, SymbolTableLocked>>>, - /// The search path. search: &'a SearchPath, @@ -53,21 +49,20 @@ pub struct FrontendPipeline<'a> { /// Record of all the options defined, unused, setted in the program. pub struct OptionsRecord { /// List of all unused declared options. - unused_options: Vec<(ShortString, ShortString)>, + unused: Vec<(ShortString, ShortString)>, /// All setted options. - setted_options: Vec<(ShortString, ShortString, Expression)>, + setted: Vec<(ShortString, ShortString, Expression)>, /// All declared options. - declared_options: Vec<(ShortString, ShortString, Expression)>, + declared: Vec<(ShortString, ShortString, TypeInfo, Expression)>, } impl fmt::Display for OptionsRecord { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Self { setted_options, .. } = self; - match setted_options.iter().map(|(a, b, _)| a.len() + b.len()).max() { + match self.setted.iter().map(|(a, b, _)| a.len() + b.len()).max() { None | Some(0) => Ok(()), - Some(len) => setted_options.iter().try_for_each(|(module, name, expression)| { + Some(len) => self.setted.iter().try_for_each(|(module, name, expression)| { writeln!(f, "{module}.{name: >len$} {expression}", len = len - module.len()) }), } @@ -110,6 +105,45 @@ pub struct FrontendOutput { pub library: Library, } +impl FrontendOutput { + /// Get all the declared functions from the output of the frontend. + pub fn declared_functions(&self) -> impl Iterator<Item = (&str, ShortString, Vec<&TypeInfo>, &TypeInfo)> { + fn handle_function<'a>( + module: &'a str, + name: ShortString, + body: &'a FunctionBody, + ) -> (&'a str, ShortString, Vec<&'a TypeInfo>, &'a TypeInfo) { + let arguments = body + .type_specifiers() + .flat_map(|ty| Some(ty?.type_info())) + .collect::<Vec<_>>(); + let returns = body + .return_type() + .map(TypeSpecifier::type_info) + .unwrap_or(TypeInfo::default_ref()); + (module, name, arguments, returns) + } + + fn handle_stmt<'a>( + module: &'a str, + stmt: &'a Stmt, + ) -> Option<(&'a str, ShortString, Vec<&'a TypeInfo>, &'a TypeInfo)> { + match stmt { + Stmt::FunctionDeclaration(d) => Some(handle_function(module, d.name().to_string().into(), d.body())), + Stmt::LocalFunction(d) => Some(handle_function(module, d.name().to_string().into(), d.body())), + Stmt::JobDeclaration(d) => Some(handle_function(module, d.name().to_string().into(), d.body())), + _ => None, + } + } + + let this = self.ast.nodes().stmts().map(|stmt| (&self.main.name, stmt)); + let others = self.imports.iter().map(|(m, a)| a.nodes().stmts().map(move |s| (m, s))); + + this.chain(others.flatten()) + .flat_map(|(module, stmt)| handle_stmt(module, stmt)) + } +} + impl From<CompilerResult> for FrontendOutput { fn from(value: CompilerResult) -> Self { let CompilerResult { ast, imports, main, options, library } = value; @@ -125,13 +159,10 @@ impl From<CompilerResult> for FrontendOutput { impl fmt::Display for FrontendOutput { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Self { imports, main, options, library, .. } = self; - write!(f, "imported modules: ")?; - imports.keys().try_for_each(|module| write!(f, "{module} "))?; - writeln!(f)?; - + let Self { imports, main, options, .. } = self; + writeln!(f, "imported modules:")?; + imports.keys().try_for_each(|module| writeln!(f, " - {module}"))?; writeln!(f, "{options}")?; - writeln!(f, "{library}")?; writeln!(f, "{main:#?}") } } @@ -151,7 +182,7 @@ impl From<()> for FrontendError { #[derive(Default)] struct ImportAllResult { - declared_options: Vec<(ShortString, ShortString, Expression)>, + declared: Vec<(ShortString, ShortString, TypeInfo, Expression)>, unused: Vec<(ShortString, ShortString)>, } @@ -162,8 +193,8 @@ impl FromIterator<ImportAllResult> for ImportAllResult { return Default::default(); }; iter.fold(ret, |mut ret, item| { - let Self { mut declared_options, unused } = item; - ret.declared_options.append(&mut declared_options); + let Self { mut declared, unused } = item; + ret.declared.append(&mut declared); ret.unused.retain(|option| unused.contains(option)); ret }) @@ -173,15 +204,15 @@ impl FromIterator<ImportAllResult> for ImportAllResult { impl<'a> FrontendPipeline<'a> { /// Create a new pipeline to parse, to check and do black magic. pub fn new(SourceCode { name, code }: &'a SourceCode) -> Self { + log::debug!(target: "vvs_parser", "run script `{name}`"); Self { program: Cow::Borrowed(code.as_ref()), options: None, imports: Default::default(), - symbols: vec![], is_main: true, - search: &DEFAULT_SEARCH_PATH, - process_path: vec![ShortString::new(name.as_ref())], library: Rc::new(RefCell::new(Library::default())), + process_path: vec![ShortString::new(name.as_ref())], + search: &DEFAULT_SEARCH_PATH, passes: &[], } } @@ -246,7 +277,10 @@ impl<'a> FrontendPipeline<'a> { })?; let CompilerResult { - ast, imports, options: OptionsRecord { unused_options, declared_options, .. }, .. + ast, + imports, // Will replace the thing we already took + options: OptionsRecord { unused, declared, .. }, + .. } = FrontendPipeline { process_path: self.process_path.clone().join(ShortString::new(name)), imports: mem::take(&mut self.imports), // Will be replaced. @@ -255,7 +289,6 @@ impl<'a> FrontendPipeline<'a> { Cow::Owned(ref program) => Cow::Borrowed(program.as_str()), }, options: self.options.clone(), - symbols: self.symbols.clone(), library: self.library.clone(), is_main: false, ..*self @@ -267,61 +300,60 @@ impl<'a> FrontendPipeline<'a> { let news = HashSet::<&ShortString>::from_iter(imports.keys()); (prev.len() <= news.len()) && (prev.intersection(&news).count() == prev.len()) }); + let _ = mem::replace(&mut self.imports, imports); // Replace with a more complete import list. self.imports.insert(ShortString::new(name), Rc::new((ast, program))); - Ok(ImportAllResult { declared_options, unused: unused_options }) + Ok(ImportAllResult { declared, unused }) })) } /// Process the pipeline. Some returned values may be hidden from the user, or things /// transformed a but more for the main result, but here we keep them. fn process(mut self) -> Result<CompilerResult, FrontendError> { - let ast = AstResult::parse_fallible(&self.program).into_result().map_err(|err| { - let _ = ErrorReport::new().add_errors(self.name(), &self.program, err).report(); - })?; + // Parse the source code. + let name = self.name().clone(); + let ast = AstResult::parse_fallible(&self.program) + .into_result() + .map_err(|err| ErrorReport::errors(&name, &self.program, err))?; - let StmtCheckerResult { decl_opts, setd_opts, main, imported } = - StmtChecker::new(&ast, (self.name().as_str(), self.program.as_ref())) + // Import stuff and check vivy specific statements… + let StmtCheckerResult { setted, main, imported } = + StmtChecker::new(&ast, (name.as_str(), self.program.as_ref())) .is_main(self.is_main) .process()?; - let ImportAllResult { mut declared_options, unused } = self.import_all(imported)?; - - declared_options.extend( - decl_opts - .into_iter() - .map(|(name, value)| (self.name().clone(), name, value.into())), - ); + let result = self.import_all(imported)?; + let ImportAllResult { mut declared, unused } = result; + // Type checker + replace options in the AST… + let loc = (name.as_str(), self.program.as_ref()); let symbols = SymbolTable::try_from(&ast) - .map_err(|err| { - let _ = ErrorReport::new() - .add_errors(self.name(), &self.program, [err]) - .report(); - })? - .import(self.symbols.iter().map(Rc::as_ref)); - - let loc = (self.name().as_str(), self.program.as_ref()); + .map_err(|err| ErrorReport::error(&name, &self.program, err))? + .lock(); TypeCollector::new(&ast, loc, &mut self.library.borrow_mut()).process()?; - TypeChecker::new(&ast, loc, &self.library.borrow(), symbols).process()?; + let opts = TypeChecker::new(&ast, loc, &self.library.borrow(), symbols) + .process()? + .options; - let (ast, OptsTransformResult { unused }) = match self.options.clone() { - None => (ast, Default::default()), - Some(options) => { - let (ast, results) = OptsTransform::new(ast, loc, &options).process()?; - (ast, OptsTransformResult { unused: results.unused.without(|opt| unused.contains(opt)) }) - } - }; + let mut option_table = self.options.map(Rc::unwrap_or_clone).unwrap_or_default(); + option_table + .section_mut("self") + .extend(opts.iter().cloned().map(|(name, _, default)| (name, default))); + declared.extend(opts.into_iter().map(|(n, t, v)| (name.clone(), n, t, v))); + + let (ast, results) = OptsTransform::new(ast, loc, &option_table, &declared).process()?; + let unused_options = results.unused.without(|opt| unused.contains(opt)); + // User passes + return the result Ok(CompilerResult { ast: self.passes.iter().try_fold(ast, |prog, pass| { - log::debug!("apply pass {} on module {}", pass.name(), self.name()); - pass.process(prog, self.name(), self.program.as_ref()) + log::info!("apply pass {} on module {name}", pass.name()); + pass.process(prog, &name, self.program.as_ref()) })?, imports: self.imports, - options: OptionsRecord { declared_options, setted_options: setd_opts, unused_options: unused }, - library: self.library.clone(), - main: main.unwrap_or_default(), + library: self.library, + options: OptionsRecord { declared, setted, unused: unused_options }, + main: main.unwrap_or_default().with_name(name), }) } diff --git a/src/Rust/vvs_parser/src/vivy/main_program.rs b/src/Rust/vvs_parser/src/vivy/main_program.rs index 54ddd090..656534e3 100644 --- a/src/Rust/vvs_parser/src/vivy/main_program.rs +++ b/src/Rust/vvs_parser/src/vivy/main_program.rs @@ -4,8 +4,11 @@ use crate::{ast::Expression, ShortString}; use derive_more::{IntoIterator, IsVariant}; /// The main program to execute in the parsed script. -#[derive(Default, Debug)] +#[derive(Debug)] pub struct MainProgram { + /// The name of the program, i.e. the main module. + pub name: ShortString, + /// The initial variable name of the read ASS file. pub initial: ShortString, @@ -16,7 +19,23 @@ pub struct MainProgram { pub returns: MainProgramReturns, } +impl Default for MainProgram { + fn default() -> Self { + Self { + name: ShortString::new_static("stdin"), + initial: ShortString::new_static("init"), + steps: Default::default(), + returns: Default::default(), + } + } +} + impl MainProgram { + /// Set the name of the progeam. + pub fn with_name(self, name: ShortString) -> Self { + Self { name, ..self } + } + /// Set the initial variable name. pub fn with_initial(self, initial: ShortString) -> Self { Self { initial, ..self } diff --git a/src/Rust/vvs_parser/src/vivy/mod.rs b/src/Rust/vvs_parser/src/vivy/mod.rs index e28ae6ec..8435c2e6 100644 --- a/src/Rust/vvs_parser/src/vivy/mod.rs +++ b/src/Rust/vvs_parser/src/vivy/mod.rs @@ -10,7 +10,9 @@ mod symbol_table; use std::{borrow::Cow, fs, io, path::Path}; -pub use self::{error_report::ErrorReport, frontend_pipeline::*, main_program::*, passes::UserFrontendPass}; +pub use self::{ + error_report::ErrorReport, frontend_pipeline::*, main_program::*, passes::UserFrontendPass, search_path::SearchPath, +}; /// The representation of a source file. pub struct SourceCode { diff --git a/src/Rust/vvs_parser/src/vivy/passes/opts_transform.rs b/src/Rust/vvs_parser/src/vivy/passes/opts_transform.rs index d6976e49..b3fb20d2 100644 --- a/src/Rust/vvs_parser/src/vivy/passes/opts_transform.rs +++ b/src/Rust/vvs_parser/src/vivy/passes/opts_transform.rs @@ -6,7 +6,10 @@ declare_pass! { #[allow(dead_code)] transform::OptsTransform: 'a {} - aux { options: &'a OptionTable } + aux { + options: &'a OptionTable, + setted: &'a [(ShortString, ShortString, TypeInfo, Expression)] + } result { unused: Vec<(ShortString, ShortString)> } } diff --git a/src/Rust/vvs_parser/src/vivy/passes/stmt_checker.rs b/src/Rust/vvs_parser/src/vivy/passes/stmt_checker.rs index 0bb444e7..517dc2f5 100644 --- a/src/Rust/vvs_parser/src/vivy/passes/stmt_checker.rs +++ b/src/Rust/vvs_parser/src/vivy/passes/stmt_checker.rs @@ -14,14 +14,14 @@ declare_pass! { /// The statement checker is here to verify that statements are not used in incorrect positions. visit::StmtChecker: 'a { in_job: bool, + decl_opts: Vec<(ShortString, OptionDefaultValue)>, } opt { is_main: bool } result { - imported: Vec<TokenReference>, - setd_opts: Vec<(ShortString, ShortString, Expression)>, - decl_opts: Vec<(ShortString, OptionDefaultValue)>, - main: Option<MainProgram>, + imported: Vec<TokenReference>, + setted: Vec<(ShortString, ShortString, Expression)>, + main: Option<MainProgram>, } } @@ -32,6 +32,7 @@ impl crate::visitors::Visitor for StmtChecker<'_> { Stmt::Main(main) => self.check_main(main), Stmt::Assignment(assignment) => self.check_option_set(assignment), Stmt::OptionDecl(declaration) => self.check_option_decl(declaration), + Stmt::FunctionCall(call) => self.check_top_level_function_call(call), stmt @ Stmt::FunctionDeclaration(_) | stmt @ Stmt::LocalFunction(_) => stmt.visit(self), stmt @ Stmt::JobDeclaration(_) => { @@ -112,6 +113,23 @@ impl StmtChecker<'_> { } } + fn check_top_level_function_call(&mut self, call: &FunctionCall) { + let token = call.tokens().next().unwrap(); + let Prefix::Name(function) = call.prefix() else { + return self.error(token, "unexpected function call here, can only call the 'assert' functions here"); + }; + + match call.suffixes().next() { + Some(Suffix::Call(Call::AnonymousCall(_))) if call.suffixes().count() == 1 => {} + _ => return self.error(token, "expected to call the 'assert' functions simply here…"), + } + + match function.token_type().as_str() { + "assert" | "assert_eq" | "assert_neq" => {} + function => self.error(token, format!("can't call function '{function}' in this position")), + } + } + fn check_option_decl(&mut self, declaration: &OptionDecl) { declaration.declarations().for_each(|(n, ty, v)| match n.token_type() { TokenType::Identifier { identifier } if self.decl_opts.iter().any(|(opt, _)| opt == identifier) => { @@ -175,7 +193,7 @@ impl StmtChecker<'_> { .into_iter() .zip(assignation.expressions().into_iter().cloned()) .map(|((module, name), val)| (module, name, val)); - self.setd_opts.extend(options); + self.setted.extend(options); } fn check_main(&mut self, main: &Main) { diff --git a/src/Rust/vvs_parser/src/vivy/passes/type_checker.rs b/src/Rust/vvs_parser/src/vivy/passes/type_checker.rs index 71556dbb..8a826036 100644 --- a/src/Rust/vvs_parser/src/vivy/passes/type_checker.rs +++ b/src/Rust/vvs_parser/src/vivy/passes/type_checker.rs @@ -1,35 +1,49 @@ +use std::borrow::Cow; + use crate::{ ast::*, node::Node, tokenizer::*, traits::DefaultRef, vivy::{library::*, passes::declare_pass, symbol_table::*}, + ShortString, }; declare_pass! { /// The type checker is here to verify type corectness of the program and infer types, etc. To /// check the types we will need to do custom things... - /// - /// TODO: Verify unique functions at top level, with correct names visit::TypeChecker: 'a {} aux { library: &'a Library, symbols: SymbolTable<'a, TypeInfo, SymbolTableLocked>, } + + result { + options: Vec<(ShortString, TypeInfo, Expression)>, + } +} + +macro_rules! box_error { + ($token: expr, $format: literal $(,)?) => { Box::new(AstError::from_parts($token.into(), format!($format ))) }; + ($token: expr, $format: literal, $($args: expr),+ $(,)?) => { Box::new(AstError::from_parts($token.into(), format!($format, $($args),+))) }; } impl crate::visitors::Visitor for TypeChecker<'_> { fn visit_ast(&mut self, ast: &Ast) { log::error!("todo: verify unique functions at top level, with correct names"); for error in Vec::from_iter(ast.nodes().stmts().flat_map(|stmt| match stmt { - Stmt::JobDeclaration(decl) => self.type_compute().callable(decl.body(), true).err(), Stmt::FunctionDeclaration(decl) => self.type_compute().callable(decl.body(), false).err(), - Stmt::LocalFunction(decl) => self.type_compute().callable(decl.body(), false).err(), + Stmt::JobDeclaration(decl) => self.type_compute().callable(decl.body(), true).err(), Stmt::Assignment(assignments) => self.type_compute().assignments(assignments).map(|_| ()).err(), - Stmt::OptionDecl(_) | Stmt::Import(_) => None, // NOOP / Types already registered... + Stmt::LocalFunction(decl) => self.type_compute().callable(decl.body(), false).err(), + Stmt::OptionDecl(decl) => self.handle_type_decl(decl).err(), + Stmt::FunctionCall(call) => self.handle_top_level_function_call(call).err(), + + Stmt::Import(_) => None, Stmt::TypeDeclaration(_) => unimplemented!("custom types are tbd"), - _ => unreachable!("already checked that they are not here"), + + stmt => unreachable!("already checked that they are not here: {stmt}"), })) { self.report.add_errors(self.name, self.source, [*error]); } @@ -43,6 +57,70 @@ impl TypeChecker<'_> { fn type_compute(&self) -> TypeCompute { TypeCompute::new(self.library, self.symbols.sub_scope()) } + + fn handle_top_level_function_call(&mut self, call: &FunctionCall) -> Result<(), TypeComputeError> { + let token = call.tokens().next().unwrap(); + let Prefix::Name(function) = call.prefix() else { + return Err(box_error!(token, "expected a function name here")); + }; + + let args: Vec<Cow<_>> = match call.suffixes().next() { + Some(Suffix::Call(Call::AnonymousCall(args))) if call.suffixes().count() == 1 => { + Vec::from_iter(args.as_arguments().into_iter().map(|field| match field { + Cow::Borrowed(field) => Cow::Borrowed(field.into()), + Cow::Owned(field) => Cow::Owned(field.into()), + })) + } + _ => return Err(box_error!(token, "expected a simple function call here")), + }; + + match function.token_type().as_str() { + "assert" => match args.len() { + 1 => self.type_compute().expr_as_type(TypeInfo::number(), &args[0]), + 2 => { + self.type_compute().expr_as_type(TypeInfo::number(), &args[0])?; + self.type_compute().expr_as_type(TypeInfo::string(), &args[1])?; + Ok(()) + } + n => Err(box_error!(token, "expected 1 or 2 arguments when calling 'assert', got {n}")), + }, + function @ ("assert_eq" | "assert_neq") => match args.len() { + 2 => { + let (ty, _) = self.type_compute().expr(&args[0])?; + self.type_compute().expr_as_type(&ty, &args[1]) + } + 3 => { + let (ty, _) = self.type_compute().expr(&args[0])?; + self.type_compute().expr_as_type(&ty, &args[1])?; + self.type_compute().expr_as_type(TypeInfo::string(), &args[2]) + } + n => Err(box_error!(token, "expected 2 or 3 arguments when calling '{function}', got {n}")), + }, + function => Err(box_error!(token, "unexpected call to function '{function}', expected 'assert' functions")), + } + } + + fn handle_type_decl(&mut self, decl: &OptionDecl) -> Result<(), TypeComputeError> { + if decl.names().len() != decl.expressions().len() { + return Err(box_error!(decl.option_token(), "")); + } + + let expressions = decl + .expressions() + .iter() + .map(|expr| (expr, self.type_compute().expr(expr))); + let names = decl + .names() + .iter() + .map(|name| ShortString::new(name.token_type().as_str())); + let options = names + .zip(expressions) + .map(|(n, (e, r))| r.map(|(t, _)| (n, t, e.clone()))) + .collect::<Result<Vec<_>, _>>()?; + self.options.extend(options); + + Ok(()) + } } /// The thing that will actually compute types and returns errors in case of problems. @@ -57,11 +135,6 @@ struct TypeCompute<'a> { type TypeComputeError = Box<AstError>; type TypeComputeResult<'a> = Result<(TypeInfo, SymbolTable<'a, TypeInfo, SymbolTableMutable>), TypeComputeError>; -macro_rules! box_error { - ($token: expr, $format: literal $(,)?) => { Box::new(AstError::from_parts($token.into(), format!($format ))) }; - ($token: expr, $format: literal, $($args: expr),+ $(,)?) => { Box::new(AstError::from_parts($token.into(), format!($format, $($args),+))) }; -} - fn unsupported<T>(token: &TokenReference, whats: &str) -> Result<T, TypeComputeError> { Err(box_error!(token, "{whats} are not supported for now")) } @@ -88,7 +161,7 @@ impl<'a> TypeCompute<'a> { fn assignments(self, assignments: &'a Assignment) -> TypeComputeResult<'a> { let mut assignments = assignments.variables().iter().zip(assignments.expressions()); - assignments.try_for_each(|(dest, expr)| self.sub().assignation(dest, expr))?; + assignments.try_for_each(|(dest, expr)| self.sub().assignation(dest, expr).map(|_| ()))?; Ok((TypeInfo::default(), self.symbols)) } @@ -121,48 +194,36 @@ impl<'a> TypeCompute<'a> { func_args: &FunctionArgs, token: &TokenReference, ) -> Result<TypeInfo, TypeComputeError> { - match func_args { - FunctionArgs::String(_) => match arguments.first() { - Some(type_info) if arguments.len() == 1 && type_info == TypeInfo::string() => Ok(returns.clone()), - Some(type_info) if arguments.len() == 1 => Err(box_error!( - token, - "try to pass a string argument to a function that a variable of type {type_info}" - )), - Some(_) => { - Err(box_error!(token, "try to pass an argument to a function that takes {}", arguments.len())) - } - None => Err(box_error!(token, "try to pass an argument to a function that takes none",)), - }, - - FunctionArgs::TableConstructor(_) if arguments.len() != 1 => Err(box_error!( - token, - "try to pass a table as argument to a function that takes {} arguments", - arguments.len() - )), - FunctionArgs::TableConstructor(args) => { - debug_assert_eq!(arguments.len(), 1); - todo!("find a way to correctly parse expected types..."); - // We can pass a table because we expect a table, or because we pass named - // arguments... - } - - FunctionArgs::Parentheses { arguments: args, .. } => { - if args.len() != arguments.len() { - let (arg_len, func_len) = (args.len(), arguments.len()); - return Err(box_error!( - token, - "try to pass {arg_len} arguments to a function that takes {func_len}" - )); - } - let mut arguments = args.iter().enumerate().zip(arguments.iter()); - match arguments.find_map(|((idx, arg), type_info)| { - self.sub() - .expr_as_type(type_info, arg) - .is_err() - .then(|| (idx, arg.tokens().next().unwrap(), type_info)) - }) { - Some((n, tok, ty)) => Err(box_error!(tok, "expected argument n°{n} to be of type {ty}")), - None => Ok(returns.clone()), + log::error!("what to do in case of table?"); + + match arguments { + // Treat the passed thing as a table construct if needed. + [TypeInfo::Basic(name)] if name.token_type().as_str() == "table" => todo!(), + + // Treat the thing as an argument list. + _ => { + let func_args = func_args.as_arguments(); + func_args + .iter() + .any(|f| matches!(&**f, Field::ExpressionKey { .. } | Field::NameKey { .. })) + .then_some(()) + .ok_or_else(|| { + box_error!(token, "passing named argument to a function that didn't expect a table") + })?; + + let args = Vec::from_iter(func_args.into_iter().map(|field| match field { + Cow::Borrowed(field) => Cow::Borrowed(Into::<&_>::into(field)), + Cow::Owned(field) => Cow::Owned(field.into()), + })); + + let (arguments_len, args_len) = (arguments.len(), args.len()); + if args_len != arguments_len { + Err(box_error!(token, "argument count mismatch, expected {arguments_len}, got {args_len}")) + } else { + args.into_iter() + .zip(arguments) + .try_for_each(|(expr, ty)| self.sub().expr_as_type(ty, &expr)) + .map(|_| returns.clone()) } } } @@ -202,9 +263,7 @@ impl<'a> TypeCompute<'a> { match unop { Minus(_) | Not(_) | Tilde(_) if ty == *TypeInfo::number() => Ok((ty, symbols)), Hash(_) if is_container => Ok((TypeInfo::number().clone(), symbols)), - _ => { - Err(box_error!(unop.token(), "can't apply this unary operator to an expression of type '{ty}'")) - } + _ => Err(box_error!(unop.token(), "can't apply this operator to expression of type '{ty}'")), } } @@ -224,10 +283,7 @@ impl<'a> TypeCompute<'a> { Ok((ty, self.symbols)) } - _ => Err(box_error!( - binop.token(), - "can't apply this binary operator to an expression of type '{ty}'", - )), + _ => Err(box_error!(binop.token(), "can't apply this operator to expression of type '{ty}'")), } } @@ -369,17 +425,14 @@ impl<'a> TypeCompute<'a> { let variables = assignment.names().iter(); let variables = variables .zip(assignment.type_specifiers().map(|ty| ty.map(TypeSpecifier::type_info))) - .zip(assignment.expressions()); - let variables = variables.map(|((name, ty), val)| { - let ty = match ty { - None => self.sub().expr(val)?.0, + .zip(assignment.expressions()) + .map(|((name, ty), val)| match ty { + None => Ok((name.token_type().as_str().into(), self.sub().expr(val)?.0)), Some(ty) => { self.sub().expr_as_type(ty, val)?; - ty.clone() + Ok((name.token_type().as_str().into(), ty.clone())) } - }; - Ok::<_, TypeComputeError>((name.token_type().as_str().into(), ty)) - }); + }); self.symbols .extend(variables.collect::<Result<Vec<_>, TypeComputeError>>()?) .map_err(|variable| { @@ -388,27 +441,49 @@ impl<'a> TypeCompute<'a> { Ok((TypeInfo::default(), self.symbols)) } - fn compound_assignment(self, assignment: &'a CompoundAssignment) -> TypeComputeResult<'a> { - todo!() - } - fn yields(self, yields: &'a Yield) -> TypeComputeResult<'a> { if !self.in_job { - Err(box_error!(yields.token(), "yielding values is only possible from jobs")) - } else { - todo!() + return Err(box_error!(yields.token(), "yielding values is only possible from jobs")); } + + for expr in yields.yields().iter() { + match (self.sub().expr(expr)?.0, self.returns) { + (TypeInfo::Basic(expr), TypeInfo::Basic(returns)) + if matches!( + (expr.token_type().as_str(), returns.token_type().as_str()), + ("line" | "syllabe" | "lines" | "syllabes", "line") | ("syllabe" | "syllabes", "syllabe") + ) => {} + (expr, returns) => { + return Err(box_error!( + expr.tokens().next().unwrap(), + "can't yield an expression of type '{expr}' as '{returns}'" + )) + } + } + } + + Ok((TypeInfo::default(), self.symbols)) } - fn assignation(self, variable: &'a Var, expression: &'a Expression) -> Result<(), TypeComputeError> { + fn compound_assignment(self, assignment: &'a CompoundAssignment) -> TypeComputeResult<'a> { + let symbols = self.symbols.clone(); + let ty = self.assignation(assignment.lhs(), assignment.rhs())?; + match assignment.compound_operator() { + op if op.is_numerical_op() && ty == *TypeInfo::number() => Ok((TypeInfo::default(), symbols)), + CompoundOp::TwoDotsEqual(_) if ty == *TypeInfo::string() => Ok((TypeInfo::default(), symbols)), + operator => Err(box_error!(operator.token(), "can't apply this operator to a variable of type '{ty}'")), + } + } + + fn assignation(self, variable: &'a Var, expression: &'a Expression) -> Result<TypeInfo, TypeComputeError> { match variable { - Var::Expression(_) => todo!(), + Var::Expression(var) => todo!("handle assign into varexpr: {var:?}"), Var::Name(variable) => match variable.token_type() { TokenType::Identifier { identifier } if self.symbols.is_global(identifier) => { Err(box_error!(variable, "try to assign a global variable")) } TokenType::Identifier { identifier } => match self.symbols.get(identifier).cloned() { - Some(ty) => self.expr_as_type(&ty, expression), + Some(ty) => self.expr_as_type(&ty, expression).map(|_| ty), None => Err(box_error!(variable, "try to assign an undefined variable")), }, _ => unreachable!(), @@ -416,7 +491,7 @@ impl<'a> TypeCompute<'a> { } } - fn stmt(self, stmt: &'a Stmt) -> TypeComputeResult<'a> { + fn stmt(mut self, stmt: &'a Stmt) -> TypeComputeResult<'a> { match stmt { Stmt::CompoundAssignment(assignments) => self.compound_assignment(assignments), Stmt::LocalAssignment(assignment) => self.local_assignment(assignment), @@ -464,12 +539,49 @@ impl<'a> TypeCompute<'a> { Ok((TypeInfo::default(), self.symbols)) } - Stmt::GenericFor(_) => todo!(), + Stmt::GenericFor(for_stmt) if for_stmt.names().len() == for_stmt.expressions().len() => { + let symbols = self.symbols.clone(); + self.symbols.extend(for_stmt + .type_specifiers() + .zip(for_stmt.names().iter().map(|name| ShortString::new(name.token_type().as_str()))) + .zip(for_stmt.expressions()) + .map(|((ty, name), expr)| match ty { + None => self.sub().expr(expr).map(|(ty, _)| (name, ty)), + Some(ty) => self.sub().expr_as_type(ty.type_info(), expr).map(|_| (name, ty.type_info().clone())), + }) + .collect::<Result<Vec<_>, _>>()?) + .map_err(|name| box_error!(for_stmt.for_token(), "can't declare a variable with the name '{name}', is it a global?"))?; + + self.sub().in_loop().block(for_stmt.block())?; + Ok((TypeInfo::default(), symbols)) + } + Stmt::GenericFor(for_stmt) if for_stmt.expressions().len() == 1 => { + let (it_type, _) = self.sub().expr(for_stmt.expressions().first_value().expect("oupsy"))?; + let symbols = self.symbols.clone(); + let names = for_stmt.names(); + let handle = |name| box_error!(for_stmt.for_token(), "can't declare a variable with the name '{name}', is it a global?"); + + match for_stmt.names().len() { + 1 => self.symbols + .set(names.first_value().unwrap().token_type().as_str(), it_type) + .map_err(handle)?, + 2 => self.symbols + .set(names.first_value().unwrap().token_type().as_str(), TypeInfo::number().clone()) + .map_err(handle)? + .set(names.iter().nth(1).unwrap().token_type().as_str(), it_type) + .map_err(handle)?, + n => return Err(box_error!(for_stmt.for_token(), "expected one or two variables for this generic for, got {n} of them")), + }; - Stmt::JobDeclaration(decl) => { - Err(box_error!(decl.function_token(), "nested declaration of job is forbidden")) + self.sub().in_loop().block(for_stmt.block())?; + Ok((TypeInfo::default(), symbols)) } + Stmt::JobDeclaration(decl) => Err(box_error!(decl.function_token(), "nested declaration of job is forbidden")), + Stmt::GenericFor(for_stmt) => Err(box_error!(for_stmt.for_token(), + "a generic for loop can either have the same amount of iterators as the expressions, or a single expression" + )), + Stmt::FunctionDeclaration(decl) => unsupported(decl.function_token(), "nested declaration of functions"), Stmt::LocalFunction(decl) => unsupported(decl.function_token(), "nested declaration of functions"), Stmt::TypeDeclaration(_) => unsupported(stmt.tokens().next().unwrap(), "custom types"), diff --git a/src/Rust/vvs_parser/src/vivy/search_path.rs b/src/Rust/vvs_parser/src/vivy/search_path.rs index fd24ed86..ec3f1bf5 100644 --- a/src/Rust/vvs_parser/src/vivy/search_path.rs +++ b/src/Rust/vvs_parser/src/vivy/search_path.rs @@ -1,10 +1,15 @@ //! Used to search for other files when using the `import` statement in Vivy Script. -use std::{borrow::Cow, fs, path::PathBuf}; +use std::{ + borrow::Cow, + fs, + path::{Path, PathBuf}, +}; /// Search path, can pass the name of a module and will try to return the associated code. #[derive(Default)] pub struct SearchPath { + script_folder: Option<PathBuf>, folders: Vec<PathBuf>, } @@ -14,36 +19,34 @@ pub const VIVY_SCRIPT_EXTENSION: &str = "vvs"; impl SearchPath { /// Create a new search path. pub const fn new() -> Self { - Self { folders: Vec::new() } + Self { script_folder: None, folders: Vec::new() } } /// Resolve the search path to get the desired code. pub fn resolve(&self, import: &str) -> Option<Cow<'static, str>> { - self.folders.iter().find_map(|folder| { + self.script_folder.iter().chain(&self.folders).find_map(|folder| { let mut path = folder.join(import); path.set_extension(VIVY_SCRIPT_EXTENSION); - let code = fs::read_to_string(path).ok()?; - Some(Cow::Owned(code)) + Some(Cow::Owned(fs::read_to_string(path).ok()?)) }) } - /// Add a folder to the search list. - pub fn push(&mut self, folder: PathBuf) { - self.folders.push(folder) + /// Set the folder in which the script resides. + pub fn with_script_folder(self, folder: impl AsRef<Path>) -> Self { + Self { script_folder: Some(folder.as_ref().to_path_buf()), ..self } } -} -impl Extend<PathBuf> for SearchPath { - /// Add multiple folders to the search list. - fn extend<T: IntoIterator<Item = PathBuf>>(&mut self, iter: T) { - self.folders.extend(iter) + /// Add a folder to the search list. + pub fn with_include(mut self, folder: PathBuf) -> Self { + self.folders.push(folder); + self } } impl FromIterator<PathBuf> for SearchPath { /// Create a search list from a list of folders. fn from_iter<T: IntoIterator<Item = PathBuf>>(iter: T) -> Self { - Self { folders: iter.into_iter().collect() } + Self { folders: iter.into_iter().collect(), script_folder: None } } } diff --git a/src/Rust/vvs_parser/src/vivy/symbol_table.rs b/src/Rust/vvs_parser/src/vivy/symbol_table.rs index 72203c7c..aa8341d9 100644 --- a/src/Rust/vvs_parser/src/vivy/symbol_table.rs +++ b/src/Rust/vvs_parser/src/vivy/symbol_table.rs @@ -22,9 +22,6 @@ pub struct SymbolTable<'a, T: Clone, State> { /// A possible parent for this scope. parent: Option<&'a SymbolTable<'a, T, SymbolTableLocked>>, - /// Imported scopes. - imported: Vec<&'a SymbolTable<'a, T, SymbolTableLocked>>, - /// List of symbols from this scope. symbols: HashMap<ShortString, T>, @@ -34,14 +31,14 @@ pub struct SymbolTable<'a, T: Clone, State> { impl<T: Clone, State> Default for SymbolTable<'_, T, State> { fn default() -> Self { - Self { parent: None, symbols: HashMap::new(), imported: vec![], _state: marker::PhantomData } + Self { parent: None, symbols: HashMap::new(), _state: marker::PhantomData } } } impl<'a, T: Clone> SymbolTable<'a, T, SymbolTableLocked> { /// Create a new sub-scoped symbol table. pub fn sub_scope(&'a self) -> SymbolTable<'a, T, SymbolTableMutable> { - SymbolTable { parent: Some(self), symbols: Default::default(), imported: vec![], _state: marker::PhantomData } + SymbolTable { parent: Some(self), symbols: Default::default(), _state: marker::PhantomData } } /// Get the root scope. @@ -57,8 +54,8 @@ impl<'a, T: Clone> SymbolTable<'a, T, SymbolTableLocked> { impl<'a, T: Clone> SymbolTable<'a, T, SymbolTableMutable> { /// Lock the table. pub fn lock(self) -> SymbolTable<'a, T, SymbolTableLocked> { - let Self { parent, imported, symbols, .. } = self; - SymbolTable::<'a, T, SymbolTableLocked> { parent, imported, symbols, _state: marker::PhantomData } + let Self { parent, symbols, .. } = self; + SymbolTable::<'a, T, SymbolTableLocked> { parent, symbols, _state: marker::PhantomData } } /// Add a new variable to the current scope. We override symbols (non-UB shadowing of @@ -83,15 +80,6 @@ impl<'a, T: Clone> SymbolTable<'a, T, SymbolTableMutable> { iter.into_iter() .try_fold(self, |this, (symbol, value)| this.set(symbol, value)) } - - /// Imports other scopes into this one. - pub fn import( - mut self, - iter: impl IntoIterator<Item = &'a SymbolTable<'a, T, SymbolTableLocked>>, - ) -> SymbolTable<'a, T, SymbolTableLocked> { - self.imported.extend(iter); - self.lock() - } } impl<'a, T: Clone, State> SymbolTable<'a, T, State> { @@ -99,7 +87,6 @@ impl<'a, T: Clone, State> SymbolTable<'a, T, State> { pub fn get(&self, symbol: impl AsRef<str>) -> Option<&T> { self.symbols .get(symbol.as_ref()) - .or_else(|| self.imported.iter().find_map(|scope| scope.get(symbol.as_ref()))) .or_else(|| self.parent?.get(symbol.as_ref())) } @@ -186,14 +173,9 @@ fn fold_table_on_stmt<'a>( // Function declarations Stmt::LocalFunction(local) => handle_decl(symbol, local.name().token_type().as_str(), local.body()), Stmt::FunctionDeclaration(decl) => { - let name = decl.name().names().into_iter().next().unwrap().token_type().as_str(); - handle_decl(symbol, name, decl.body()) + handle_decl(symbol, decl.name().names().first_value().unwrap().token_type().as_str(), decl.body()) } - // Options & constants - Stmt::OptionDecl(_) => todo!(), - Stmt::LocalAssignment(_) => todo!("handle constants"), - // Ignore _ => Ok(symbol), } diff --git a/src/Rust/vvs_parser_derive/Cargo.toml b/src/Rust/vvs_parser_derive/Cargo.toml index f25fee11..8615d3cf 100644 --- a/src/Rust/vvs_parser_derive/Cargo.toml +++ b/src/Rust/vvs_parser_derive/Cargo.toml @@ -4,9 +4,13 @@ license = "MPL-2.0" description = "Internally used for the vvs_parser project. Do not use." version.workspace = true -authors.workspace = true edition.workspace = true +authors = [ + "Maëlle Martin <maelle.martin@proton.me>", + "Kampfkarren <kampfkarren@gmail.com>", +] + [lib] proc-macro = true diff --git a/src/Rust/vvs_procmacro/src/vvrt/gen.rs b/src/Rust/vvs_procmacro/src/vvrt/gen.rs index 77a6e0f2..67a46a28 100644 --- a/src/Rust/vvs_procmacro/src/vvrt/gen.rs +++ b/src/Rust/vvs_procmacro/src/vvrt/gen.rs @@ -1,41 +1,8 @@ -use crate::vvrt::{MethodInfo, Role, Type}; +use crate::vvrt::{MethodInfo, Role}; use anyhow::bail; use proc_macro2::TokenStream; use quote::quote; -fn gen_type_call(ty: &Type, arg: &syn::Ident) -> TokenStream { - match ty { - Type::None => quote! { LLVMVoidTypeInContext(#arg) }, - Type::Integer => quote! { LLVMInt32TypeInContext(#arg) }, - Type::Floating => quote! { LLVMFloatTypeInContext(#arg) }, - Type::Boolean => quote! { LLVMInt1TypeInContext(#arg) }, - Type::StrPtr => quote! { LLVMPointerTypeInContext(#arg, 0) }, - Type::VVRT(vvrt) => { - let vvrt = syn::Ident::new(vvrt, arg.span()); - quote! { #vvrt::llvm_type(#arg) } - } - } -} - -fn get_asttype(ty: &Type) -> TokenStream { - match ty { - Type::None => quote! { ASTType::Nil }, - Type::Integer => quote! { ASTType::Integer }, - Type::Floating => quote! { ASTType::Floating }, - Type::Boolean => quote! { ASTType::Boolean }, - Type::StrPtr => quote! { ASTType::String }, - Type::VVRT("VVRTString") => quote! { ASTType::String }, - Type::VVRT("VVRTTable") => quote! { ASTType::AnyTable }, - Type::VVRT("VVRTSeq") => quote! { ASTType::AnySequence }, - Type::VVRT("VVRTAny") => quote! { ASTType::Any }, - Type::VVRT("VVRTVariant") => quote! { ASTType::Variant }, - Type::VVRT("VVRTType") => quote! { ASTType::Integer }, - Type::VVRT("VVRTLine") => quote! { ASTType::Line }, - Type::VVRT("VVRTSyllabe") => quote! { ASTType::Syllabe }, - ty => unreachable!("unknown type {ty:?}"), - } -} - pub(crate) fn gen_method_infos(name: syn::Ident, infos: Vec<MethodInfo>) -> anyhow::Result<TokenStream> { macro_rules! get_manage { ($role: ident) => { @@ -79,32 +46,11 @@ pub(crate) fn gen_method_infos(name: syn::Ident, infos: Vec<MethodInfo>) -> anyh _ => bail!("clone and drop functions must be present at the same time, or none of them"), }; - let methods = infos.into_iter().map(|(name, _, args, returns)| { - let llvm_returns = gen_type_call(&returns, &arg); - let llvm_args_len: u32 = args.len().try_into().expect("too many arguments"); - let llvm_args = quote_vec!(args - .iter() - .map(|ty| gen_type_call(ty, &arg)) - .reduce(|acc, item| quote! { #acc, #item })); - - let ast_returns = get_asttype(&returns); - let ast_args = quote_vec!(args - .into_iter() - .map(|ty| get_asttype(&ty)) - .reduce(|acc, item| quote! { #acc, #item })); - + let methods = infos.into_iter().map(|(name, ..)| { let func = syn::Ident::new(&name, arg.span()); - quote! {{ let func: unsafe extern "C" fn() = unsafe { std::mem::transmute(#func as *const ()) }; - let mut llvm_args = #llvm_args; // We compute the vector here for safety. - let ast_type = ASTType::Function(#ast_args, Box::new(#ast_returns)); // Also needs the ast type. - let functy = vvs_llvm::sys::LLVMFunctionType( - #llvm_returns, // Should be Ok there. - llvm_args.as_mut_ptr(), #llvm_args_len, // We pre-computed the length here. - 0 // No variadic functions there. - ); - (#name, (func, functy, ast_type)) + (#name, func) }} }); let methods = quote_vec!(methods.reduce(|acc, item| quote! { #acc, #item })); @@ -121,7 +67,6 @@ pub(crate) fn gen_method_infos(name: syn::Ident, infos: Vec<MethodInfo>) -> anyh } unsafe fn llvm_properties(c: LLVMContextRef) -> Properties { - use vvs_lang::ast::ASTType; Properties { manage: #manage, equality: #manage_eq, diff --git a/src/Rust/vvs_runtime/Cargo.toml b/src/Rust/vvs_runtime/Cargo.toml index fe6bd029..25ee1821 100644 --- a/src/Rust/vvs_runtime/Cargo.toml +++ b/src/Rust/vvs_runtime/Cargo.toml @@ -7,15 +7,16 @@ edition.workspace = true license.workspace = true [dependencies] -log.workspace = true -anyhow.workspace = true -serde.workspace = true -serde_json.workspace = true - +log.workspace = true +anyhow.workspace = true +serde.workspace = true +serde_json.workspace = true vvs_runtime_types.workspace = true +vvs_ass.workspace = true vvs_utils.workspace = true -vvs_llvm.workspace = true + +vvs_llvm = { workspace = true, features = ["init", "bindings"] } [build-dependencies] anyhow.workspace = true -vvs_llvm = { workspace = true, features = ["link"] } +vvs_llvm = { workspace = true, features = ["init", "bindings", "link"] } diff --git a/src/Rust/vvs_runtime/src/jit.rs b/src/Rust/vvs_runtime/src/jit.rs index b642be58..411ad78e 100644 --- a/src/Rust/vvs_runtime/src/jit.rs +++ b/src/Rust/vvs_runtime/src/jit.rs @@ -1,5 +1,5 @@ use crate::workers::Workers; -use anyhow::{bail, Context}; +use anyhow::{bail, Context as _}; use std::{ ffi::{c_int, c_void, CStr, CString}, ptr, @@ -8,8 +8,10 @@ use std::{ Arc, }, }; +use vvs_ass::ASSContainer; +use vvs_llvm::prelude::{Context, Module}; use vvs_llvm::sys::*; -use vvs_runtime_types::VVRTSymbol; +use vvs_runtime_types::{types::VVRTTable, VVRTSymbol}; #[allow(dead_code)] pub struct JIT { @@ -65,10 +67,7 @@ extern "C" fn orc_sym_filter(_: *mut c_void, sym: LLVMOrcSymbolStringPoolEntryRe impl JIT { /// Create a new JIT thingy to execute Vivy Script code. pub fn new() -> anyhow::Result<JIT> { - if let Err(err) = vvs_llvm::init::initialize_llvm() { - log::error!("{err}"); - } - + let _ = vvs_llvm::init::initialize_llvm(); unsafe { // Create the jit. let mut jit = ptr::null_mut(); @@ -115,21 +114,16 @@ impl JIT { /// Get the context out of the JIT thingy. Used to create a LLVM module to be used latter by /// the JIT. - pub fn ctx(&self) -> LLVMContextRef { - unsafe { LLVMOrcThreadSafeContextGetContext(self.tsctx) } + pub fn ctx(&self) -> Context { + unsafe { Context::from_ptr(LLVMOrcThreadSafeContextGetContext(self.tsctx)) } } /// Add a new module to the JIT engine. - /// - /// # Safety - /// The passed module ([LLVMModuleRef]) must not be a null pointer and must be a valid LLVM - /// module with valid code for the runtime. This module should be created by the [vvs_codegen] - /// crate or you can have problems executing it... - pub unsafe fn add_module(&self, module: LLVMModuleRef) -> anyhow::Result<()> { - let tsm = LLVMOrcCreateNewThreadSafeModule(module, self.tsctx); - let rc = LLVMOrcLLJITAddLLVMIRModule(self.jit, self.dylib, tsm); - llvm_expect!(rc, "failed to add the module into the jit"); - Ok(()) + pub fn with_module(&self, module: Module) -> anyhow::Result<&Self> { + let tsm = unsafe { LLVMOrcCreateNewThreadSafeModule(module.as_ptr(), self.tsctx) }; + let rc = unsafe { LLVMOrcLLJITAddLLVMIRModule(self.jit, self.dylib, tsm) }; + unsafe { llvm_expect!(rc, "failed to add the module into the jit") }; + Ok(self) } /// LookUp a symbol defined in the JIT. @@ -143,6 +137,11 @@ impl JIT { ); Ok(symbol.with_addr(addr)) } + + /// Run the program on an ass file. + pub fn run_on_file(&self, ass: ASSContainer<VVRTTable>) -> anyhow::Result<&Self> { + unimplemented!("use the ASS container and insert it into the JIT: {ass:#?}") + } } impl Drop for JIT { diff --git a/src/Rust/vvs_runtime_types/Cargo.toml b/src/Rust/vvs_runtime_types/Cargo.toml index 7c652ec6..321d01d0 100644 --- a/src/Rust/vvs_runtime_types/Cargo.toml +++ b/src/Rust/vvs_runtime_types/Cargo.toml @@ -7,18 +7,15 @@ edition.workspace = true license.workspace = true [dependencies] -log.workspace = true -serde.workspace = true -serde_json.workspace = true -anyhow.workspace = true -hashbrown.workspace = true - +log.workspace = true +serde.workspace = true +serde_json.workspace = true +anyhow.workspace = true +hashbrown.workspace = true unicode-segmentation.workspace = true - -vvs_procmacro.workspace = true -vvs_utils.workspace = true -vvs_lang.workspace = true -vvs_ass.workspace = true +vvs_procmacro.workspace = true +vvs_utils.workspace = true +vvs_ass.workspace = true vvs_llvm = { workspace = true, features = ["sys"] } diff --git a/src/Rust/vvs_runtime_types/src/mangle.rs b/src/Rust/vvs_runtime_types/src/mangle.rs index 3003f418..9e3bb2a5 100644 --- a/src/Rust/vvs_runtime_types/src/mangle.rs +++ b/src/Rust/vvs_runtime_types/src/mangle.rs @@ -1,6 +1,5 @@ use anyhow::{anyhow, bail}; use std::{ffi::CString, str::FromStr}; -use vvs_lang::ast::ASTType; use vvs_llvm::sys::*; /// Materialize which type of symbol we have. @@ -70,20 +69,6 @@ impl<'a> VVRTSymbol<'a> { Ok(Self { sym: ty, module, name, addr: None }) } - pub fn method(ty: &ASTType, name: &'a str) -> anyhow::Result<Self> { - let module = match ty { - ASTType::Any => "Any", - ASTType::Line => "Line", - ASTType::String => "String", - ASTType::Syllabe => "Syllabe", - ty if ty.is_variant() => "Variant", - ty if ty.is_table() => "Table", - ty if ty.is_sequence() => "Sequence", - ty => bail!("can't have methods for type `{ty}`"), - }; - Ok(VVRTSymbol { sym: VVRTSymbolType::Method, module, name, addr: None }) - } - /// Builder-like syntax to take a symbol and associate it to an execution address gotten from /// the JIT engine. We ignore any previous address. pub fn with_addr(mut self, addr: LLVMOrcExecutorAddress) -> Self { diff --git a/src/Rust/vvs_runtime_types/src/vvll.rs b/src/Rust/vvs_runtime_types/src/vvll.rs index b10ea344..85650122 100644 --- a/src/Rust/vvs_runtime_types/src/vvll.rs +++ b/src/Rust/vvs_runtime_types/src/vvll.rs @@ -1,8 +1,7 @@ #![allow(non_snake_case)] use hashbrown::{HashMap, HashSet}; -use std::{ffi::CString, sync::OnceLock}; -use vvs_lang::ast::ASTType; +use std::sync::OnceLock; use vvs_llvm::sys::*; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -29,7 +28,7 @@ pub struct Properties { pub len: Option<&'static str>, /// Stores all the methods with some informations about them. - pub methods: HashMap<&'static str, (unsafe extern "C" fn(), LLVMTypeRef, ASTType)>, + pub methods: HashMap<&'static str, unsafe extern "C" fn()>, } /// Trait used to give to the codegen the layout and functions associated with a type that is @@ -64,38 +63,3 @@ unsafe fn managed_types() -> &'static mut HashSet<LLVMTypeRef> { MANAGED_TYPES.get_or_init(HashSet::new); MANAGED_TYPES.get_mut().expect(" should have been created") } - -/// Register all the exported types and functions into a module. -/// -/// # Safety -/// - The [LLVMContextRef] must be valid -/// - the [LLVMModuleRef] must be valid -/// - This function is not thread-safe -pub unsafe fn LLVMRegisterExportedIntoModule( - c: LLVMContextRef, - m: LLVMModuleRef, -) -> Vec<(&'static str, ASTType, LLVMValueRef, LLVMTypeRef)> { - let mut ret = Vec::default(); - - macro_rules! handle { - ($ty: ty, $($tys: ty),+ $(,)?) => { - handle!($ty); - handle!($($tys),+); - }; - ($ty: ty) => { - let Properties { manage, equality: _, len: _, methods } = <$ty>::llvm_properties(c); - if manage.is_some() { - managed_types().insert(<$ty>::llvm_type(c)); - } - for (name, (_, ty, asttype)) in methods { - let cstr = CString::new(name).expect("invalid function name"); - let function = unsafe { LLVMAddFunction(m, cstr.as_ptr(), ty) }; - ret.push((name, asttype, function, ty)); - } - }; - } - - use crate::types::*; - handle!(VVRTType, VVRTAny, VVRTSeq, VVRTString, VVRTTable); - ret -} diff --git a/src/Rust/vvs_shortstring/Cargo.toml b/src/Rust/vvs_shortstring/Cargo.toml new file mode 100644 index 00000000..bd1e3222 --- /dev/null +++ b/src/Rust/vvs_shortstring/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "vvs_shortstring" + +license.workspace = true +version.workspace = true +edition.workspace = true + +description = "A short string wrapper from full-moon initialy" + +authors = [ + "Maëlle Martin <maelle.martin@proton.me>", + "Kampfkarren <kampfkarren@gmail.com>", +] + +[dependencies] +serde.workspace = true +smol_str.workspace = true diff --git a/src/Rust/vvs_parser/src/short_string.rs b/src/Rust/vvs_shortstring/src/lib.rs similarity index 100% rename from src/Rust/vvs_parser/src/short_string.rs rename to src/Rust/vvs_shortstring/src/lib.rs diff --git a/src/Rust/vvs_utils/src/xdg/config.rs b/src/Rust/vvs_utils/src/xdg/config.rs index 49339f5f..0c70a533 100644 --- a/src/Rust/vvs_utils/src/xdg/config.rs +++ b/src/Rust/vvs_utils/src/xdg/config.rs @@ -1,9 +1,8 @@ //! Utilities to extend the [XDGFolder] thing with config files: take into account the //! deserialization, merge of configs and others. -use crate::xdg::{MaybeFolderList, XDGError, XDGFindBehaviour, XDGFolder}; -use serde::{Deserialize, Serialize}; -use std::rc::Rc; +use crate::xdg::{MaybeFolderList, XDGConfigFileSerde, XDGError, XDGFindBehaviour, XDGFolder}; +use std::{fs, marker}; /// Search configurations in all config folders and merge them. #[derive(Debug)] @@ -20,63 +19,46 @@ pub struct XDGConfigFirst; #[derive(Debug)] pub struct XDGConfigMergedSilent; -pub type DeserializeFunctionPtr<'a, Format> = - Rc<dyn Fn(String) -> Result<Format, Box<dyn std::error::Error>> + 'static>; - /// We will write one impl block for merged searches. mod private_merged { - use crate::xdg::*; - use serde::Deserialize; - use std::collections::VecDeque; + use super::*; /// Search with merging. pub trait Sealed: Sized { - fn try_read<'a, Format>(config: &XDGConfig<'a, Format, Self>) -> Result<Format, XDGError> - where - Format: for<'de> Deserialize<'de> + Extend<Format>; + fn try_read<Format: XDGConfigFileSerde>(config: &XDGConfig<Format, Self>) -> Result<Format, XDGError>; } impl Sealed for XDGConfigMergedSilent { - fn try_read<'a, Format>(config: &XDGConfig<'a, Format, Self>) -> Result<Format, XDGError> - where - Format: for<'de> Deserialize<'de> + Extend<Format>, - { - use XDGError::*; - let result = config.search_files()?.into_iter().filter_map(|file| { - let file = std::fs::read_to_string(file) - .map_err(|err| log::error!(target: "xdg", "{}", ConfigIO(config.app.to_string(), config.get_file().to_string(), err))) - .ok()?; - config.deserialize.as_ref()(file) - .map_err(|err| log::error!(target: "xdg", "{}", DeserializeError(config.app.to_string(), config.get_file().to_string(), err))) - .ok() - }); - - let mut result: VecDeque<Format> = result.collect(); + fn try_read<Format: XDGConfigFileSerde>(config: &XDGConfig<Format, Self>) -> Result<Format, XDGError> { + let mut result = config.search_files()?.into_iter().filter_map(|file| { + let file = Result::ok(fs::read_to_string(file) + .map_err(|err| log::error!(target: "xdg", "{}", XDGError::ConfigIO(config.app.to_string(), config.get_file().to_string(), err))))?; + Result::ok(Format::deserialize(&file) + .map_err(|e| log::error!(target: "xdg", "deserialize error for {} on file {}: {e}", config.app, config.get_file()))) + }); + let mut first = result - .pop_front() - .ok_or(ConfigNotFound(config.app.to_string(), config.get_file().to_string()))?; + .next() + .ok_or_else(|| XDGError::ConfigNotFound(config.app.to_string(), config.get_file().to_string()))?; first.extend(result); Ok(first) } } impl Sealed for XDGConfigMerged { - fn try_read<'a, Format>(config: &XDGConfig<'a, Format, Self>) -> Result<Format, XDGError> - where - Format: for<'de> Deserialize<'de> + Extend<Format>, - { - use XDGError::*; - let mut result: VecDeque<Format> = Default::default(); - for file in config.search_files()?.into_iter() { - let file = std::fs::read_to_string(file) - .map_err(|err| ConfigIO(config.app.to_string(), config.get_file().to_string(), err))?; - let file = config.deserialize.as_ref()(file) - .map_err(|err| DeserializeError(config.app.to_string(), config.get_file().to_string(), err))?; - result.push_back(file); - } + fn try_read<Format: XDGConfigFileSerde>(config: &XDGConfig<Format, Self>) -> Result<Format, XDGError> { + let mut result = Iterator::collect::<Result<Vec<_>, _>>(config.search_files()?.into_iter().map(|file| { + let file = fs::read_to_string(file) + .map_err(|err| XDGError::ConfigIO(config.app.to_string(), config.get_file().to_string(), err))?; + let file = Format::deserialize(&file).map_err(|err| { + log::error!(target: "xdg", "deserialize error for application {} on file {}: {err}", config.app, config.get_file()); + XDGError::DeserializeError(config.app.to_string(), config.get_file().to_string()) + })?; + Ok(file) + }))?.into_iter(); let mut first = result - .pop_front() - .ok_or(ConfigNotFound(config.app.to_string(), config.get_file().to_string()))?; + .next() + .ok_or_else(|| XDGError::ConfigNotFound(config.app.to_string(), config.get_file().to_string()))?; first.extend(result); Ok(first) } @@ -98,60 +80,30 @@ mod private_merged { /// - If the `Merged` parameter is [XDGConfigMerged] and `Format` implements [Extend] on itself, /// then all the found files are parsed and then merged, an error is returned on the first /// failure of the deserialization process. -pub struct XDGConfig<'a, Format, Merged> -where - Format: for<'de> Deserialize<'de>, -{ +#[derive(Debug)] +pub struct XDGConfig<'a, Format: XDGConfigFileSerde, Merged> { /// The application name. app: &'a str, /// The file name. If not present will be defaulted to `config` at resolution time. file: Option<&'a str>, - /// Deserializer function. - deserialize: DeserializeFunctionPtr<'a, Format>, - /// Stores the format for deserialization. - _type: std::marker::PhantomData<Format>, + _type: marker::PhantomData<Format>, /// Stores the format for deserialization. - _merged: std::marker::PhantomData<Merged>, + _merged: marker::PhantomData<Merged>, } -impl<'a, Format, Merged> std::fmt::Debug for XDGConfig<'a, Format, Merged> -where - Format: for<'de> Deserialize<'de>, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("XDGConfig") - .field("app", &self.app) - .field("file", &self.get_file()) - .field("merged", &self._merged) - .finish() - } -} - -impl<'a, Format, Merged> XDGConfig<'a, Format, Merged> -where - Format: for<'de> Deserialize<'de>, -{ +impl<'a, Format: XDGConfigFileSerde, Merged> XDGConfig<'a, Format, Merged> { /// By default we say that the default config file is ${FOLDER}/${APP}/config like many /// applications do. const DEFAULT_FILE: &'static str = "config"; /// Create a new [XDGConfig] helper. #[inline] - pub fn new( - app: &'a str, - deserialize: impl Fn(String) -> Result<Format, Box<dyn std::error::Error>> + 'static, - ) -> Self { - Self { - app, - deserialize: Rc::new(deserialize), - file: Default::default(), - _type: Default::default(), - _merged: Default::default(), - } + pub fn new(app: &'a str) -> Self { + Self { app, file: Default::default(), _type: Default::default(), _merged: Default::default() } } /// Change the file to resolve. @@ -184,38 +136,31 @@ where } } -impl<'a, Format> XDGConfig<'a, Format, XDGConfigFirst> -where - Format: for<'de> Deserialize<'de>, -{ +impl<'a, Format: XDGConfigFileSerde> XDGConfig<'a, Format, XDGConfigFirst> { /// Try to read the config file and deserialize it. pub fn try_read(&self) -> Result<Format, XDGError> { let Some(file) = self.search_files()?.into_first() else { return Err(XDGError::ConfigNotFound(self.app.to_string(), self.get_file().to_string())); }; - let file = std::fs::read_to_string(file) + let file = fs::read_to_string(file) .map_err(|err| XDGError::ConfigIO(self.app.to_string(), self.get_file().to_string(), err))?; - self.deserialize.as_ref()(file) - .map_err(|err| XDGError::DeserializeError(self.app.to_string(), self.get_file().to_string(), err)) + Format::deserialize(&file) + .map_err(|err| { + log::error!(target: "xdg", "deserialize error on application {} on file {}: {err}", self.app, self.get_file()); + XDGError::DeserializeError(self.app.to_string(), self.get_file().to_string()) + }) } } -impl<'a, Format> XDGConfig<'a, Format, XDGConfigFirst> -where - Format: for<'de> Deserialize<'de> + Serialize, -{ +impl<'a, Format: XDGConfigFileSerde> XDGConfig<'a, Format, XDGConfigFirst> { /// Try to read the config file and deserialize it. If an error is encountred at any point, log /// it and return the provided default. If needed the default value is written on the disk, /// note that this operation may fail silently. #[inline] - pub fn read_or( - &self, - serialize: impl FnOnce(&Format) -> Result<String, Box<dyn std::error::Error>>, - default: Format, - ) -> Format { + pub fn read_or(&self, default: Format) -> Format { self.try_read().unwrap_or_else(|err| { log::error!(target: "xdg", "read error, return default value to user: {err}"); - self.write_silent(serialize, default) + self.write_silent(default) }) } @@ -223,36 +168,28 @@ where /// it and return the provided default. If needed the default value is written on the disk, /// note that this operation may fail silently. #[inline] - pub fn read_or_else( - &self, - serialize: impl FnOnce(&Format) -> Result<String, Box<dyn std::error::Error>>, - cb: impl FnOnce() -> Format, - ) -> Format { + pub fn read_or_else(&self, cb: impl FnOnce() -> Format) -> Format { self.try_read().unwrap_or_else(|err| { log::error!(target: "xdg", "read error, return default value to user: {err}"); - self.write_silent(serialize, cb()) + self.write_silent(cb()) }) } /// Write a value to the default config file location, the one with the higher priority /// (usually the user's one) - fn write( - &self, - serialize: impl FnOnce(&Format) -> Result<String, Box<dyn std::error::Error>>, - value: &Format, - ) -> Result<(), XDGError> { - let content = serialize(value) - .map_err(|err| XDGError::SerializeError(self.app.to_string(), self.get_file().to_string(), err))?; - let Some(path) = XDGFolder::ConfigDirs + fn write(&self, value: &Format) -> Result<(), XDGError> { + let content = Format::serialize(value) + .map_err(|err| { + log::error!(target: "xdg", "serialize error for application {} on file {}: {err}", self.app, self.get_file()); + XDGError::SerializeError(self.app.to_string(), self.get_file().to_string()) + })?; + + let path = XDGFolder::ConfigDirs .find(self.app, self.get_file(), XDGFindBehaviour::FirstOrCreate)? .into_first() - else { - unreachable!( - "the user must have at least one location to place a config file, permission or fs quota problem?" - ) - }; + .expect("the user must have at least one location to place a config file, permission or fs quota problem?"); - match std::fs::create_dir_all( + match fs::create_dir_all( path.parent() .ok_or(XDGError::ConfigFileHasNoParentFolder(self.app.to_string(), self.get_file().to_string()))?, ) { @@ -262,65 +199,44 @@ where _ => {} } - std::fs::write(path, content) - .map_err(|err| XDGError::ConfigIO(self.app.to_string(), self.get_file().to_string(), err))?; - - Ok(()) + fs::write(path, content) + .map_err(|err| XDGError::ConfigIO(self.app.to_string(), self.get_file().to_string(), err)) } /// Same as [XDGConfig::write] but log any error and fail silently, returning the value that /// was attempted to be written to disk. #[inline] - fn write_silent( - &self, - serialize: impl FnOnce(&Format) -> Result<String, Box<dyn std::error::Error>>, - value: Format, - ) -> Format { - if let Err(err) = self.write(serialize, &value) { + fn write_silent(&self, value: Format) -> Format { + if let Err(err) = self.write(&value) { log::error!(target: "xdg", "failed to write default with err: {err}") } value } } -impl<'a, Format> XDGConfig<'a, Format, XDGConfigFirst> -where - Format: for<'de> Deserialize<'de> + Serialize + Default, -{ +impl<'a, Format: XDGConfigFileSerde + Default> XDGConfig<'a, Format, XDGConfigFirst> { /// Try to read the config file and deserialize it. If an error is encountred at any point, log /// it and return the default. If needed the default value is written on the disk, note that /// this operation may fail silently. #[inline] - pub fn read_or_default( - &self, - serialize: impl FnOnce(&Format) -> Result<String, Box<dyn std::error::Error>>, - ) -> Format { + pub fn read_or_default(&self) -> Format { self.try_read().unwrap_or_else(|err| { log::error!(target: "xdg", "read error, return default value to user: {err}"); - self.write_silent(serialize, Default::default()) + self.write_silent(Default::default()) }) } } // XDGConfigMerged + XDGConfigMergedSilent -impl<'a, Format, Merged> XDGConfig<'a, Format, Merged> -where - Format: for<'de> Deserialize<'de> + Extend<Format>, - Merged: private_merged::Sealed, -{ +impl<'a, Format: XDGConfigFileSerde, Merged: private_merged::Sealed> XDGConfig<'a, Format, Merged> { /// When trying to read or write the default, we write the file with the same logic as the /// [XDGConfigFirst] variant, we add this function to reduce the code to write for the write /// logic... #[inline] fn to_config_first(&self) -> XDGConfig<Format, XDGConfigFirst> { - XDGConfig::<Format, XDGConfigFirst> { - app: self.app, - file: self.file, - deserialize: self.deserialize.clone(), - _type: std::marker::PhantomData, - _merged: std::marker::PhantomData, - } + let Self { app, file, .. } = *self; + XDGConfig::<Format, XDGConfigFirst> { app, file, _type: marker::PhantomData, _merged: marker::PhantomData } } /// Try to read the config files and deserialize them. If one config file failed to be @@ -334,24 +250,16 @@ where } } -impl<'a, Format, Merged> XDGConfig<'a, Format, Merged> -where - Format: for<'de> Deserialize<'de> + Extend<Format> + Serialize, - Merged: private_merged::Sealed, -{ +impl<'a, Format: XDGConfigFileSerde, Merged: private_merged::Sealed> XDGConfig<'a, Format, Merged> { /// Try to read the config files and deserialize them. If an error is encountred at any point, /// log it and return the provided default if the merge was not silent, skip the file if it was /// silent. If needed the default value is written on the disk, note that this operation may /// fail silently. #[inline] - pub fn read_or( - &self, - serialize: impl FnOnce(&Format) -> Result<String, Box<dyn std::error::Error>>, - default: Format, - ) -> Format { + pub fn read_or(&self, default: Format) -> Format { self.try_read().unwrap_or_else(|err| { log::error!(target: "xdg", "read error, return default value to user: {err}"); - self.to_config_first().write_silent(serialize, default) + self.to_config_first().write_silent(default) }) } @@ -360,35 +268,24 @@ where /// silent. If needed the default value is written on the disk, note that this operation may /// fail silently. #[inline] - pub fn read_or_else( - &self, - serialize: impl FnOnce(&Format) -> Result<String, Box<dyn std::error::Error>>, - cb: impl FnOnce() -> Format, - ) -> Format { + pub fn read_or_else(&self, cb: impl FnOnce() -> Format) -> Format { self.try_read().unwrap_or_else(|err| { log::error!(target: "xdg", "read error, return default value to user: {err}"); - self.to_config_first().write_silent(serialize, cb()) + self.to_config_first().write_silent(cb()) }) } } -impl<'a, Format, Merged> XDGConfig<'a, Format, Merged> -where - Format: for<'de> Deserialize<'de> + Extend<Format> + Serialize + Default, - Merged: private_merged::Sealed, -{ +impl<'a, Format: XDGConfigFileSerde + Default, Merged: private_merged::Sealed> XDGConfig<'a, Format, Merged> { /// Try to read the config files and deserialize them. If an error is encountred at any point, /// log it and return the provided default if the merge was not silent, skip the file if it was /// silent. If needed the default value is written on the disk, note that this operation may /// fail silently. #[inline] - pub fn read_or_default( - &self, - serialize: impl FnOnce(&Format) -> Result<String, Box<dyn std::error::Error>>, - ) -> Format { + pub fn read_or_default(&self) -> Format { self.try_read().unwrap_or_else(|err| { log::error!(target: "xdg", "read error, return default value to user: {err}"); - self.to_config_first().write_silent(serialize, Default::default()) + self.to_config_first().write_silent(Default::default()) }) } } diff --git a/src/Rust/vvs_utils/src/xdg/mod.rs b/src/Rust/vvs_utils/src/xdg/mod.rs index 54ca2644..8435840a 100644 --- a/src/Rust/vvs_utils/src/xdg/mod.rs +++ b/src/Rust/vvs_utils/src/xdg/mod.rs @@ -8,11 +8,9 @@ mod config; mod folders; mod options; mod paths; +mod traits; -pub use config::*; -pub use folders::*; -pub use options::*; -pub use paths::*; +pub use self::{config::*, folders::*, options::*, paths::*, traits::*}; use std::{io::Error as IoError, path::PathBuf}; use thiserror::Error; @@ -40,9 +38,9 @@ pub enum XDGError { #[error("io error for config file {1} for application {0}: {2}")] ConfigIO(String, String, IoError), - #[error("deserialization failed on file {1} for application {0} with: {2}")] - DeserializeError(String, String, Box<dyn std::error::Error>), + #[error("deserialization failed on file {1} for application {0}")] + DeserializeError(String, String), - #[error("serialization failed on file {1} for application {0} with: {2}")] - SerializeError(String, String, Box<dyn std::error::Error>), + #[error("serialization failed on file {1} for application {0}")] + SerializeError(String, String), } diff --git a/src/Rust/vvs_utils/src/xdg/traits.rs b/src/Rust/vvs_utils/src/xdg/traits.rs new file mode 100644 index 00000000..728ef7cf --- /dev/null +++ b/src/Rust/vvs_utils/src/xdg/traits.rs @@ -0,0 +1,7 @@ +pub trait XDGConfigFileSerde: Sized + Extend<Self> { + type Error: std::error::Error; + + fn deserialize(input: &str) -> Result<Self, Self::Error>; + + fn serialize(&self) -> Result<String, Self::Error>; +} -- GitLab