diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..65c4363 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,53 @@ +name: CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +env: + CARGO_TERM_COLOR: always + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Run tests + run: cargo test --workspace + continue-on-error: true + + - name: Check formatting + run: cargo fmt --all -- --check + + - name: Clippy + run: cargo clippy --workspace + + build-check: + name: Build Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Build + run: cargo build --release diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b3ec15d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,153 @@ +name: Release + +on: + push: + tags: + - 'v*' + +env: + CARGO_TERM_COLOR: always + BINARY_NAME: typr + +permissions: + contents: write + +jobs: + build: + name: Build ${{ matrix.target }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + # Linux + - target: x86_64-unknown-linux-gnu + os: ubuntu-latest + archive: tar.gz + - target: aarch64-unknown-linux-gnu + os: ubuntu-latest + archive: tar.gz + cross: true + + # Windows + - target: x86_64-pc-windows-msvc + os: windows-latest + archive: zip + - target: aarch64-pc-windows-msvc + os: windows-latest + archive: zip + + # macOS + - target: x86_64-apple-darwin + os: macos-latest + archive: tar.gz + - target: aarch64-apple-darwin + os: macos-latest + archive: tar.gz + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Install cross (Linux ARM) + if: matrix.cross + run: cargo install cross --git https://github.com/cross-rs/cross + + - name: Build (cross) + if: matrix.cross + run: cross build --release --target ${{ matrix.target }} + + - name: Build (native) + if: ${{ !matrix.cross }} + run: cargo build --release --target ${{ matrix.target }} + + - name: Prepare artifacts (Unix) + if: matrix.os != 'windows-latest' + run: | + cd target/${{ matrix.target }}/release + tar -czvf ../../../${{ env.BINARY_NAME }}-${{ github.ref_name }}-${{ matrix.target }}.tar.gz ${{ env.BINARY_NAME }} + + - name: Prepare artifacts (Windows) + if: matrix.os == 'windows-latest' + shell: pwsh + run: | + cd target/${{ matrix.target }}/release + Compress-Archive -Path ${{ env.BINARY_NAME }}.exe -DestinationPath ../../../${{ env.BINARY_NAME }}-${{ github.ref_name }}-${{ matrix.target }}.zip + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ env.BINARY_NAME }}-${{ matrix.target }} + path: ${{ env.BINARY_NAME }}-${{ github.ref_name }}-${{ matrix.target }}.${{ matrix.archive }} + + release: + name: Create Release + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + + - name: Generate checksums + run: | + cd artifacts + sha256sum * > checksums.txt + cat checksums.txt + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + files: | + artifacts/* + generate_release_notes: true + draft: false + prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build-wasm: + name: Build WASM + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + + - name: Build WASM + run: wasm-pack build --release --target web crates/typr-wasm + + - name: Prepare WASM artifacts + run: | + mkdir -p wasm-dist + cp crates/typr-wasm/pkg/*.wasm wasm-dist/ + cp crates/typr-wasm/pkg/*.js wasm-dist/ + cd wasm-dist + tar -czvf ../typr-wasm-${{ github.ref_name }}.tar.gz * + + - name: Upload WASM artifact + uses: actions/upload-artifact@v4 + with: + name: typr-wasm + path: typr-wasm-${{ github.ref_name }}.tar.gz + + - name: Add WASM to Release + uses: softprops/action-gh-release@v2 + with: + files: typr-wasm-${{ github.ref_name }}.tar.gz + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a67f704 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "playground"] + path = playground + url = git@github.com:we-data-ch/typr-playground.github.io.git diff --git a/.header.bin b/.header.bin deleted file mode 100644 index a6abbb8..0000000 Binary files a/.header.bin and /dev/null differ diff --git a/.name_list.bin b/.name_list.bin deleted file mode 100644 index a45ba3c..0000000 Binary files a/.name_list.bin and /dev/null differ diff --git a/Cargo.lock b/Cargo.lock index 376005f..b9417c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,6 +155,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + [[package]] name = "bytecount" version = "0.6.8" @@ -334,35 +340,6 @@ version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" -[[package]] -name = "extendr-api" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea54977c6e37236839ffcbc20b5dcea58aa32ae43fbef54a81e1011dc6b19061" -dependencies = [ - "extendr-ffi", - "extendr-macros", - "once_cell", - "paste", -] - -[[package]] -name = "extendr-ffi" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c76777174a82bdb3e66872f580687d3d0143eed1df9b9cd72b321b9596a23ca7" - -[[package]] -name = "extendr-macros" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "661cc4ae29de9c4dafe16cfcbda1dbb9f31bd2568f96ebad232cc1f9bcc8b04d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "fd-lock" version = "4.0.4" @@ -495,11 +472,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "rand_core", "wasip2", "wasip3", + "wasm-bindgen", ] [[package]] @@ -688,6 +667,16 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" +[[package]] +name = "js-sys" +version = "0.3.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f0862381daaec758576dcc22eb7bbf4d7efd67328553f3b45a412a51a3fb21" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "leb128fmt" version = "0.1.0" @@ -916,12 +905,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "percent-encoding" version = "2.3.2" @@ -1107,6 +1090,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "rustyline" version = "17.0.2" @@ -1166,6 +1155,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -1557,11 +1557,18 @@ checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" [[package]] name = "typr" version = "0.4.19" +dependencies = [ + "typr-cli", + "typr-core", +] + +[[package]] +name = "typr-cli" +version = "0.4.19" dependencies = [ "anyhow", "bincode", "clap", - "extendr-api", "indexmap", "lsp-types", "miette", @@ -1577,6 +1584,38 @@ dependencies = [ "thiserror", "tokio", "tower-lsp", + "typr-core", +] + +[[package]] +name = "typr-core" +version = "0.4.19" +dependencies = [ + "anyhow", + "bincode", + "getrandom", + "indexmap", + "miette", + "nom", + "nom_locate", + "rand", + "rpds", + "serde", + "serde_json", + "tap", + "thiserror", +] + +[[package]] +name = "typr-wasm" +version = "0.4.19" +dependencies = [ + "serde", + "serde-wasm-bindgen", + "serde_json", + "typr-core", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -1674,6 +1713,51 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de241cdc66a9d91bd84f097039eb140cdc6eec47e0cdbaf9d932a1dd6c35866" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12fdf6649048f2e3de6d7d5ff3ced779cdedee0e0baffd7dff5cdfa3abc8a52" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e63d1795c565ac3462334c1e396fd46dbf481c40f51f5072c310717bc4fb309" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f9cdac23a5ce71f6bf9f8824898a501e511892791ea2a0c6b8568c68b9cb53" +dependencies = [ + "unicode-ident", +] + [[package]] name = "wasm-encoder" version = "0.244.0" @@ -1708,6 +1792,16 @@ dependencies = [ "semver", ] +[[package]] +name = "web-sys" +version = "0.3.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c7c5718134e770ee62af3b6b4a84518ec10101aad610c024b64d6ff29bb1ff" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi-util" version = "0.1.11" diff --git a/Cargo.toml b/Cargo.toml index ac2836c..0331c83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,32 +1,69 @@ +[workspace] +resolver = "2" +members = [ + ".", + "crates/typr-core", + "crates/typr-cli", + "crates/typr-wasm", +] + [package] name = "typr" +description = "A typed superset of R - transpiler and type checker" +keywords = ["R", "type-checker", "language", "transpiler"] +categories = ["compilers", "development-tools"] +readme = "README.md" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[[bin]] +name = "typr" +path = "src/main.rs" + +[dependencies] +typr-cli.workspace = true + +[dev-dependencies] +typr-core.workspace = true + +[workspace.package] version = "0.4.19" edition = "2021" authors = ["Fabrice Hategekimana "] -description = "A superset of the legendary R" license = "Apache-2.0" repository = "https://github.com/fabriceHategekimana/typr" -keywords = ["R", "type-checker", "language", "transpiler"] -readme = "README.md" -[dependencies] +[workspace.dependencies] nom = "8.0.0" +nom_locate = "5.0.0" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.133" -clap = { version = "4.5", features = ["derive"] } thiserror = "2.0.12" miette = { version = "7.6.0", features = ["fancy"] } -nom_locate = "5.0.0" rpds = "1.1.1" -bincode = "1.3" -rustyline = "17.0.2" -syntect = "5.3.0" -extendr-api = "0.8.1" -anyhow = "1.0.100" -lsp-types = "0.94" -tower-lsp = "0.20" -tokio = { version = "1", features = ["full"] } indexmap = { version = "2.13.0", features = ["serde"] } tap = "1.0.1" rand = "0.10.0" +anyhow = "1.0.100" + +clap = { version = "4.5", features = ["derive"] } +tokio = { version = "1", features = ["full"] } +tower-lsp = "0.20" +lsp-types = "0.94" +rustyline = "17.0.2" +syntect = "5.3.0" +bincode = "1.3" +extendr-api = "0.8.1" + +typr-core = { path = "crates/typr-core" } +typr-cli = { path = "crates/typr-cli" } +typr-wasm = { path = "crates/typr-wasm" } +[profile.release] +opt-level = 3 +lto = true +codegen-units = 1 +strip = true diff --git a/build-wasm.sh b/build-wasm.sh new file mode 100644 index 0000000..425feea --- /dev/null +++ b/build-wasm.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +wasm-pack build --release --target web crates/typr-wasm + +cp crates/typr-wasm/pkg/*.wasm playground/public/wasm/ +cp crates/typr-wasm/pkg/*.js playground/public/wasm/ + +echo "WASM built and copied to playground/public/wasm/" diff --git a/crates/typr-cli/Cargo.toml b/crates/typr-cli/Cargo.toml new file mode 100644 index 0000000..4496bfd --- /dev/null +++ b/crates/typr-cli/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "typr-cli" +description = "Command-line interface, REPL, and LSP server for TypR - a typed superset of R" +keywords = ["R", "type-checker", "cli", "lsp", "repl"] +readme = "README.md" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[[bin]] +name = "typr" +path = "src/main.rs" + +[lib] +name = "typr_cli" +path = "src/lib.rs" + +[dependencies] +# Use typr-core for core logic +typr-core.workspace = true + +# Pure dependencies (needed for parsing/type manipulation) +nom.workspace = true +nom_locate.workspace = true +serde.workspace = true +serde_json.workspace = true +thiserror.workspace = true +miette.workspace = true +rpds.workspace = true +indexmap.workspace = true +tap.workspace = true +rand.workspace = true +anyhow.workspace = true + +# CLI/System dependencies (NOT WASM-compatible) +clap.workspace = true +tokio.workspace = true +tower-lsp.workspace = true +lsp-types.workspace = true +rustyline.workspace = true +syntect.workspace = true +bincode.workspace = true diff --git a/crates/typr-cli/README.md b/crates/typr-cli/README.md new file mode 100644 index 0000000..64d5b50 --- /dev/null +++ b/crates/typr-cli/README.md @@ -0,0 +1,54 @@ +# TypR CLI + +Command-line interface, REPL, and LSP server for TypR - a typed superset of R. + +This crate provides the CLI layer for TypR, depending on `typr-core` for the core logic. It includes: + +- **Command-line interface** with project management commands +- **Interactive REPL** with syntax highlighting +- **Language Server Protocol (LSP) server** for IDE integration +- **Filesystem-based source and output handlers** + +## Usage + +```bash +# Create a new project +typr new myproject + +# Check types +typr check + +# Build to R +typr build + +# Run +typr run + +# Start REPL +typr repl + +# Start LSP server +typr lsp +``` + +## Architecture + +This crate follows the same design philosophy as `typr-wasm`: +- **Minimal wrapper** - Only CLI-specific code, no business logic +- **Uses typr-core abstractions** - Implements `SourceProvider` for filesystem +- **Clear dependency separation** - CLI deps (clap, tokio, tower-lsp) stay here + +## Modules + +- `cli` - Main CLI entry point with clap +- `repl` - Interactive REPL with rustyline +- `lsp` - LSP server with tower-lsp +- `project` - Project management commands +- `fs_provider` - Filesystem implementations of core traits +- `engine` - Build/compile utilities +- `io` - R execution utilities +- `metaprogramming` - Module expansion + +## License + +Apache-2.0 diff --git a/crates/typr-cli/configs/.Rbuildignore b/crates/typr-cli/configs/.Rbuildignore new file mode 100644 index 0000000..9d10976 --- /dev/null +++ b/crates/typr-cli/configs/.Rbuildignore @@ -0,0 +1,5 @@ +^.*\.Rproj$ +^\.Rproj\.user$ +^TypR$ +^\.git$ +^\.gitignore$ diff --git a/crates/typr-cli/configs/.gitignore b/crates/typr-cli/configs/.gitignore new file mode 100644 index 0000000..97e50fd --- /dev/null +++ b/crates/typr-cli/configs/.gitignore @@ -0,0 +1,6 @@ +r#"^.*\.Rproj$ +^\.Rproj\.user$ +^TypR$ +^\.git$ +^\.gitignore$ +"# diff --git a/crates/typr-cli/configs/.gitkeep b/crates/typr-cli/configs/.gitkeep new file mode 100644 index 0000000..01c205c --- /dev/null +++ b/crates/typr-cli/configs/.gitkeep @@ -0,0 +1,2 @@ +# This file ensures the R directory is tracked by git + diff --git a/crates/typr-cli/configs/.gitkeep2 b/crates/typr-cli/configs/.gitkeep2 new file mode 100644 index 0000000..094baac --- /dev/null +++ b/crates/typr-cli/configs/.gitkeep2 @@ -0,0 +1 @@ +# This file ensures the man directory is tracked by git diff --git a/crates/typr-cli/configs/DESCRIPTION b/crates/typr-cli/configs/DESCRIPTION new file mode 100644 index 0000000..2d11700 --- /dev/null +++ b/crates/typr-cli/configs/DESCRIPTION @@ -0,0 +1,13 @@ +Package: {{PACKAGE_NAME}} +Title: What the Package Does (One Line, Title Case) +Version: 0.0.0.9000 +Authors@R: + person("First", "Last", , "first.last@example.com", role = c("aut", "cre")) +Description: What the package does (one paragraph). +License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a + license +Encoding: UTF-8 +Roxygen: list(markdown = TRUE) +RoxygenNote: 7.3.2 +Suggests: + testthat (>= 3.0.0) diff --git a/crates/typr-cli/configs/NAMESPACE b/crates/typr-cli/configs/NAMESPACE new file mode 100644 index 0000000..e97f5e6 --- /dev/null +++ b/crates/typr-cli/configs/NAMESPACE @@ -0,0 +1,4 @@ +# Generated by roxygen2: do not edit by hand + +exportPattern("^[[:alpha:]]+") + diff --git a/crates/typr-cli/configs/README.md b/crates/typr-cli/configs/README.md new file mode 100644 index 0000000..3fae591 --- /dev/null +++ b/crates/typr-cli/configs/README.md @@ -0,0 +1,38 @@ +# {{PACKAGE_NAME}} + +A package generated from TypR code. + +## Installation + +You can install the development version from GitHub: + +```r +# install.packages("devtools") +devtools::install_github("your-username/{{PACKAGE_NAME}}") +``` + +## Usage + +```r +library({{PACKAGE_NAME}}) + +# Your functions will be available here +``` + +## Development + +This package is generated from TypR source code located in the `TypR/` directory. + +To build the package: + +```bash +# Check and build +typr check +typr build + +# Run tests +typr test + +# Build and run +typr run +``` diff --git a/crates/typr-cli/configs/bin/.std_js.bin b/crates/typr-cli/configs/bin/.std_js.bin new file mode 100644 index 0000000..0a8d077 Binary files /dev/null and b/crates/typr-cli/configs/bin/.std_js.bin differ diff --git a/crates/typr-cli/configs/bin/.std_js_typed.bin b/crates/typr-cli/configs/bin/.std_js_typed.bin new file mode 100644 index 0000000..a37240f Binary files /dev/null and b/crates/typr-cli/configs/bin/.std_js_typed.bin differ diff --git a/crates/typr-cli/configs/bin/.std_r.bin b/crates/typr-cli/configs/bin/.std_r.bin new file mode 100644 index 0000000..18229be Binary files /dev/null and b/crates/typr-cli/configs/bin/.std_r.bin differ diff --git a/crates/typr-cli/configs/bin/.std_r_typed.bin b/crates/typr-cli/configs/bin/.std_r_typed.bin new file mode 100644 index 0000000..383afa6 Binary files /dev/null and b/crates/typr-cli/configs/bin/.std_r_typed.bin differ diff --git a/crates/typr-cli/configs/images/TypR_logo.png b/crates/typr-cli/configs/images/TypR_logo.png new file mode 100644 index 0000000..bb31473 Binary files /dev/null and b/crates/typr-cli/configs/images/TypR_logo.png differ diff --git a/crates/typr-cli/configs/images/TypR_logo_black_background.png b/crates/typr-cli/configs/images/TypR_logo_black_background.png new file mode 100644 index 0000000..6a1922e Binary files /dev/null and b/crates/typr-cli/configs/images/TypR_logo_black_background.png differ diff --git a/crates/typr-cli/configs/instructions.md b/crates/typr-cli/configs/instructions.md new file mode 100644 index 0000000..a1e5f26 --- /dev/null +++ b/crates/typr-cli/configs/instructions.md @@ -0,0 +1,11 @@ +Pour commencer + + cd {{PACKAGE_NAME}}, name + # Éditez TypR/main.ty avec votre code TypR" + # Puis utilisez les commandes:" + typr check # Vérifier le code" + typr build # Compiler vers R" + typr run # Compiler et exécuter" + typr test # Lancer les tests" + +Ou ouvrez le projet dans RStudio avec le fichier {{PACKAGE_NAME}}.Rproj", name diff --git a/crates/typr-cli/configs/main.ty b/crates/typr-cli/configs/main.ty new file mode 100644 index 0000000..c349873 --- /dev/null +++ b/crates/typr-cli/configs/main.ty @@ -0,0 +1,5 @@ +# Main TypR code goes here +# This will be compiled to R code + +# Example function +print("Hello world"); diff --git a/crates/typr-cli/configs/package_structure.md b/crates/typr-cli/configs/package_structure.md new file mode 100644 index 0000000..b2fac15 --- /dev/null +++ b/crates/typr-cli/configs/package_structure.md @@ -0,0 +1,20 @@ +structure du package: +{{PACKAGE_NAME}} +├── R/ # Code R généré +├── TypR/ # Code source TypR +│ └── main.ty +├── man/ # Documentation +├── tests/ # Tests +│ ├── testthat.R +│ └── testthat/ +│ └── test-basic.R +├── data/ # Données du package +├── inst/ # Fichiers installés +├── src/ # Code source (C++, Fortran) +├── vignettes/ # Vignettes/tutoriels +├── DESCRIPTION # Métadonnées du package +├── NAMESPACE # Exports et imports +├── README.md # Documentation +├── .Rbuildignore # Fichiers ignorés lors du build +├── .gitignore # Fichiers ignorés par git +└── {{PACKAGE_NAME}}.Rproj # Projet RStudio", name, name); diff --git a/crates/typr-cli/configs/rproj.Rproj b/crates/typr-cli/configs/rproj.Rproj new file mode 100644 index 0000000..672605d --- /dev/null +++ b/crates/typr-cli/configs/rproj.Rproj @@ -0,0 +1,23 @@ +Version: 1.0 + +RestoreWorkspace: No +SaveWorkspace: No +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX + +AutoAppendNewline: Yes +StripTrailingWhitespace: Yes +LineEndingConversion: Posix + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source +PackageRoxygenize: rd,collate,namespace + diff --git a/crates/typr-cli/configs/src/data.toml b/crates/typr-cli/configs/src/data.toml new file mode 100644 index 0000000..078bed5 --- /dev/null +++ b/crates/typr-cli/configs/src/data.toml @@ -0,0 +1 @@ +release_version = 19 diff --git a/crates/typr-cli/configs/src/functions_JS.txt b/crates/typr-cli/configs/src/functions_JS.txt new file mode 100644 index 0000000..d4f2fd4 --- /dev/null +++ b/crates/typr-cli/configs/src/functions_JS.txt @@ -0,0 +1,586 @@ +eval +parseInt +parseFloat +isNaN +isFinite +encodeURI +encodeURIComponent +decodeURI +decodeURIComponent +escape +unescape +setTimeout +clearTimeout +setInterval +clearInterval +queueMicrotask +requestAnimationFrame +cancelAnimationFrame +alert +confirm +prompt +atob +btoa +Object.assign +Object.create +Object.defineProperty +Object.defineProperties +Object.entries +Object.freeze +Object.fromEntries +Object.getOwnPropertyDescriptor +Object.getOwnPropertyDescriptors +Object.getOwnPropertyNames +Object.getOwnPropertySymbols +Object.getPrototypeOf +Object.hasOwn +Object.is +Object.isExtensible +Object.isFrozen +Object.isSealed +Object.keys +Object.preventExtensions +Object.seal +Object.setPrototypeOf +Object.values +Array.from +Array.isArray +Array.of +Array.prototype.at +Array.prototype.concat +Array.prototype.copyWithin +Array.prototype.entries +Array.prototype.every +Array.prototype.fill +Array.prototype.filter +Array.prototype.find +Array.prototype.findIndex +Array.prototype.findLast +Array.prototype.findLastIndex +Array.prototype.flat +Array.prototype.flatMap +Array.prototype.forEach +Array.prototype.includes +Array.prototype.indexOf +Array.prototype.join +Array.prototype.keys +Array.prototype.lastIndexOf +Array.prototype.map +Array.prototype.pop +Array.prototype.push +Array.prototype.reduce +Array.prototype.reduceRight +Array.prototype.reverse +Array.prototype.shift +Array.prototype.slice +Array.prototype.some +Array.prototype.sort +Array.prototype.splice +Array.prototype.toLocaleString +Array.prototype.toReversed +Array.prototype.toSorted +Array.prototype.toSpliced +Array.prototype.toString +Array.prototype.unshift +Array.prototype.values +Array.prototype.with +String.fromCharCode +String.fromCodePoint +String.raw +String.prototype.at +String.prototype.charAt +String.prototype.charCodeAt +String.prototype.codePointAt +String.prototype.concat +String.prototype.endsWith +String.prototype.includes +String.prototype.indexOf +String.prototype.isWellFormed +String.prototype.lastIndexOf +String.prototype.localeCompare +String.prototype.match +String.prototype.matchAll +String.prototype.normalize +String.prototype.padEnd +String.prototype.padStart +String.prototype.repeat +String.prototype.replace +String.prototype.replaceAll +String.prototype.search +String.prototype.slice +String.prototype.split +String.prototype.startsWith +String.prototype.substring +String.prototype.toLocaleLowerCase +String.prototype.toLocaleUpperCase +String.prototype.toLowerCase +String.prototype.toString +String.prototype.toUpperCase +String.prototype.toWellFormed +String.prototype.trim +String.prototype.trimEnd +String.prototype.trimStart +String.prototype.valueOf +Number.isFinite +Number.isInteger +Number.isNaN +Number.isSafeInteger +Number.parseFloat +Number.parseInt +Number.prototype.toExponential +Number.prototype.toFixed +Number.prototype.toLocaleString +Number.prototype.toPrecision +Number.prototype.toString +Number.prototype.valueOf +Math.abs +Math.acos +Math.acosh +Math.asin +Math.asinh +Math.atan +Math.atan2 +Math.atanh +Math.cbrt +Math.ceil +Math.clz32 +Math.cos +Math.cosh +Math.exp +Math.expm1 +Math.floor +Math.fround +Math.hypot +Math.imul +Math.log +Math.log10 +Math.log1p +Math.log2 +Math.max +Math.min +Math.pow +Math.random +Math.round +Math.sign +Math.sin +Math.sinh +Math.sqrt +Math.tan +Math.tanh +Math.trunc +Date.now +Date.parse +Date.UTC +Date.prototype.getDate +Date.prototype.getDay +Date.prototype.getFullYear +Date.prototype.getHours +Date.prototype.getMilliseconds +Date.prototype.getMinutes +Date.prototype.getMonth +Date.prototype.getSeconds +Date.prototype.getTime +Date.prototype.getTimezoneOffset +Date.prototype.getUTCDate +Date.prototype.getUTCDay +Date.prototype.getUTCFullYear +Date.prototype.getUTCHours +Date.prototype.getUTCMilliseconds +Date.prototype.getUTCMinutes +Date.prototype.getUTCMonth +Date.prototype.getUTCSeconds +Date.prototype.setDate +Date.prototype.setFullYear +Date.prototype.setHours +Date.prototype.setMilliseconds +Date.prototype.setMinutes +Date.prototype.setMonth +Date.prototype.setSeconds +Date.prototype.setTime +Date.prototype.setUTCDate +Date.prototype.setUTCFullYear +Date.prototype.setUTCHours +Date.prototype.setUTCMilliseconds +Date.prototype.setUTCMinutes +Date.prototype.setUTCMonth +Date.prototype.setUTCSeconds +Date.prototype.toDateString +Date.prototype.toISOString +Date.prototype.toJSON +Date.prototype.toLocaleDateString +Date.prototype.toLocaleString +Date.prototype.toLocaleTimeString +Date.prototype.toString +Date.prototype.toTimeString +Date.prototype.toUTCString +Date.prototype.valueOf +RegExp.prototype.exec +RegExp.prototype.test +RegExp.prototype.toString +JSON.parse +JSON.stringify +console.assert +console.clear +console.count +console.countReset +console.debug +console.dir +console.dirxml +console.error +console.group +console.groupCollapsed +console.groupEnd +console.info +console.log +console.table +console.time +console.timeEnd +console.timeLog +console.trace +console.warn +Promise.all +Promise.allSettled +Promise.any +Promise.race +Promise.reject +Promise.resolve +Promise.prototype.catch +Promise.prototype.finally +Promise.prototype.then +Map.prototype.clear +Map.prototype.delete +Map.prototype.entries +Map.prototype.forEach +Map.prototype.get +Map.prototype.has +Map.prototype.keys +Map.prototype.set +Map.prototype.values +Set.prototype.add +Set.prototype.clear +Set.prototype.delete +Set.prototype.entries +Set.prototype.forEach +Set.prototype.has +Set.prototype.keys +Set.prototype.values +WeakMap.prototype.delete +WeakMap.prototype.get +WeakMap.prototype.has +WeakMap.prototype.set +WeakSet.prototype.add +WeakSet.prototype.delete +WeakSet.prototype.has +Reflect.apply +Reflect.construct +Reflect.defineProperty +Reflect.deleteProperty +Reflect.get +Reflect.getOwnPropertyDescriptor +Reflect.getPrototypeOf +Reflect.has +Reflect.isExtensible +Reflect.ownKeys +Reflect.preventExtensions +Reflect.set +Reflect.setPrototypeOf +Proxy.revocable +ArrayBuffer.isView +ArrayBuffer.prototype.slice +DataView.prototype.getBigInt64 +DataView.prototype.getBigUint64 +DataView.prototype.getFloat32 +DataView.prototype.getFloat64 +DataView.prototype.getInt8 +DataView.prototype.getInt16 +DataView.prototype.getInt32 +DataView.prototype.getUint8 +DataView.prototype.getUint16 +DataView.prototype.getUint32 +DataView.prototype.setBigInt64 +DataView.prototype.setBigUint64 +DataView.prototype.setFloat32 +DataView.prototype.setFloat64 +DataView.prototype.setInt8 +DataView.prototype.setInt16 +DataView.prototype.setInt32 +DataView.prototype.setUint8 +DataView.prototype.setUint16 +DataView.prototype.setUint32 +Intl.getCanonicalLocales +Intl.Collator +Intl.DateTimeFormat +Intl.DisplayNames +Intl.ListFormat +Intl.Locale +Intl.NumberFormat +Intl.PluralRules +Intl.RelativeTimeFormat +Intl.Segmenter +Symbol.for +Symbol.keyFor +Symbol.prototype.toString +Symbol.prototype.valueOf +BigInt.asIntN +BigInt.asUintN +BigInt.prototype.toLocaleString +BigInt.prototype.toString +BigInt.prototype.valueOf +Error.prototype.toString +Function.prototype.apply +Function.prototype.bind +Function.prototype.call +Function.prototype.toString +Boolean.prototype.toString +Boolean.prototype.valueOf +TypedArray.from +TypedArray.of +TypedArray.prototype.at +TypedArray.prototype.copyWithin +TypedArray.prototype.entries +TypedArray.prototype.every +TypedArray.prototype.fill +TypedArray.prototype.filter +TypedArray.prototype.find +TypedArray.prototype.findIndex +TypedArray.prototype.findLast +TypedArray.prototype.findLastIndex +TypedArray.prototype.forEach +TypedArray.prototype.includes +TypedArray.prototype.indexOf +TypedArray.prototype.join +TypedArray.prototype.keys +TypedArray.prototype.lastIndexOf +TypedArray.prototype.map +TypedArray.prototype.reduce +TypedArray.prototype.reduceRight +TypedArray.prototype.reverse +TypedArray.prototype.set +TypedArray.prototype.slice +TypedArray.prototype.some +TypedArray.prototype.sort +TypedArray.prototype.subarray +TypedArray.prototype.toLocaleString +TypedArray.prototype.toReversed +TypedArray.prototype.toSorted +TypedArray.prototype.toString +TypedArray.prototype.values +TypedArray.prototype.with +Atomics.add +Atomics.and +Atomics.compareExchange +Atomics.exchange +Atomics.isLockFree +Atomics.load +Atomics.notify +Atomics.or +Atomics.store +Atomics.sub +Atomics.wait +Atomics.waitAsync +Atomics.xor +WeakRef.prototype.deref +FinalizationRegistry.prototype.register +FinalizationRegistry.prototype.unregister +fetch +window.open +window.close +window.focus +window.blur +window.stop +window.print +window.postMessage +window.getComputedStyle +window.matchMedia +window.scroll +window.scrollBy +window.scrollTo +window.resizeBy +window.resizeTo +window.moveBy +window.moveTo +crypto.getRandomValues +crypto.randomUUID +performance.now +performance.mark +performance.measure +performance.clearMarks +performance.clearMeasures +performance.getEntries +performance.getEntriesByName +performance.getEntriesByType +structuredClone +document.getElementById() +document.querySelector() +document.querySelectorAll() +document.getElementsByClassName() +document.getElementsByTagName() +document.getElementsByName() +document.createElement() +document.createTextNode() +document.createDocumentFragment() +document.createAttribute() +document.createComment() +document.createEvent() +element.querySelector() +element.querySelectorAll() +element.getElementsByClassName() +element.getElementsByTagName() +element.closest() +element.matches() +element.getAttribute() +element.setAttribute() +element.removeAttribute() +element.hasAttribute() +element.getAttributeNames() +element.toggleAttribute() +element.classList.add() +element.classList.remove() +element.classList.toggle() +element.classList.contains() +element.classList.replace() +element.innerHTML +element.outerHTML +element.textContent +element.innerText +element.appendChild() +element.append() +element.prepend() +element.insertBefore() +element.insertAdjacentElement() +element.insertAdjacentHTML() +element.insertAdjacentText() +element.removeChild() +element.remove() +element.replaceChild() +element.replaceWith() +element.cloneNode() +element.parentNode +element.parentElement +element.children +element.childNodes +element.firstChild +element.firstElementChild +element.lastChild +element.lastElementChild +element.nextSibling +element.nextElementSibling +element.previousSibling +element.previousElementSibling +element.id +element.className +element.tagName +element.nodeName +element.nodeType +element.nodeValue +element.getBoundingClientRect() +element.getClientRects() +element.offsetWidth +element.offsetHeight +element.offsetTop +element.offsetLeft +element.offsetParent +element.clientWidth +element.clientHeight +element.clientTop +element.clientLeft +element.scrollWidth +element.scrollHeight +element.scrollTop +element.scrollLeft +element.scrollIntoView() +element.scrollBy() +element.scrollTo() +element.style +element.style.setProperty() +element.style.getPropertyValue() +element.style.removeProperty() +window.getComputedStyle() +element.addEventListener() +element.removeEventListener() +element.dispatchEvent() +element.onclick (et autres on*) +element.focus() +element.blur() +element.hasFocus() +element.checkValidity() +element.reportValidity() +element.setCustomValidity() +element.submit() +element.reset() +element.dataset +element.hasChildNodes() +element.contains() +element.compareDocumentPosition() +node.appendChild() +node.cloneNode() +node.contains() +node.hasChildNodes() +node.insertBefore() +node.isConnected +node.isEqualNode() +node.isSameNode() +node.normalize() +node.removeChild() +node.replaceChild() +document.body +document.head +document.documentElement +document.title +document.URL +document.domain +document.cookie +document.readyState +document.forms +document.images +document.links +document.scripts +document.styleSheets +document.write() +document.writeln() +document.open() +document.close() +document.hasFocus() +document.adoptNode() +document.importNode() +window.innerWidth +window.innerHeight +window.outerWidth +window.outerHeight +window.scrollX +window.scrollY +window.pageXOffset +window.pageYOffset +window.scroll() +window.scrollBy() +window.scrollTo() +event.preventDefault() +event.stopPropagation() +event.stopImmediatePropagation() +event.target +event.currentTarget +event.type +event.bubbles +event.cancelable +event.defaultPrevented +event.eventPhase +event.timeStamp +collection.item() +collection.namedItem() +nodeList.forEach() +nodeList.entries() +nodeList.keys() +nodeList.values() +MutationObserver() +observer.observe() +observer.disconnect() +observer.takeRecords() +IntersectionObserver() +observer.observe() +observer.unobserve() +observer.disconnect() +observer.takeRecords() +ResizeObserver() +observer.observe() +observer.unobserve() +observer.disconnect() diff --git a/crates/typr-cli/configs/src/functions_R.txt b/crates/typr-cli/configs/src/functions_R.txt new file mode 100644 index 0000000..3b220ef --- /dev/null +++ b/crates/typr-cli/configs/src/functions_R.txt @@ -0,0 +1,758 @@ +%*% +%/% +%||% +%in% +%o% +%x% +%==% +abbreviate +abs +acos +acosh +activeBindingFunction +addNA +addTaskCallback +agrep +agrepl +alist +all +allowInterrupts +anyDuplicated +anyNA +aperm +append +apply +Arg +args +array +array2DF +arrayInd +as__array +as__array__default +as__call +as__character +as__complex +as__data__frame +as__Date +as__difftime +as__double +as__environment +as__expression +as__factor +as__function +as__hexmode +as__integer +as__list +as__logical +as__matrix +as__name +as__null +as__null__default +as__numeric +as__numeric_version +as__octmode +as__ordered +as__package_version +as__pairlist +as__POSIXct +as__POSIXlt +as__qr +as__raw +as__single +as__symbol +as__table +as__vector +asin +asinh +asNamespace +asplit +asS3 +asS4 +assign +atan +atan2 +atanh +attach +attachNamespace +attr +attributes +autoload +autoloader +backsolve +balancePOSIXlt +baseenv +basename +besselI +besselJ +besselK +besselY +beta +bindingIsActive +bindingIsLocked +bindtextdomain +bitwAnd +bitwNot +bitwOr +bitwShiftL +bitwShiftR +bitwXor +body +bquote +break +browser +browserCondition +browserSetDebug +browserText +builtins +by +bzfile +c +capture__output +cat +call +callCC +capabilities +casefold +cbind +ceiling +char__expand +character +charmatch +charToRaw +chartr +chkDots +chol +chol2inv +choose +chooseOpsMethod +chooseOpsMethod__default +class +clearPushBack +close +closeAllConnections +col +colMeans +colnames +colSums +commandArgs +comment +complex +computeRestarts +conditionCall +conditionMessage +conflictRules +conflicts +Conj +contributors +cos +cosh +cospi +crossprod +Cstack_info +cummax +cummin +cumprod +cumsum +curlGetHeaders +cut +data__class +data__frame +data__matrix +date +debug +debuggingState +debugonce +declare +delayedAssign +deparse +deparse1 +det +detach +determinant +dget +diag +diff +difftime +digamma +dim +dimnames +dir +dirname +do__call +dontCheck +double +dput +dQuote +drop +droplevels +dump +duplicated +dynGet +eapply +eigen +emptyenv +enc2native +enc2utf8 +encodeString +Encoding +endsWith +enquote +environment +environmentIsLocked +environmentName +errorCondition +eval +evalq +Exec +exists +exp +expm1 +expression +extSoftVersion +F +factor +factorial +fifo +file +Filter +Find +findInterval +findPackageEnv +findRestart +floor +flush +for +force +forceAndCall +formals +format +formatC +formatDL +forwardsolve +function +gamma +gc +gcinfo +gctorture +gctorture2 +get +get0 +getAllConnections +getCallingDLL +getCallingDLLe +getConnection +getDLLRegisteredRoutines +getElement +geterrmessage +getExportedValue +getHook +getLoadedDLLs +getNamespace +getNamespaceExports +getNamespaceImports +getNamespaceInfo +getNamespaceName +getNamespaceUsers +getNamespaceVersion +getNativeSymbolInfo +getOption +getRversion +getSrcLines +getTaskCallbackNames +gettext +gettextf +getwd +gl +globalCallingHandlers +globalenv +gregexec +gregexpr +grep +grepl +grepRaw +grouping +gsub +gzcon +gzfile +I +iconv +iconvlist +icuGetCollate +icuSetCollate +identical +identity +if +ifelse +Im +importIntoEnv +invisible +infoRDS +inherits +integer +interaction +interactive +intersect +intToBits +intToUtf8 +inverse__rle +invokeRestart +invokeRestartInteractively +is__array +is__atomic +is__call +is__character +is__complex +is__data__frame +is__double +is__element +is__environment +is__expression +is__factor +is__finite +is__finite__POSIXlt +is__function +is__infinite +is__infinite__POSIXlt +is__integer +is__language +is__list +is__loaded +is__logical +is__matrix +is__na +is__name +is__nan +is__null +is__numeric +is__object +is__ordered +is__package_version +is__pairlist +is__primitive +is__qr +is__R +is__raw +is__recursive +is__single +is__symbol +is__table +is__unsorted +is__vector +isa +isatty +isBaseNamespace +isdebugged +isFALSE +isIncomplete +isNamespace +isNamespaceLoaded +ISOdate +ISOdatetime +isOpen +isRestart +isS4 +isSeekable +isSymmetric +isSymmetric__matrix +isTRUE +jitter +julian +kappa +kronecker +l10n_info +La_library +La_version +La__svd +labels +lapply +lazyLoad +lazyLoadDBexec +lazyLoadDBfetch +lbeta +lchoose +length +length__POSIXlt +lengths +letters +LETTERS +levels +levels__default +lfactorial +lgamma +libcurlVersion +library +licence +license +list2DF +list2env +load +loadedNamespaces +loadingNamespaceInfo +loadNamespace +local +lockBinding +lockEnvironment +log +log10 +log1p +log2 +logb +logical +lower__tri +ls +make__names +make__unique +makeActiveBinding +mapply +margin__table +marginSums +mat__or__vec +match +matrix +max__col +mean +mem__maxNSize +mem__maxVSize +memCompress +memDecompress +memory__profile +merge +message +mget +min +missing +Mod +mode +months +mtfrm +nameOfClass +names +namespaceExport +namespaceImport +namespaceImportClasses +namespaceImportFrom +namespaceImportMethods +nargs +nchar +ncol +NCOL +Negate +new__env +next +NextMethod +ngettext +nlevels +noquote +norm +normalizePath +nrow +NROW +nullfile +numeric +numeric_version +numToBits +numToInts +nzchar +objects +oldClass +OlsonNames +on__exit +open +options +order +ordered +outer +package_version +packageEvent +packageHasNamespace +packageNotFoundError +packageStartupMessage +packBits +pairlist +parent__env +parent__frame +parse +parseNamespaceFile +paste +paste0 +pcre_config +pi +pipe +plot +pmatch +pmax +pmin +polyroot +pos__to__env +Position +pretty +prettyNum +print +prmatrix +proc__time +prod +prop__table +proportions +provideDimnames +psigamma +pushBack +pushBackLength +q +qr +quarters +quit +quote +R_compiled_by +R_system_version +range +rank +rapply +raw +rawConnection +rawConnectionValue +rawShift +rawToBits +rawToChar +rbind +rcond +Re +read__dcf +readBin +readChar +readline +readLines +readRDS +readRenviron +Recall +Reduce +reg__finalizer +regexec +regexpr +registerS3method +registerS3methods +regmatches +remove +removeTaskCallback +rep +rep_len +replace +replicate +require +requireNamespace +restartDescription +restartFormals +retracemem +return +returnValue +rev +rle +rm +RNGkind +RNGversion +round +row +rowMeans +rownames +rowsum +rowSums +sample +sample__int +sapply +save +saveRDS +scale +scan +search +searchpaths +seek +seq +seq_along +sequence +sequence__default +serialize +serverSocket +set__seed +setdiff +setequal +setHook +setNamespaceInfo +setSessionTimeLimit +setTimeLimit +setwd +showConnections +shQuote +sign +signalCondition +signif +simpleCondition +simpleError +simpleMessage +simpleWarning +simplify2array +sin +single +sinh +sink +sinpi +slice__index +socketAccept +socketConnection +socketSelect +socketTimeout +solve +sort +sort_by +source +split +sprintf +sqrt +sQuote +srcfile +srcfilealias +srcfilecopy +srcref +standardGeneric +startsWith +stderr +stdin +stdout +stop +stopifnot +storage__mode +str +str2expression +str2lang +strftime +strptime +strrep +strsplit +strtoi +strtrim +strwrap +sub +subset +substitute +substr +substring +sum +summary +suppressMessages +suppressPackageStartupMessages +suppressWarnings +suspendInterrupts +svd +sweep +switch +sys__call +sys__calls +Sys__chmod +Sys__Date +sys__frame +sys__frames +sys__function +Sys__getenv +Sys__getlocale +Sys__getpid +Sys__glob +Sys__info +sys__load__image +Sys__localeconv +sys__nframe +sys__on__exit +sys__parent +sys__parents +Sys__readlink +sys__save__image +Sys__setenv +Sys__setFileTime +Sys__setLanguage +Sys__setlocale +Sys__sleep +sys__source +sys__status +Sys__time +Sys__timezone +Sys__umask +Sys__unsetenv +Sys__which +system +system2 +t +T +table +tabulate +Tailcall +tan +tanh +tanpi +tapply +taskCallbackManager +tcrossprod +tempdir +tempfile +textConnection +textConnectionValue +tolower +topenv +toString +toupper +trace +traceback +tracemem +tracingState +transform +trigamma +trimws +trunc +truncate +try +tryCatch +tryInvokeRestart +typeof +unCfillPOSIXlt +unclass +undebug +union +unique +units +unlink +unlist +unloadNamespace +unlockBinding +unname +unserialize +unsplit +untrace +untracemem +unz +upper__tri +url +use +UseMethod +utf8ToInt +validEnc +validUTF8 +vector +Vectorize +version +warning +warningCondition +warnings +weekdays +which +while +with +withAutoprint +withCallingHandlers +within +withRestarts +withVisible +write +writeBin +writeChar +writeLines +xor +xpdrows__data__frame +xtfrm +xzfile +zapsmall diff --git a/crates/typr-cli/configs/src/std.R b/crates/typr-cli/configs/src/std.R new file mode 100644 index 0000000..d7fbb9d --- /dev/null +++ b/crates/typr-cli/configs/src/std.R @@ -0,0 +1,349 @@ +sys.info <- function() { Sys.info() } +sys.getenv <- function() { Sys.getenv() } +sys.setenv <- function(var, val) { Sys.setenv(var = val) } +sys.time <- function() { Sys.time() } +sys.date <- function() { Sys.Date() } +sys.sleep <- function(n) { Sys.sleep(n) } +sys.which <- function(s) { Sys.which(s) } +sys.timezone <- function() { Sys.timezone() } +sys.setlocale <- function() { Sys.setlocale() } + + +struct <- function(x, new_class) { + if (is.null(x)) { + return(x) + } + + old <- oldClass(x) + + if (is.null(old)) { + class(x) <- new_class + } else { + class(x) <- union(old, new_class) + } + + return(x) +} + +let_type <- function(x, new_class) { + class(x) <- "" + class(x) <- x |> new_class() + return(x) +} + +typed_vec <- function(...) { + x <- list(...) + + # Vérifier si tous les arguments héritent de "typed_vec" + all_typed <- all(vapply(x, function(item) inherits(item, "typed_vec"), logical(1))) + + if (all_typed && length(x) > 0) { + # Combiner les paramètres data de chaque typed_vec + combined_data <- unlist(lapply(x, function(item) item$data), recursive = FALSE) + + return(structure( + list(data = combined_data), + class = "typed_vec" + )) + } + + # Sinon, retourner la structure normale + structure( + list(data = x), + class = "typed_vec" + ) +} + +length.typed_vec <- function(x) { + length(x$data) +} + +`[[.typed_vec` <- function(x, i) { + x$data[[i]] +} + +apply.typed_vec <- function(X, FUN, ...) { + # Appliquer la fonction à chaque élément de data + results <- lapply(X$data, FUN, ...) + + # Retourner un nouveau typed_vec avec les résultats + typed_vec(results) +} + +vec_apply <- function(f, ...) { + args <- list(...) + + # Appliquer typed_vec sur les arguments qui n'héritent pas de "typed_std" + args <- lapply(args, function(x) { + if (!inherits(x, "typed_vec")) { + typed_vec(x) + } else { + x + } + }) + + lengths <- vapply(args, length, integer(1)) + n <- max(lengths) + + if (any(lengths == 0)) { + return(structure( + list(data = list()), + class = "typed_vec" + )) + } + + # Optionnel : sécurité façon R + if (any(n %% lengths != 0)) { + stop("Incompatible vector lengths") + } + + # Recyclage + recycled <- lapply(args, function(x) { + if (length(x) == n) { + x$data + } else { + rep(x$data, length.out = n) + } + }) + + results <- vector("list", n) + for (i in seq_len(n)) { + # Extraire les éléments à la position i de chaque argument + elements <- lapply(recycled, `[[`, i) + # Appeler f qui fera son propre dispatch S3 + results[[i]] <- do.call(f, elements) + } + + + # Vérifier si tous les arguments héritent de "typed_vec" + all_typed <- all(vapply(results, function(item) inherits(item, "typed_vec"), logical(1))) + + if (all_typed && length(results) > 0) { + # Combiner les paramètres data de chaque typed_vec + combined_data <- unlist(lapply(results, function(item) item$data), recursive = FALSE) + + return(structure( + list(data = combined_data), + class = "typed_vec" + )) + } + + structure( + list( + data = results + #data = do.call(Map, c(list(f), recycled)) + ), + class = "typed_vec" + ) +} + +vec_apply_fun <- function(fun_vec, ...) { + # Appliquer typed_vec sur fun_vec s'il n'hérite pas de "typed_vec" + if (!inherits(fun_vec, "typed_vec")) { + fun_vec <- typed_vec(fun_vec) + } + + args <- list(...) + + # Appliquer typed_vec sur les arguments qui n'héritent pas de "typed_vec" + args <- lapply(args, function(x) { + if (!inherits(x, "typed_vec")) { + typed_vec(x) + } else { + x + } + }) + + # Toutes les longueurs + lengths <- c(length(fun_vec), vapply(args, length, integer(1))) + n <- max(lengths) + + if (any(lengths == 0)) { + return(structure( + list(data = list()), + class = "typed_vec" + )) + } + + # Sécurité optionnelle + if (any(n %% lengths != 0)) { + stop("Incompatible vector lengths") + } + + # Recyclage + funs <- if (length(fun_vec) == n) + fun_vec$data + else + rep(fun_vec$data, length.out = n) + + recycled_args <- lapply(args, function(x) { + if (length(x) == n) x$data + else rep(x$data, length.out = n) + }) + + # Application élément-wise avec results intermédiaires + results <- vector("list", n) + for (i in seq_len(n)) { + f <- funs[[i]] + params <- lapply(recycled_args, `[[`, i) + # Appeler f qui fera son propre dispatch S3 + results[[i]] <- do.call(f, params) + } + + # Vérifier si tous les éléments de results héritent de "typed_vec" + all_typed <- all(vapply(results, function(item) inherits(item, "typed_vec"), logical(1))) + + if (all_typed && length(results) > 0) { + # Combiner les paramètres data de chaque typed_vec + combined_data <- unlist(lapply(results, function(item) item$data), recursive = FALSE) + + return(structure( + list(data = combined_data), + class = "typed_vec" + )) + } + + structure( + list(data = results), + class = "typed_vec" + ) +} + +reduce.typed_vec <- function(vec, f, init = NULL) { + # Appliquer typed_vec sur vec s'il n'hérite pas de "typed_vec" + if (!inherits(vec, "typed_vec")) { + vec <- typed_vec(vec) + } + + n <- length(vec) + + # Si le vecteur est vide + if (n == 0) { + if (is.null(init)) { + stop("Cannot reduce empty vector without initial value") + } + return(init) + } + + # Déterminer la valeur initiale de l'accumulateur + if (is.null(init)) { + # Commencer avec le premier élément + accumulator <- vec$data[[1]] + start_index <- 2 + } else { + # Commencer avec la valeur initiale fournie + accumulator <- init + start_index <- 1 + } + + # Si on a déjà tout consommé + if (start_index > n) { + return(accumulator) + } + + # Réduction itérative + for (i in start_index:n) { + # Appeler f qui fera son propre dispatch S3 + accumulator <- f(accumulator, vec$data[[i]]) + if (inherits(accumulator, "typed_vec")) { + accumulator <- accumulator$data[[1]] + } + } + + return(structure( + list(data = list(accumulator)), + class = "typed_vec" + )) +} + +sum.typed_vec <- function(x, ...) { + reduce(x, `+`) +} + +print.typed_vec <- function(x, ...) { + n <- length(x$data) + + # Cas spécial : liste vide + if (n == 0) { + cat("Empty typed_vec\n") + return(invisible(x)) + } + + # Cas spécial : longueur 1, afficher directement le contenu + if (n == 1) { + el <- x$data[[1]] + + if (is.function(el)) { + cat("\n") + } else { + print(el) + } + + return(invisible(x)) + } + + # Cas général : longueur > 1 + cat("typed_vec [", n, "]\n", sep = "") + + for (i in seq_len(n)) { + cat("[", i, "] ", sep = "") + el <- x$data[[i]] + + # Délégation au print S3 de l'élément + if (is.function(el)) { + # Affichage plus compact pour les fonctions + fname <- tryCatch( + deparse(substitute(el)), + error = function(e) "" + ) + cat("\n") + } else { + print(el) + } + + if (i < n) cat("\n") + } + + invisible(x) +} + +get.typed_vec <- function(a, name) { + a$data[[1]][[name]] +} + +get.data <- function(a, name) { + a$data[[1]] +} + +get.list <- function(a, name) { + a$data[[1]][[name]] +} + +get.any <- function(a, name) { + a[[name]] +} + +print.Integer <- function(i) { + cat(unclass(i)) + invisible(i) +} + +print.Character <- function(c) { + cat(unclass(c)) + invisible(c) +} + +print.Boolean <- function(b) { + cat(unclass(b)) + invisible(b) +} + +print.Number <- function(n) { + cat(unclass(n)) + invisible(n) +} + +`%==%.default` <- function(x, y) { + unclass(x) == unclass(y) +} + diff --git a/crates/typr-cli/configs/std/default.ty b/crates/typr-cli/configs/std/default.ty new file mode 100644 index 0000000..af8d7d5 --- /dev/null +++ b/crates/typr-cli/configs/std/default.ty @@ -0,0 +1,101 @@ +@nchar: (a: char) -> int; + +let len <- fn(a: char): int; + nchar(a) +}; + +@sys__info: () -> char; + +@sys__getenv: () -> [#N, char]; + +@sys__setenv: (var: char, val: char) -> [#N, char]; + +@sys__time: () -> char; + +@sys__date: () -> char; + +@sys__sleep: (n: int) -> .None; + +@sys__which: (n: char) -> char; + +@sys__timezone: () -> char; + +@sys__setlocale: () -> .None; + +@as__character: (a: A) -> char; + +@as__numeric: (a: A) -> num; + +@as__integer: (a: A) -> int; + +@as__logical: (a: A) -> int; + +@map: (a: [#N, T], f: (T) -> U) -> [#N, U]; + +@rev: (a: [#N, T]) -> [#N, T]; + +@mean: (a: [#N, T]) -> T; + +@sd: (a: [#N, T]) -> T; + +@min: (a: [#N, T]) -> T; + +@max: (a: [#N, T]) -> T; + +@add: (a: int, b: int) -> int; + +@add: (a: num, b: num) -> num; + +@minus: (a: int, b: int) -> int; + +@minus: (a: num, b: num) -> num; + +@mul: (a: int, b: int) -> int; + +@mul: (a: num, b: num) -> num; + +@div: (a: int, b: int) -> int; + +@div: (a: num, b: num) -> num; + +@plot: (a: [#N, num], b: [#N, num], type: char) -> .None; + +@get: (a: {}, b: char) -> T; + +@print: (a: char) -> .None; + +@seq: (a: #I, b: #J, c: #K) -> [#J-#I/#K, int]; + +@substr: (a: char, b: int, e: int) -> char; + +@sub: (a: char, b: char, c: char) -> char; + +let replace <- (s: char, old: char, new: char) -> char; + sub(old, new, s) +}; + +@gsub: (a: char, b: char, c: char) -> char; + +let replace_all <- fn(s: char, old: char, new: char): char; + gsub(old, new, s) +}; + +@strsplit: (s: char, d: char) -> [#N, char]; + +let split <- fn(s: char, d: char): [#N, char]; + strsplit(s, d) +}; + +@join: (a: [#N, char], s: char) -> char; + +@tolower: (a: char) -> char; + +@toupper: (a: char) -> char; + +@startsWith: (a: char, b: char) -> bool; + +@endsWith: (a: char, b: char) -> bool; + +@grepl: (a: char, b: char) -> bool; + +@contains: (a: char, b: char) -> bool; diff --git a/crates/typr-cli/configs/std/file.ty b/crates/typr-cli/configs/std/file.ty new file mode 100644 index 0000000..2f7eb70 --- /dev/null +++ b/crates/typr-cli/configs/std/file.ty @@ -0,0 +1,26 @@ +# File System management ---------- + +@getwd: () -> char; + +@setwd: (path: char) -> char; + +@dir: () -> [#N, char]; + +@list__files: () -> [#N, char]; + +@file__exists: (file: char) -> bool; + +@file__create: (file: char) -> bool; + +@file__remove: (file: char) -> bool; + +@file__rename: (old: char, new: char) -> bool; + +@file__copy: (source: char, dest: char) -> bool; + +@dir__create: (source: char, dest: char) -> bool; + +@unlink: (target: char) -> bool; + + +# -------------------------------- diff --git a/crates/typr-cli/configs/std/lin_alg.ty b/crates/typr-cli/configs/std/lin_alg.ty new file mode 100644 index 0000000..eb3eda9 --- /dev/null +++ b/crates/typr-cli/configs/std/lin_alg.ty @@ -0,0 +1,11 @@ +@dot: (m: [#M, [#P, int]], n: [#P, [#N, int]]): [#M, [#N, int]]; + +@t: (m: [#M, [#N, T]]): [#N, [#M, T]]; + +let lvec <- fn(a: [#M, T]): [1, [#M, T]]; + [a] +}; + +let cvec <- (a: [#M, T]): [#M, [1, T]]; + a.lvec().t() +}; diff --git a/crates/typr-cli/configs/std/option.ty b/crates/typr-cli/configs/std/option.ty new file mode 100644 index 0000000..6e9c09a --- /dev/null +++ b/crates/typr-cli/configs/std/option.ty @@ -0,0 +1,41 @@ +# ERROR HANDLING ---------- +@stop: (msg: char) -> Empty; + +# OPTION TYPE ------------- +type Option = .Some(T) | .None; + +let unwrap <- fn(value: Option): T { + match value { + Some(v) => v, + None => stop("The value is not unwrappable.") + } +}; + +let expect <- fn(value: Option, msg: char): T { + match value { + Some(v) => v, + None => stop(msg) + } +}; + + +let unwrap_or <- fn(value: Option, alternative: T): T { + match value { + Some(v) => v, + None => alternative + } +}; + +let is_some <- fn(value: Option): bool { + match value { + Some(v) => true, + None => false + } +}; + +let is_none <- fn(value: Option): bool { + match value { + Some(v) => false, + None => true + } +}; diff --git a/crates/typr-cli/configs/std/plot.ty b/crates/typr-cli/configs/std/plot.ty new file mode 100644 index 0000000..c837bc3 --- /dev/null +++ b/crates/typr-cli/configs/std/plot.ty @@ -0,0 +1,13 @@ +# PLOT FUNCTION ---------- +@plot: (a: [#N, num], b: [#N, num], c: char, xlim: [2, num], ylim: [2, num], log: char, main: char, sub: char, xlab: char, ylab: char, ann: bool, axes: bool) -> .None; + +type Plot = { x: [#N, num], y: [#N, num], type: char, xlim: [2, num], ylim: [2, num], log: char, main: char, sub: char, xlab: char, ylab: char, ann: bool, axes: bool}; + +let bplot: (): Plot; + :{ x: [0.5], y: [0.5], type: "p", xlim: [0.0, 5.0], ylim: [0.0, 5.0], log: "", main: "", sub: "", xlab: "", ylab: "", ann: true, axes: true} +}; + +let show <- fn(p: Plot): .None; + plot(p.x, p.y, p.type, p.xlim, p.ylim, p.log, p.main, p.sub, p.xlab, p.ylab, p.ann, p.axes) +}; +#--------------------- diff --git a/crates/typr-cli/configs/std/saved.ty b/crates/typr-cli/configs/std/saved.ty new file mode 100644 index 0000000..a1ba403 --- /dev/null +++ b/crates/typr-cli/configs/std/saved.ty @@ -0,0 +1,19 @@ +@print: (c: char) -> T; + +@seq: (a: #I, b: #J, c: #K) -> [#J-#I/#K+1, int]; + +@append: (a: [#M, T], e: T) -> [#M+1, T]; + +@mul: (a: int, b: int) -> int; + +@mul: (a: num, b: num) -> num; + +@map: (a: [#N, T], f: (T) -> U) -> [#N, U]; + +@dot: (m: [#M, [#P, int]], n: [#P, [#N, int]]) -> [#M, [#N, int]]; + +@t: (m: [#M, [#N, T]]) -> [#N, [#M, T]]; + +@add: (a: num, b: num) -> num; + +@add: (a: int, b: int) -> int; diff --git a/crates/typr-cli/configs/std/std_JS.ty b/crates/typr-cli/configs/std/std_JS.ty new file mode 100644 index 0000000..83ec032 --- /dev/null +++ b/crates/typr-cli/configs/std/std_JS.ty @@ -0,0 +1,12 @@ +@add: (T, T) -> T; +@minus: (T, T) -> T; +@mul: (T, T) -> T; +@div: (T, T) -> T; +@seq: (#M, #N, #O) -> [#N+1-#M/#O, int]; +@test_that: (char, Any) -> Empty; +@expect_equal: (T, T) -> Empty; +@expect_true: (bool) -> Empty; +@expect_false: (bool) -> Empty; +@expect_null: (Any) -> Empty; +@expect_type: (Any, char) -> Empty; +@expect_s3_class: (Any, char) -> Empty; diff --git a/crates/typr-cli/configs/std/std_R.ty b/crates/typr-cli/configs/std/std_R.ty new file mode 100644 index 0000000..234708b --- /dev/null +++ b/crates/typr-cli/configs/std/std_R.ty @@ -0,0 +1,19 @@ +@`+`: (int, int) -> int; +@`+`: (num, num) -> num; +@`-`: (int, int) -> int; +@`-`: (num, num) -> num; +@`/`: (int, int) -> int; +@`/`: (num, num) -> num; +@`*`: (int, int) -> int; +@`*`: (num, num) -> num; +@`&&`: (bool, bool) -> bool; +@`||`: (bool, bool) -> bool; +@`+`: (Vec[#M, T], Vec[#M, T]) -> Vec[#M, T]; +@as__character: (Any) -> char; +@source: (char) -> Empty; +@reduce: ([#N, T], (T, U) -> T) -> T; +@sum: ([#N, T]) -> T; +@test_that: (char, Any) -> Empty; +@expect_true: (bool) -> Empty; +@expect_false: (T, T) -> Empty; +@expect_equal: (T, T) -> Empty; diff --git a/crates/typr-cli/configs/std/system.ty b/crates/typr-cli/configs/std/system.ty new file mode 100644 index 0000000..e0873b7 --- /dev/null +++ b/crates/typr-cli/configs/std/system.ty @@ -0,0 +1,14 @@ +# System execution ---------- + +@system2: (command: char, args: [#N, char], stdout: char, stderr: char, stdin: char) -> char; + +type System2 = { command: char, args: [#N, char], stdout: char, stderr: char, stdin: char}; + +let bsystem2 <- fn(command: char): System2 { + :{ command: command, args: [""], stdout: "", stderr: "", stdin: ""} +}; + +let exec <- fn(s: System2): char { + system2(s.command, s.args, s.stdout, s.stderr, s.stdin) +}; +# -------------------------------- diff --git a/crates/typr-cli/configs/std/test.ty b/crates/typr-cli/configs/std/test.ty new file mode 100644 index 0000000..d797ec6 --- /dev/null +++ b/crates/typr-cli/configs/std/test.ty @@ -0,0 +1 @@ +@a: int; diff --git a/crates/typr-cli/configs/test-basic.R b/crates/typr-cli/configs/test-basic.R new file mode 100644 index 0000000..30ea38e --- /dev/null +++ b/crates/typr-cli/configs/test-basic.R @@ -0,0 +1,3 @@ +test_that("{{PACKAGE_NAME}} works", { + expect_true(TRUE) +}) diff --git a/crates/typr-cli/configs/testthat.R b/crates/typr-cli/configs/testthat.R new file mode 100644 index 0000000..e15c632 --- /dev/null +++ b/crates/typr-cli/configs/testthat.R @@ -0,0 +1,4 @@ +library(testthat) +library({{PACKAGE_NAME}}) + +test_check("{{PACKAGE_NAME}}") diff --git a/crates/typr-cli/src/cli.rs b/crates/typr-cli/src/cli.rs new file mode 100644 index 0000000..e727b1a --- /dev/null +++ b/crates/typr-cli/src/cli.rs @@ -0,0 +1,125 @@ +//! Command-line interface for TypR +//! +//! Provides the main CLI commands: +//! - `typr new `: Create a new TypR project +//! - `typr check [file]`: Type-check a file or project +//! - `typr build [file]`: Transpile to R +//! - `typr run [file]`: Build and execute +//! - `typr test`: Run tests +//! - `typr repl`: Start interactive REPL +//! - `typr lsp`: Start Language Server Protocol server + +use crate::project::{ + build_file, build_project, check_file, check_project, clean, cran, document, load, new, + pkg_install, pkg_uninstall, run_file, run_project, test, use_package, +}; +use crate::repl; +use crate::standard_library::standard_library; +use clap::{Parser, Subcommand}; +use std::path::PathBuf; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Cli { + #[arg(value_name = "FILE")] + file: Option, + + #[arg(short, long, value_name = "TARGET", default_value = "r")] + target: Option, + + #[command(subcommand)] + command: Option, +} + +#[derive(Subcommand, Debug)] +enum Commands { + New { + name: String, + }, + Check { + #[arg(value_name = "FILE")] + file: Option, + }, + Build { + #[arg(value_name = "FILE")] + file: Option, + }, + Run { + #[arg(value_name = "FILE")] + file: Option, + }, + Test, + Pkg { + #[command(subcommand)] + pkg_command: PkgCommands, + }, + Document, + Use { + package_name: String, + }, + Load, + Cran, + Std, + Clean, + Repl, + Lsp, +} + +#[derive(Subcommand, Debug)] +enum PkgCommands { + Install, + Uninstall, +} + +/// Main entry point for the CLI +pub fn start() { + let cli = Cli::parse(); + if let Some(path) = cli.file { + if cli.command.is_none() { + run_file(&path); + return; + } + } + + match cli.command { + Some(Commands::New { name }) => new(&name), + Some(Commands::Check { file }) => match file { + Some(path) => check_file(&path), + _ => check_project(), + }, + Some(Commands::Build { file }) => match file { + Some(path) => build_file(&path), + _ => build_project(), + }, + Some(Commands::Run { file }) => match file { + Some(path) => run_file(&path), + _ => run_project(), + }, + Some(Commands::Test) => test(), + Some(Commands::Pkg { pkg_command }) => match pkg_command { + PkgCommands::Install => pkg_install(), + PkgCommands::Uninstall => pkg_uninstall(), + }, + Some(Commands::Document) => document(), + Some(Commands::Use { package_name }) => use_package(&package_name), + Some(Commands::Load) => load(), + Some(Commands::Cran) => cran(), + Some(Commands::Std) => standard_library(), + Some(Commands::Clean) => clean(), + Some(Commands::Lsp) => { + // Use a larger stack size (8MB) to avoid stack overflow + // during deep recursive parsing/type-checking operations + let rt = tokio::runtime::Builder::new_multi_thread() + .thread_stack_size(8 * 1024 * 1024) + .enable_all() + .build() + .unwrap(); + rt.block_on(crate::lsp::run_lsp()); + } + Some(Commands::Repl) => repl::start(), + _ => { + println!("Please specify a subcommand or file to execute"); + std::process::exit(1); + } + } +} diff --git a/src/utils/engine.rs b/crates/typr-cli/src/engine.rs similarity index 85% rename from src/utils/engine.rs rename to crates/typr-cli/src/engine.rs index 765152d..06c0b55 100644 --- a/src/utils/engine.rs +++ b/crates/typr-cli/src/engine.rs @@ -1,25 +1,29 @@ +//! Build engine utilities for TypR CLI +//! +//! Provides functions for: +//! - Parsing code files +//! - Compiling with error collection +//! - Writing standard library files + #![allow(dead_code)] -use crate::components::context::config::Environment; -use crate::components::context::Context; -use crate::components::error_message::syntax_error::SyntaxError; -use crate::components::error_message::typr_error::TypRError; -use crate::components::language::Lang; -use crate::components::r#type::Type; -use crate::processes::parsing::parse; -use crate::processes::parsing::ParseResult; -use crate::processes::type_checking::typing_with_errors; -// `TypingResult` import removed — not used in this module -use crate::utils::metaprogramming::metaprogrammation; -use crate::utils::my_io::get_os_file; -use crate::utils::my_io::read_file; +use crate::io::{get_os_file, read_file}; +use crate::metaprogramming::metaprogrammation; use nom_locate::LocatedSpan; use std::fs::File; use std::io::Write; use std::path::PathBuf; +use typr_core::components::context::config::Environment; +use typr_core::components::context::Context; +use typr_core::components::error_message::syntax_error::SyntaxError; +use typr_core::components::error_message::typr_error::TypRError; +use typr_core::components::language::Lang; +use typr_core::components::r#type::Type; +use typr_core::processes::parsing::{parse, ParseResult}; +use typr_core::typing_with_errors; pub fn write_std_for_type_checking(output_dir: &PathBuf) { - let rstd = include_str!("../../configs/std/std_R.ty"); + let rstd = include_str!("../configs/std/std_R.ty"); let std_path = output_dir.join("std.ty"); let mut rstd_file = File::create(std_path).unwrap(); rstd_file.write_all(rstd.as_bytes()).unwrap(); @@ -111,7 +115,7 @@ impl CompileResult { } /// Get only type errors - pub fn type_errors(&self) -> Vec<&crate::components::error_message::type_error::TypeError> { + pub fn type_errors(&self) -> Vec<&typr_core::components::error_message::type_error::TypeError> { self.errors .iter() .filter_map(|e| match e { @@ -125,7 +129,6 @@ impl CompileResult { /// Parse and type-check code, returning all errors collected /// /// This is the main entry point for compiling TypR code with error collection. -/// It performs both parsing and type checking, collecting all errors along the way. pub fn compile_code_with_errors(path: &PathBuf, environment: Environment) -> CompileResult { let file = get_os_file(path.to_str().unwrap()); let file_content = read_file(path).expect(&format!("Path {:?} not found", path)); diff --git a/crates/typr-cli/src/fs_provider.rs b/crates/typr-cli/src/fs_provider.rs new file mode 100644 index 0000000..e6f6a01 --- /dev/null +++ b/crates/typr-cli/src/fs_provider.rs @@ -0,0 +1,214 @@ +//! FileSystem-based implementations of typr-core traits +//! +//! These implementations allow the CLI app to use the filesystem +//! for reading sources and writing outputs. + +use std::fs; +use std::path::PathBuf; +use typr_core::{OutputError, OutputHandler, PackageChecker, PackageError, SourceProvider}; + +/// Filesystem-based source provider for native compilation. +/// +/// Reads TypR source files from the filesystem. +#[derive(Debug, Clone)] +pub struct FileSystemSourceProvider { + base_path: PathBuf, +} + +impl FileSystemSourceProvider { + /// Create a new provider with a base path for resolving relative paths + pub fn new(base_path: PathBuf) -> Self { + Self { base_path } + } + + /// Create a provider for the current directory + pub fn current_dir() -> std::io::Result { + Ok(Self { + base_path: std::env::current_dir()?, + }) + } + + /// Get the full path for a source file + fn resolve_path(&self, path: &str) -> PathBuf { + if PathBuf::from(path).is_absolute() { + PathBuf::from(path) + } else { + self.base_path.join(path) + } + } +} + +impl SourceProvider for FileSystemSourceProvider { + fn get_source(&self, path: &str) -> Option { + let full_path = self.resolve_path(path); + fs::read_to_string(&full_path).ok() + } + + fn exists(&self, path: &str) -> bool { + let full_path = self.resolve_path(path); + full_path.exists() && full_path.is_file() + } + + fn list_sources(&self) -> Vec { + self.list_sources_recursive(&self.base_path) + } +} + +impl FileSystemSourceProvider { + /// Recursively list all .ty files under a directory + fn list_sources_recursive(&self, dir: &PathBuf) -> Vec { + let mut sources = Vec::new(); + + if let Ok(entries) = fs::read_dir(dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + sources.extend(self.list_sources_recursive(&path)); + } else if path.extension().map_or(false, |ext| ext == "ty") { + if let Ok(relative) = path.strip_prefix(&self.base_path) { + sources.push(relative.to_string_lossy().to_string()); + } + } + } + } + + sources + } +} + +/// Filesystem-based output handler for native compilation. +/// +/// Writes transpiled R code and related files to the filesystem. +#[derive(Debug, Clone)] +pub struct FileSystemOutputHandler { + output_dir: PathBuf, +} + +impl FileSystemOutputHandler { + /// Create a new handler that writes to the specified directory + pub fn new(output_dir: PathBuf) -> Self { + Self { output_dir } + } + + /// Create a handler for the current directory + pub fn current_dir() -> std::io::Result { + Ok(Self { + output_dir: std::env::current_dir()?, + }) + } + + /// Ensure the output directory exists + fn ensure_dir(&self) -> Result<(), OutputError> { + fs::create_dir_all(&self.output_dir).map_err(|e| OutputError { + message: format!("Failed to create output directory: {}", e), + }) + } + + /// Write content to a file in the output directory + fn write_file(&self, filename: &str, content: &str) -> Result<(), OutputError> { + self.ensure_dir()?; + let path = self.output_dir.join(filename); + fs::write(&path, content).map_err(|e| OutputError { + message: format!("Failed to write {}: {}", path.display(), e), + }) + } +} + +impl OutputHandler for FileSystemOutputHandler { + fn write_r_code(&mut self, filename: &str, content: &str) -> Result<(), OutputError> { + self.write_file(filename, content) + } + + fn write_type_annotations(&mut self, filename: &str, content: &str) -> Result<(), OutputError> { + self.write_file( + &format!("{}_types.R", filename.trim_end_matches(".R")), + content, + ) + } + + fn write_generic_functions( + &mut self, + filename: &str, + content: &str, + ) -> Result<(), OutputError> { + self.write_file( + &format!("{}_generics.R", filename.trim_end_matches(".R")), + content, + ) + } +} + +/// Native R package checker that can query R for package availability. +/// +/// Uses Rscript to check if packages are installed. +#[derive(Debug, Clone)] +pub struct NativePackageChecker { + /// Cache of known package types + package_types: std::collections::HashMap, +} + +impl NativePackageChecker { + pub fn new() -> Self { + Self { + package_types: std::collections::HashMap::new(), + } + } +} + +impl Default for NativePackageChecker { + fn default() -> Self { + Self::new() + } +} + +impl PackageChecker for NativePackageChecker { + fn is_package_available(&self, name: &str) -> bool { + use std::process::Command; + + // Use Rscript to check if package is available + let result = Command::new("Rscript") + .arg("-e") + .arg(format!("cat(requireNamespace('{}', quietly = TRUE))", name)) + .output(); + + match result { + Ok(output) => { + let stdout = String::from_utf8_lossy(&output.stdout); + stdout.trim() == "TRUE" + } + Err(_) => false, + } + } + + fn install_package(&mut self, name: &str) -> Result<(), PackageError> { + use std::process::Command; + + let result = Command::new("Rscript") + .arg("-e") + .arg(format!( + "install.packages('{}', repos='https://cloud.r-project.org')", + name + )) + .output(); + + match result { + Ok(output) => { + if output.status.success() { + Ok(()) + } else { + let stderr = String::from_utf8_lossy(&output.stderr); + Err(PackageError { + message: format!("Failed to install package '{}': {}", name, stderr), + }) + } + } + Err(e) => Err(PackageError { + message: format!("Failed to run Rscript: {}", e), + }), + } + } + + fn get_package_types(&self, name: &str) -> Option { + self.package_types.get(name).cloned() + } +} diff --git a/src/utils/my_io.rs b/crates/typr-cli/src/io.rs similarity index 67% rename from src/utils/my_io.rs rename to crates/typr-cli/src/io.rs index 3dca4b3..7417005 100644 --- a/src/utils/my_io.rs +++ b/crates/typr-cli/src/io.rs @@ -1,15 +1,22 @@ +//! I/O utilities for TypR CLI +//! +//! Provides utilities for: +//! - Reading files +//! - Executing R scripts +//! - Executing TypeScript (for future targets) + #![allow(dead_code)] -use std::process::Command; + use std::fs; use std::path::PathBuf; -use crate::components::context::config::Environment; - +use std::process::Command; +use typr_core::components::context::config::Environment; pub fn get_os_file(file: &str) -> String { - if cfg!(windows){ - file.replace("\\", r"\") + if cfg!(windows) { + file.replace("\\", r"\") } else { - file.to_string() + file.to_string() } } @@ -17,39 +24,36 @@ pub fn read_file(path: &PathBuf) -> Option { let file = get_os_file(path.to_str().unwrap()); match fs::read_to_string(&file) { Ok(res) => Some(res), - _ => None + _ => None, } } pub fn read_file_from_name(name: &str, environment: Environment) -> String { let base = match environment { - Environment::StandAlone | Environment::Repl => "", - Environment::Project => "TypR/" + Environment::StandAlone | Environment::Repl | Environment::Wasm => "", + Environment::Project => "TypR/", }; let file = get_os_file(&format!("{}{}.ty", base, name)); fs::read_to_string(&file).expect(&format!("Can't Read file {}", name)) } pub fn execute_r() -> () { - match Command::new("Rscript") - .arg(get_os_file("app.R")) - .output() - { + match Command::new("Rscript").arg(get_os_file("app.R")).output() { Ok(output) => { let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); - + if output.status.success() { println!("Execution: \n{}", stdout); } else { println!("Error (code {}): \n{}", output.status, stderr); if !stdout.is_empty() { - println!("Sortie standard: \n{}", stdout); + println!("Standard output: \n{}", stdout); } } - }, + } Err(e) => { - println!("Échec lors de l'exécution de la commande: {}", e); + println!("Failed to execute command: {}", e); } } } @@ -63,18 +67,18 @@ pub fn execute_r_with_path(execution_path: &PathBuf, file_name: &str) -> () { Ok(output) => { let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); - + if output.status.success() { println!("Execution: \n{}", stdout); } else { println!("Error (code {}): \n{}", output.status, stderr); if !stdout.is_empty() { - println!("Sortie standard: \n{}", stdout); + println!("Standard output: \n{}", stdout); } } - }, + } Err(e) => { - println!("Échec lors de l'exécution de la commande: {}", e); + println!("Failed to execute command: {}", e); } } } @@ -88,54 +92,56 @@ pub fn execute_r_with_path2(execution_path: &PathBuf, file_name: &str) -> String Ok(output) => { let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); - + if output.status.success() { format!("{}", stdout) } else { println!("Error (code {}): \n{}", output.status, stderr); if !stdout.is_empty() { - format!("Sortie standard: \n{}", stdout) - } else { "".to_string() } + format!("Standard output: \n{}", stdout) + } else { + "".to_string() + } } - }, + } Err(e) => { - format!("Échec lors de l'exécution de la commande: {}", e) + format!("Failed to execute command: {}", e) } } } pub fn execute_typescript() -> () { - println!("Compilation TypeScript: "); - - // Compiler le fichier TypeScript en JavaScript + println!("TypeScript compilation: "); + + // Compile TypeScript to JavaScript let tsc_output = Command::new("tsc") .arg("app.ts") .output() - .expect("Échec lors de la compilation TypeScript"); - + .expect("Failed during TypeScript compilation"); + if !tsc_output.status.success() { let stderr = String::from_utf8_lossy(&tsc_output.stderr); - println!("Erreur de compilation TypeScript: {}", stderr); + println!("TypeScript compilation error: {}", stderr); return; } - - println!("Exécution JavaScript: "); - - // Exécuter le fichier JavaScript compilé + + println!("JavaScript execution: "); + + // Execute compiled JavaScript file let node_output = Command::new("node") .arg("app.js") .output() - .expect("Échec lors de l'exécution de Node.js"); - + .expect("Failed during Node.js execution"); + let stdout = String::from_utf8_lossy(&node_output.stdout); let stderr = String::from_utf8_lossy(&node_output.stderr); - + if !node_output.status.success() { - println!("Erreur d'exécution JavaScript: {}", stderr); + println!("JavaScript execution error: {}", stderr); } else { println!("{}", stdout); if !stderr.is_empty() { - println!("Avertissements: {}", stderr); + println!("Warnings: {}", stderr); } } } diff --git a/crates/typr-cli/src/lib.rs b/crates/typr-cli/src/lib.rs new file mode 100644 index 0000000..d0c33ee --- /dev/null +++ b/crates/typr-cli/src/lib.rs @@ -0,0 +1,58 @@ +//! # TypR CLI +//! +//! Command-line interface, REPL, and LSP server for TypR - a typed superset of R. +//! +//! This crate provides the CLI layer for TypR, depending on `typr-core` for +//! the core logic. It includes: +//! +//! - Command-line interface with project management commands +//! - Interactive REPL with syntax highlighting +//! - Language Server Protocol (LSP) server for IDE integration +//! - Filesystem-based source and output handlers +//! +//! ## Usage +//! +//! ```bash +//! # Create a new project +//! typr new myproject +//! +//! # Check types +//! typr check +//! +//! # Build to R +//! typr build +//! +//! # Run +//! typr run +//! +//! # Start REPL +//! typr repl +//! +//! # Start LSP server +//! typr lsp +//! ``` + +// Re-export typr-core for users who want access to core types +pub use typr_core; + +// CLI modules +pub mod cli; +pub mod engine; +pub mod fs_provider; +pub mod io; +pub mod lsp; +pub mod lsp_parser; +pub mod metaprogramming; +pub mod project; +pub mod repl; +pub mod standard_library; + +// Re-export commonly used items +pub use cli::start; +pub use fs_provider::{FileSystemOutputHandler, FileSystemSourceProvider, NativePackageChecker}; + +// Re-export typr-core abstractions +pub use typr_core::{ + CompileError, CompileOutput, Compiler, InMemoryOutputHandler, InMemorySourceProvider, + OutputHandler, PackageChecker, SourceProvider, StubPackageChecker, TranspileResult, +}; diff --git a/src/interface/lsp.rs b/crates/typr-cli/src/lsp.rs similarity index 61% rename from src/interface/lsp.rs rename to crates/typr-cli/src/lsp.rs index ac06374..d2cf9b0 100644 --- a/src/interface/lsp.rs +++ b/crates/typr-cli/src/lsp.rs @@ -4,16 +4,15 @@ //! - **Hover** provider: shows inferred types with Markdown syntax highlighting //! - **Completion** provider: context-aware autocompletion for variables, functions, and type aliases //! - Trigger characters: `.`, `$`, `>` (for `|>`), `:` (for type annotations) -//! - **Diagnostics** provider: real-time error checking for syntax and type errors +//! - **Diagnostics** (push model): real-time error checking via `textDocument/publishDiagnostics` +//! - Diagnostics are published on `didOpen` and `didChange` events //! - **Go to Definition** provider: jump to symbol definitions (variables, functions, type aliases) //! - **Workspace Symbol** provider: search for symbols across all open documents //! //! Launch with `typr lsp`. The server communicates over stdin/stdout using //! the standard LSP JSON-RPC protocol. -use super::parser; -use crate::components::context::Context; -use crate::processes::parsing::parse; +use crate::lsp_parser; use nom_locate::LocatedSpan; use std::collections::HashMap; use std::sync::Arc; @@ -21,6 +20,10 @@ use tokio::sync::RwLock; use tower_lsp::jsonrpc::Result; use tower_lsp::lsp_types::*; use tower_lsp::{Client, LanguageServer, LspService, Server}; +use typr_core::components::context::Context; +use typr_core::components::language::var::Var; +use typr_core::components::language::Lang; +use typr_core::processes::parsing::parse; type Span<'a> = LocatedSpan<&'a str, String>; @@ -45,11 +48,6 @@ impl LanguageServer for Backend { resolve_provider: None, ..Default::default() }), - diagnostic_provider: Some(DiagnosticServerCapabilities::Options( - DiagnosticOptions { - ..Default::default() - } - )), workspace_symbol_provider: Some(OneOf::Left(true)), definition_provider: Some(OneOf::Left(true)), ..Default::default() @@ -72,29 +70,33 @@ impl LanguageServer for Backend { async fn did_open(&self, params: DidOpenTextDocumentParams) { let uri = params.text_document.uri.clone(); let content = params.text_document.text.clone(); - + let mut docs = self.documents.write().await; docs.insert(uri.clone(), content.clone()); drop(docs); // Release the lock before computing diagnostics - + // Compute and publish diagnostics let diagnostics = self.compute_diagnostics(&content, &uri).await; - self.client.publish_diagnostics(uri, diagnostics, None).await; + self.client + .publish_diagnostics(uri, diagnostics, None) + .await; } async fn did_change(&self, params: DidChangeTextDocumentParams) { let uri = params.text_document.uri.clone(); let mut docs = self.documents.write().await; - + // Full-sync mode: each change event contains the complete text. if let Some(change) = params.content_changes.into_iter().next() { let content = change.text.clone(); docs.insert(uri.clone(), content.clone()); drop(docs); // Release the lock before computing diagnostics - + // Compute and publish diagnostics let diagnostics = self.compute_diagnostics(&content, &uri).await; - self.client.publish_diagnostics(uri, diagnostics, None).await; + self.client + .publish_diagnostics(uri, diagnostics, None) + .await; } } @@ -113,16 +115,14 @@ impl LanguageServer for Backend { // the LSP event loop. let content_owned = content.clone(); let info = tokio::task::spawn_blocking(move || { - parser::find_type_at(&content_owned, position.line, position.character) + lsp_parser::find_type_at(&content_owned, position.line, position.character) }) .await - .ok() // if the blocking task panicked, treat as None + .ok() // if the blocking task panicked, treat as None .flatten(); match info { Some(hover_info) => Ok(Some(Hover { - // Use MarkupContent so the editor renders the Markdown - // (bold, italic, code spans) produced by highlight_type(). contents: HoverContents::Markup(MarkupContent { kind: MarkupKind::Markdown, value: hover_info.type_display, @@ -147,7 +147,7 @@ impl LanguageServer for Backend { // Offload parsing + typing to a blocking thread (same strategy as hover). let content_owned = content.clone(); let items = tokio::task::spawn_blocking(move || { - parser::get_completions_at(&content_owned, position.line, position.character) + lsp_parser::get_completions_at(&content_owned, position.line, position.character) }) .await .ok() @@ -167,13 +167,13 @@ impl LanguageServer for Backend { ) -> Result>> { let query = params.query.to_lowercase(); let docs = self.documents.read().await; - + let mut all_symbols = Vec::new(); - + for (uri, content) in docs.iter() { let content_owned = content.clone(); let uri_owned = uri.clone(); - + // Offload parsing to a blocking thread let symbols = tokio::task::spawn_blocking(move || { get_workspace_symbols(&content_owned, &uri_owned) @@ -181,15 +181,15 @@ impl LanguageServer for Backend { .await .ok() .unwrap_or_default(); - + all_symbols.extend(symbols); } - + // Filter symbols by query (case-insensitive substring match) if !query.is_empty() { all_symbols.retain(|sym| sym.name.to_lowercase().contains(&query)); } - + if all_symbols.is_empty() { Ok(None) } else { @@ -224,7 +224,7 @@ impl LanguageServer for Backend { let uri_owned = uri.clone(); let file_path_owned = file_path.clone(); let info = tokio::task::spawn_blocking(move || { - parser::find_definition_at( + lsp_parser::find_definition_at( &content_owned, position.line, position.character, @@ -258,13 +258,10 @@ impl LanguageServer for Backend { impl Backend { /// Compute diagnostics for a document by parsing and type-checking. - /// - /// This is called on `did_open` and `did_change` events to provide - /// real-time error feedback to the user. async fn compute_diagnostics(&self, content: &str, uri: &Url) -> Vec { let content_owned = content.to_string(); let file_name = uri.to_string(); - + tokio::task::spawn_blocking(move || { check_code_and_extract_errors(&content_owned, &file_name) }) @@ -283,49 +280,28 @@ impl Backend { } /// Check the code and extract errors from parsing and type-checking. -/// -/// This function attempts to: -/// 1. Parse the code using the TypR parser -/// 2. Type-check the AST using the TypR type checker -/// -/// If either step fails (panics), the panic is caught and converted into -/// an LSP diagnostic. -/// -/// The `file_name` parameter is used by miette to include position information -/// in error messages (e.g., `[file.ty:4:1]`). -/// -/// **Note**: We need to save the file to disk temporarily so that the error -/// system can read it back when formatting error messages. fn check_code_and_extract_errors(content: &str, file_name: &str) -> Vec { let mut diagnostics = Vec::new(); - + // Convert URI to file path if needed let path = if file_name.starts_with("file://") { file_name.strip_prefix("file://").unwrap_or(file_name) } else { file_name }; - - // Note: We don't write to disk here to avoid triggering file change warnings in editors. - // The error system should work with in-memory content provided by the LSP. - // let _ = std::fs::write(path, content); - + // 1. Attempt parsing - // Pass the file path (not URI) so HelpData can store it for error messages let span: Span = LocatedSpan::new_extra(content, path.to_string()); let parse_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| parse(span))); - + let ast = match parse_result { Ok(result) => { // Collect syntax errors from the parsed AST for syntax_error in &result.errors { - // Use simple_message() for LSP - it doesn't require file access let msg = syntax_error.simple_message(); - // Extract position from SyntaxError's HelpData let range = if let Some(help_data) = syntax_error.get_help_data() { let offset = help_data.get_offset(); let pos = offset_to_position(offset, content); - // Create a range that spans a reasonable portion of the error let end_col = find_token_end(content, offset, pos); Range::new(pos, Position::new(pos.line, end_col)) } else { @@ -346,7 +322,6 @@ fn check_code_and_extract_errors(content: &str, file_name: &str) -> Vec Vec { - // Collect type errors from the typing result - use crate::components::error_message::typr_error::TypRError; - + use typr_core::TypRError; + for error in result.get_errors() { - // Use simple_message() for LSP - it doesn't require file access let msg = error.simple_message(); let severity = match error { TypRError::Type(_) => DiagnosticSeverity::ERROR, TypRError::Syntax(_) => DiagnosticSeverity::WARNING, }; - // Extract position from TypRError's HelpData let range = if let Some(help_data) = error.get_help_data() { let offset = help_data.get_offset(); let pos = offset_to_position(offset, content); - // Try to find a reasonable end position (end of token or line) let end_col = find_token_end(content, offset, pos); Range::new(pos, Position::new(pos.line, end_col)) } else { @@ -397,11 +368,9 @@ fn check_code_and_extract_errors(content: &str, file_name: &str) -> Vec { - // Fallback for unexpected panics during type checking if let Some(diagnostic) = extract_diagnostic_from_panic(&panic_info, content) { diagnostics.push(diagnostic); } else { - // Fallback: generic type error diagnostics.push(Diagnostic { range: Range::new(Position::new(0, 0), Position::new(0, 1)), severity: Some(DiagnosticSeverity::ERROR), @@ -412,23 +381,15 @@ fn check_code_and_extract_errors(content: &str, file_name: &str) -> Vec, content: &str, ) -> Option { - // The panic can be either a String or a &str let message = if let Some(s) = panic_info.downcast_ref::() { s.as_str() } else if let Some(s) = panic_info.downcast_ref::<&str>() { @@ -436,17 +397,10 @@ fn extract_diagnostic_from_panic( } else { return None; }; - - // Debug: uncomment to see the raw error message - // eprintln!("=== RAW ERROR MESSAGE ===\n{}\n=== END ===", message); - - // Try to extract position information from the error message + let range = extract_position_from_error(message, content) - .unwrap_or_else(|| { - // Fallback: point to the beginning of the file - Range::new(Position::new(0, 0), Position::new(0, 1)) - }); - + .unwrap_or_else(|| Range::new(Position::new(0, 0), Position::new(0, 1))); + Some(Diagnostic { range, severity: Some(DiagnosticSeverity::ERROR), @@ -457,38 +411,17 @@ fn extract_diagnostic_from_panic( } /// Clean an error message for display in the LSP. -/// -/// This removes ANSI color codes, miette formatting artifacts, and -/// extracts just the main error message. -/// -/// Example input: -/// ``` -/// thread 'main' panicked at src/processes/type_checking/function_application.rs:39:28: -/// Err( × Type error: Function `+` not defined in this scope. -/// ╭─[TypR/main.ty:4:1] -/// 3 │ -/// 4 │ 1 + true -/// · ▲ -/// · ╰── Not defined in this scope -/// ╰──── -/// ) -/// ``` -/// Should extract: "Type error: Function `+` not defined in this scope." fn clean_error_message(msg: &str) -> String { - // Remove ANSI escape codes let without_ansi = strip_ansi_codes(msg); - - // Look for the error message after "× " (miette marker) + for line in without_ansi.lines() { let trimmed = line.trim(); if let Some(pos) = trimmed.find("× ") { let message = &trimmed[pos + 2..]; - // Remove trailing period and trim whitespace return message.trim().trim_end_matches('.').to_string(); } } - - // Fallback: take the first non-empty line + without_ansi .lines() .find(|line| !line.trim().is_empty()) @@ -501,13 +434,11 @@ fn clean_error_message(msg: &str) -> String { fn strip_ansi_codes(s: &str) -> String { let mut result = String::with_capacity(s.len()); let mut chars = s.chars().peekable(); - + while let Some(ch) = chars.next() { if ch == '\x1b' { - // Skip ANSI escape sequence if chars.peek() == Some(&'[') { - chars.next(); // consume '[' - // Skip until we find a letter (end of ANSI sequence) + chars.next(); while let Some(&c) = chars.peek() { chars.next(); if c.is_ascii_alphabetic() { @@ -519,43 +450,31 @@ fn strip_ansi_codes(s: &str) -> String { result.push(ch); } } - + result } /// Attempt to extract position information from an error message. -/// -/// TypR error messages contain miette-formatted position info like: -/// ``` -/// ╭─[TypR/main.ty:4:1] -/// 4 │ 1 + true -/// · ▲ -/// ``` -/// This extracts the line number (4) and tries to find the column from the "▲" marker. fn extract_position_from_error(message: &str, content: &str) -> Option { let without_ansi = strip_ansi_codes(message); - - // Look for the position marker: "╭─[filename:line:col]" or just "[filename:line:col]" + for line in without_ansi.lines() { - // Find the opening bracket [ and closing bracket ] if let Some(bracket_start) = line.find('[') { if let Some(bracket_end) = line[bracket_start..].find(']') { let location = &line[bracket_start + 1..bracket_start + bracket_end]; - - // Parse "TypR/main.ty:4:1" or "/path/to/file.ty:4:1" -> line 4, col 1 + if let Some(last_colon) = location.rfind(':') { if let Some(second_last_colon) = location[..last_colon].rfind(':') { let line_str = &location[second_last_colon + 1..last_colon]; let col_str = &location[last_colon + 1..]; - - if let (Ok(line_num), Ok(col_num)) = (line_str.parse::(), col_str.parse::()) { - // miette uses 1-based indexing, LSP uses 0-based + + if let (Ok(line_num), Ok(col_num)) = + (line_str.parse::(), col_str.parse::()) + { let line = line_num.saturating_sub(1); let col = col_num.saturating_sub(1); - - // Try to find the length of the error token let length = extract_error_length(&without_ansi, content, line); - + return Some(Range::new( Position::new(line, col), Position::new(line, col + length), @@ -566,21 +485,18 @@ fn extract_position_from_error(message: &str, content: &str) -> Option { } } } - + None } /// Try to extract the length of the error token from the miette diagram. -/// Looks for the "▲" or "╰──" markers to determine span length. fn extract_error_length(message: &str, content: &str, line: u32) -> u32 { - // Look for lines with the error marker "▲" or underline "╰──" let mut found_line_number = false; let mut marker_col = None; - + for msg_line in message.lines() { let trimmed = msg_line.trim_start(); - - // Check if this line shows the line number we're looking for + if let Some(pipe_pos) = trimmed.find("│") { if let Ok(num) = trimmed[..pipe_pos].trim().parse::() { if num == line + 1 { @@ -589,31 +505,26 @@ fn extract_error_length(message: &str, content: &str, line: u32) -> u32 { } } } - - // If we found the line, look for the marker on the next line + if found_line_number && trimmed.contains("▲") { - // Count spaces before the marker to find column if let Some(marker_pos) = trimmed.find("▲") { marker_col = Some(marker_pos as u32); break; } } } - - // If we found a marker column, try to get the token length from the actual content + if let Some(_col) = marker_col { - // Get the actual line from content if let Some(content_line) = content.lines().nth(line as usize) { - // Try to find the token at that position - // For now, return a reasonable default length - return content_line.trim().split_whitespace() + return content_line + .trim() + .split_whitespace() .next() .map(|s| s.len() as u32) .unwrap_or(1); } } - - // Default: single character + 1 } @@ -621,7 +532,7 @@ fn extract_error_length(message: &str, content: &str, line: u32) -> u32 { fn offset_to_position(offset: usize, content: &str) -> Position { let mut line = 0u32; let mut col = 0u32; - + for (i, ch) in content.chars().enumerate() { if i >= offset { break; @@ -633,17 +544,15 @@ fn offset_to_position(offset: usize, content: &str) -> Position { col += 1; } } - + Position::new(line, col) } /// Find the end column of a token starting at the given offset. -/// This helps create a more accurate range for the diagnostic. fn find_token_end(content: &str, offset: usize, start_pos: Position) -> u32 { let bytes = content.as_bytes(); let mut end_offset = offset; - - // Skip until we find a token boundary (whitespace, punctuation, or end of line) + while end_offset < bytes.len() { let ch = bytes[end_offset] as char; if ch.is_whitespace() || ch == ';' || ch == ',' || ch == ')' || ch == ']' || ch == '}' { @@ -651,8 +560,7 @@ fn find_token_end(content: &str, offset: usize, start_pos: Position) -> u32 { } end_offset += 1; } - - // Calculate the end column + let token_len = (end_offset - offset) as u32; if token_len == 0 { start_pos.character + 1 @@ -665,43 +573,25 @@ fn find_token_end(content: &str, offset: usize, start_pos: Position) -> u32 { // ── WORKSPACE SYMBOLS ───────────────────────────────────────────────────── // ══════════════════════════════════════════════════════════════════════════ -use crate::components::language::Lang; -use crate::components::language::var::Var; - /// Get all symbols from a document for workspace/symbol support. -/// -/// This parses the document and extracts all top-level symbols including: -/// - Let bindings (variables and functions) -/// - Type aliases -/// - Modules -/// - Signatures #[allow(deprecated)] fn get_workspace_symbols(content: &str, file_uri: &Url) -> Vec { let mut symbols = Vec::new(); - - // Parse the document + let span: Span = LocatedSpan::new_extra(content, file_uri.path().to_string()); let parse_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| parse(span))); - + let ast = match parse_result { Ok(result) => result.ast, - Err(_) => return symbols, // If parsing fails, return empty symbols + Err(_) => return symbols, }; - - // Collect symbols from the AST + collect_symbols_from_ast(&ast, content, file_uri, None, &mut symbols); - + symbols } /// Recursively collect symbols from an AST node. -/// -/// This traverses the AST and extracts symbol information for: -/// - `Let` bindings: variables and functions -/// - `Alias`: type aliases -/// - `Module`: module definitions -/// - `Signature`: type signatures -/// - `Lines` and `Scope`: recursively process children #[allow(deprecated)] fn collect_symbols_from_ast( lang: &Lang, @@ -716,13 +606,13 @@ fn collect_symbols_from_ast( collect_symbols_from_ast(stmt, content, file_uri, container_name.clone(), symbols); } } - + Lang::Scope(statements, _) => { for stmt in statements { collect_symbols_from_ast(stmt, content, file_uri, container_name.clone(), symbols); } } - + Lang::Let(var_lang, typ, body, _) => { if let Ok(var) = Var::try_from(var_lang) { let name = var.get_name(); @@ -730,15 +620,13 @@ fn collect_symbols_from_ast( let offset = help_data.get_offset(); let pos = offset_to_position(offset, content); let end_col = find_token_end(content, offset, pos); - - // Determine symbol kind based on type annotation or body - // Check both the annotated type and the body to determine if it's a function + let kind = if typ.is_function() || body.is_function() { SymbolKind::FUNCTION } else { SymbolKind::VARIABLE }; - + symbols.push(SymbolInformation { name: name.clone(), kind, @@ -750,12 +638,11 @@ fn collect_symbols_from_ast( container_name: container_name.clone(), tags: None, }); - - // Recursively check body for nested declarations (e.g., functions defined inside let) + collect_symbols_from_ast(body, content, file_uri, Some(name), symbols); } } - + Lang::Alias(var_lang, _params, _typ, _) => { if let Ok(var) = Var::try_from(var_lang) { let name = var.get_name(); @@ -763,7 +650,7 @@ fn collect_symbols_from_ast( let offset = help_data.get_offset(); let pos = offset_to_position(offset, content); let end_col = find_token_end(content, offset, pos); - + symbols.push(SymbolInformation { name, kind: SymbolKind::TYPE_PARAMETER, @@ -777,12 +664,12 @@ fn collect_symbols_from_ast( }); } } - + Lang::Module(name, members, _, _, help_data) => { let offset = help_data.get_offset(); let pos = offset_to_position(offset, content); let end_col = pos.character + name.len() as u32; - + symbols.push(SymbolInformation { name: name.clone(), kind: SymbolKind::MODULE, @@ -794,20 +681,19 @@ fn collect_symbols_from_ast( container_name: container_name.clone(), tags: None, }); - - // Recursively process module members + for member in members { collect_symbols_from_ast(member, content, file_uri, Some(name.clone()), symbols); } } - + Lang::Signature(var, _typ, _) => { let name = var.get_name(); let help_data = var.get_help_data(); let offset = help_data.get_offset(); let pos = offset_to_position(offset, content); let end_col = find_token_end(content, offset, pos); - + symbols.push(SymbolInformation { name, kind: SymbolKind::FUNCTION, @@ -820,21 +706,16 @@ fn collect_symbols_from_ast( tags: None, }); } - + Lang::Function(_, _, body, _) => { - // Check for nested declarations in function body collect_symbols_from_ast(body, content, file_uri, container_name, symbols); } - - // Other variants don't define workspace-level symbols + _ => {} } } /// Start the LSP server. Blocks until the client disconnects. -/// -/// This is an `async` function meant to be driven by a `tokio` runtime -/// (see `cli.rs` for the `block_on` call). pub async fn run_lsp() { let stdin = tokio::io::stdin(); let stdout = tokio::io::stdout(); @@ -846,219 +727,3 @@ pub async fn run_lsp() { Server::new(stdin, stdout, socket).serve(service).await; } - -// ══════════════════════════════════════════════════════════════════════════ -// ── TESTS ───────────────────────────────────────────────────────────────── -// ══════════════════════════════════════════════════════════════════════════ - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_strip_ansi_codes() { - let input = "\x1b[31mError\x1b[0m: Something went wrong"; - let expected = "Error: Something went wrong"; - assert_eq!(strip_ansi_codes(input), expected); - } - - #[test] - fn test_strip_ansi_codes_no_ansi() { - let input = "Plain text without ANSI codes"; - assert_eq!(strip_ansi_codes(input), input); - } - - #[test] - fn test_clean_error_message() { - let input = "\x1b[31m\nType error: int expected, got string\n\x1b[0m"; - let result = clean_error_message(input); - assert_eq!(result, "Type error: int expected, got string"); - } - - #[test] - fn test_clean_error_message_miette_format() { - let input = r#"thread 'main' panicked at src/processes/type_checking/function_application.rs:39:28: -Err( × Type error: Function `+` not defined in this scope. - ╭─[TypR/main.ty:4:1] - 3 │ - 4 │ 1 + true - · ▲ - · ╰── Not defined in this scope - ╰──── -)"#; - let result = clean_error_message(input); - assert_eq!(result, "Type error: Function `+` not defined in this scope"); - } - - #[test] - fn test_extract_position_from_miette_error() { - let message = r#"Err( × Type error: Function `+` not defined in this scope. - ╭─[TypR/main.ty:4:1] - 3 │ - 4 │ 1 + true - · ▲ - · ╰── Not defined in this scope - ╰──── -)"#; - let content = "line0\nline1\nline2\n1 + true\n"; - let range = extract_position_from_error(message, content); - - assert!(range.is_some()); - if let Some(r) = range { - // Line 4 in 1-based = line 3 in 0-based - assert_eq!(r.start.line, 3); - // Column 1 in 1-based = column 0 in 0-based - assert_eq!(r.start.character, 0); - } - } - - #[test] - fn test_offset_to_position() { - let content = "line 0\nline 1\nline 2"; - - // Offset 0 should be at line 0, col 0 - assert_eq!(offset_to_position(0, content), Position::new(0, 0)); - - // Offset 7 (just after first newline) should be at line 1, col 0 - assert_eq!(offset_to_position(7, content), Position::new(1, 0)); - - // Offset 14 (just after second newline) should be at line 2, col 0 - assert_eq!(offset_to_position(14, content), Position::new(2, 0)); - } - - #[test] - fn test_check_valid_code() { - let code = "let x: int <- 42;"; - let diagnostics = check_code_and_extract_errors(code, "test.ty"); - // Valid code should produce no diagnostics - assert_eq!(diagnostics.len(), 0); - } - - #[test] - fn test_check_syntax_error() { - // Missing semicolon - let code = "let x: int <- 42"; - let diagnostics = check_code_and_extract_errors(code, "test.ty"); - // Should produce at least one diagnostic - assert!(!diagnostics.is_empty()); - } - - #[test] - fn test_check_type_error() { - // Type error: trying to use + with incompatible types - let code = "1 + true;"; - let diagnostics = check_code_and_extract_errors(code, "test.ty"); - // Should produce at least one diagnostic - assert!(!diagnostics.is_empty()); - if let Some(diag) = diagnostics.first() { - assert_eq!(diag.severity, Some(DiagnosticSeverity::ERROR)); - // Verify that the error position is extracted correctly - // The error should be at position 0 (start of "1") - assert_eq!(diag.range.start.line, 0); - } - } - - #[test] - fn test_real_multiline_error() { - // Real error case with multiple lines - let code = "let a <- 1;\nlet b <- 2;\nlet c <- 3;\n1 + true;"; - let diagnostics = check_code_and_extract_errors(code, "test.ty"); - - // Should detect the error - assert!(!diagnostics.is_empty()); - - if let Some(diag) = diagnostics.first() { - assert_eq!(diag.severity, Some(DiagnosticSeverity::ERROR)); - assert!(!diag.message.is_empty()); - // The error "1 + true" is on line 4 (0-indexed: line 3) - // Verify that the position is correctly extracted from HelpData - assert_eq!(diag.range.start.line, 3, "Error should be on line 4 (0-indexed: 3)"); - } - } - - #[test] - fn test_error_position_extraction() { - // Test that error positions are correctly extracted - // Using "1 + true" which produces a FunctionNotFound error - let code = "let x <- 1;\nlet y <- 2;\n1 + true;"; - let diagnostics = check_code_and_extract_errors(code, "test.ty"); - - assert!(!diagnostics.is_empty()); - - if let Some(diag) = diagnostics.first() { - // The error "1 + true" is on line 3 (0-indexed: 2) - assert_eq!(diag.range.start.line, 2, "Error should be on line 3 (0-indexed: 2)"); - // The error starts at column 0 (start of "1") - assert_eq!(diag.range.start.character, 0); - } - } - - // ── workspace/symbol tests ─────────────────────────────────────────────── - - #[test] - fn test_workspace_symbols_let_binding() { - let code = "let myVariable <- 42;"; - let uri = Url::parse("file:///test.ty").unwrap(); - let symbols = get_workspace_symbols(code, &uri); - - assert_eq!(symbols.len(), 1); - assert_eq!(symbols[0].name, "myVariable"); - assert_eq!(symbols[0].kind, SymbolKind::VARIABLE); - } - - #[test] - fn test_workspace_symbols_function() { - let code = "let add <- fn(a: int, b: int): int { a + b };"; - let uri = Url::parse("file:///test.ty").unwrap(); - let symbols = get_workspace_symbols(code, &uri); - - assert_eq!(symbols.len(), 1); - assert_eq!(symbols[0].name, "add"); - assert_eq!(symbols[0].kind, SymbolKind::FUNCTION); - } - - #[test] - fn test_workspace_symbols_type_alias() { - let code = "type MyInt <- int;"; - let uri = Url::parse("file:///test.ty").unwrap(); - let symbols = get_workspace_symbols(code, &uri); - - assert_eq!(symbols.len(), 1); - assert_eq!(symbols[0].name, "MyInt"); - assert_eq!(symbols[0].kind, SymbolKind::TYPE_PARAMETER); - } - - #[test] - fn test_workspace_symbols_multiple() { - let code = "let x <- 1;\nlet y <- 2;\ntype MyType <- int;"; - let uri = Url::parse("file:///test.ty").unwrap(); - let symbols = get_workspace_symbols(code, &uri); - - assert_eq!(symbols.len(), 3); - - let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect(); - assert!(names.contains(&"x")); - assert!(names.contains(&"y")); - assert!(names.contains(&"MyType")); - } - - #[test] - fn test_workspace_symbols_empty() { - let code = "42;"; // Just an expression, no symbols defined - let uri = Url::parse("file:///test.ty").unwrap(); - let symbols = get_workspace_symbols(code, &uri); - - assert!(symbols.is_empty()); - } - - #[test] - fn test_workspace_symbols_invalid_syntax() { - let code = "let x <- "; // Incomplete code - let uri = Url::parse("file:///test.ty").unwrap(); - let symbols = get_workspace_symbols(code, &uri); - - // Should not panic, may return empty or partial results - // Just verify it doesn't crash - let _ = symbols; - } -} diff --git a/src/interface/parser.rs b/crates/typr-cli/src/lsp_parser.rs similarity index 56% rename from src/interface/parser.rs rename to crates/typr-cli/src/lsp_parser.rs index 8dae306..e4dac28 100644 --- a/src/interface/parser.rs +++ b/crates/typr-cli/src/lsp_parser.rs @@ -6,21 +6,21 @@ //! 2. Parses and type-checks the whole document using the project's //! pipeline (`parse` → `typing`) to build a fully-populated `Context`. //! 3. Looks up the identifier in that context and returns its type. -//! 4. Renders the type string with Markdown syntax highlighting, using -//! the same semantic categories as the REPL highlighter in `repl.rs`. - -use crate::components::context::config::Environment; -use crate::components::context::Context; -use crate::components::language::var::Var; -use crate::components::language::Lang; -use crate::components::r#type::type_system::TypeSystem; -use crate::components::r#type::Type; -use crate::processes::parsing::parse; -use crate::processes::type_checking::typing; -use crate::utils::metaprogramming::metaprogrammation; +//! 4. Renders the type string with Markdown syntax highlighting. + +use crate::metaprogramming::metaprogrammation; use nom_locate::LocatedSpan; use std::path::Path; -use tower_lsp::lsp_types::{Position, Range}; +use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, Position, Range}; +use typr_core::components::context::config::Environment; +use typr_core::components::context::Context; +use typr_core::components::language::var::Var; +use typr_core::components::language::Lang; +use typr_core::components::r#type::type_system::TypeSystem; +use typr_core::components::r#type::Type; +use typr_core::processes::parsing::parse; +use typr_core::typing; +use typr_core::utils::builder; type Span<'a> = LocatedSpan<&'a str, String>; @@ -99,7 +99,6 @@ fn detect_environment(file_path: &str) -> Environment { let path = Path::new(file_path); let mut dir = path.parent(); - // Walk up the directory tree to find DESCRIPTION and NAMESPACE while let Some(d) = dir { let description = d.join("DESCRIPTION"); let namespace = d.join("NAMESPACE"); @@ -113,17 +112,6 @@ fn detect_environment(file_path: &str) -> Environment { } /// Main entry-point called by the LSP goto_definition handler. -/// -/// Finds the definition location of the identifier under the cursor. -/// Returns `None` when: -/// - the cursor is not on an identifier, or -/// - parsing or type-checking fails, or -/// - the identifier is not found in the context (e.g., built-in or undefined). -/// -/// The `file_path` parameter is used to: -/// - Store the correct file name in HelpData for local definitions -/// - Detect the environment (Project vs StandAlone) for module imports -/// - Resolve cross-file definitions pub fn find_definition_at( content: &str, line: u32, @@ -150,13 +138,11 @@ pub fn find_definition_at( let final_context = type_context.context; // 5. Look up the variable in the context to find its definition. - // We need to find the Var that matches this name and get its HelpData. let definition_var = final_context .variables() .find(|(var, _)| var.get_name() == word) .map(|(var, _)| var.clone()); - // Also check aliases if not found in variables let definition_var = definition_var.or_else(|| { final_context .aliases() @@ -172,16 +158,11 @@ pub fn find_definition_at( // 6. Determine if the definition is in a different file. let (source_content, file_path_result) = if definition_file.is_empty() || definition_file == file_path { - // Definition is in the current file (content.to_string(), None) } else { - // Definition is in a different file - read its content match std::fs::read_to_string(&definition_file) { Ok(external_content) => (external_content, Some(definition_file)), - Err(_) => { - // If we can't read the file, fall back to current file - (content.to_string(), None) - } + Err(_) => (content.to_string(), None), } }; @@ -218,7 +199,7 @@ fn offset_to_position(offset: usize, content: &str) -> Position { // ── word extraction ──────────────────────────────────────────────────────── /// Extract the contiguous word (identifier or numeric literal) that contains -/// the given cursor position. Returns the word text and its LSP Range. +/// the given cursor position. fn extract_word_at(content: &str, line: u32, character: u32) -> Option<(String, Range)> { let source_line = content.lines().nth(line as usize)?; @@ -229,7 +210,6 @@ fn extract_word_at(content: &str, line: u32, character: u32) -> Option<(String, let bytes = source_line.as_bytes(); let col = character as usize; - // Ensure cursor is on a word character (or one position past one). if col >= bytes.len() || !is_word_char(bytes[col]) { if col == 0 { return None; @@ -275,8 +255,7 @@ fn extract_word_at(content: &str, line: u32, character: u32) -> Option<(String, )) } -/// A character is part of a word if it is alphanumeric, an underscore, or a dot -/// (TypR / R identifiers can contain dots). +/// A character is part of a word if it is alphanumeric, an underscore, or a dot. fn is_word_char(b: u8) -> bool { b.is_ascii_alphanumeric() || b == b'_' || b == b'.' } @@ -285,8 +264,6 @@ fn is_word_char(b: u8) -> bool { /// For numeric literals that are not in the context, return their literal type. fn infer_literal_type(word: &str) -> Option { - use crate::utils::builder; - if let Ok(i) = word.parse::() { return Some(builder::integer_type(i)); } @@ -297,32 +274,14 @@ fn infer_literal_type(word: &str) -> Option { } // ── Markdown type highlighter ────────────────────────────────────────────── -// -// Semantic colour mapping (mirrors repl.rs `colors` mod): -// -// REPL colour │ Markdown rendering │ Applies to -// ──────────────┼────────────────────────┼───────────────────────────── -// KEYWORD │ ***bold italic*** │ `fn`, `Module`, `interface` -// FUNCTION/TYPE │ **bold** │ primitive types, alias names, -// (cyan) │ │ tag names, `list`, `Seq` -// NUMBER │ *italic* │ numeric literals in types -// STRING │ `code` │ char-literal values -// OPERATOR │ plain │ `->`, `|`, `&`, `+`, `-`, … -// BRACKET │ plain │ `( ) [ ] { }` -// GENERIC │ *italic* │ `T`, `A`, `#N`, `%L` -// - -/// Primitive type names that should be rendered in **bold** (FUNCTION/TYPE colour). + +/// Primitive type names that should be rendered in **bold**. const PRIMITIVE_TYPES: &[&str] = &["int", "num", "bool", "char", "any", "Empty"]; -/// Keywords rendered in ***bold italic*** (KEYWORD colour). +/// Keywords rendered in ***bold italic***. const TYPE_KEYWORDS: &[&str] = &["fn", "Module", "interface", "class"]; /// Highlight a TypR type string into inline Markdown. -/// -/// The function does a single forward scan, accumulating word tokens and -/// emitting formatted spans. Non-word characters (operators, brackets, -/// punctuation) are emitted as-is. pub fn highlight_type(type_str: &str) -> String { let mut out = String::with_capacity(type_str.len() * 2); let chars: Vec = type_str.chars().collect(); @@ -332,22 +291,22 @@ pub fn highlight_type(type_str: &str) -> String { while i < len { let ch = chars[i]; - // ── generic prefixes: #identifier or %identifier ────────────── + // ── generic prefixes: #identifier or %identifier ────────────── if (ch == '#' || ch == '%') && i + 1 < len && (chars[i + 1].is_alphanumeric() || chars[i + 1] == '_') { let start = i; - i += 1; // skip # or % + i += 1; while i < len && (chars[i].is_alphanumeric() || chars[i] == '_') { i += 1; } let token: String = chars[start..i].iter().collect(); - out.push_str(&format!("*{}*", token)); // italic (generic) + out.push_str(&format!("*{}*", token)); continue; } - // ── char-literal string values: sequences like "hello" or 'hello' ─ + // ── char-literal string values ───────────────────────────────── if ch == '"' || ch == '\'' { let delim = ch; let start = i; @@ -356,14 +315,14 @@ pub fn highlight_type(type_str: &str) -> String { i += 1; } if i < len { - i += 1; // consume closing delimiter + i += 1; } let token: String = chars[start..i].iter().collect(); - out.push_str(&format!("`{}`", token)); // code span (STRING colour) + out.push_str(&format!("`{}`", token)); continue; } - // ── word token (letter / underscore / digit-start handled below) ── + // ── word token ────────────────────────────────────────────────── if ch.is_alphabetic() || ch == '_' { let start = i; while i < len && (chars[i].is_alphanumeric() || chars[i] == '_' || chars[i] == '.') { @@ -374,18 +333,18 @@ pub fn highlight_type(type_str: &str) -> String { continue; } - // ── numeric literal (NUMBER colour → italic) ───────────────────── + // ── numeric literal ───────────────────────────────────────────── if ch.is_ascii_digit() { let start = i; while i < len && (chars[i].is_ascii_digit() || chars[i] == '.') { i += 1; } let token: String = chars[start..i].iter().collect(); - out.push_str(&format!("*{}*", token)); // italic + out.push_str(&format!("*{}*", token)); continue; } - // ── tag dot-prefix: .TagName ────────────────────────────────────── + // ── tag dot-prefix: .TagName ──────────────────────────────────── if ch == '.' && i + 1 < len && chars[i + 1].is_alphabetic() { out.push('.'); i += 1; @@ -394,19 +353,18 @@ pub fn highlight_type(type_str: &str) -> String { i += 1; } let word: String = chars[start..i].iter().collect(); - // Tag names → bold (same as type names) out.push_str(&format!("**{}**", word)); continue; } - // ── arrow operator `->` ─────────────────────────────────────────── + // ── arrow operator `->` ───────────────────────────────────────── if ch == '-' && i + 1 < len && chars[i + 1] == '>' { - out.push_str(" → "); // use the prettier unicode arrow, plain text + out.push_str(" → "); i += 2; continue; } - // ── everything else: operators, brackets, commas, spaces ────────── + // ── everything else ───────────────────────────────────────────── out.push(ch); i += 1; } @@ -417,25 +375,19 @@ pub fn highlight_type(type_str: &str) -> String { /// Classify a word and wrap it in the appropriate Markdown formatting. fn colorize_word(word: &str) -> String { if TYPE_KEYWORDS.contains(&word) { - // ***bold italic*** — KEYWORD colour (magenta in REPL) format!("***{}***", word) } else if PRIMITIVE_TYPES.contains(&word) { - // **bold** — FUNCTION/TYPE colour (cyan in REPL) format!("**{}**", word) } else if is_generic_name(word) { - // *italic* — generic type variable format!("*{}*", word) } else if word.chars().next().map_or(false, |c| c.is_uppercase()) { - // Capitalised → user-defined type / alias name → **bold** format!("**{}**", word) } else { - // Anything else (field names, variable names inside types) → plain word.to_string() } } -/// A generic name is a single uppercase ASCII letter, optionally followed by -/// digits (e.g. `T`, `A`, `T1`). +/// A generic name is a single uppercase ASCII letter, optionally followed by digits. fn is_generic_name(word: &str) -> bool { let mut chars = word.chars(); match chars.next() { @@ -448,23 +400,12 @@ fn is_generic_name(word: &str) -> bool { // ── AUTOCOMPLETION ──────────────────────────────────────────────────────── // ══════════════════════════════════════════════════════════════════════════ -use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind}; - /// Main entry point for LSP completion requests. -/// -/// Parses and type-checks the entire document, detects the syntactic context -/// at the cursor position, and returns context-appropriate completion items. -/// -/// Supports three trigger types: -/// - `|>` (pipe): functions whose first parameter matches the left-hand type -/// - `$` (dollar): record/list fields -/// - `.` (dot): module members, record fields, or applicable functions (hybrid) pub fn get_completions_at(content: &str, line: u32, character: u32) -> Vec { - // 1. Parse + type-check the document WITHOUT the cursor line to avoid incomplete code + // 1. Parse + type-check the document WITHOUT the cursor line let final_context = match parse_document_without_cursor_line(content, line) { Some(ctx) => ctx, None => { - // Fallback: try parsing the whole document anyway let span: Span = LocatedSpan::new_extra(content, String::new()); let parse_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| parse(span))); @@ -484,8 +425,7 @@ pub fn get_completions_at(content: &str, line: u32, character: u32) -> Vec Vec Option { let lines: Vec<&str> = content.lines().collect(); - // Build document without cursor line let mut filtered_lines = Vec::new(); for (idx, line) in lines.iter().enumerate() { if idx != cursor_line as usize { @@ -518,18 +456,12 @@ fn parse_document_without_cursor_line(content: &str, cursor_line: u32) -> Option let filtered_content = filtered_lines.join("\n"); let span: Span = LocatedSpan::new_extra(&filtered_content, String::new()); - // Parse the document let parse_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| parse(span))).ok()?; let ast = parse_result.ast; - // Use Context::default() to start with the standard library let context = Context::default(); - // For proper context propagation, we need to type each statement sequentially - // so that earlier definitions are available in later expressions. - // The standard typing() for Lang::Lines doesn't properly propagate context - // for single statements, so we manually iterate. let final_context = if let Lang::Lines(exprs, _) = &ast { let mut ctx = context.clone(); for expr in exprs { @@ -541,7 +473,6 @@ fn parse_document_without_cursor_line(content: &str, cursor_line: u32) -> Option } ctx } else { - // Fallback for non-Lines AST std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| typing(&context, &ast))) .ok()? .context @@ -554,29 +485,14 @@ fn parse_document_without_cursor_line(content: &str, cursor_line: u32) -> Option #[derive(Debug, Clone)] enum CompletionCtx { - /// Type annotation context (e.g., after `let x: ` or in `fn(a: `) Type, - /// Module member access (e.g., `MyModule.`) Module(String), - /// Pipe operator (e.g., `value |>`) Pipe(String), - /// Record field access via $ (e.g., `myrecord$`) RecordField(String), - /// Dot access - hybrid (module members, record fields, or applicable functions) DotAccess(String), - /// General expression context (runtime values) Expression, } -/// Extract a multi-line prefix context (like rust-analyzer). -/// This includes previous lines to handle cases where the trigger is on a new line. -/// -/// Example: -/// ``` -/// mylist -/// .█ -/// ``` -/// Should extract "mylist\n ." to properly detect the completion context. fn extract_multiline_prefix(content: &str, line: u32, character: u32) -> String { let lines: Vec<&str> = content.lines().collect(); let current_line_idx = line as usize; @@ -585,64 +501,48 @@ fn extract_multiline_prefix(content: &str, line: u32, character: u32) -> String return String::new(); } - // Get current line up to cursor let current_line_part = lines[current_line_idx] .get(..character as usize) .unwrap_or(""); - // Look back up to 10 lines to find context - // This handles cases like: - // myvar - // .field let lookback_lines = 10; let start_line = current_line_idx.saturating_sub(lookback_lines); - // Collect lines from start_line to current (inclusive) let mut context_lines = Vec::new(); for i in start_line..current_line_idx { context_lines.push(lines[i]); } context_lines.push(current_line_part); - // Join with newlines to preserve structure context_lines.join("\n") } fn detect_completion_context(prefix: &str) -> CompletionCtx { let trimmed = prefix.trim_end(); - // Pipe operator: "expr |>" (must check BEFORE checking '>' alone) if trimmed.ends_with("|>") { let before_pipe = trimmed[..trimmed.len() - 2].trim(); return CompletionCtx::Pipe(extract_expression_before(before_pipe)); } - // Record field access via $: "record$" - // Note: we check for $ at the end (just typed) OR just before cursor if let Some(dollar_pos) = trimmed.rfind('$') { - // Only trigger if $ is at the end or followed by whitespace (including newlines) let after_dollar = &trimmed[dollar_pos + 1..]; if after_dollar.is_empty() || after_dollar.chars().all(|c| c.is_whitespace()) { let before_dollar = trimmed[..dollar_pos].trim_end(); if !before_dollar.is_empty() { - // Extract the last expression before $ (handling multiline) let expr = extract_last_expression(before_dollar); return CompletionCtx::RecordField(expr); } } } - // Dot access - could be module or record field or function application if let Some(dot_pos) = trimmed.rfind('.') { - // Only trigger if . is at the end or followed by whitespace (including newlines) let after_dot = &trimmed[dot_pos + 1..]; if after_dot.is_empty() || after_dot.chars().all(|c| c.is_whitespace()) { let before_dot = trimmed[..dot_pos].trim_end(); if !before_dot.is_empty() { - // Extract the last expression before . (handling multiline) let expr = extract_last_expression(before_dot); - // Check if it looks like a module name (starts with uppercase) if expr.chars().next().map_or(false, |c| c.is_uppercase()) { return CompletionCtx::Module(expr); } else { @@ -652,7 +552,6 @@ fn detect_completion_context(prefix: &str) -> CompletionCtx { } } - // Type annotation: "let x: " or "fn(a: " if let Some(colon_pos) = trimmed.rfind(':') { let after_colon = &trimmed[colon_pos + 1..]; if !after_colon.contains('=') && !after_colon.contains(';') { @@ -660,32 +559,16 @@ fn detect_completion_context(prefix: &str) -> CompletionCtx { } } - // Type alias definition: "type MyType = " if trimmed.trim_start().starts_with("type ") && trimmed.contains('=') { return CompletionCtx::Type; } - // Default: expression (runtime values) CompletionCtx::Expression } -/// Extract the last expression from a multi-line context. -/// This handles cases like: -/// ``` -/// myvar -/// . -/// ``` -/// Should extract "myvar" even with newlines/whitespace. -/// -/// Also handles complex expressions like: -/// - `calculate(x).process()` → extracts the whole chain -/// - `list(a = 1, b = 2)` → extracts the whole literal -/// - `x[0].field` → extracts the whole accessor chain fn extract_last_expression(s: &str) -> String { let trimmed = s.trim_end(); - // Split by statement terminators (;, newline when starting a new statement) - // For simplicity, we split by ; and newlines, then take the last non-empty part let parts: Vec<&str> = trimmed .split(|c| c == ';' || c == '\n') .filter(|p| !p.trim().is_empty()) @@ -697,10 +580,6 @@ fn extract_last_expression(s: &str) -> String { return String::new(); } - // Now extract the last complete expression from this statement - // We want to capture function calls, field access, indexing, etc. - // Strategy: scan backwards and track depth of (), [], {} - // Stop only at statement-level operators when at depth 0 let mut depth_paren = 0; let mut depth_bracket = 0; let mut depth_brace = 0; @@ -712,7 +591,6 @@ fn extract_last_expression(s: &str) -> String { '(' => { depth_paren -= 1; if depth_paren < 0 { - // Unmatched opening paren - start here start = i + 1; break; } @@ -721,7 +599,6 @@ fn extract_last_expression(s: &str) -> String { '[' => { depth_bracket -= 1; if depth_bracket < 0 { - // Unmatched opening bracket - start here start = i + 1; break; } @@ -730,23 +607,16 @@ fn extract_last_expression(s: &str) -> String { '{' => { depth_brace -= 1; if depth_brace < 0 { - // Unmatched opening brace - start here start = i + 1; break; } } - // Only stop at these operators when at depth 0 (not inside any parens/brackets) - // Don't stop at '=' inside function calls like `list(a = 1)` ',' if depth_paren == 0 && depth_bracket == 0 && depth_brace == 0 => { - // Comma at top level - start after it start = i + 1; break; } - // Assignment and comparison operators - but only at top level '<' | '>' if depth_paren == 0 && depth_bracket == 0 && depth_brace == 0 => { - // Check if it's <- (assignment) or just comparison if i > 0 && last_statement.as_bytes().get(i - 1) == Some(&b'-') { - // It's part of <-, skip continue; } start = i + 1; @@ -759,13 +629,9 @@ fn extract_last_expression(s: &str) -> String { last_statement[start..].trim().to_string() } -/// Extract the expression before a trigger (simple heuristic). -/// Looks backward for word boundaries, operators, or delimiters. fn extract_expression_before(s: &str) -> String { let trimmed = s.trim_end(); - // Find the start of the last expression by scanning backwards - // Stop at operators, semicolons, or opening delimiters let mut depth = 0; let mut start = trimmed.len(); @@ -792,14 +658,9 @@ fn extract_expression_before(s: &str) -> String { // ── Completion generators ────────────────────────────────────────────────── -/// Completions for type annotation contexts. -/// Adds a leading space so "let x:" becomes "let x: int" fn get_type_completions(context: &Context) -> Vec { - use crate::utils::builder; - let mut items = Vec::new(); - // Primitive types (keywords in the language, like TypeScript) let primitives = [ ("int", builder::integer_type_default()), ("num", builder::number_type()), @@ -818,7 +679,6 @@ fn get_type_completions(context: &Context) -> Vec { }); } - // User-defined type aliases (interfaces/type contracts) for (var, typ) in context.aliases() { if var.is_alias() { items.push(CompletionItem { @@ -831,7 +691,6 @@ fn get_type_completions(context: &Context) -> Vec { } } - // Module-exported type aliases (interfaces/type contracts) for (var, typ) in context.module_aliases() { items.push(CompletionItem { label: var.get_name(), @@ -845,7 +704,6 @@ fn get_type_completions(context: &Context) -> Vec { items } -/// Completions for module member access (e.g., `MyModule.█`). fn get_module_completions(context: &Context, module_name: &str) -> Vec { let module_context = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { context.extract_module_as_vartype(module_name) @@ -867,7 +725,6 @@ fn get_module_completions(context: &Context, module_name: &str) -> Vec Vec █`). -/// -/// Filters functions whose first parameter type is compatible with the -/// type of the expression on the left-hand side. fn get_pipe_completions(context: &Context, expr: &str) -> Vec { - // Try to infer the type of the expression let expr_type = infer_expression_type(context, expr); let mut items = Vec::new(); - // Get all functions (user + std) let all_functions: Vec<_> = context .get_all_generic_functions() .into_iter() @@ -904,10 +755,8 @@ fn get_pipe_completions(context: &Context, expr: &str) -> Vec { .collect(); for (var, typ) in all_functions { - // Check if this function can accept expr_type as first parameter if let Some(first_param_type) = get_first_parameter_type(&typ) { if expr_type.is_subtype(&first_param_type, context).0 { - // For pipe completions, add a leading space so "expr |>" becomes "expr |> func" items.push(CompletionItem { label: var.get_name(), insert_text: Some(format!(" {}", var.get_name())), @@ -922,9 +771,6 @@ fn get_pipe_completions(context: &Context, expr: &str) -> Vec { items } -/// Completions for record field access via $ (e.g., `myrecord$█`). -/// -/// Looks up the type of the record expression and returns its fields. fn get_record_field_completions(context: &Context, expr: &str) -> Vec { let record_type = infer_expression_type(context, expr); @@ -942,21 +788,11 @@ fn get_record_field_completions(context: &Context, expr: &str) -> Vec Vec { let mut items = Vec::new(); - #[cfg(test)] - eprintln!("DEBUG get_dot_completions: expr = {:?}", expr); - let expr_type = infer_expression_type(context, expr); - // 1. If it's a record, show fields if let Type::Record(fields, _) = &expr_type { for arg_type in fields { items.push(CompletionItem { @@ -968,8 +804,6 @@ fn get_dot_completions(context: &Context, expr: &str) -> Vec { } } - // 2. Show applicable functions (same logic as pipe) - // Functions whose first parameter type matches the expression type let all_functions: Vec<_> = context .get_all_generic_functions() .into_iter() @@ -998,11 +832,9 @@ fn get_dot_completions(context: &Context, expr: &str) -> Vec { items } -/// Completions for general expression contexts (runtime values). fn get_expression_completions(context: &Context) -> Vec { let mut items = Vec::new(); - // User-defined variables (non-function values) for (var, typ) in context.variables() { if !typ.is_function() && !var.is_alias() { items.push(var_to_completion_item( @@ -1013,7 +845,6 @@ fn get_expression_completions(context: &Context) -> Vec { } } - // User-defined functions for (var, typ) in context.get_all_generic_functions() { items.push(var_to_completion_item( &var, @@ -1022,7 +853,6 @@ fn get_expression_completions(context: &Context) -> Vec { )); } - // Standard library functions for (var, typ) in &context.typing_context.standard_library() { if typ.is_function() { items.push(var_to_completion_item( @@ -1036,11 +866,7 @@ fn get_expression_completions(context: &Context) -> Vec { items } -/// Fallback completions when parsing or type-checking fails. -/// Returns primitive types as they are language keywords. fn get_fallback_completions() -> Vec { - use crate::utils::builder; - let mut items = Vec::new(); let primitives = [ @@ -1065,98 +891,51 @@ fn get_fallback_completions() -> Vec { // ── Type inference helpers ───────────────────────────────────────────────── -/// Attempt to infer the type of an expression string. -/// -/// This supports complex expressions like rust-analyzer: -/// - Simple variables: `myvar` -/// - Function calls: `calculate(x)` -/// - Field access: `record.field` -/// - Chaining: `x.process().result` -/// - Literals: `42`, `"hello"`, `list(a = 1)` -/// -/// Strategy: parse and type-check the expression in the current context fn infer_expression_type(context: &Context, expr: &str) -> Type { - use crate::utils::builder; - let trimmed = expr.trim(); if trimmed.is_empty() { return builder::any_type(); } - // First, try the simple case: a known variable name let types = context.get_types_from_name(trimmed); if !types.is_empty() { return types.last().unwrap().clone(); } - // Try to parse as a literal if let Some(typ) = infer_literal_type(trimmed) { return typ; } - // For complex expressions (function calls, field access, etc.), - // we need to parse and type-check the expression - // Strategy: parse the expression as a standalone statement and infer its type let result = parse_and_infer_expression_type(context, trimmed); result.unwrap_or_else(|| builder::any_type()) } -/// Parse and type-check an expression to infer its type. -/// This handles complex expressions like: -/// - `calculate(x)` → type of calculate's return value -/// - `list(a = 1, b = 2)` → {a: int, b: int} -/// - `myvar.field` → type of field -/// - `func(x).method()` → method-style chained calls fn parse_and_infer_expression_type(context: &Context, expr: &str) -> Option { - // First, try to handle method-style dot notation (e.g., `expr.func()`) - // by converting it to standard function calls let normalized_expr = normalize_dot_calls(context, expr); - // Wrap the expression in a minimal parseable statement - // We'll try to parse it as: `__temp <- expr;` - // Adding semicolon ensures proper statement termination let wrapped = format!("__temp <- {};", normalized_expr); let span: Span = LocatedSpan::new_extra(&wrapped, "".to_string()); - // Try to parse let ast = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| parse(span))) .ok()? .ast; - // Try to type-check using typing_with_errors to avoid panics let type_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - crate::processes::type_checking::typing_with_errors(context, &ast) + typr_core::typing_with_errors(context, &ast) })) .ok()?; - // The type of the expression is the value type from the TypeContext - // (the result of typing the assignment `__temp <- expr`) Some(type_result.type_context.value.clone()) } -/// Normalize method-style dot calls to standard function calls. -/// -/// Converts expressions like: -/// - `x.incr()` → `incr(x)` -/// - `x.incr().incr()` → `incr(incr(x))` -/// - `incr(1).incr()` → `incr(incr(1))` -/// -/// This allows the type system to correctly infer the type of chained method calls. fn normalize_dot_calls(context: &Context, expr: &str) -> String { let trimmed = expr.trim(); - // Pattern: something.identifier() or something.identifier - // We need to find the rightmost dot that's followed by an identifier - - // Strategy: scan from right to left to find `.identifier(` or `.identifier` at the end - // This handles nested cases like `a.b().c()` by processing from right to left - let mut result = trimmed.to_string(); let mut changed = true; - // Keep transforming until no more changes (handles nested chains) while changed { changed = false; if let Some(transformed) = try_normalize_rightmost_dot_call(context, &result) { @@ -1168,16 +947,10 @@ fn normalize_dot_calls(context: &Context, expr: &str) -> String { result } -/// Try to normalize the rightmost method-style dot call in an expression. -/// Returns Some(transformed) if a transformation was made, None otherwise. fn try_normalize_rightmost_dot_call(context: &Context, expr: &str) -> Option { - // Find the rightmost dot that's followed by an identifier (potential method call) - // We need to handle parentheses properly: `a.b(x).c()` → rightmost is `.c()` - let chars: Vec = expr.chars().collect(); let len = chars.len(); - // Scan backwards to find the rightmost dot that could be a method call let mut paren_depth = 0; let mut bracket_depth = 0; let mut i = len; @@ -1198,9 +971,7 @@ fn try_normalize_rightmost_dot_call(context: &Context, expr: &str) -> Option { - // Found a dot at the top level, check if it's followed by an identifier if i + 1 < len && (chars[i + 1].is_alphabetic() || chars[i + 1] == '_') { - // Extract the method name let mut method_end = i + 1; while method_end < len && (chars[method_end].is_alphanumeric() || chars[method_end] == '_') @@ -1209,11 +980,9 @@ fn try_normalize_rightmost_dot_call(context: &Context, expr: &str) -> Option Option = after_method.chars().collect(); let mut depth = 0; let mut close_idx = 0; @@ -1245,12 +1011,9 @@ fn try_normalize_rightmost_dot_call(context: &Context, expr: &str) -> Option Option Option Option { match typ { Type::Function(params, _, _) => params.first().cloned(), @@ -1290,9 +1050,6 @@ fn get_first_parameter_type(typ: &Type) -> Option { } } -// ── Helpers ──────────────────────────────────────────────────────────────── - -/// Convert a `(Var, Type)` pair into a LSP `CompletionItem`. fn var_to_completion_item(var: &Var, typ: &Type, kind: CompletionItemKind) -> CompletionItem { CompletionItem { label: var.get_name(), @@ -1301,342 +1058,3 @@ fn var_to_completion_item(var: &Var, typ: &Type, kind: CompletionItemKind) -> Co ..Default::default() } } - -// ══════════════════════════════════════════════════════════════════════════ -// ── TESTS ───────────────────────────────────────────────────────────────── -// ══════════════════════════════════════════════════════════════════════════ - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_detect_completion_context_pipe() { - let prefix = "mylist |>"; - let ctx = detect_completion_context(prefix); - assert!(matches!(ctx, CompletionCtx::Pipe(_))); - } - - #[test] - fn test_detect_completion_context_pipe_with_space() { - let prefix = "mylist |> "; - let ctx = detect_completion_context(prefix); - assert!(matches!(ctx, CompletionCtx::Pipe(_))); - } - - #[test] - fn test_detect_completion_context_record_field() { - let prefix = "myrecord$"; - let ctx = detect_completion_context(prefix); - assert!(matches!(ctx, CompletionCtx::RecordField(_))); - } - - #[test] - fn test_detect_completion_context_record_field_with_space() { - let prefix = "myrecord$ "; - let ctx = detect_completion_context(prefix); - assert!(matches!(ctx, CompletionCtx::RecordField(_))); - } - - #[test] - fn test_detect_completion_context_dot_module() { - let prefix = "MyModule."; - let ctx = detect_completion_context(prefix); - assert!(matches!(ctx, CompletionCtx::Module(_))); - } - - #[test] - fn test_detect_completion_context_dot_record() { - let prefix = "myvar."; - let ctx = detect_completion_context(prefix); - assert!(matches!(ctx, CompletionCtx::DotAccess(_))); - } - - #[test] - fn test_detect_completion_context_dot_with_space() { - let prefix = "myvar. "; - let ctx = detect_completion_context(prefix); - assert!(matches!(ctx, CompletionCtx::DotAccess(_))); - } - - #[test] - fn test_detect_completion_context_dot_multiline() { - let prefix = "myvar\n ."; - let ctx = detect_completion_context(prefix); - assert!(matches!(ctx, CompletionCtx::DotAccess(_))); - if let CompletionCtx::DotAccess(expr) = ctx { - assert_eq!(expr, "myvar"); - } - } - - #[test] - fn test_detect_completion_context_dollar_multiline() { - let prefix = "myrecord\n $"; - let ctx = detect_completion_context(prefix); - assert!(matches!(ctx, CompletionCtx::RecordField(_))); - if let CompletionCtx::RecordField(expr) = ctx { - assert_eq!(expr, "myrecord"); - } - } - - #[test] - fn test_detect_completion_context_pipe_multiline() { - let prefix = "mylist\n |>"; - let ctx = detect_completion_context(prefix); - assert!(matches!(ctx, CompletionCtx::Pipe(_))); - } - - #[test] - fn test_detect_completion_context_type_annotation() { - let prefix = "let x: "; - let ctx = detect_completion_context(prefix); - assert!(matches!(ctx, CompletionCtx::Type)); - } - - #[test] - fn test_detect_completion_context_type_annotation_just_colon() { - let prefix = "let x:"; - let ctx = detect_completion_context(prefix); - assert!(matches!(ctx, CompletionCtx::Type)); - } - - #[test] - fn test_detect_completion_context_function_param_type() { - let prefix = "fn foo(a:"; - let ctx = detect_completion_context(prefix); - assert!(matches!(ctx, CompletionCtx::Type)); - } - - #[test] - fn test_detect_completion_context_expression() { - let prefix = "let x = "; - let ctx = detect_completion_context(prefix); - assert!(matches!(ctx, CompletionCtx::Expression)); - } - - #[test] - fn test_extract_expression_before() { - // Test with semicolon separator (common in R-like syntax) - assert_eq!(extract_expression_before("a; b; c"), "c"); - // Test with comma separator (function arguments) - assert_eq!(extract_expression_before("x, y, z"), "z"); - } - - #[test] - fn test_extract_last_expression() { - // Simple identifier - assert_eq!(extract_last_expression("myvar"), "myvar"); - - // With trailing whitespace - assert_eq!(extract_last_expression("myvar "), "myvar"); - - // With newlines - assert_eq!(extract_last_expression("myvar\n "), "myvar"); - - // After semicolon - assert_eq!(extract_last_expression("a; b; myvar"), "myvar"); - - // Complex expression with multiple lines - assert_eq!(extract_last_expression("let x = foo\nmyvar"), "myvar"); - - // Function calls (NEW - like rust-analyzer) - assert_eq!(extract_last_expression("calculate(x)"), "calculate(x)"); - - // Function with multiple args - assert_eq!(extract_last_expression("foo(a, b, c)"), "foo(a, b, c)"); - - // Nested function calls - assert_eq!( - extract_last_expression("outer(inner(x))"), - "outer(inner(x))" - ); - - // Field access chain - assert_eq!( - extract_last_expression("x.field.subfield"), - "x.field.subfield" - ); - - // Function call with field access - assert_eq!( - extract_last_expression("calculate(x).result"), - "calculate(x).result" - ); - - // List literal - assert_eq!( - extract_last_expression("list(a = 1, b = 2)"), - "list(a = 1, b = 2)" - ); - - // Full function call (not after comma, the whole thing) - assert_eq!(extract_last_expression("foo(a, bar(x))"), "foo(a, bar(x))"); - } - - #[test] - fn test_extract_multiline_prefix() { - let content = "line1\nline2\nline3"; - // Extract from line 2 (0-indexed), position 3 - let result = extract_multiline_prefix(content, 2, 3); - assert!(result.contains("line1")); - assert!(result.contains("line2")); - assert!(result.ends_with("lin")); - } - - // ── Go to definition tests ──────────────────────────────────────────────── - - #[test] - fn test_offset_to_position_start() { - let content = "let x <- 42;"; - let pos = offset_to_position(0, content); - assert_eq!(pos.line, 0); - assert_eq!(pos.character, 0); - } - - #[test] - fn test_offset_to_position_same_line() { - let content = "let x <- 42;"; - let pos = offset_to_position(4, content); - assert_eq!(pos.line, 0); - assert_eq!(pos.character, 4); - } - - #[test] - fn test_offset_to_position_multiline() { - let content = "let x <- 1;\nlet y <- 2;"; - // Position of 'y' (after newline: 12 chars + 4 = offset 16) - let pos = offset_to_position(16, content); - assert_eq!(pos.line, 1); - assert_eq!(pos.character, 4); - } - - #[test] - fn test_find_definition_simple_variable() { - let code = "let myVar <- 42;\nmyVar;"; - // Try to find definition of 'myVar' on line 1, col 0 - // Use empty file_path for tests (no cross-file resolution needed) - let result = find_definition_at(code, 1, 0, ""); - - assert!(result.is_some()); - if let Some(def_info) = result { - // The definition should point to line 0 (where 'let myVar' is) - assert_eq!(def_info.range.start.line, 0); - // file_path should be None for same-file definitions - assert!(def_info.file_path.is_none()); - } - } - - #[test] - fn test_find_definition_function() { - let code = "let add <- fn(a: int, b: int): int { a + b };\nadd(1, 2);"; - // Try to find definition of 'add' on line 1 - let result = find_definition_at(code, 1, 0, ""); - - assert!(result.is_some()); - if let Some(def_info) = result { - // The definition should point to line 0 - assert_eq!(def_info.range.start.line, 0); - assert!(def_info.file_path.is_none()); - } - } - - #[test] - fn test_find_definition_type_alias() { - let code = "type MyInt <- int;\nlet x: MyInt <- 42;"; - // Try to find definition of 'MyInt' on line 1 - let result = find_definition_at(code, 1, 7, ""); - - assert!(result.is_some()); - if let Some(def_info) = result { - // The definition should point to line 0 - assert_eq!(def_info.range.start.line, 0); - assert!(def_info.file_path.is_none()); - } - } - - #[test] - fn test_find_definition_undefined() { - let code = "let x <- undefined_var;"; - // Try to find definition of 'undefined_var' which doesn't exist - let result = find_definition_at(code, 0, 9, ""); - - // Should return None because 'undefined_var' is not defined - assert!(result.is_none()); - } - - #[test] - fn test_find_definition_literal() { - let code = "let x <- 42;"; - // Try to find definition of '42' (a literal) - let result = find_definition_at(code, 0, 9, ""); - - // Literals don't have definitions - assert!(result.is_none()); - } - - // ── Completion with function chaining tests ──────────────────────────────── - - #[test] - fn test_dot_completion_with_function_call() { - // Define a function incr: (int) -> int - // Then test that incr(1). suggests incr again - let code = "let incr <- fn(x: int): int { x + 1 };\nincr(1)."; - let completions = get_completions_at(code, 1, 8); - - // Should find incr as a completion because incr(1) returns int - // and incr takes int as first parameter - let has_incr = completions.iter().any(|item| item.label == "incr"); - assert!( - has_incr, - "Expected 'incr' in completions, got: {:?}", - completions.iter().map(|c| &c.label).collect::>() - ); - } - - #[test] - fn test_dot_completion_with_chained_calls() { - // Test incr(1).incr(). should suggest incr - let code = "let incr <- fn(x: int): int { x + 1 };\nincr(1).incr()."; - let completions = get_completions_at(code, 1, 15); - - let has_incr = completions.iter().any(|item| item.label == "incr"); - assert!( - has_incr, - "Expected 'incr' in completions for chained call, got: {:?}", - completions.iter().map(|c| &c.label).collect::>() - ); - } - - #[test] - fn test_infer_expression_type_function_call() { - use crate::utils::fluent_parser::FluentParser; - - // Use FluentParser to properly set up the context with user-defined function - let parser = FluentParser::new() - .push("let incr <- fn(x: int): int { x + 1 };") - .run(); - - let final_context = parser.get_context(); - - // Verify incr is in the context - let incr_types = final_context.get_types_from_name("incr"); - assert!(!incr_types.is_empty(), "incr should be in the context"); - - // Test that infer_expression_type correctly infers incr(1) as int - let expr_type = infer_expression_type(&final_context, "incr(1)"); - - assert!( - matches!(expr_type, Type::Integer(_, _)), - "Expected Integer type for incr(1), got: {:?}", - expr_type.pretty() - ); - } - - #[test] - fn test_extract_last_expression_with_method_chain() { - // Test that function chains are extracted correctly - assert_eq!(extract_last_expression("incr(1)"), "incr(1)"); - assert_eq!(extract_last_expression("incr(1).incr()"), "incr(1).incr()"); - assert_eq!(extract_last_expression("a.b().c()"), "a.b().c()"); - } -} diff --git a/crates/typr-cli/src/main.rs b/crates/typr-cli/src/main.rs new file mode 100644 index 0000000..66a4d19 --- /dev/null +++ b/crates/typr-cli/src/main.rs @@ -0,0 +1,18 @@ +//! TypR CLI main entry point +//! +//! This is the main executable for the TypR command-line interface. + +mod cli; +mod engine; +mod fs_provider; +mod io; +mod lsp; +mod lsp_parser; +mod metaprogramming; +mod project; +mod repl; +mod standard_library; + +fn main() { + cli::start() +} diff --git a/src/utils/metaprogramming.rs b/crates/typr-cli/src/metaprogramming.rs similarity index 82% rename from src/utils/metaprogramming.rs rename to crates/typr-cli/src/metaprogramming.rs index 35e51d9..e0d58ce 100644 --- a/src/utils/metaprogramming.rs +++ b/crates/typr-cli/src/metaprogramming.rs @@ -1,9 +1,12 @@ -use crate::components::context::config::Environment; -use crate::components::language::Lang; -use crate::processes::parsing::parse; -use crate::utils::my_io::get_os_file; -use crate::utils::my_io::read_file_from_name; +//! Metaprogramming utilities for TypR CLI +//! +//! Handles module imports and file expansion. + +use crate::io::{get_os_file, read_file_from_name}; use nom_locate::LocatedSpan; +use typr_core::components::context::config::Environment; +use typr_core::components::language::Lang; +use typr_core::processes::parsing::parse; fn import_file_module_code(line: &Lang, environment: Environment) -> Lang { match line { diff --git a/src/utils/project_management.rs b/crates/typr-cli/src/project.rs similarity index 60% rename from src/utils/project_management.rs rename to crates/typr-cli/src/project.rs index f31e6fc..e743fde 100644 --- a/src/utils/project_management.rs +++ b/crates/typr-cli/src/project.rs @@ -1,67 +1,101 @@ -use crate::processes::type_checking::type_checker::TypeChecker; -use crate::utils::engine::write_std_for_type_checking; -use crate::components::context::config::Environment; -use crate::utils::my_io::execute_r_with_path; -use crate::processes::type_checking::typing; -use crate::components::context::Context; -use crate::utils::engine::parse_code; -use std::process::Command; +//! Project management utilities for TypR CLI +//! +//! Provides functions for: +//! - Creating new projects +//! - Building and checking projects +//! - Running tests +//! - Package management + +use crate::engine::{parse_code, write_std_for_type_checking}; +use crate::io::execute_r_with_path; +use std::fs; +use std::fs::File; use std::fs::OpenOptions; -use std::path::PathBuf; -use std::path::Path; use std::io::Write; -use std::fs::File; -use std::fs; +use std::path::Path; +use std::path::PathBuf; +use std::process::Command; +use typr_core::components::context::config::Environment; +use typr_core::components::context::Context; +use typr_core::processes::type_checking::type_checker::TypeChecker; +use typr_core::typing; pub fn write_header(context: Context, output_dir: &PathBuf, environment: Environment) -> () { - let type_anotations = context.get_type_anotations(); - let mut app = match environment { - Environment::Repl => OpenOptions::new() - .append(true).create(true).write(true).open(output_dir.join(".repl.R")), - _ => OpenOptions::new() - .create(true).write(true) - .open(output_dir - .join(context.get_environment().to_base_path()) - .join("c_types.R")) - }.unwrap(); - - app.write_all(type_anotations.as_bytes()).unwrap(); - - let generic_functions = context.get_all_generic_functions().iter() - .map(|(var, _)| var.get_name()) - .filter(|x| !x.contains("<-")) - .map(|fn_name| format!("#' @export\n{} <- function(x, ...) UseMethod('{}', x)", fn_name, fn_name.replace("`", ""))) - .collect::>().join("\n"); - let mut app = match environment { - Environment::Repl => OpenOptions::new() - .append(true).create(true).write(true).open(output_dir.join(".repl.R")), - _ => OpenOptions::new() - .create(true).write(true) - .open(output_dir - .join(context.get_environment().to_string()) - .join("b_generic_functions.R")) - }.unwrap(); - app.write_all((generic_functions+"\n").as_bytes()).unwrap(); + let type_anotations = context.get_type_anotations(); + let mut app = match environment { + Environment::Repl => OpenOptions::new() + .append(true) + .create(true) + .write(true) + .open(output_dir.join(".repl.R")), + _ => OpenOptions::new().create(true).write(true).open( + output_dir + .join(context.get_environment().to_base_path()) + .join("c_types.R"), + ), + } + .unwrap(); + + app.write_all(type_anotations.as_bytes()).unwrap(); + + let generic_functions = context + .get_all_generic_functions() + .iter() + .map(|(var, _)| var.get_name()) + .filter(|x| !x.contains("<-")) + .map(|fn_name| { + format!( + "#' @export\n{} <- function(x, ...) UseMethod('{}', x)", + fn_name, + fn_name.replace("`", "") + ) + }) + .collect::>() + .join("\n"); + let mut app = match environment { + Environment::Repl => OpenOptions::new() + .append(true) + .create(true) + .write(true) + .open(output_dir.join(".repl.R")), + _ => OpenOptions::new().create(true).write(true).open( + output_dir + .join(context.get_environment().to_string()) + .join("b_generic_functions.R"), + ), + } + .unwrap(); + app.write_all((generic_functions + "\n").as_bytes()) + .unwrap(); } -pub fn write_to_r_lang(content: String, output_dir: &PathBuf, file_name: &str, environment: Environment) -> () { - let rstd = include_str!("../../configs/src/std.R"); - let std_path = output_dir.join("a_std.R"); - let mut rstd_file = File::create(std_path).unwrap(); - rstd_file.write_all(rstd.as_bytes()).unwrap(); - - let app_path = output_dir.join(file_name); - let mut app = match environment { - Environment::Repl => OpenOptions::new().append(true).write(true).create(true).open(app_path), - _ => File::create(app_path) - }.unwrap(); - let source = match environment { - Environment::Project | Environment::Repl => "", - Environment::StandAlone => "source('b_generic_functions.R')\nsource('c_types.R')" - }; - app.write_all( - format!("{}\n{}", source, content).as_bytes() - ).unwrap(); +pub fn write_to_r_lang( + content: String, + output_dir: &PathBuf, + file_name: &str, + environment: Environment, +) -> () { + let rstd = include_str!("../configs/src/std.R"); + let std_path = output_dir.join("a_std.R"); + let mut rstd_file = File::create(std_path).unwrap(); + rstd_file.write_all(rstd.as_bytes()).unwrap(); + + let app_path = output_dir.join(file_name); + let mut app = match environment { + Environment::Repl => OpenOptions::new() + .append(true) + .write(true) + .create(true) + .open(app_path), + _ => File::create(app_path), + } + .unwrap(); + let source = match environment { + Environment::Project | Environment::Repl | Environment::Wasm => "", + Environment::StandAlone => "source('b_generic_functions.R')\nsource('c_types.R')", + }; + app.write_all(format!("{}\n{}", source, content).as_bytes()) + .unwrap(); } pub fn new(name: &str) { @@ -74,70 +108,113 @@ pub fn new(name: &str) { std::process::exit(1); } }; - + let project_path = current_dir.join(name); - + if let Err(e) = fs::create_dir(&project_path) { eprintln!("Error creating project directory: {}", e); std::process::exit(1); } - + // Classic architecture of a R package let package_folders = vec![ - "R", // R code - "TypR", // TypR code - "man", // Documentation - "tests", // Tests - "data", // Data - "inst", // Installed files - "src", // Source code (C++, Fortran, etc.) - "vignettes", // Vignettes/tutorials + "R", // R code + "TypR", // TypR code + "man", // Documentation + "tests", // Tests + "data", // Data + "inst", // Installed files + "src", // Source code (C++, Fortran, etc.) + "vignettes", // Vignettes/tutorials ]; - + for folder in package_folders { let folder_path = project_path.join(folder); if let Err(e) = fs::create_dir(&folder_path) { - eprintln!("Warning: Unable to create the folder {}: {}", folder_path.display(), e); + eprintln!( + "Warning: Unable to create the folder {}: {}", + folder_path.display(), + e + ); } } - + let tests_testthat = project_path.join("tests/testthat"); if let Err(e) = fs::create_dir(&tests_testthat) { eprintln!("Warning: Unable to create the tests/testthat folder: {}", e); } - + let package_files = vec![ - ("DESCRIPTION", include_str!("../../configs/DESCRIPTION").replace("{{PACKAGE_NAME}}", name)), - ("NAMESPACE", include_str!("../../configs/NAMESPACE").replace("{{PACKAGE_NAME}}", name)), - (".Rbuildignore", include_str!("../../configs/.Rbuildignore").replace("{{PACKAGE_NAME}}", name)), - (".gitignore", include_str!("../../configs/.gitignore").replace("{{PACKAGE_NAME}}", name)), - ("TypR/main.ty", include_str!("../../configs/main.ty").replace("{{PACKAGE_NAME}}", name)), - ("R/.gitkeep", include_str!("../../configs/.gitkeep").replace("{{PACKAGE_NAME}}", name)), - ("tests/testthat.R", include_str!("../../configs/testthat.R").replace("{{PACKAGE_NAME}}", name)), - ("man/.gitkeep", include_str!("../../configs/.gitkeep2").replace("{{PACKAGE_NAME}}", name)), - ("README.md", include_str!("../../configs/README.md").replace("{{PACKAGE_NAME}}", name)), - ("rproj.Rproj", include_str!("../../configs/rproj.Rproj").to_string()), + ( + "DESCRIPTION", + include_str!("../configs/DESCRIPTION").replace("{{PACKAGE_NAME}}", name), + ), + ( + "NAMESPACE", + include_str!("../configs/NAMESPACE").replace("{{PACKAGE_NAME}}", name), + ), + ( + ".Rbuildignore", + include_str!("../configs/.Rbuildignore").replace("{{PACKAGE_NAME}}", name), + ), + ( + ".gitignore", + include_str!("../configs/.gitignore").replace("{{PACKAGE_NAME}}", name), + ), + ( + "TypR/main.ty", + include_str!("../configs/main.ty").replace("{{PACKAGE_NAME}}", name), + ), + ( + "R/.gitkeep", + include_str!("../configs/.gitkeep").replace("{{PACKAGE_NAME}}", name), + ), + ( + "tests/testthat.R", + include_str!("../configs/testthat.R").replace("{{PACKAGE_NAME}}", name), + ), + ( + "man/.gitkeep", + include_str!("../configs/.gitkeep2").replace("{{PACKAGE_NAME}}", name), + ), + ( + "README.md", + include_str!("../configs/README.md").replace("{{PACKAGE_NAME}}", name), + ), + ( + "rproj.Rproj", + include_str!("../configs/rproj.Rproj").to_string(), + ), ]; - + for (file_path, content) in package_files { let full_path = project_path.join(file_path); if let Some(parent) = full_path.parent() { if let Err(e) = fs::create_dir_all(parent) { - eprintln!("Warning: Unable to create parent directory {}: {}", parent.display(), e); + eprintln!( + "Warning: Unable to create parent directory {}: {}", + parent.display(), + e + ); continue; } } println!("Writing {} in '{:?}'", content.len(), full_path); if let Err(e) = fs::write(&full_path, content) { - eprintln!("Warning: Unable to create parent directory {}: {}", full_path.display(), e); + eprintln!( + "Warning: Unable to create parent directory {}: {}", + full_path.display(), + e + ); } } - - println!("✓ Package R '{}' successfully created!", name); - let package_structure = include_str!("../../configs/package_structure.md").replace("{{PACKAGE_NAME}}", name); + + println!("Package R '{}' successfully created!", name); + let package_structure = + include_str!("../configs/package_structure.md").replace("{{PACKAGE_NAME}}", name); println!("{}", package_structure); - - let instructions = include_str!("../../configs/instructions.md").replace("{{PACKAGE_NAME}}", name); + + let instructions = include_str!("../configs/instructions.md").replace("{{PACKAGE_NAME}}", name); println!("{}", instructions); } @@ -145,7 +222,7 @@ pub fn check_project() { let context = Context::default().set_environment(Environment::Project); let lang = parse_code(&PathBuf::from("TypR/main.ty"), context.get_environment()); let _ = typing(&context, &lang); - println!("✓ Code verification successful!"); + println!("Code verification successful!"); } pub fn check_file(path: &PathBuf) { @@ -153,8 +230,11 @@ pub fn check_file(path: &PathBuf) { let lang = parse_code(path, context.get_environment()); let dir = PathBuf::from("."); write_std_for_type_checking(&dir); - let _ = typing(&context, &lang); - println!("✓ File verification {:?} successful!", path); + let type_checker = TypeChecker::new(context.clone()).typing(&lang); + if type_checker.has_errors() { + std::process::exit(1); + } + println!("File verification {:?} successful!", path); } pub fn build_project() { @@ -163,26 +243,35 @@ pub fn build_project() { let lang = parse_code(&PathBuf::from("TypR/main.ty"), context.get_environment()); let type_checker = TypeChecker::new(context.clone()).typing(&lang); - let content = type_checker.clone().transpile(); write_header(type_checker.get_context(), &dir, Environment::Project); - write_to_r_lang(content, &PathBuf::from("R"), "d_main.R", context.get_environment()); + write_to_r_lang( + content, + &PathBuf::from("R"), + "d_main.R", + context.get_environment(), + ); document(); - println!("✓ R code successfully generated in the R/ folder"); + println!("R code successfully generated in the R/ folder"); } pub fn build_file(path: &PathBuf) { let lang = parse_code(path, Environment::StandAlone); let dir = PathBuf::from("."); - + write_std_for_type_checking(&dir); let context = Context::default(); let type_checker = TypeChecker::new(context.clone()).typing(&lang); - let r_file_name = path.file_name().unwrap().to_str().unwrap().replace(".ty", ".R"); + let r_file_name = path + .file_name() + .unwrap() + .to_str() + .unwrap() + .replace(".ty", ".R"); let content = type_checker.clone().transpile(); write_header(type_checker.get_context(), &dir, Environment::StandAlone); write_to_r_lang(content, &dir, &r_file_name, context.get_environment()); - println!("✓ Generated R code: {:?}", dir.join(&r_file_name)); + println!("Generated R code: {:?}", dir.join(&r_file_name)); } pub fn run_project() { @@ -190,7 +279,6 @@ pub fn run_project() { execute_r_with_path(&PathBuf::from("R"), "main.R"); } - pub fn run_file(path: &PathBuf) { let lang = parse_code(path, Environment::StandAlone); let dir = PathBuf::from("."); @@ -198,7 +286,12 @@ pub fn run_file(path: &PathBuf) { write_std_for_type_checking(&dir); let context = Context::default(); let type_checker = TypeChecker::new(context.clone()).typing(&lang); - let r_file_name = path.file_name().unwrap().to_str().unwrap().replace(".ty", ".R"); + let r_file_name = path + .file_name() + .unwrap() + .to_str() + .unwrap() + .replace(".ty", ".R"); let content = type_checker.clone().transpile(); write_header(type_checker.get_context(), &dir, Environment::StandAlone); write_to_r_lang(content, &dir, &r_file_name, context.get_environment()); @@ -208,14 +301,11 @@ pub fn run_file(path: &PathBuf) { pub fn test() { build_project(); let r_command = "devtools::test()".to_string(); - + println!("Execution of: R -e \"{}\"", r_command); - - let output = Command::new("R") - .arg("-e") - .arg(&r_command) - .output(); - + + let output = Command::new("R").arg("-e").arg(&r_command).output(); + match output { Ok(output) => { if output.status.success() { @@ -223,7 +313,7 @@ pub fn test() { println!("\n{}", String::from_utf8_lossy(&output.stdout)); } } else { - eprintln!("✗ Error while running tests"); + eprintln!("Error while running tests"); if !output.stderr.is_empty() { eprintln!("\n{}", String::from_utf8_lossy(&output.stderr)); } @@ -240,27 +330,27 @@ pub fn test() { pub fn get_package_name() -> Result { let description_path = PathBuf::from("DESCRIPTION"); - + if !description_path.exists() { return Err("DESCRIPTION file not found. Are you at the project root?".to_string()); } - + let content = fs::read_to_string(&description_path) .map_err(|e| format!("Error reading file DESCRIPTION: {}", e))?; - + for line in content.lines() { if line.starts_with("Package:") { let package_name = line.replace("Package:", "").trim().to_string(); return Ok(package_name); } } - + Err("Package name not found in the DESCRIPTION file".to_string()) } pub fn pkg_install() { println!("Installing the package..."); - + let current_dir = match std::env::current_dir() { Ok(dir) => dir, Err(e) => { @@ -268,30 +358,27 @@ pub fn pkg_install() { std::process::exit(1); } }; - + let project_path = current_dir.to_str().unwrap(); let r_command = format!("devtools::install_local('{}')", project_path); println!("Executing: R -e \"{}\"", r_command); - - let output = Command::new("R") - .arg("-e") - .arg(&r_command) - .output(); - + + let output = Command::new("R").arg("-e").arg(&r_command).output(); + match output { Ok(output) => { if output.status.success() { - println!("✓ Package installed successfully!"); - + println!("Package installed successfully!"); + if !output.stdout.is_empty() { println!("\n{}", String::from_utf8_lossy(&output.stdout)); } } else { - eprintln!("✗ Error during package installation"); + eprintln!("Error during package installation"); if !output.stderr.is_empty() { eprintln!("\n{}", String::from_utf8_lossy(&output.stderr)); } - + std::process::exit(1); } } @@ -305,7 +392,7 @@ pub fn pkg_install() { pub fn pkg_uninstall() { println!("Uninstalling the package..."); - + let package_name = match get_package_name() { Ok(name) => name, Err(e) => { @@ -313,27 +400,27 @@ pub fn pkg_uninstall() { std::process::exit(1); } }; - + println!("Uninstalling the package '{}'...", package_name); let r_command = format!("remove.packages('{}')", package_name); println!("Executing: R -e \"{}\"", r_command); - - let output = Command::new("R") - .arg("-e") - .arg(&r_command) - .output(); - + + let output = Command::new("R").arg("-e").arg(&r_command).output(); + match output { Ok(output) => { if output.status.success() { - println!("✓ Package '{}' successfully uninstalled!", package_name); - + println!("Package '{}' successfully uninstalled!", package_name); + if !output.stdout.is_empty() { println!("\n{}", String::from_utf8_lossy(&output.stdout)); } } else { - eprintln!("Note: The package '{}' may not have been installed or an error may have occurred", package_name); - + eprintln!( + "Note: The package '{}' may not have been installed or an error may have occurred", + package_name + ); + if !output.stderr.is_empty() { eprintln!("\n{}", String::from_utf8_lossy(&output.stderr)); } @@ -349,7 +436,7 @@ pub fn pkg_uninstall() { pub fn document() { println!("Generating package documentation..."); - + let current_dir = match std::env::current_dir() { Ok(dir) => dir, Err(e) => { @@ -357,30 +444,27 @@ pub fn document() { std::process::exit(1); } }; - + let project_path = current_dir.to_str().unwrap(); let r_command = format!("devtools::document('{}')", project_path); - - let output = Command::new("R") - .arg("-e") - .arg(&r_command) - .output(); - + + let output = Command::new("R").arg("-e").arg(&r_command).output(); + match output { Ok(output) => { if output.status.success() { - println!("✓ Documentation successfully generated!"); - + println!("Documentation successfully generated!"); + if !output.stdout.is_empty() { println!("") } } else { - eprintln!("✗ Error while generating documentation"); - + eprintln!("Error while generating documentation"); + if !output.stderr.is_empty() { eprintln!("\n{}", String::from_utf8_lossy(&output.stderr)); } - + std::process::exit(1); } } @@ -396,27 +480,27 @@ pub fn use_package(package_name: &str) { println!("Adding the package '{}' as a dependency...", package_name); let r_command = format!("devtools::use_package('{}')", package_name); println!("Execution of: R -e \"{}\"", r_command); - - let output = Command::new("R") - .arg("-e") - .arg(&r_command) - .output(); - + + let output = Command::new("R").arg("-e").arg(&r_command).output(); + match output { Ok(output) => { if output.status.success() { - println!("✓ Package '{}' successfully added to dependencies!", package_name); - + println!( + "Package '{}' successfully added to dependencies!", + package_name + ); + if !output.stdout.is_empty() { println!("\n{}", String::from_utf8_lossy(&output.stdout)); } } else { - eprintln!("✗ Error adding package '{}'", package_name); - + eprintln!("Error adding package '{}'", package_name); + if !output.stderr.is_empty() { eprintln!("\n{}", String::from_utf8_lossy(&output.stderr)); } - + std::process::exit(1); } } @@ -430,27 +514,24 @@ pub fn use_package(package_name: &str) { pub fn load() { let r_command = "devtools::load_all('.')".to_string(); - + println!("Execution of: R -e \"{}\"", r_command); - - let output = Command::new("R") - .arg("-e") - .arg(&r_command) - .output(); - + + let output = Command::new("R").arg("-e").arg(&r_command).output(); + match output { Ok(output) => { if output.status.success() { - println!("✓ Elements loaded with success!"); + println!("Elements loaded with success!"); if !output.stdout.is_empty() { println!("\n{}", String::from_utf8_lossy(&output.stdout)); } } else { - eprintln!("✗ Error while loading elements"); + eprintln!("Error while loading elements"); if !output.stderr.is_empty() { eprintln!("\n{}", String::from_utf8_lossy(&output.stderr)); } - + std::process::exit(1); } } @@ -465,25 +546,22 @@ pub fn load() { pub fn cran() { let r_command = "devtools::check()".to_string(); println!("Execution of: R -e \"{}\"", r_command); - - let output = Command::new("R") - .arg("-e") - .arg(&r_command) - .output(); - + + let output = Command::new("R").arg("-e").arg(&r_command).output(); + match output { Ok(output) => { if output.status.success() { - println!("✓ Checks passed with success!"); + println!("Checks passed with success!"); if !output.stdout.is_empty() { println!("\n{}", String::from_utf8_lossy(&output.stdout)); } } else { - eprintln!("✗ Error while checking the project"); + eprintln!("Error while checking the project"); if !output.stderr.is_empty() { eprintln!("\n{}", String::from_utf8_lossy(&output.stderr)); } - + std::process::exit(1); } } diff --git a/src/interface/repl.rs b/crates/typr-cli/src/repl.rs similarity index 68% rename from src/interface/repl.rs rename to crates/typr-cli/src/repl.rs index a600f17..fe850da 100644 --- a/src/interface/repl.rs +++ b/crates/typr-cli/src/repl.rs @@ -1,39 +1,45 @@ -use crate::utils::project_management::write_to_r_lang; -use crate::components::context::config::Environment; -use crate::utils::project_management::write_header; -use crate::components::context::Context; -use crate::utils::my_io::execute_r_with_path2; -use crate::utils::fluent_parser::FluentParser; -use rustyline::highlight::Highlighter; +//! Interactive REPL for TypR +//! +//! Provides an interactive Read-Eval-Print-Loop with: +//! - Syntax highlighting +//! - Line editing with rustyline +//! - History support +//! - Type inference display + +use crate::io::execute_r_with_path2; +use crate::project::{write_header, write_to_r_lang}; use rustyline::completion::Completer; use rustyline::error::ReadlineError; -use rustyline::validate::Validator; use rustyline::highlight::CmdKind; -use rustyline::{Config, Editor}; +use rustyline::highlight::Highlighter; use rustyline::hint::Hinter; -use std::fs::OpenOptions; -use std::path::PathBuf; +use rustyline::validate::Validator; use rustyline::Helper; +use rustyline::{Config, Editor}; use std::borrow::Cow; -use std::io::Write; use std::fs; +use std::fs::OpenOptions; +use std::io::Write; +use std::path::PathBuf; +use typr_core::components::context::config::Environment; +use typr_core::components::context::Context; +use typr_core::utils::fluent_parser::FluentParser; - -// Coloration ANSI +// ANSI color codes mod colors { pub const RESET: &str = "\x1b[0m"; - pub const KEYWORD: &str = "\x1b[35m"; // Magenta pour les mots-clés - pub const FUNCTION: &str = "\x1b[36m"; // Cyan pour les fonctions - pub const STRING: &str = "\x1b[32m"; // Vert pour les chaînes - pub const NUMBER: &str = "\x1b[33m"; // Jaune pour les nombres - pub const COMMENT: &str = "\x1b[90m"; // Gris pour les commentaires - pub const OPERATOR: &str = "\x1b[37m"; // Blanc pour les opérateurs - pub const BRACKET: &str = "\x1b[93m"; // Jaune clair pour les brackets - pub const ERROR: &str = "\x1b[91m"; // Rouge pour les erreurs - pub const OUTPUT: &str = "\x1b[34m"; // Bleu pour l'output + pub const KEYWORD: &str = "\x1b[35m"; // Magenta for keywords + pub const FUNCTION: &str = "\x1b[36m"; // Cyan for functions + pub const STRING: &str = "\x1b[32m"; // Green for strings + pub const NUMBER: &str = "\x1b[33m"; // Yellow for numbers + pub const COMMENT: &str = "\x1b[90m"; // Gray for comments + pub const OPERATOR: &str = "\x1b[37m"; // White for operators + pub const BRACKET: &str = "\x1b[93m"; // Light yellow for brackets + pub const ERROR: &str = "\x1b[91m"; // Red for errors + pub const OUTPUT: &str = "\x1b[34m"; // Blue for output } -/// Highlighter pour le langage R +/// Highlighter for R/TypR language #[derive(Clone)] struct RHighlighter; @@ -45,22 +51,81 @@ impl RHighlighter { fn is_r_keyword(word: &str) -> bool { matches!( word, - "if" | "else" | "while" | "for" | "in" | "repeat" | "break" | "next" | - "function" | "return" | "TRUE" | "FALSE" | "true" | "false" | "NULL" | "NA" | "NaN" | - "Inf" | "library" | "require" | "source" | "let" | "type" | "fn" + "if" | "else" + | "while" + | "for" + | "in" + | "repeat" + | "break" + | "next" + | "function" + | "return" + | "TRUE" + | "FALSE" + | "true" + | "false" + | "NULL" + | "NA" + | "NaN" + | "Inf" + | "library" + | "require" + | "source" + | "let" + | "type" + | "fn" ) } fn is_r_function(word: &str) -> bool { matches!( word, - "print" | "cat" | "paste" | "paste0" | "length" | "sum" | "mean" | - "median" | "sd" | "var" | "min" | "max" | "range" | "c" | "list" | - "data.frame" | "matrix" | "array" | "factor" | "as.numeric" | - "as.character" | "as.logical" | "str" | "summary" | "head" | "tail" | - "dim" | "nrow" | "ncol" | "names" | "colnames" | "rownames" | - "seq" | "rep" | "sort" | "order" | "unique" | "table" | "subset" | - "merge" | "rbind" | "cbind" | "apply" | "lapply" | "sapply" | "tapply" + "print" + | "cat" + | "paste" + | "paste0" + | "length" + | "sum" + | "mean" + | "median" + | "sd" + | "var" + | "min" + | "max" + | "range" + | "c" + | "list" + | "data.frame" + | "matrix" + | "array" + | "factor" + | "as.numeric" + | "as.character" + | "as.logical" + | "str" + | "summary" + | "head" + | "tail" + | "dim" + | "nrow" + | "ncol" + | "names" + | "colnames" + | "rownames" + | "seq" + | "rep" + | "sort" + | "order" + | "unique" + | "table" + | "subset" + | "merge" + | "rbind" + | "cbind" + | "apply" + | "lapply" + | "sapply" + | "tapply" ) } @@ -73,7 +138,7 @@ impl RHighlighter { let mut current_word = String::new(); while let Some(ch) = chars.next() { - // Gestion des commentaires + // Handle comments if ch == '#' && !in_string { in_comment = true; if !current_word.is_empty() { @@ -94,7 +159,7 @@ impl RHighlighter { continue; } - // Gestion des chaînes de caractères + // Handle strings if (ch == '"' || ch == '\'') && !in_string { if !current_word.is_empty() { result.push_str(&Self::colorize_word(¤t_word)); @@ -116,7 +181,7 @@ impl RHighlighter { continue; } - // Gestion des nombres + // Handle numbers if ch.is_numeric() || (ch == '.' && chars.peek().map_or(false, |c| c.is_numeric())) { if !current_word.is_empty() { result.push_str(&Self::colorize_word(¤t_word)); @@ -135,7 +200,7 @@ impl RHighlighter { continue; } - // Gestion des opérateurs et délimiteurs + // Handle operators and delimiters if "+-*/<>=!&|:".contains(ch) { if !current_word.is_empty() { result.push_str(&Self::colorize_word(¤t_word)); @@ -143,10 +208,19 @@ impl RHighlighter { } result.push_str(colors::OPERATOR); result.push(ch); - // Gestion des opérateurs multi-caractères + // Handle multi-character operators if let Some(&next_ch) = chars.peek() { - if matches!((ch, next_ch), ('<', '-') | ('-', '>') | ('=', '=') | - ('!', '=') | ('<', '=') | ('>', '=') | ('&', '&') | ('|', '|')) { + if matches!( + (ch, next_ch), + ('<', '-') + | ('-', '>') + | ('=', '=') + | ('!', '=') + | ('<', '=') + | ('>', '=') + | ('&', '&') + | ('|', '|') + ) { result.push(chars.next().unwrap()); } } @@ -154,7 +228,7 @@ impl RHighlighter { continue; } - // Gestion des parenthèses et crochets + // Handle parentheses and brackets if "()[]{}".contains(ch) { if !current_word.is_empty() { result.push_str(&Self::colorize_word(¤t_word)); @@ -166,7 +240,7 @@ impl RHighlighter { continue; } - // Accumulation des caractères pour former des mots + // Accumulate characters to form words if ch.is_alphanumeric() || ch == '_' || ch == '.' { current_word.push(ch); } else { @@ -178,7 +252,7 @@ impl RHighlighter { } } - // Traiter le dernier mot + // Process the last word if !current_word.is_empty() { result.push_str(&Self::colorize_word(¤t_word)); } @@ -223,20 +297,20 @@ impl Validator for RHighlighter {} impl Helper for RHighlighter {} -/// Résultat de l'exécution d'une commande R +/// Result of executing an R command #[derive(Debug, Clone)] pub struct ExecutionResult { pub output: Vec, } -/// État de la saisie utilisateur +/// State of user input #[derive(Debug, Clone, Copy, PartialEq)] enum InputState { Normal, MultiLine, } -/// Gestionnaire de l'interface CLI avec Rustyline +/// CLI interface manager with Rustyline pub struct CliInterface { editor: Editor, input_state: InputState, @@ -245,23 +319,21 @@ pub struct CliInterface { } impl CliInterface { - /// Crée une nouvelle interface CLI + /// Create a new CLI interface pub fn new() -> Result> { - let config = Config::builder() - .auto_add_history(true) - .build(); + let config = Config::builder().auto_add_history(true).build(); let highlighter = RHighlighter::new(); let mut editor = Editor::with_config(config)?; editor.set_helper(Some(highlighter)); - - // Fichier d'historique + + // History file let history_file = std::env::var("HOME") .or_else(|_| std::env::var("USERPROFILE")) .map(|home| format!("{}/.r_repl_history", home)) .unwrap_or_else(|_| ".r_repl_history".to_string()); - // Charge l'historique existant + // Load existing history let _ = editor.load_history(&history_file); Ok(CliInterface { @@ -272,26 +344,30 @@ impl CliInterface { }) } - /// Affiche le message de bienvenue + /// Display welcome message pub fn show_welcome(&self) { - println!("{}TypR REPL{}: 'exit' to quit", colors::KEYWORD, colors::RESET); + println!( + "{}TypR REPL{}: 'exit' to quit", + colors::KEYWORD, + colors::RESET + ); } - /// Lit une ligne avec le prompt approprié + /// Read a line with appropriate prompt pub fn read_line(&mut self) -> Result { let prompt = match self.input_state { InputState::Normal => format!("{}TypR>{} ", colors::KEYWORD, colors::RESET), InputState::MultiLine => format!("{}...{} ", colors::OPERATOR, colors::RESET), }; - + self.editor.readline(&prompt) } - /// Traite l'entrée utilisateur et retourne une commande à exécuter si complète + /// Process user input and return a command if complete pub fn process_input(&mut self, input: &str) -> Option { let trimmed = input.trim(); - // Commandes spéciales en mode normal + // Special commands in normal mode if self.input_state == InputState::Normal { match trimmed { "exit" | "quit" => return Some(MyCommand::Exit), @@ -301,13 +377,13 @@ impl CliInterface { } } - // Gestion du buffer multi-ligne + // Multi-line buffer management if self.input_state == InputState::MultiLine { self.command_buffer.push('\n'); } self.command_buffer.push_str(trimmed); - // Détecte si la commande est complète + // Check if the command is complete if self.is_command_complete(&self.command_buffer) { let cmd = self.command_buffer.clone(); self.command_buffer.clear(); @@ -319,7 +395,7 @@ impl CliInterface { } } - /// Détermine si une commande est complète (tous les blocs fermés) + /// Check if a command is complete (all blocks closed) fn is_command_complete(&self, cmd: &str) -> bool { let open_braces = cmd.matches('{').count(); let close_braces = cmd.matches('}').count(); @@ -333,38 +409,38 @@ impl CliInterface { && open_brackets == close_brackets } - /// Affiche un résultat d'exécution avec coloration + /// Display an execution result with colors pub fn display_result(&self, result: &ExecutionResult) { for line in &result.output { println!("{}{}{}", colors::OUTPUT, line, colors::RESET); } } - /// Affiche un message d'erreur + /// Display an error message pub fn display_error(&self, error: &str) { - eprintln!("{}✖ Erreur: {}{}", colors::ERROR, error, colors::RESET); + eprintln!("{}Error: {}{}", colors::ERROR, error, colors::RESET); } - /// Efface l'écran + /// Clear the screen pub fn clear_screen(&mut self) { self.editor.clear_screen().ok(); } - /// Sauvegarde l'historique + /// Save history pub fn save_history(&mut self) { if let Err(e) = self.editor.save_history(&self.history_file) { - eprintln!("Avertissement: Impossible de sauvegarder l'historique: {}", e); + eprintln!("Warning: Unable to save history: {}", e); } } - /// Réinitialise l'état multi-ligne (utile après Ctrl-C) + /// Reset multi-line state (useful after Ctrl-C) pub fn reset_multiline_state(&mut self) { self.input_state = InputState::Normal; self.command_buffer.clear(); } } -/// Commandes interprétées par le CLI +/// Commands interpreted by the CLI #[derive(Debug)] pub enum MyCommand { Execute(String), @@ -400,12 +476,16 @@ impl TypRExecutor { let dir = PathBuf::from("."); let r_file_name = ".repl.R"; let _ = fs::remove_file(r_file_name); - let mut file = OpenOptions::new().write(true).create(true).open(r_file_name).unwrap(); + let mut file = OpenOptions::new() + .write(true) + .create(true) + .open(r_file_name) + .unwrap(); let _ = file.write_all("source('a_std.R')\n".as_bytes()); write_header(context, &dir, Environment::Repl); - write_to_r_lang(r_code.to_string(), &dir, &r_file_name, Environment::Repl); - println!("{}{}{}", colors::NUMBER, r_type, colors::RESET); - let res = execute_r_with_path2(&dir, &r_file_name); + write_to_r_lang(r_code.to_string(), &dir, r_file_name, Environment::Repl); + println!("{}{}{}", colors::NUMBER, r_type, colors::RESET); + let res = execute_r_with_path2(&dir, r_file_name); res } @@ -416,14 +496,14 @@ impl TypRExecutor { } } -/// REPL principal qui orchestre l'exécuteur et l'interface CLI +/// Main REPL that orchestrates the executor and CLI interface pub struct RRepl { executor: TypRExecutor, cli: CliInterface, } impl RRepl { - /// Crée un nouveau REPL + /// Create a new REPL pub fn new() -> Result> { let executor = TypRExecutor::new(); let cli = CliInterface::new()?; @@ -431,7 +511,7 @@ impl RRepl { Ok(RRepl { executor, cli }) } - /// Lance la boucle REPL principale + /// Run the main REPL loop pub fn run(&mut self) -> Result<(), Box> { self.cli.show_welcome(); @@ -444,9 +524,9 @@ impl RRepl { Ok((executor, result)) => { self.executor = executor; self.cli.display_result(&result) - }, + } Err(e) => { - self.cli.display_error(&format!("Exécution échouée: {}", e)) + self.cli.display_error(&format!("Execution failed: {}", e)) } }, MyCommand::Exit => { @@ -457,50 +537,54 @@ impl RRepl { self.cli.clear_screen(); } MyCommand::Empty => { - // Ligne vide, ne rien faire + // Empty line, do nothing } } } - } Err(ReadlineError::Interrupted) => { - // Ctrl-C - Quitter proprement + // Ctrl-C - Exit gracefully println!("\n^C"); self.cli.reset_multiline_state(); println!("exiting..."); break; } Err(ReadlineError::Eof) => { - // Ctrl-D - Quitter proprement + // Ctrl-D - Exit gracefully println!("\nexiting..."); break; } Err(err) => { - self.cli - .display_error(&format!("Erreur de lecture: {}", err)); + self.cli.display_error(&format!("Read error: {}", err)); break; } } } - // Sauvegarde l'historique avant de quitter + // Save history before exiting self.cli.save_history(); Ok(()) } } +/// Start the REPL pub fn start() { match RRepl::new() { Ok(mut repl) => { if let Err(e) = repl.run() { - eprintln!("{}✖ Erreur REPL: {}{}", colors::ERROR, e, colors::RESET); + eprintln!("{}REPL error: {}{}", colors::ERROR, e, colors::RESET); std::process::exit(1); } } Err(e) => { - eprintln!("{}✖ Impossible de démarrer le processus R: {}{}", colors::ERROR, e, colors::RESET); - eprintln!(" Vérifiez que R est installé et dans le PATH"); + eprintln!( + "{}Unable to start R process: {}{}", + colors::ERROR, + e, + colors::RESET + ); + eprintln!(" Check that R is installed and in PATH"); std::process::exit(1); } } diff --git a/crates/typr-cli/src/standard_library.rs b/crates/typr-cli/src/standard_library.rs new file mode 100644 index 0000000..689ead5 --- /dev/null +++ b/crates/typr-cli/src/standard_library.rs @@ -0,0 +1,13 @@ +//! Standard library utilities for TypR CLI +//! +//! Prints the content of the standard library. + +use typr_core::components::context::Context; +use typr_core::components::r#type::type_system::TypeSystem; + +pub fn standard_library() { + let context = Context::default(); + for (var, typ) in &context.typing_context.standard_library() { + println!("{}: {}", var.get_name(), typ.pretty()); + } +} diff --git a/crates/typr-core/Cargo.toml b/crates/typr-core/Cargo.toml new file mode 100644 index 0000000..17893e8 --- /dev/null +++ b/crates/typr-core/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "typr-core" +description = "Core type checking and transpilation logic for TypR - a typed superset of R" +keywords = ["R", "type-checker", "language", "transpiler", "wasm"] +readme = "README.md" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +nom.workspace = true +nom_locate.workspace = true +serde.workspace = true +serde_json.workspace = true +thiserror.workspace = true +miette.workspace = true +rpds.workspace = true +indexmap.workspace = true +tap.workspace = true +rand.workspace = true +anyhow.workspace = true +bincode = "1.3" + +# For WASM random number generation (getrandom 0.4 uses wasm_js feature) +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { version = "0.4", features = ["wasm_js"] } + +[features] +default = [] +# Enable this feature for WASM builds - disables file system operations +wasm = [] diff --git a/crates/typr-core/configs/.Rbuildignore b/crates/typr-core/configs/.Rbuildignore new file mode 100644 index 0000000..9d10976 --- /dev/null +++ b/crates/typr-core/configs/.Rbuildignore @@ -0,0 +1,5 @@ +^.*\.Rproj$ +^\.Rproj\.user$ +^TypR$ +^\.git$ +^\.gitignore$ diff --git a/crates/typr-core/configs/.gitignore b/crates/typr-core/configs/.gitignore new file mode 100644 index 0000000..97e50fd --- /dev/null +++ b/crates/typr-core/configs/.gitignore @@ -0,0 +1,6 @@ +r#"^.*\.Rproj$ +^\.Rproj\.user$ +^TypR$ +^\.git$ +^\.gitignore$ +"# diff --git a/crates/typr-core/configs/.gitkeep b/crates/typr-core/configs/.gitkeep new file mode 100644 index 0000000..01c205c --- /dev/null +++ b/crates/typr-core/configs/.gitkeep @@ -0,0 +1,2 @@ +# This file ensures the R directory is tracked by git + diff --git a/crates/typr-core/configs/.gitkeep2 b/crates/typr-core/configs/.gitkeep2 new file mode 100644 index 0000000..094baac --- /dev/null +++ b/crates/typr-core/configs/.gitkeep2 @@ -0,0 +1 @@ +# This file ensures the man directory is tracked by git diff --git a/crates/typr-core/configs/DESCRIPTION b/crates/typr-core/configs/DESCRIPTION new file mode 100644 index 0000000..2d11700 --- /dev/null +++ b/crates/typr-core/configs/DESCRIPTION @@ -0,0 +1,13 @@ +Package: {{PACKAGE_NAME}} +Title: What the Package Does (One Line, Title Case) +Version: 0.0.0.9000 +Authors@R: + person("First", "Last", , "first.last@example.com", role = c("aut", "cre")) +Description: What the package does (one paragraph). +License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a + license +Encoding: UTF-8 +Roxygen: list(markdown = TRUE) +RoxygenNote: 7.3.2 +Suggests: + testthat (>= 3.0.0) diff --git a/crates/typr-core/configs/NAMESPACE b/crates/typr-core/configs/NAMESPACE new file mode 100644 index 0000000..e97f5e6 --- /dev/null +++ b/crates/typr-core/configs/NAMESPACE @@ -0,0 +1,4 @@ +# Generated by roxygen2: do not edit by hand + +exportPattern("^[[:alpha:]]+") + diff --git a/crates/typr-core/configs/README.md b/crates/typr-core/configs/README.md new file mode 100644 index 0000000..3fae591 --- /dev/null +++ b/crates/typr-core/configs/README.md @@ -0,0 +1,38 @@ +# {{PACKAGE_NAME}} + +A package generated from TypR code. + +## Installation + +You can install the development version from GitHub: + +```r +# install.packages("devtools") +devtools::install_github("your-username/{{PACKAGE_NAME}}") +``` + +## Usage + +```r +library({{PACKAGE_NAME}}) + +# Your functions will be available here +``` + +## Development + +This package is generated from TypR source code located in the `TypR/` directory. + +To build the package: + +```bash +# Check and build +typr check +typr build + +# Run tests +typr test + +# Build and run +typr run +``` diff --git a/crates/typr-core/configs/bin/.std_js.bin b/crates/typr-core/configs/bin/.std_js.bin new file mode 100644 index 0000000..0a8d077 Binary files /dev/null and b/crates/typr-core/configs/bin/.std_js.bin differ diff --git a/crates/typr-core/configs/bin/.std_js_typed.bin b/crates/typr-core/configs/bin/.std_js_typed.bin new file mode 100644 index 0000000..a37240f Binary files /dev/null and b/crates/typr-core/configs/bin/.std_js_typed.bin differ diff --git a/crates/typr-core/configs/bin/.std_r.bin b/crates/typr-core/configs/bin/.std_r.bin new file mode 100644 index 0000000..18229be Binary files /dev/null and b/crates/typr-core/configs/bin/.std_r.bin differ diff --git a/crates/typr-core/configs/bin/.std_r_typed.bin b/crates/typr-core/configs/bin/.std_r_typed.bin new file mode 100644 index 0000000..383afa6 Binary files /dev/null and b/crates/typr-core/configs/bin/.std_r_typed.bin differ diff --git a/crates/typr-core/configs/images/TypR_logo.png b/crates/typr-core/configs/images/TypR_logo.png new file mode 100644 index 0000000..bb31473 Binary files /dev/null and b/crates/typr-core/configs/images/TypR_logo.png differ diff --git a/crates/typr-core/configs/images/TypR_logo_black_background.png b/crates/typr-core/configs/images/TypR_logo_black_background.png new file mode 100644 index 0000000..6a1922e Binary files /dev/null and b/crates/typr-core/configs/images/TypR_logo_black_background.png differ diff --git a/crates/typr-core/configs/instructions.md b/crates/typr-core/configs/instructions.md new file mode 100644 index 0000000..a1e5f26 --- /dev/null +++ b/crates/typr-core/configs/instructions.md @@ -0,0 +1,11 @@ +Pour commencer + + cd {{PACKAGE_NAME}}, name + # Éditez TypR/main.ty avec votre code TypR" + # Puis utilisez les commandes:" + typr check # Vérifier le code" + typr build # Compiler vers R" + typr run # Compiler et exécuter" + typr test # Lancer les tests" + +Ou ouvrez le projet dans RStudio avec le fichier {{PACKAGE_NAME}}.Rproj", name diff --git a/crates/typr-core/configs/main.ty b/crates/typr-core/configs/main.ty new file mode 100644 index 0000000..c349873 --- /dev/null +++ b/crates/typr-core/configs/main.ty @@ -0,0 +1,5 @@ +# Main TypR code goes here +# This will be compiled to R code + +# Example function +print("Hello world"); diff --git a/crates/typr-core/configs/package_structure.md b/crates/typr-core/configs/package_structure.md new file mode 100644 index 0000000..b2fac15 --- /dev/null +++ b/crates/typr-core/configs/package_structure.md @@ -0,0 +1,20 @@ +structure du package: +{{PACKAGE_NAME}} +├── R/ # Code R généré +├── TypR/ # Code source TypR +│ └── main.ty +├── man/ # Documentation +├── tests/ # Tests +│ ├── testthat.R +│ └── testthat/ +│ └── test-basic.R +├── data/ # Données du package +├── inst/ # Fichiers installés +├── src/ # Code source (C++, Fortran) +├── vignettes/ # Vignettes/tutoriels +├── DESCRIPTION # Métadonnées du package +├── NAMESPACE # Exports et imports +├── README.md # Documentation +├── .Rbuildignore # Fichiers ignorés lors du build +├── .gitignore # Fichiers ignorés par git +└── {{PACKAGE_NAME}}.Rproj # Projet RStudio", name, name); diff --git a/crates/typr-core/configs/rproj.Rproj b/crates/typr-core/configs/rproj.Rproj new file mode 100644 index 0000000..672605d --- /dev/null +++ b/crates/typr-core/configs/rproj.Rproj @@ -0,0 +1,23 @@ +Version: 1.0 + +RestoreWorkspace: No +SaveWorkspace: No +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX + +AutoAppendNewline: Yes +StripTrailingWhitespace: Yes +LineEndingConversion: Posix + +BuildType: Package +PackageUseDevtools: Yes +PackageInstallArgs: --no-multiarch --with-keep.source +PackageRoxygenize: rd,collate,namespace + diff --git a/crates/typr-core/configs/src/data.toml b/crates/typr-core/configs/src/data.toml new file mode 100644 index 0000000..078bed5 --- /dev/null +++ b/crates/typr-core/configs/src/data.toml @@ -0,0 +1 @@ +release_version = 19 diff --git a/crates/typr-core/configs/src/functions_JS.txt b/crates/typr-core/configs/src/functions_JS.txt new file mode 100644 index 0000000..d4f2fd4 --- /dev/null +++ b/crates/typr-core/configs/src/functions_JS.txt @@ -0,0 +1,586 @@ +eval +parseInt +parseFloat +isNaN +isFinite +encodeURI +encodeURIComponent +decodeURI +decodeURIComponent +escape +unescape +setTimeout +clearTimeout +setInterval +clearInterval +queueMicrotask +requestAnimationFrame +cancelAnimationFrame +alert +confirm +prompt +atob +btoa +Object.assign +Object.create +Object.defineProperty +Object.defineProperties +Object.entries +Object.freeze +Object.fromEntries +Object.getOwnPropertyDescriptor +Object.getOwnPropertyDescriptors +Object.getOwnPropertyNames +Object.getOwnPropertySymbols +Object.getPrototypeOf +Object.hasOwn +Object.is +Object.isExtensible +Object.isFrozen +Object.isSealed +Object.keys +Object.preventExtensions +Object.seal +Object.setPrototypeOf +Object.values +Array.from +Array.isArray +Array.of +Array.prototype.at +Array.prototype.concat +Array.prototype.copyWithin +Array.prototype.entries +Array.prototype.every +Array.prototype.fill +Array.prototype.filter +Array.prototype.find +Array.prototype.findIndex +Array.prototype.findLast +Array.prototype.findLastIndex +Array.prototype.flat +Array.prototype.flatMap +Array.prototype.forEach +Array.prototype.includes +Array.prototype.indexOf +Array.prototype.join +Array.prototype.keys +Array.prototype.lastIndexOf +Array.prototype.map +Array.prototype.pop +Array.prototype.push +Array.prototype.reduce +Array.prototype.reduceRight +Array.prototype.reverse +Array.prototype.shift +Array.prototype.slice +Array.prototype.some +Array.prototype.sort +Array.prototype.splice +Array.prototype.toLocaleString +Array.prototype.toReversed +Array.prototype.toSorted +Array.prototype.toSpliced +Array.prototype.toString +Array.prototype.unshift +Array.prototype.values +Array.prototype.with +String.fromCharCode +String.fromCodePoint +String.raw +String.prototype.at +String.prototype.charAt +String.prototype.charCodeAt +String.prototype.codePointAt +String.prototype.concat +String.prototype.endsWith +String.prototype.includes +String.prototype.indexOf +String.prototype.isWellFormed +String.prototype.lastIndexOf +String.prototype.localeCompare +String.prototype.match +String.prototype.matchAll +String.prototype.normalize +String.prototype.padEnd +String.prototype.padStart +String.prototype.repeat +String.prototype.replace +String.prototype.replaceAll +String.prototype.search +String.prototype.slice +String.prototype.split +String.prototype.startsWith +String.prototype.substring +String.prototype.toLocaleLowerCase +String.prototype.toLocaleUpperCase +String.prototype.toLowerCase +String.prototype.toString +String.prototype.toUpperCase +String.prototype.toWellFormed +String.prototype.trim +String.prototype.trimEnd +String.prototype.trimStart +String.prototype.valueOf +Number.isFinite +Number.isInteger +Number.isNaN +Number.isSafeInteger +Number.parseFloat +Number.parseInt +Number.prototype.toExponential +Number.prototype.toFixed +Number.prototype.toLocaleString +Number.prototype.toPrecision +Number.prototype.toString +Number.prototype.valueOf +Math.abs +Math.acos +Math.acosh +Math.asin +Math.asinh +Math.atan +Math.atan2 +Math.atanh +Math.cbrt +Math.ceil +Math.clz32 +Math.cos +Math.cosh +Math.exp +Math.expm1 +Math.floor +Math.fround +Math.hypot +Math.imul +Math.log +Math.log10 +Math.log1p +Math.log2 +Math.max +Math.min +Math.pow +Math.random +Math.round +Math.sign +Math.sin +Math.sinh +Math.sqrt +Math.tan +Math.tanh +Math.trunc +Date.now +Date.parse +Date.UTC +Date.prototype.getDate +Date.prototype.getDay +Date.prototype.getFullYear +Date.prototype.getHours +Date.prototype.getMilliseconds +Date.prototype.getMinutes +Date.prototype.getMonth +Date.prototype.getSeconds +Date.prototype.getTime +Date.prototype.getTimezoneOffset +Date.prototype.getUTCDate +Date.prototype.getUTCDay +Date.prototype.getUTCFullYear +Date.prototype.getUTCHours +Date.prototype.getUTCMilliseconds +Date.prototype.getUTCMinutes +Date.prototype.getUTCMonth +Date.prototype.getUTCSeconds +Date.prototype.setDate +Date.prototype.setFullYear +Date.prototype.setHours +Date.prototype.setMilliseconds +Date.prototype.setMinutes +Date.prototype.setMonth +Date.prototype.setSeconds +Date.prototype.setTime +Date.prototype.setUTCDate +Date.prototype.setUTCFullYear +Date.prototype.setUTCHours +Date.prototype.setUTCMilliseconds +Date.prototype.setUTCMinutes +Date.prototype.setUTCMonth +Date.prototype.setUTCSeconds +Date.prototype.toDateString +Date.prototype.toISOString +Date.prototype.toJSON +Date.prototype.toLocaleDateString +Date.prototype.toLocaleString +Date.prototype.toLocaleTimeString +Date.prototype.toString +Date.prototype.toTimeString +Date.prototype.toUTCString +Date.prototype.valueOf +RegExp.prototype.exec +RegExp.prototype.test +RegExp.prototype.toString +JSON.parse +JSON.stringify +console.assert +console.clear +console.count +console.countReset +console.debug +console.dir +console.dirxml +console.error +console.group +console.groupCollapsed +console.groupEnd +console.info +console.log +console.table +console.time +console.timeEnd +console.timeLog +console.trace +console.warn +Promise.all +Promise.allSettled +Promise.any +Promise.race +Promise.reject +Promise.resolve +Promise.prototype.catch +Promise.prototype.finally +Promise.prototype.then +Map.prototype.clear +Map.prototype.delete +Map.prototype.entries +Map.prototype.forEach +Map.prototype.get +Map.prototype.has +Map.prototype.keys +Map.prototype.set +Map.prototype.values +Set.prototype.add +Set.prototype.clear +Set.prototype.delete +Set.prototype.entries +Set.prototype.forEach +Set.prototype.has +Set.prototype.keys +Set.prototype.values +WeakMap.prototype.delete +WeakMap.prototype.get +WeakMap.prototype.has +WeakMap.prototype.set +WeakSet.prototype.add +WeakSet.prototype.delete +WeakSet.prototype.has +Reflect.apply +Reflect.construct +Reflect.defineProperty +Reflect.deleteProperty +Reflect.get +Reflect.getOwnPropertyDescriptor +Reflect.getPrototypeOf +Reflect.has +Reflect.isExtensible +Reflect.ownKeys +Reflect.preventExtensions +Reflect.set +Reflect.setPrototypeOf +Proxy.revocable +ArrayBuffer.isView +ArrayBuffer.prototype.slice +DataView.prototype.getBigInt64 +DataView.prototype.getBigUint64 +DataView.prototype.getFloat32 +DataView.prototype.getFloat64 +DataView.prototype.getInt8 +DataView.prototype.getInt16 +DataView.prototype.getInt32 +DataView.prototype.getUint8 +DataView.prototype.getUint16 +DataView.prototype.getUint32 +DataView.prototype.setBigInt64 +DataView.prototype.setBigUint64 +DataView.prototype.setFloat32 +DataView.prototype.setFloat64 +DataView.prototype.setInt8 +DataView.prototype.setInt16 +DataView.prototype.setInt32 +DataView.prototype.setUint8 +DataView.prototype.setUint16 +DataView.prototype.setUint32 +Intl.getCanonicalLocales +Intl.Collator +Intl.DateTimeFormat +Intl.DisplayNames +Intl.ListFormat +Intl.Locale +Intl.NumberFormat +Intl.PluralRules +Intl.RelativeTimeFormat +Intl.Segmenter +Symbol.for +Symbol.keyFor +Symbol.prototype.toString +Symbol.prototype.valueOf +BigInt.asIntN +BigInt.asUintN +BigInt.prototype.toLocaleString +BigInt.prototype.toString +BigInt.prototype.valueOf +Error.prototype.toString +Function.prototype.apply +Function.prototype.bind +Function.prototype.call +Function.prototype.toString +Boolean.prototype.toString +Boolean.prototype.valueOf +TypedArray.from +TypedArray.of +TypedArray.prototype.at +TypedArray.prototype.copyWithin +TypedArray.prototype.entries +TypedArray.prototype.every +TypedArray.prototype.fill +TypedArray.prototype.filter +TypedArray.prototype.find +TypedArray.prototype.findIndex +TypedArray.prototype.findLast +TypedArray.prototype.findLastIndex +TypedArray.prototype.forEach +TypedArray.prototype.includes +TypedArray.prototype.indexOf +TypedArray.prototype.join +TypedArray.prototype.keys +TypedArray.prototype.lastIndexOf +TypedArray.prototype.map +TypedArray.prototype.reduce +TypedArray.prototype.reduceRight +TypedArray.prototype.reverse +TypedArray.prototype.set +TypedArray.prototype.slice +TypedArray.prototype.some +TypedArray.prototype.sort +TypedArray.prototype.subarray +TypedArray.prototype.toLocaleString +TypedArray.prototype.toReversed +TypedArray.prototype.toSorted +TypedArray.prototype.toString +TypedArray.prototype.values +TypedArray.prototype.with +Atomics.add +Atomics.and +Atomics.compareExchange +Atomics.exchange +Atomics.isLockFree +Atomics.load +Atomics.notify +Atomics.or +Atomics.store +Atomics.sub +Atomics.wait +Atomics.waitAsync +Atomics.xor +WeakRef.prototype.deref +FinalizationRegistry.prototype.register +FinalizationRegistry.prototype.unregister +fetch +window.open +window.close +window.focus +window.blur +window.stop +window.print +window.postMessage +window.getComputedStyle +window.matchMedia +window.scroll +window.scrollBy +window.scrollTo +window.resizeBy +window.resizeTo +window.moveBy +window.moveTo +crypto.getRandomValues +crypto.randomUUID +performance.now +performance.mark +performance.measure +performance.clearMarks +performance.clearMeasures +performance.getEntries +performance.getEntriesByName +performance.getEntriesByType +structuredClone +document.getElementById() +document.querySelector() +document.querySelectorAll() +document.getElementsByClassName() +document.getElementsByTagName() +document.getElementsByName() +document.createElement() +document.createTextNode() +document.createDocumentFragment() +document.createAttribute() +document.createComment() +document.createEvent() +element.querySelector() +element.querySelectorAll() +element.getElementsByClassName() +element.getElementsByTagName() +element.closest() +element.matches() +element.getAttribute() +element.setAttribute() +element.removeAttribute() +element.hasAttribute() +element.getAttributeNames() +element.toggleAttribute() +element.classList.add() +element.classList.remove() +element.classList.toggle() +element.classList.contains() +element.classList.replace() +element.innerHTML +element.outerHTML +element.textContent +element.innerText +element.appendChild() +element.append() +element.prepend() +element.insertBefore() +element.insertAdjacentElement() +element.insertAdjacentHTML() +element.insertAdjacentText() +element.removeChild() +element.remove() +element.replaceChild() +element.replaceWith() +element.cloneNode() +element.parentNode +element.parentElement +element.children +element.childNodes +element.firstChild +element.firstElementChild +element.lastChild +element.lastElementChild +element.nextSibling +element.nextElementSibling +element.previousSibling +element.previousElementSibling +element.id +element.className +element.tagName +element.nodeName +element.nodeType +element.nodeValue +element.getBoundingClientRect() +element.getClientRects() +element.offsetWidth +element.offsetHeight +element.offsetTop +element.offsetLeft +element.offsetParent +element.clientWidth +element.clientHeight +element.clientTop +element.clientLeft +element.scrollWidth +element.scrollHeight +element.scrollTop +element.scrollLeft +element.scrollIntoView() +element.scrollBy() +element.scrollTo() +element.style +element.style.setProperty() +element.style.getPropertyValue() +element.style.removeProperty() +window.getComputedStyle() +element.addEventListener() +element.removeEventListener() +element.dispatchEvent() +element.onclick (et autres on*) +element.focus() +element.blur() +element.hasFocus() +element.checkValidity() +element.reportValidity() +element.setCustomValidity() +element.submit() +element.reset() +element.dataset +element.hasChildNodes() +element.contains() +element.compareDocumentPosition() +node.appendChild() +node.cloneNode() +node.contains() +node.hasChildNodes() +node.insertBefore() +node.isConnected +node.isEqualNode() +node.isSameNode() +node.normalize() +node.removeChild() +node.replaceChild() +document.body +document.head +document.documentElement +document.title +document.URL +document.domain +document.cookie +document.readyState +document.forms +document.images +document.links +document.scripts +document.styleSheets +document.write() +document.writeln() +document.open() +document.close() +document.hasFocus() +document.adoptNode() +document.importNode() +window.innerWidth +window.innerHeight +window.outerWidth +window.outerHeight +window.scrollX +window.scrollY +window.pageXOffset +window.pageYOffset +window.scroll() +window.scrollBy() +window.scrollTo() +event.preventDefault() +event.stopPropagation() +event.stopImmediatePropagation() +event.target +event.currentTarget +event.type +event.bubbles +event.cancelable +event.defaultPrevented +event.eventPhase +event.timeStamp +collection.item() +collection.namedItem() +nodeList.forEach() +nodeList.entries() +nodeList.keys() +nodeList.values() +MutationObserver() +observer.observe() +observer.disconnect() +observer.takeRecords() +IntersectionObserver() +observer.observe() +observer.unobserve() +observer.disconnect() +observer.takeRecords() +ResizeObserver() +observer.observe() +observer.unobserve() +observer.disconnect() diff --git a/crates/typr-core/configs/src/functions_R.txt b/crates/typr-core/configs/src/functions_R.txt new file mode 100644 index 0000000..3b220ef --- /dev/null +++ b/crates/typr-core/configs/src/functions_R.txt @@ -0,0 +1,758 @@ +%*% +%/% +%||% +%in% +%o% +%x% +%==% +abbreviate +abs +acos +acosh +activeBindingFunction +addNA +addTaskCallback +agrep +agrepl +alist +all +allowInterrupts +anyDuplicated +anyNA +aperm +append +apply +Arg +args +array +array2DF +arrayInd +as__array +as__array__default +as__call +as__character +as__complex +as__data__frame +as__Date +as__difftime +as__double +as__environment +as__expression +as__factor +as__function +as__hexmode +as__integer +as__list +as__logical +as__matrix +as__name +as__null +as__null__default +as__numeric +as__numeric_version +as__octmode +as__ordered +as__package_version +as__pairlist +as__POSIXct +as__POSIXlt +as__qr +as__raw +as__single +as__symbol +as__table +as__vector +asin +asinh +asNamespace +asplit +asS3 +asS4 +assign +atan +atan2 +atanh +attach +attachNamespace +attr +attributes +autoload +autoloader +backsolve +balancePOSIXlt +baseenv +basename +besselI +besselJ +besselK +besselY +beta +bindingIsActive +bindingIsLocked +bindtextdomain +bitwAnd +bitwNot +bitwOr +bitwShiftL +bitwShiftR +bitwXor +body +bquote +break +browser +browserCondition +browserSetDebug +browserText +builtins +by +bzfile +c +capture__output +cat +call +callCC +capabilities +casefold +cbind +ceiling +char__expand +character +charmatch +charToRaw +chartr +chkDots +chol +chol2inv +choose +chooseOpsMethod +chooseOpsMethod__default +class +clearPushBack +close +closeAllConnections +col +colMeans +colnames +colSums +commandArgs +comment +complex +computeRestarts +conditionCall +conditionMessage +conflictRules +conflicts +Conj +contributors +cos +cosh +cospi +crossprod +Cstack_info +cummax +cummin +cumprod +cumsum +curlGetHeaders +cut +data__class +data__frame +data__matrix +date +debug +debuggingState +debugonce +declare +delayedAssign +deparse +deparse1 +det +detach +determinant +dget +diag +diff +difftime +digamma +dim +dimnames +dir +dirname +do__call +dontCheck +double +dput +dQuote +drop +droplevels +dump +duplicated +dynGet +eapply +eigen +emptyenv +enc2native +enc2utf8 +encodeString +Encoding +endsWith +enquote +environment +environmentIsLocked +environmentName +errorCondition +eval +evalq +Exec +exists +exp +expm1 +expression +extSoftVersion +F +factor +factorial +fifo +file +Filter +Find +findInterval +findPackageEnv +findRestart +floor +flush +for +force +forceAndCall +formals +format +formatC +formatDL +forwardsolve +function +gamma +gc +gcinfo +gctorture +gctorture2 +get +get0 +getAllConnections +getCallingDLL +getCallingDLLe +getConnection +getDLLRegisteredRoutines +getElement +geterrmessage +getExportedValue +getHook +getLoadedDLLs +getNamespace +getNamespaceExports +getNamespaceImports +getNamespaceInfo +getNamespaceName +getNamespaceUsers +getNamespaceVersion +getNativeSymbolInfo +getOption +getRversion +getSrcLines +getTaskCallbackNames +gettext +gettextf +getwd +gl +globalCallingHandlers +globalenv +gregexec +gregexpr +grep +grepl +grepRaw +grouping +gsub +gzcon +gzfile +I +iconv +iconvlist +icuGetCollate +icuSetCollate +identical +identity +if +ifelse +Im +importIntoEnv +invisible +infoRDS +inherits +integer +interaction +interactive +intersect +intToBits +intToUtf8 +inverse__rle +invokeRestart +invokeRestartInteractively +is__array +is__atomic +is__call +is__character +is__complex +is__data__frame +is__double +is__element +is__environment +is__expression +is__factor +is__finite +is__finite__POSIXlt +is__function +is__infinite +is__infinite__POSIXlt +is__integer +is__language +is__list +is__loaded +is__logical +is__matrix +is__na +is__name +is__nan +is__null +is__numeric +is__object +is__ordered +is__package_version +is__pairlist +is__primitive +is__qr +is__R +is__raw +is__recursive +is__single +is__symbol +is__table +is__unsorted +is__vector +isa +isatty +isBaseNamespace +isdebugged +isFALSE +isIncomplete +isNamespace +isNamespaceLoaded +ISOdate +ISOdatetime +isOpen +isRestart +isS4 +isSeekable +isSymmetric +isSymmetric__matrix +isTRUE +jitter +julian +kappa +kronecker +l10n_info +La_library +La_version +La__svd +labels +lapply +lazyLoad +lazyLoadDBexec +lazyLoadDBfetch +lbeta +lchoose +length +length__POSIXlt +lengths +letters +LETTERS +levels +levels__default +lfactorial +lgamma +libcurlVersion +library +licence +license +list2DF +list2env +load +loadedNamespaces +loadingNamespaceInfo +loadNamespace +local +lockBinding +lockEnvironment +log +log10 +log1p +log2 +logb +logical +lower__tri +ls +make__names +make__unique +makeActiveBinding +mapply +margin__table +marginSums +mat__or__vec +match +matrix +max__col +mean +mem__maxNSize +mem__maxVSize +memCompress +memDecompress +memory__profile +merge +message +mget +min +missing +Mod +mode +months +mtfrm +nameOfClass +names +namespaceExport +namespaceImport +namespaceImportClasses +namespaceImportFrom +namespaceImportMethods +nargs +nchar +ncol +NCOL +Negate +new__env +next +NextMethod +ngettext +nlevels +noquote +norm +normalizePath +nrow +NROW +nullfile +numeric +numeric_version +numToBits +numToInts +nzchar +objects +oldClass +OlsonNames +on__exit +open +options +order +ordered +outer +package_version +packageEvent +packageHasNamespace +packageNotFoundError +packageStartupMessage +packBits +pairlist +parent__env +parent__frame +parse +parseNamespaceFile +paste +paste0 +pcre_config +pi +pipe +plot +pmatch +pmax +pmin +polyroot +pos__to__env +Position +pretty +prettyNum +print +prmatrix +proc__time +prod +prop__table +proportions +provideDimnames +psigamma +pushBack +pushBackLength +q +qr +quarters +quit +quote +R_compiled_by +R_system_version +range +rank +rapply +raw +rawConnection +rawConnectionValue +rawShift +rawToBits +rawToChar +rbind +rcond +Re +read__dcf +readBin +readChar +readline +readLines +readRDS +readRenviron +Recall +Reduce +reg__finalizer +regexec +regexpr +registerS3method +registerS3methods +regmatches +remove +removeTaskCallback +rep +rep_len +replace +replicate +require +requireNamespace +restartDescription +restartFormals +retracemem +return +returnValue +rev +rle +rm +RNGkind +RNGversion +round +row +rowMeans +rownames +rowsum +rowSums +sample +sample__int +sapply +save +saveRDS +scale +scan +search +searchpaths +seek +seq +seq_along +sequence +sequence__default +serialize +serverSocket +set__seed +setdiff +setequal +setHook +setNamespaceInfo +setSessionTimeLimit +setTimeLimit +setwd +showConnections +shQuote +sign +signalCondition +signif +simpleCondition +simpleError +simpleMessage +simpleWarning +simplify2array +sin +single +sinh +sink +sinpi +slice__index +socketAccept +socketConnection +socketSelect +socketTimeout +solve +sort +sort_by +source +split +sprintf +sqrt +sQuote +srcfile +srcfilealias +srcfilecopy +srcref +standardGeneric +startsWith +stderr +stdin +stdout +stop +stopifnot +storage__mode +str +str2expression +str2lang +strftime +strptime +strrep +strsplit +strtoi +strtrim +strwrap +sub +subset +substitute +substr +substring +sum +summary +suppressMessages +suppressPackageStartupMessages +suppressWarnings +suspendInterrupts +svd +sweep +switch +sys__call +sys__calls +Sys__chmod +Sys__Date +sys__frame +sys__frames +sys__function +Sys__getenv +Sys__getlocale +Sys__getpid +Sys__glob +Sys__info +sys__load__image +Sys__localeconv +sys__nframe +sys__on__exit +sys__parent +sys__parents +Sys__readlink +sys__save__image +Sys__setenv +Sys__setFileTime +Sys__setLanguage +Sys__setlocale +Sys__sleep +sys__source +sys__status +Sys__time +Sys__timezone +Sys__umask +Sys__unsetenv +Sys__which +system +system2 +t +T +table +tabulate +Tailcall +tan +tanh +tanpi +tapply +taskCallbackManager +tcrossprod +tempdir +tempfile +textConnection +textConnectionValue +tolower +topenv +toString +toupper +trace +traceback +tracemem +tracingState +transform +trigamma +trimws +trunc +truncate +try +tryCatch +tryInvokeRestart +typeof +unCfillPOSIXlt +unclass +undebug +union +unique +units +unlink +unlist +unloadNamespace +unlockBinding +unname +unserialize +unsplit +untrace +untracemem +unz +upper__tri +url +use +UseMethod +utf8ToInt +validEnc +validUTF8 +vector +Vectorize +version +warning +warningCondition +warnings +weekdays +which +while +with +withAutoprint +withCallingHandlers +within +withRestarts +withVisible +write +writeBin +writeChar +writeLines +xor +xpdrows__data__frame +xtfrm +xzfile +zapsmall diff --git a/crates/typr-core/configs/src/std.R b/crates/typr-core/configs/src/std.R new file mode 100644 index 0000000..d7fbb9d --- /dev/null +++ b/crates/typr-core/configs/src/std.R @@ -0,0 +1,349 @@ +sys.info <- function() { Sys.info() } +sys.getenv <- function() { Sys.getenv() } +sys.setenv <- function(var, val) { Sys.setenv(var = val) } +sys.time <- function() { Sys.time() } +sys.date <- function() { Sys.Date() } +sys.sleep <- function(n) { Sys.sleep(n) } +sys.which <- function(s) { Sys.which(s) } +sys.timezone <- function() { Sys.timezone() } +sys.setlocale <- function() { Sys.setlocale() } + + +struct <- function(x, new_class) { + if (is.null(x)) { + return(x) + } + + old <- oldClass(x) + + if (is.null(old)) { + class(x) <- new_class + } else { + class(x) <- union(old, new_class) + } + + return(x) +} + +let_type <- function(x, new_class) { + class(x) <- "" + class(x) <- x |> new_class() + return(x) +} + +typed_vec <- function(...) { + x <- list(...) + + # Vérifier si tous les arguments héritent de "typed_vec" + all_typed <- all(vapply(x, function(item) inherits(item, "typed_vec"), logical(1))) + + if (all_typed && length(x) > 0) { + # Combiner les paramètres data de chaque typed_vec + combined_data <- unlist(lapply(x, function(item) item$data), recursive = FALSE) + + return(structure( + list(data = combined_data), + class = "typed_vec" + )) + } + + # Sinon, retourner la structure normale + structure( + list(data = x), + class = "typed_vec" + ) +} + +length.typed_vec <- function(x) { + length(x$data) +} + +`[[.typed_vec` <- function(x, i) { + x$data[[i]] +} + +apply.typed_vec <- function(X, FUN, ...) { + # Appliquer la fonction à chaque élément de data + results <- lapply(X$data, FUN, ...) + + # Retourner un nouveau typed_vec avec les résultats + typed_vec(results) +} + +vec_apply <- function(f, ...) { + args <- list(...) + + # Appliquer typed_vec sur les arguments qui n'héritent pas de "typed_std" + args <- lapply(args, function(x) { + if (!inherits(x, "typed_vec")) { + typed_vec(x) + } else { + x + } + }) + + lengths <- vapply(args, length, integer(1)) + n <- max(lengths) + + if (any(lengths == 0)) { + return(structure( + list(data = list()), + class = "typed_vec" + )) + } + + # Optionnel : sécurité façon R + if (any(n %% lengths != 0)) { + stop("Incompatible vector lengths") + } + + # Recyclage + recycled <- lapply(args, function(x) { + if (length(x) == n) { + x$data + } else { + rep(x$data, length.out = n) + } + }) + + results <- vector("list", n) + for (i in seq_len(n)) { + # Extraire les éléments à la position i de chaque argument + elements <- lapply(recycled, `[[`, i) + # Appeler f qui fera son propre dispatch S3 + results[[i]] <- do.call(f, elements) + } + + + # Vérifier si tous les arguments héritent de "typed_vec" + all_typed <- all(vapply(results, function(item) inherits(item, "typed_vec"), logical(1))) + + if (all_typed && length(results) > 0) { + # Combiner les paramètres data de chaque typed_vec + combined_data <- unlist(lapply(results, function(item) item$data), recursive = FALSE) + + return(structure( + list(data = combined_data), + class = "typed_vec" + )) + } + + structure( + list( + data = results + #data = do.call(Map, c(list(f), recycled)) + ), + class = "typed_vec" + ) +} + +vec_apply_fun <- function(fun_vec, ...) { + # Appliquer typed_vec sur fun_vec s'il n'hérite pas de "typed_vec" + if (!inherits(fun_vec, "typed_vec")) { + fun_vec <- typed_vec(fun_vec) + } + + args <- list(...) + + # Appliquer typed_vec sur les arguments qui n'héritent pas de "typed_vec" + args <- lapply(args, function(x) { + if (!inherits(x, "typed_vec")) { + typed_vec(x) + } else { + x + } + }) + + # Toutes les longueurs + lengths <- c(length(fun_vec), vapply(args, length, integer(1))) + n <- max(lengths) + + if (any(lengths == 0)) { + return(structure( + list(data = list()), + class = "typed_vec" + )) + } + + # Sécurité optionnelle + if (any(n %% lengths != 0)) { + stop("Incompatible vector lengths") + } + + # Recyclage + funs <- if (length(fun_vec) == n) + fun_vec$data + else + rep(fun_vec$data, length.out = n) + + recycled_args <- lapply(args, function(x) { + if (length(x) == n) x$data + else rep(x$data, length.out = n) + }) + + # Application élément-wise avec results intermédiaires + results <- vector("list", n) + for (i in seq_len(n)) { + f <- funs[[i]] + params <- lapply(recycled_args, `[[`, i) + # Appeler f qui fera son propre dispatch S3 + results[[i]] <- do.call(f, params) + } + + # Vérifier si tous les éléments de results héritent de "typed_vec" + all_typed <- all(vapply(results, function(item) inherits(item, "typed_vec"), logical(1))) + + if (all_typed && length(results) > 0) { + # Combiner les paramètres data de chaque typed_vec + combined_data <- unlist(lapply(results, function(item) item$data), recursive = FALSE) + + return(structure( + list(data = combined_data), + class = "typed_vec" + )) + } + + structure( + list(data = results), + class = "typed_vec" + ) +} + +reduce.typed_vec <- function(vec, f, init = NULL) { + # Appliquer typed_vec sur vec s'il n'hérite pas de "typed_vec" + if (!inherits(vec, "typed_vec")) { + vec <- typed_vec(vec) + } + + n <- length(vec) + + # Si le vecteur est vide + if (n == 0) { + if (is.null(init)) { + stop("Cannot reduce empty vector without initial value") + } + return(init) + } + + # Déterminer la valeur initiale de l'accumulateur + if (is.null(init)) { + # Commencer avec le premier élément + accumulator <- vec$data[[1]] + start_index <- 2 + } else { + # Commencer avec la valeur initiale fournie + accumulator <- init + start_index <- 1 + } + + # Si on a déjà tout consommé + if (start_index > n) { + return(accumulator) + } + + # Réduction itérative + for (i in start_index:n) { + # Appeler f qui fera son propre dispatch S3 + accumulator <- f(accumulator, vec$data[[i]]) + if (inherits(accumulator, "typed_vec")) { + accumulator <- accumulator$data[[1]] + } + } + + return(structure( + list(data = list(accumulator)), + class = "typed_vec" + )) +} + +sum.typed_vec <- function(x, ...) { + reduce(x, `+`) +} + +print.typed_vec <- function(x, ...) { + n <- length(x$data) + + # Cas spécial : liste vide + if (n == 0) { + cat("Empty typed_vec\n") + return(invisible(x)) + } + + # Cas spécial : longueur 1, afficher directement le contenu + if (n == 1) { + el <- x$data[[1]] + + if (is.function(el)) { + cat("\n") + } else { + print(el) + } + + return(invisible(x)) + } + + # Cas général : longueur > 1 + cat("typed_vec [", n, "]\n", sep = "") + + for (i in seq_len(n)) { + cat("[", i, "] ", sep = "") + el <- x$data[[i]] + + # Délégation au print S3 de l'élément + if (is.function(el)) { + # Affichage plus compact pour les fonctions + fname <- tryCatch( + deparse(substitute(el)), + error = function(e) "" + ) + cat("\n") + } else { + print(el) + } + + if (i < n) cat("\n") + } + + invisible(x) +} + +get.typed_vec <- function(a, name) { + a$data[[1]][[name]] +} + +get.data <- function(a, name) { + a$data[[1]] +} + +get.list <- function(a, name) { + a$data[[1]][[name]] +} + +get.any <- function(a, name) { + a[[name]] +} + +print.Integer <- function(i) { + cat(unclass(i)) + invisible(i) +} + +print.Character <- function(c) { + cat(unclass(c)) + invisible(c) +} + +print.Boolean <- function(b) { + cat(unclass(b)) + invisible(b) +} + +print.Number <- function(n) { + cat(unclass(n)) + invisible(n) +} + +`%==%.default` <- function(x, y) { + unclass(x) == unclass(y) +} + diff --git a/crates/typr-core/configs/std/default.ty b/crates/typr-core/configs/std/default.ty new file mode 100644 index 0000000..af8d7d5 --- /dev/null +++ b/crates/typr-core/configs/std/default.ty @@ -0,0 +1,101 @@ +@nchar: (a: char) -> int; + +let len <- fn(a: char): int; + nchar(a) +}; + +@sys__info: () -> char; + +@sys__getenv: () -> [#N, char]; + +@sys__setenv: (var: char, val: char) -> [#N, char]; + +@sys__time: () -> char; + +@sys__date: () -> char; + +@sys__sleep: (n: int) -> .None; + +@sys__which: (n: char) -> char; + +@sys__timezone: () -> char; + +@sys__setlocale: () -> .None; + +@as__character: (a: A) -> char; + +@as__numeric: (a: A) -> num; + +@as__integer: (a: A) -> int; + +@as__logical: (a: A) -> int; + +@map: (a: [#N, T], f: (T) -> U) -> [#N, U]; + +@rev: (a: [#N, T]) -> [#N, T]; + +@mean: (a: [#N, T]) -> T; + +@sd: (a: [#N, T]) -> T; + +@min: (a: [#N, T]) -> T; + +@max: (a: [#N, T]) -> T; + +@add: (a: int, b: int) -> int; + +@add: (a: num, b: num) -> num; + +@minus: (a: int, b: int) -> int; + +@minus: (a: num, b: num) -> num; + +@mul: (a: int, b: int) -> int; + +@mul: (a: num, b: num) -> num; + +@div: (a: int, b: int) -> int; + +@div: (a: num, b: num) -> num; + +@plot: (a: [#N, num], b: [#N, num], type: char) -> .None; + +@get: (a: {}, b: char) -> T; + +@print: (a: char) -> .None; + +@seq: (a: #I, b: #J, c: #K) -> [#J-#I/#K, int]; + +@substr: (a: char, b: int, e: int) -> char; + +@sub: (a: char, b: char, c: char) -> char; + +let replace <- (s: char, old: char, new: char) -> char; + sub(old, new, s) +}; + +@gsub: (a: char, b: char, c: char) -> char; + +let replace_all <- fn(s: char, old: char, new: char): char; + gsub(old, new, s) +}; + +@strsplit: (s: char, d: char) -> [#N, char]; + +let split <- fn(s: char, d: char): [#N, char]; + strsplit(s, d) +}; + +@join: (a: [#N, char], s: char) -> char; + +@tolower: (a: char) -> char; + +@toupper: (a: char) -> char; + +@startsWith: (a: char, b: char) -> bool; + +@endsWith: (a: char, b: char) -> bool; + +@grepl: (a: char, b: char) -> bool; + +@contains: (a: char, b: char) -> bool; diff --git a/crates/typr-core/configs/std/file.ty b/crates/typr-core/configs/std/file.ty new file mode 100644 index 0000000..2f7eb70 --- /dev/null +++ b/crates/typr-core/configs/std/file.ty @@ -0,0 +1,26 @@ +# File System management ---------- + +@getwd: () -> char; + +@setwd: (path: char) -> char; + +@dir: () -> [#N, char]; + +@list__files: () -> [#N, char]; + +@file__exists: (file: char) -> bool; + +@file__create: (file: char) -> bool; + +@file__remove: (file: char) -> bool; + +@file__rename: (old: char, new: char) -> bool; + +@file__copy: (source: char, dest: char) -> bool; + +@dir__create: (source: char, dest: char) -> bool; + +@unlink: (target: char) -> bool; + + +# -------------------------------- diff --git a/crates/typr-core/configs/std/lin_alg.ty b/crates/typr-core/configs/std/lin_alg.ty new file mode 100644 index 0000000..eb3eda9 --- /dev/null +++ b/crates/typr-core/configs/std/lin_alg.ty @@ -0,0 +1,11 @@ +@dot: (m: [#M, [#P, int]], n: [#P, [#N, int]]): [#M, [#N, int]]; + +@t: (m: [#M, [#N, T]]): [#N, [#M, T]]; + +let lvec <- fn(a: [#M, T]): [1, [#M, T]]; + [a] +}; + +let cvec <- (a: [#M, T]): [#M, [1, T]]; + a.lvec().t() +}; diff --git a/crates/typr-core/configs/std/option.ty b/crates/typr-core/configs/std/option.ty new file mode 100644 index 0000000..6e9c09a --- /dev/null +++ b/crates/typr-core/configs/std/option.ty @@ -0,0 +1,41 @@ +# ERROR HANDLING ---------- +@stop: (msg: char) -> Empty; + +# OPTION TYPE ------------- +type Option = .Some(T) | .None; + +let unwrap <- fn(value: Option): T { + match value { + Some(v) => v, + None => stop("The value is not unwrappable.") + } +}; + +let expect <- fn(value: Option, msg: char): T { + match value { + Some(v) => v, + None => stop(msg) + } +}; + + +let unwrap_or <- fn(value: Option, alternative: T): T { + match value { + Some(v) => v, + None => alternative + } +}; + +let is_some <- fn(value: Option): bool { + match value { + Some(v) => true, + None => false + } +}; + +let is_none <- fn(value: Option): bool { + match value { + Some(v) => false, + None => true + } +}; diff --git a/crates/typr-core/configs/std/plot.ty b/crates/typr-core/configs/std/plot.ty new file mode 100644 index 0000000..c837bc3 --- /dev/null +++ b/crates/typr-core/configs/std/plot.ty @@ -0,0 +1,13 @@ +# PLOT FUNCTION ---------- +@plot: (a: [#N, num], b: [#N, num], c: char, xlim: [2, num], ylim: [2, num], log: char, main: char, sub: char, xlab: char, ylab: char, ann: bool, axes: bool) -> .None; + +type Plot = { x: [#N, num], y: [#N, num], type: char, xlim: [2, num], ylim: [2, num], log: char, main: char, sub: char, xlab: char, ylab: char, ann: bool, axes: bool}; + +let bplot: (): Plot; + :{ x: [0.5], y: [0.5], type: "p", xlim: [0.0, 5.0], ylim: [0.0, 5.0], log: "", main: "", sub: "", xlab: "", ylab: "", ann: true, axes: true} +}; + +let show <- fn(p: Plot): .None; + plot(p.x, p.y, p.type, p.xlim, p.ylim, p.log, p.main, p.sub, p.xlab, p.ylab, p.ann, p.axes) +}; +#--------------------- diff --git a/crates/typr-core/configs/std/saved.ty b/crates/typr-core/configs/std/saved.ty new file mode 100644 index 0000000..a1ba403 --- /dev/null +++ b/crates/typr-core/configs/std/saved.ty @@ -0,0 +1,19 @@ +@print: (c: char) -> T; + +@seq: (a: #I, b: #J, c: #K) -> [#J-#I/#K+1, int]; + +@append: (a: [#M, T], e: T) -> [#M+1, T]; + +@mul: (a: int, b: int) -> int; + +@mul: (a: num, b: num) -> num; + +@map: (a: [#N, T], f: (T) -> U) -> [#N, U]; + +@dot: (m: [#M, [#P, int]], n: [#P, [#N, int]]) -> [#M, [#N, int]]; + +@t: (m: [#M, [#N, T]]) -> [#N, [#M, T]]; + +@add: (a: num, b: num) -> num; + +@add: (a: int, b: int) -> int; diff --git a/crates/typr-core/configs/std/std_JS.ty b/crates/typr-core/configs/std/std_JS.ty new file mode 100644 index 0000000..83ec032 --- /dev/null +++ b/crates/typr-core/configs/std/std_JS.ty @@ -0,0 +1,12 @@ +@add: (T, T) -> T; +@minus: (T, T) -> T; +@mul: (T, T) -> T; +@div: (T, T) -> T; +@seq: (#M, #N, #O) -> [#N+1-#M/#O, int]; +@test_that: (char, Any) -> Empty; +@expect_equal: (T, T) -> Empty; +@expect_true: (bool) -> Empty; +@expect_false: (bool) -> Empty; +@expect_null: (Any) -> Empty; +@expect_type: (Any, char) -> Empty; +@expect_s3_class: (Any, char) -> Empty; diff --git a/crates/typr-core/configs/std/std_R.ty b/crates/typr-core/configs/std/std_R.ty new file mode 100644 index 0000000..234708b --- /dev/null +++ b/crates/typr-core/configs/std/std_R.ty @@ -0,0 +1,19 @@ +@`+`: (int, int) -> int; +@`+`: (num, num) -> num; +@`-`: (int, int) -> int; +@`-`: (num, num) -> num; +@`/`: (int, int) -> int; +@`/`: (num, num) -> num; +@`*`: (int, int) -> int; +@`*`: (num, num) -> num; +@`&&`: (bool, bool) -> bool; +@`||`: (bool, bool) -> bool; +@`+`: (Vec[#M, T], Vec[#M, T]) -> Vec[#M, T]; +@as__character: (Any) -> char; +@source: (char) -> Empty; +@reduce: ([#N, T], (T, U) -> T) -> T; +@sum: ([#N, T]) -> T; +@test_that: (char, Any) -> Empty; +@expect_true: (bool) -> Empty; +@expect_false: (T, T) -> Empty; +@expect_equal: (T, T) -> Empty; diff --git a/crates/typr-core/configs/std/system.ty b/crates/typr-core/configs/std/system.ty new file mode 100644 index 0000000..e0873b7 --- /dev/null +++ b/crates/typr-core/configs/std/system.ty @@ -0,0 +1,14 @@ +# System execution ---------- + +@system2: (command: char, args: [#N, char], stdout: char, stderr: char, stdin: char) -> char; + +type System2 = { command: char, args: [#N, char], stdout: char, stderr: char, stdin: char}; + +let bsystem2 <- fn(command: char): System2 { + :{ command: command, args: [""], stdout: "", stderr: "", stdin: ""} +}; + +let exec <- fn(s: System2): char { + system2(s.command, s.args, s.stdout, s.stderr, s.stdin) +}; +# -------------------------------- diff --git a/crates/typr-core/configs/std/test.ty b/crates/typr-core/configs/std/test.ty new file mode 100644 index 0000000..d797ec6 --- /dev/null +++ b/crates/typr-core/configs/std/test.ty @@ -0,0 +1 @@ +@a: int; diff --git a/crates/typr-core/configs/test-basic.R b/crates/typr-core/configs/test-basic.R new file mode 100644 index 0000000..30ea38e --- /dev/null +++ b/crates/typr-core/configs/test-basic.R @@ -0,0 +1,3 @@ +test_that("{{PACKAGE_NAME}} works", { + expect_true(TRUE) +}) diff --git a/crates/typr-core/configs/testthat.R b/crates/typr-core/configs/testthat.R new file mode 100644 index 0000000..e15c632 --- /dev/null +++ b/crates/typr-core/configs/testthat.R @@ -0,0 +1,4 @@ +library(testthat) +library({{PACKAGE_NAME}}) + +test_check("{{PACKAGE_NAME}}") diff --git a/crates/typr-core/src/abstractions.rs b/crates/typr-core/src/abstractions.rs new file mode 100644 index 0000000..a3e70e6 --- /dev/null +++ b/crates/typr-core/src/abstractions.rs @@ -0,0 +1,239 @@ +//! Abstraction traits for platform-independent operations +//! +//! These traits allow typr-core to work both natively (with filesystem access) +//! and in WebAssembly (with in-memory sources). + +use std::collections::HashMap; + +/// Provides source code content for compilation. +/// +/// This trait abstracts away file system access, allowing the compiler +/// to work with in-memory sources (useful for WASM and testing). +pub trait SourceProvider { + /// Get the source code for a given file path + fn get_source(&self, path: &str) -> Option; + + /// Check if a source file exists + fn exists(&self, path: &str) -> bool { + self.get_source(path).is_some() + } + + /// List available source files (for module resolution) + fn list_sources(&self) -> Vec { + vec![] + } +} + +/// In-memory source provider for WASM and testing +#[derive(Debug, Clone, Default)] +pub struct InMemorySourceProvider { + sources: HashMap, +} + +impl InMemorySourceProvider { + /// Create a new empty source provider + pub fn new() -> Self { + Self { + sources: HashMap::new(), + } + } + + /// Add a source file + pub fn add_source(&mut self, path: &str, content: &str) { + self.sources.insert(path.to_string(), content.to_string()); + } + + /// Add a source file (builder pattern) + pub fn with_source(mut self, path: &str, content: &str) -> Self { + self.add_source(path, content); + self + } + + /// Remove a source file + pub fn remove_source(&mut self, path: &str) { + self.sources.remove(path); + } + + /// Clear all sources + pub fn clear(&mut self) { + self.sources.clear(); + } +} + +impl SourceProvider for InMemorySourceProvider { + fn get_source(&self, path: &str) -> Option { + self.sources.get(path).cloned() + } + + fn exists(&self, path: &str) -> bool { + self.sources.contains_key(path) + } + + fn list_sources(&self) -> Vec { + self.sources.keys().cloned().collect() + } +} + +/// Handles output from transpilation. +/// +/// This trait allows customizing where transpiled R code goes - +/// to files, memory buffers, or anywhere else. +pub trait OutputHandler { + /// Write transpiled R code + fn write_r_code(&mut self, filename: &str, content: &str) -> Result<(), OutputError>; + + /// Write type annotations + fn write_type_annotations(&mut self, filename: &str, content: &str) -> Result<(), OutputError>; + + /// Write generic function declarations + fn write_generic_functions(&mut self, filename: &str, content: &str) + -> Result<(), OutputError>; +} + +/// In-memory output handler for WASM and testing +#[derive(Debug, Clone, Default)] +pub struct InMemoryOutputHandler { + pub outputs: HashMap, +} + +impl InMemoryOutputHandler { + pub fn new() -> Self { + Self { + outputs: HashMap::new(), + } + } + + pub fn get_output(&self, filename: &str) -> Option<&String> { + self.outputs.get(filename) + } +} + +impl OutputHandler for InMemoryOutputHandler { + fn write_r_code(&mut self, filename: &str, content: &str) -> Result<(), OutputError> { + self.outputs + .insert(filename.to_string(), content.to_string()); + Ok(()) + } + + fn write_type_annotations(&mut self, filename: &str, content: &str) -> Result<(), OutputError> { + self.outputs + .insert(format!("{}_types", filename), content.to_string()); + Ok(()) + } + + fn write_generic_functions( + &mut self, + filename: &str, + content: &str, + ) -> Result<(), OutputError> { + self.outputs + .insert(format!("{}_generics", filename), content.to_string()); + Ok(()) + } +} + +/// Output operation error +#[derive(Debug, Clone)] +pub struct OutputError { + pub message: String, +} + +impl std::fmt::Display for OutputError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Output error: {}", self.message) + } +} + +impl std::error::Error for OutputError {} + +/// Checks and optionally installs R packages. +/// +/// In native mode, this can execute R commands. +/// In WASM mode, this can be a no-op or return cached information. +pub trait PackageChecker { + /// Check if a package is available + fn is_package_available(&self, name: &str) -> bool; + + /// Try to install a package (may be a no-op in WASM) + fn install_package(&mut self, name: &str) -> Result<(), PackageError>; + + /// Get package type information (cached) + fn get_package_types(&self, name: &str) -> Option; +} + +/// Stub package checker that does nothing (for WASM) +#[derive(Debug, Clone, Default)] +pub struct StubPackageChecker { + available_packages: HashMap, +} + +impl StubPackageChecker { + pub fn new() -> Self { + Self { + available_packages: HashMap::new(), + } + } + + /// Pre-register a package with its type information + pub fn register_package(&mut self, name: &str, types: &str) { + self.available_packages + .insert(name.to_string(), types.to_string()); + } +} + +impl PackageChecker for StubPackageChecker { + fn is_package_available(&self, name: &str) -> bool { + self.available_packages.contains_key(name) + } + + fn install_package(&mut self, _name: &str) -> Result<(), PackageError> { + // No-op in WASM - packages must be pre-registered + Ok(()) + } + + fn get_package_types(&self, name: &str) -> Option { + self.available_packages.get(name).cloned() + } +} + +/// Package operation error +#[derive(Debug, Clone)] +pub struct PackageError { + pub message: String, +} + +impl std::fmt::Display for PackageError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Package error: {}", self.message) + } +} + +impl std::error::Error for PackageError {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_in_memory_source_provider() { + let mut provider = InMemorySourceProvider::new(); + provider.add_source("test.ty", "let x: Number = 42;"); + + assert!(provider.exists("test.ty")); + assert!(!provider.exists("nonexistent.ty")); + assert_eq!( + provider.get_source("test.ty"), + Some("let x: Number = 42;".to_string()) + ); + } + + #[test] + fn test_builder_pattern() { + let provider = InMemorySourceProvider::new() + .with_source("a.ty", "let a = 1;") + .with_source("b.ty", "let b = 2;"); + + assert!(provider.exists("a.ty")); + assert!(provider.exists("b.ty")); + } +} diff --git a/src/components/context/config.rs b/crates/typr-core/src/components/context/config.rs similarity index 73% rename from src/components/context/config.rs rename to crates/typr-core/src/components/context/config.rs index 09fc22d..7951af5 100644 --- a/src/components/context/config.rs +++ b/crates/typr-core/src/components/context/config.rs @@ -1,26 +1,43 @@ use crate::components::context::Context; -use clap::Parser; use serde::{Deserialize, Serialize}; use std::fmt; -#[derive(Debug, Parser, Clone, Copy, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum Environment { StandAlone, Project, Repl, + /// WebAssembly environment - all code is inlined, no file I/O + Wasm, } impl Environment { pub fn to_base_path(self) -> String { self.to_string() } + + /// Check if this environment supports file I/O + pub fn supports_file_io(self) -> bool { + match self { + Environment::StandAlone | Environment::Project | Environment::Repl => true, + Environment::Wasm => false, + } + } + + /// Check if external files should be inlined + pub fn should_inline_files(self) -> bool { + match self { + Environment::Wasm => true, + _ => false, + } + } } impl fmt::Display for Environment { fn fmt(self: &Self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let res = match self { Environment::Project => "R/", - Environment::StandAlone | Environment::Repl => "", + Environment::StandAlone | Environment::Repl | Environment::Wasm => "", }; write!(f, "{}", res) } diff --git a/src/components/context/graph.rs b/crates/typr-core/src/components/context/graph.rs similarity index 97% rename from src/components/context/graph.rs rename to crates/typr-core/src/components/context/graph.rs index 9aca086..673ac84 100644 --- a/src/components/context/graph.rs +++ b/crates/typr-core/src/components/context/graph.rs @@ -84,7 +84,7 @@ impl Graph { } pub fn print_hierarchy(&self) { - println!("{}", self.get_hierarchy()); + eprintln!("{}", self.get_hierarchy()); } pub fn get_supertypes(&self, typ: &T, context: &Context) -> Vec { @@ -167,14 +167,14 @@ impl Node { .collect(), }; if graph == self { - println!( + eprintln!( "add {} to one of the children of {}", typ.pretty(), self.value.pretty() ); self.add_subtype(typ) } else { - println!( + eprintln!( "{} is not a subtype of {}'s subtypes: {}", typ.pretty(), self.value.pretty(), @@ -227,7 +227,7 @@ impl Node { fn switch_if_reverse_subtype_trace(self, typ: T, context: &Context) -> Self { if self.value.is_subtype_raw(&typ, context) { - println!( + eprintln!( "{} is a subtype of the entry {}", self.value.pretty(), typ.pretty() @@ -237,7 +237,7 @@ impl Node { subtypes: vec![Node::from(self.value).set_subtypes(self.subtypes)], } } else { - println!( + eprintln!( "{} is not a subtype of {} abort this branch", typ.pretty(), self.value.pretty() @@ -267,7 +267,7 @@ impl Node { self.subtypes.len(), ) { (true, 0) => { - println!( + eprintln!( "{} is a subtype of the leaf {}", typ.pretty(), self.value.pretty() @@ -275,7 +275,7 @@ impl Node { self.add_subtype(typ) } (true, _) => { - println!( + eprintln!( "{} is a subtype of the node {}", typ.pretty(), self.value.pretty() @@ -302,10 +302,10 @@ impl Node { pub fn get_supertypes_trace(&self, target_type: &T, context: &Context) -> Vec { if target_type == &self.value { - println!("found the root of {} we backtrack", target_type.pretty()); + eprintln!("found the root of {} we backtrack", target_type.pretty()); vec![] } else if target_type.is_subtype_raw(&self.value, context) { - println!( + eprintln!( "{} is subtype of {} we check the subtypes", target_type.pretty(), self.value.pretty() @@ -316,7 +316,7 @@ impl Node { .chain([self.value.clone()].iter().cloned()) .collect::>() } else { - println!( + eprintln!( "{} is not subtype of {} ABORT this branch", target_type.pretty(), self.value.pretty() diff --git a/src/components/context/mod.rs b/crates/typr-core/src/components/context/mod.rs similarity index 98% rename from src/components/context/mod.rs rename to crates/typr-core/src/components/context/mod.rs index 450c298..443b027 100644 --- a/src/components/context/mod.rs +++ b/crates/typr-core/src/components/context/mod.rs @@ -2,20 +2,20 @@ pub mod config; pub mod graph; pub mod vartype; -use crate::processes::type_checking::type_comparison::reduce_type; -use crate::components::context::unification_map::UnificationMap; -use crate::processes::type_checking::match_types_to_generic; -use crate::components::language::var_function::VarFunction; -use crate::components::r#type::argument_type::ArgumentType; -use crate::components::context::config::TargetLanguage; -use crate::components::r#type::type_system::TypeSystem; -use crate::components::context::config::Environment; -use crate::components::context::vartype::VarType; use crate::components::context::config::Config; +use crate::components::context::config::Environment; +use crate::components::context::config::TargetLanguage; use crate::components::context::graph::Graph; +use crate::components::context::unification_map::UnificationMap; +use crate::components::context::vartype::VarType; use crate::components::language::var::Var; +use crate::components::language::var_function::VarFunction; use crate::components::language::Lang; +use crate::components::r#type::argument_type::ArgumentType; +use crate::components::r#type::type_system::TypeSystem; use crate::components::r#type::Type; +use crate::processes::type_checking::match_types_to_generic; +use crate::processes::type_checking::type_comparison::reduce_type; use crate::processes::type_checking::unification_map; use crate::utils::builder; use crate::utils::standard_library::not_in_blacklist; @@ -186,9 +186,14 @@ impl Context { let var_type = self .typing_context .clone() - .pipe(|vt| (reduced_type.is_interface() && lang.is_variable()) - .then(|| vt.clone().push_interface(lang.clone(), reduced_type, typ.clone(), context)) - .unwrap_or(vt.push_var_type(&[(lang.clone(), typ.clone())]))) + .pipe(|vt| { + (reduced_type.is_interface() && lang.is_variable()) + .then(|| { + vt.clone() + .push_interface(lang.clone(), reduced_type, typ.clone(), context) + }) + .unwrap_or(vt.push_var_type(&[(lang.clone(), typ.clone())])) + }) .push_types(&types); let new_subtypes = self.subtypes.add_types(&types, context); Context { diff --git a/src/components/context/vartype.rs b/crates/typr-core/src/components/context/vartype.rs similarity index 89% rename from src/components/context/vartype.rs rename to crates/typr-core/src/components/context/vartype.rs index 91212dc..4a49371 100644 --- a/src/components/context/vartype.rs +++ b/crates/typr-core/src/components/context/vartype.rs @@ -5,24 +5,28 @@ unreachable_code, unused_assignments )] -use crate::components::context::config::TargetLanguage; -use crate::components::r#type::type_system::TypeSystem; -use crate::processes::parsing::type_token::TypeToken; -use crate::components::r#type::vector_type::VecType; -use crate::components::r#type::alias_type::Alias; use crate::components::context::config::Config; -use crate::components::language::var::Var; +use crate::components::context::config::TargetLanguage; use crate::components::context::Context; +use crate::components::language::var::Var; use crate::components::language::Lang; +use crate::components::r#type::alias_type::Alias; +use crate::components::r#type::type_system::TypeSystem; +use crate::components::r#type::vector_type::VecType; use crate::components::r#type::Type; -use serde::{Deserialize, Serialize}; +use crate::processes::parsing::type_token::TypeToken; use crate::utils::builder; use indexmap::IndexSet; -use std::io::Write; +use serde::{Deserialize, Serialize}; use std::iter::Rev; +use std::ops::Add; + +#[cfg(not(feature = "wasm"))] use std::fs::File; +#[cfg(not(feature = "wasm"))] use std::io::Read; -use std::ops::Add; +#[cfg(not(feature = "wasm"))] +use std::io::Write; pub fn same_var_type(element1: &(Var, Type), element2: &(Var, Type)) -> bool { (element1.0.get_name() == element2.0.get_name()) @@ -90,27 +94,34 @@ impl VarType { } } - pub fn push_interface(self, var: Var, typ: Type, original_type: Type, context: &Context) -> VarType { + pub fn push_interface( + self, + var: Var, + typ: Type, + original_type: Type, + context: &Context, + ) -> VarType { match typ { Type::Interface(args, _) => { - let alias = original_type.clone() + let alias = original_type + .clone() .to_alias(context) .unwrap_or(Alias::default()) .set_opacity(false) .to_type(); - args - .iter() + args.iter() .map(|arg_typ| { ( arg_typ.clone().to_var(context), - arg_typ - .get_type() - .replace_function_types(builder::self_generic_type(), alias.clone()), + arg_typ.get_type().replace_function_types( + builder::self_generic_type(), + alias.clone(), + ), ) }) .fold(self, |acc, x| acc.push_var_type(&[x])) .push_var_type(&[(var, alias)]) - }, + } _ => self, } } @@ -358,7 +369,7 @@ impl VarType { } pub fn print_aliases(&self) { - println!("{}", self.get_aliases()); + eprintln!("{}", self.get_aliases()); } pub fn variable_exist(&self, var: Var) -> Option { @@ -455,6 +466,8 @@ impl VarType { } } + /// Save to a file (only available in native mode) + #[cfg(not(feature = "wasm"))] pub fn save(&self, path: &str) -> Result<(), Box> { let binary_data = bincode::serialize(self)?; let mut file = File::create(path)?; @@ -462,6 +475,14 @@ impl VarType { Ok(()) } + /// Stub for WASM mode + #[cfg(feature = "wasm")] + pub fn save(&self, _path: &str) -> Result<(), Box> { + Err("File saving not supported in WASM mode".into()) + } + + /// Load from a file path (only available in native mode) + #[cfg(not(feature = "wasm"))] pub fn load(self, path: &str) -> Result> { let mut file = File::open(path)?; let mut buffer = Vec::new(); @@ -470,30 +491,42 @@ impl VarType { Ok(self + var_type) } + /// Stub for WASM mode + #[cfg(feature = "wasm")] + pub fn load(self, _path: &str) -> Result> { + Err("File loading not supported in WASM mode".into()) + } + + /// Load R standard library (embedded at compile time) pub fn load_r(self) -> Result> { let buffer = include_bytes!("../../../configs/bin/.std_r.bin"); let var_type: VarType = bincode::deserialize(buffer)?; Ok(self + var_type) } + /// Load typed R standard library (embedded at compile time) pub fn load_typed_r(self) -> Result> { let buffer = include_bytes!("../../../configs/bin/.std_r_typed.bin"); let var_type: VarType = bincode::deserialize(buffer)?; Ok(self + var_type) } + /// Load JS standard library (embedded at compile time) pub fn load_js(self) -> Result> { let buffer = include_bytes!("../../../configs/bin/.std_js.bin"); let var_type: VarType = bincode::deserialize(buffer)?; Ok(self + var_type) } + /// Load typed JS standard library (embedded at compile time) pub fn load_typed_js(self) -> Result> { let buffer = include_bytes!("../../../configs/bin/.std_js_typed.bin"); let var_type: VarType = bincode::deserialize(buffer)?; Ok(self + var_type) } + /// Load from file (only available in native mode) + #[cfg(not(feature = "wasm"))] pub fn from_file(path: &str) -> Result> { let mut file = File::open(path)?; let mut buffer = Vec::new(); @@ -502,6 +535,18 @@ impl VarType { Ok(var_type) } + /// Stub for WASM mode + #[cfg(feature = "wasm")] + pub fn from_file(_path: &str) -> Result> { + Err("File loading not supported in WASM mode".into()) + } + + /// Load from bytes (WASM-compatible alternative to from_file) + pub fn from_bytes(data: &[u8]) -> Result> { + let var_type: VarType = bincode::deserialize(data)?; + Ok(var_type) + } + pub fn standard_library(&self) -> Vec<(Var, Type)> { self.std.iter().cloned().collect() } diff --git a/crates/typr-core/src/components/error_message/help_data.rs b/crates/typr-core/src/components/error_message/help_data.rs new file mode 100644 index 0000000..0df329a --- /dev/null +++ b/crates/typr-core/src/components/error_message/help_data.rs @@ -0,0 +1,111 @@ +use crate::components::language::Lang; +use nom_locate::LocatedSpan; +use serde::{Deserialize, Serialize}; + +#[cfg(not(feature = "wasm"))] +use std::fs; + +use std::cell::RefCell; +use std::collections::HashMap; + +// Thread-local storage for source content (used in WASM mode) +thread_local! { + static SOURCE_CACHE: RefCell> = RefCell::new(HashMap::new()); +} + +/// Register source content for a file (used for WASM and error display) +pub fn register_source(file_name: &str, content: &str) { + SOURCE_CACHE.with(|cache| { + cache + .borrow_mut() + .insert(file_name.to_string(), content.to_string()); + }); +} + +/// Clear all registered sources +pub fn clear_sources() { + SOURCE_CACHE.with(|cache| { + cache.borrow_mut().clear(); + }); +} + +/// Get source from cache +fn get_cached_source(file_name: &str) -> Option { + SOURCE_CACHE.with(|cache| cache.borrow().get(file_name).cloned()) +} + +#[derive(Debug, PartialEq, Serialize, Deserialize, Eq, Clone, Hash, Default)] +pub struct HelpData { + offset: usize, + file_name: String, +} + +impl HelpData { + pub fn get_offset(&self) -> usize { + self.offset + } + + pub fn get_file_name(&self) -> String { + self.file_name.clone() + } + + /// Get file data (filename, content) for error display. + /// + /// In WASM mode, this uses the registered source cache. + /// In native mode, this falls back to filesystem if not in cache. + pub fn get_file_data(&self) -> Option<(String, String)> { + let file_name = self.get_file_name(); + if file_name.is_empty() { + return None; + } + + // First try the cache + if let Some(text) = get_cached_source(&file_name) { + return Some((file_name, text)); + } + + // In native mode, fall back to filesystem + #[cfg(not(feature = "wasm"))] + { + match fs::read_to_string(&file_name).ok() { + Some(text) => { + // Cache it for future use + register_source(&file_name, &text); + Some((file_name, text)) + } + None => None, + } + } + + #[cfg(feature = "wasm")] + { + None + } + } + + pub fn random() -> Self { + HelpData { + offset: 7_usize, + file_name: "asfdlwone".to_string(), + } + } +} + +impl From> for HelpData { + fn from(ls: LocatedSpan<&str, String>) -> Self { + HelpData { + offset: ls.location_offset(), + file_name: ls.extra, + } + } +} + +impl From> for HelpData { + fn from(val: Vec) -> Self { + if !val.is_empty() { + val[0].clone().into() + } else { + HelpData::default() + } + } +} diff --git a/src/components/error_message/help_message.rs b/crates/typr-core/src/components/error_message/help_message.rs similarity index 100% rename from src/components/error_message/help_message.rs rename to crates/typr-core/src/components/error_message/help_message.rs diff --git a/src/components/error_message/locatable.rs b/crates/typr-core/src/components/error_message/locatable.rs similarity index 100% rename from src/components/error_message/locatable.rs rename to crates/typr-core/src/components/error_message/locatable.rs diff --git a/src/components/error_message/message_template.rs b/crates/typr-core/src/components/error_message/message_template.rs similarity index 100% rename from src/components/error_message/message_template.rs rename to crates/typr-core/src/components/error_message/message_template.rs diff --git a/src/components/error_message/mod.rs b/crates/typr-core/src/components/error_message/mod.rs similarity index 100% rename from src/components/error_message/mod.rs rename to crates/typr-core/src/components/error_message/mod.rs diff --git a/src/components/error_message/syntax_error.rs b/crates/typr-core/src/components/error_message/syntax_error.rs similarity index 89% rename from src/components/error_message/syntax_error.rs rename to crates/typr-core/src/components/error_message/syntax_error.rs index e2ff4c1..646d29f 100644 --- a/src/components/error_message/syntax_error.rs +++ b/crates/typr-core/src/components/error_message/syntax_error.rs @@ -4,7 +4,6 @@ use crate::components::error_message::help_message::SingleBuilder; use crate::components::r#type::Type; use miette::Result; use serde::{Deserialize, Serialize}; -use std::fs; #[derive(Debug, Clone, Serialize, Deserialize)] pub enum SyntaxError { @@ -39,19 +38,22 @@ impl SyntaxError { } } +/// Default file data when source is not available +fn default_file_data() -> (String, String) { + ("std.ty".to_string(), String::new()) +} + impl ErrorMsg for SyntaxError { fn display(self) -> String { let msg: Result<()> = match self { SyntaxError::FunctionWithoutType(help_data) => { - let (file_name, text) = help_data - .get_file_data() - .unwrap_or(("std.ty".to_string(), fs::read_to_string("std.ty").unwrap())); + let (file_name, text) = help_data.get_file_data().unwrap_or_else(default_file_data); SingleBuilder::new(file_name, text) .pos((help_data.get_offset(), 0)) .build() } SyntaxError::FunctionWithoutReturnType(help_data) => { - let (file_name, text) = help_data.get_file_data().unwrap(); + let (file_name, text) = help_data.get_file_data().unwrap_or_else(default_file_data); SingleBuilder::new(file_name, text) .pos((help_data.get_offset(), 0)) .text("Hey You forgot to specify the function return type after the ':' : 'fn(...): Type'") @@ -60,7 +62,7 @@ impl ErrorMsg for SyntaxError { .build() } SyntaxError::ForgottenSemicolon(help_data) => { - let (file_name, text) = help_data.get_file_data().unwrap(); + let (file_name, text) = help_data.get_file_data().unwrap_or_else(default_file_data); SingleBuilder::new(file_name, text) .pos((help_data.get_offset(), 1)) .text("You forgot a semicolon at the end of your statement") diff --git a/src/components/error_message/type_error.rs b/crates/typr-core/src/components/error_message/type_error.rs similarity index 88% rename from src/components/error_message/type_error.rs rename to crates/typr-core/src/components/error_message/type_error.rs index c23b0ef..c3bdbb3 100644 --- a/src/components/error_message/type_error.rs +++ b/crates/typr-core/src/components/error_message/type_error.rs @@ -8,7 +8,6 @@ use crate::components::language::Lang; use crate::components::r#type::type_system::TypeSystem; use crate::components::r#type::Type; use miette::Result; -use std::fs; #[derive(Debug, Clone)] pub enum TypeError { @@ -111,6 +110,11 @@ impl TypeError { } } +/// Default file data when source is not available +fn default_file_data() -> (String, String) { + ("std.ty".to_string(), String::new()) +} + // main impl ErrorMsg for TypeError { fn display(self) -> String { @@ -133,7 +137,7 @@ impl ErrorMsg for TypeError { TypeError::AliasNotFound(typ) => { let (file_name, text) = typ .get_file_name_and_text() - .unwrap_or(("".to_string(), "".to_string())); + .unwrap_or_else(|| ("".to_string(), "".to_string())); SingleBuilder::new(file_name, text) .pos((typ.get_help_data().get_offset(), 0)) .text(format!("Alias {} not defined in this scope.", typ.pretty())) @@ -143,10 +147,8 @@ impl ErrorMsg for TypeError { TypeError::Let(t1, t2) => { let help_data1 = t1.get_help_data(); let help_data2 = t2.get_help_data(); - let (file_name, text) = help_data1.get_file_data().unwrap_or(( - "std.ty".to_string(), - fs::read_to_string("std.ty").unwrap_or("".to_string()), - )); + let (file_name, text) = + help_data1.get_file_data().unwrap_or_else(default_file_data); DoubleBuilder::new(file_name.clone(), text.clone(), file_name, text) .pos1((help_data1.get_offset(), 0)) .pos2((help_data2.get_offset(), 1)) @@ -162,12 +164,10 @@ impl ErrorMsg for TypeError { TypeError::Param(t1, t2) => { let help_data1 = t1.get_help_data(); let help_data2 = t2.get_help_data(); - let (file_name1, text1) = help_data1 - .get_file_data() - .unwrap_or(("std.ty".to_string(), fs::read_to_string("std.ty").unwrap())); - let (file_name2, text2) = help_data2 - .get_file_data() - .unwrap_or(("std.ty".to_string(), fs::read_to_string("std.ty").unwrap())); + let (file_name1, text1) = + help_data1.get_file_data().unwrap_or_else(default_file_data); + let (file_name2, text2) = + help_data2.get_file_data().unwrap_or_else(default_file_data); DoubleBuilder::new(file_name1, text1, file_name2, text2) .pos1((help_data1.get_offset(), 0)) .pos2((help_data2.get_offset(), 1)) @@ -185,10 +185,10 @@ impl ErrorMsg for TypeError { let help_data2 = t2.get_help_data(); let (file_name1, text1) = help_data1 .get_file_data() - .unwrap_or(("std.ty".to_string(), "".to_string())); + .unwrap_or_else(|| ("std.ty".to_string(), "".to_string())); let (file_name2, text2) = help_data2 .get_file_data() - .unwrap_or(("std.ty".to_string(), "".to_string())); + .unwrap_or_else(|| ("std.ty".to_string(), "".to_string())); DoubleBuilder::new(file_name1, text1, file_name2, text2) .pos1((help_data1.get_offset(), 0)) .pos2((help_data2.get_offset(), 1)) @@ -204,12 +204,10 @@ impl ErrorMsg for TypeError { TypeError::UnmatchingReturnType(t1, t2) => { let help_data1 = t1.get_help_data(); let help_data2 = t2.get_help_data(); - let (file_name1, text1) = help_data1 - .get_file_data() - .unwrap_or(("std.ty".to_string(), fs::read_to_string("std.ty").unwrap())); - let (file_name2, text2) = help_data2 - .get_file_data() - .unwrap_or(("std.ty".to_string(), fs::read_to_string("std.ty").unwrap())); + let (file_name1, text1) = + help_data1.get_file_data().unwrap_or_else(default_file_data); + let (file_name2, text2) = + help_data2.get_file_data().unwrap_or_else(default_file_data); DoubleBuilder::new(file_name1, text1, file_name2, text2) .pos1((help_data1.get_offset(), 0)) .pos2((help_data2.get_offset(), 1)) @@ -222,7 +220,7 @@ impl ErrorMsg for TypeError { let help_data = fun.get_help_data(); let (file_name, text) = help_data .get_file_data() - .unwrap_or(("std.ty".to_string(), "".to_string())); + .unwrap_or_else(|| ("std.ty".to_string(), "".to_string())); let res = SingleBuilder::new(file_name, text) .pos((help_data.get_offset(), 0)) .text("The function doesn't exist"); @@ -234,7 +232,7 @@ impl ErrorMsg for TypeError { let var2 = Var::from_language(var).unwrap(); let (file_name, text) = help_data .get_file_data() - .unwrap_or(("std.ty".to_string(), "".to_string())); + .unwrap_or_else(|| ("std.ty".to_string(), "".to_string())); SingleBuilder::new(file_name, text) .pos((help_data.get_offset(), 0)) .pos_text(format!("Undefined variable '{}'", var2.get_name())) @@ -287,10 +285,8 @@ impl ErrorMsg for TypeError { TypeError::FieldNotFound((name, h), typ) => { let help_data1 = h; let help_data2 = typ.get_help_data(); - let (file_name, text) = help_data1.get_file_data().unwrap_or(( - "std.ty".to_string(), - fs::read_to_string("std.ty").unwrap_or("".to_string()), - )); + let (file_name, text) = + help_data1.get_file_data().unwrap_or_else(default_file_data); DoubleBuilder::new(file_name.clone(), text.clone(), file_name, text) .pos1((help_data1.get_offset(), 0)) .pos2((help_data2.get_offset(), 1)) @@ -300,10 +296,7 @@ impl ErrorMsg for TypeError { .build() } TypeError::WrongExpression(help_data) => { - let (file_name, text) = help_data.get_file_data().unwrap_or(( - "std.ty".to_string(), - fs::read_to_string("std.ty").unwrap_or("".to_string()), - )); + let (file_name, text) = help_data.get_file_data().unwrap_or_else(default_file_data); SingleBuilder::new(file_name, text) .pos((help_data.get_offset(), 0)) .build() @@ -313,14 +306,14 @@ impl ErrorMsg for TypeError { let help_data2 = t2.get_help_data(); let (file_name1, text1) = help_data1 .get_file_data() - .unwrap_or(("std.ty".to_string(), "".to_string())); + .unwrap_or_else(|| ("std.ty".to_string(), "".to_string())); let (file_name2, text2) = help_data2 .get_file_data() - .unwrap_or(("std.ty".to_string(), "".to_string())); + .unwrap_or_else(|| ("std.ty".to_string(), "".to_string())); DoubleBuilder::new(file_name1, text1, file_name2, text2) .pos1((help_data1.get_offset(), 0)) .pos2((help_data2.get_offset(), 1)) - .text(format!("Wrong indexing")) + .text("Wrong indexing".to_string()) .pos_text1(format!("{} Can't be indexed", t1.pretty2())) .pos_text2(format!("Has a bigger dimension {}", t2.pretty2())) .build() diff --git a/src/components/error_message/typr_error.rs b/crates/typr-core/src/components/error_message/typr_error.rs similarity index 100% rename from src/components/error_message/typr_error.rs rename to crates/typr-core/src/components/error_message/typr_error.rs diff --git a/src/components/language/argument_value.rs b/crates/typr-core/src/components/language/argument_value.rs similarity index 100% rename from src/components/language/argument_value.rs rename to crates/typr-core/src/components/language/argument_value.rs diff --git a/src/components/language/array_lang.rs b/crates/typr-core/src/components/language/array_lang.rs similarity index 100% rename from src/components/language/array_lang.rs rename to crates/typr-core/src/components/language/array_lang.rs diff --git a/src/components/language/function_lang.rs b/crates/typr-core/src/components/language/function_lang.rs similarity index 100% rename from src/components/language/function_lang.rs rename to crates/typr-core/src/components/language/function_lang.rs diff --git a/src/components/language/mod.rs b/crates/typr-core/src/components/language/mod.rs similarity index 100% rename from src/components/language/mod.rs rename to crates/typr-core/src/components/language/mod.rs diff --git a/src/components/language/module_lang.rs b/crates/typr-core/src/components/language/module_lang.rs similarity index 100% rename from src/components/language/module_lang.rs rename to crates/typr-core/src/components/language/module_lang.rs diff --git a/src/components/language/operators.rs b/crates/typr-core/src/components/language/operators.rs similarity index 100% rename from src/components/language/operators.rs rename to crates/typr-core/src/components/language/operators.rs diff --git a/src/components/language/var.rs b/crates/typr-core/src/components/language/var.rs similarity index 100% rename from src/components/language/var.rs rename to crates/typr-core/src/components/language/var.rs diff --git a/src/components/language/var_function.rs b/crates/typr-core/src/components/language/var_function.rs similarity index 100% rename from src/components/language/var_function.rs rename to crates/typr-core/src/components/language/var_function.rs diff --git a/src/components/mod.rs b/crates/typr-core/src/components/mod.rs similarity index 100% rename from src/components/mod.rs rename to crates/typr-core/src/components/mod.rs diff --git a/src/components/type/alias_type.rs b/crates/typr-core/src/components/type/alias_type.rs similarity index 72% rename from src/components/type/alias_type.rs rename to crates/typr-core/src/components/type/alias_type.rs index d73d259..01b3f3d 100644 --- a/src/components/type/alias_type.rs +++ b/crates/typr-core/src/components/type/alias_type.rs @@ -1,18 +1,22 @@ -use crate::processes::parsing::type_token::TypeToken; use crate::components::r#type::HelpData; use crate::components::r#type::Type; use rand::prelude::*; pub struct Alias { - name: String, - params: Vec, - opacity: bool, - help_data: HelpData + name: String, + params: Vec, + opacity: bool, + help_data: HelpData, } impl Alias { pub fn new(name: String, params: Vec, opacity: bool, help_data: HelpData) -> Self { - Self { name, params, opacity, help_data } + Self { + name, + params, + opacity, + help_data, + } } pub fn set_opacity(self, val: bool) -> Self { @@ -25,7 +29,6 @@ impl Alias { pub fn to_type(self) -> Type { Type::Alias(self.name, self.params, self.opacity, self.help_data) } - } impl Default for Alias { @@ -33,7 +36,7 @@ impl Default for Alias { let mut rng = rand::rng(); // Version la plus lisible et la plus utilisée - //// Generate and shuffle a sequence: + //// Generate and shuffle a sequence: let nums: Vec = (1..=1000).collect(); let nombre = nums.choose(&mut rng).unwrap(); let name = format!("Opaque{nombre}"); @@ -41,7 +44,7 @@ impl Default for Alias { name, params: vec![], opacity: false, - help_data: HelpData::default() - } + help_data: HelpData::default(), + } } } diff --git a/src/components/type/argument_type.rs b/crates/typr-core/src/components/type/argument_type.rs similarity index 100% rename from src/components/type/argument_type.rs rename to crates/typr-core/src/components/type/argument_type.rs diff --git a/src/components/type/array_type.rs b/crates/typr-core/src/components/type/array_type.rs similarity index 100% rename from src/components/type/array_type.rs rename to crates/typr-core/src/components/type/array_type.rs diff --git a/src/components/type/function_type.rs b/crates/typr-core/src/components/type/function_type.rs similarity index 97% rename from src/components/type/function_type.rs rename to crates/typr-core/src/components/type/function_type.rs index 392bcf8..c9b3564 100644 --- a/src/components/type/function_type.rs +++ b/crates/typr-core/src/components/type/function_type.rs @@ -74,7 +74,9 @@ impl FunctionType { .map(|x| x.get_size_type()) .collect::>(); validate_vectorization(unique_types) - .and_then(|hash| hash.iter().cloned().max_by_key(|x| x.0)) + .and_then(|hash: HashSet<(i32, VecType, Type)>| { + hash.iter().cloned().max_by_key(|x| x.0) + }) .map(|(index, vectyp, _)| (vectyp, index)) .and_then(|max_index| { context diff --git a/src/components/type/generic.rs b/crates/typr-core/src/components/type/generic.rs similarity index 100% rename from src/components/type/generic.rs rename to crates/typr-core/src/components/type/generic.rs diff --git a/src/components/type/index.rs b/crates/typr-core/src/components/type/index.rs similarity index 100% rename from src/components/type/index.rs rename to crates/typr-core/src/components/type/index.rs diff --git a/src/components/type/js_types.rs b/crates/typr-core/src/components/type/js_types.rs similarity index 100% rename from src/components/type/js_types.rs rename to crates/typr-core/src/components/type/js_types.rs diff --git a/src/components/type/mod.rs b/crates/typr-core/src/components/type/mod.rs similarity index 99% rename from src/components/type/mod.rs rename to crates/typr-core/src/components/type/mod.rs index 0a76ca7..e7a11df 100644 --- a/src/components/type/mod.rs +++ b/crates/typr-core/src/components/type/mod.rs @@ -1,4 +1,5 @@ #![allow(dead_code)] +pub mod alias_type; pub mod argument_type; pub mod array_type; pub mod function_type; @@ -15,37 +16,36 @@ pub mod type_system; pub mod typer; pub mod union_type; pub mod vector_type; -pub mod alias_type; -use crate::processes::type_checking::type_comparison::reduce_type; -use crate::processes::parsing::operation_priority::TokenKind; +use crate::components::context::Context; +use crate::components::error_message::help_data::HelpData; use crate::components::error_message::locatable::Locatable; +use crate::components::language::var::Var; +use crate::components::r#type::alias_type::Alias; use crate::components::r#type::argument_type::ArgumentType; use crate::components::r#type::function_type::FunctionType; +use crate::components::r#type::module_type::ModuleType; +use crate::components::r#type::tchar::Tchar; +use crate::components::r#type::tint::Tint; use crate::components::r#type::type_category::TypeCategory; use crate::components::r#type::type_operator::TypeOperator; -use crate::components::error_message::help_data::HelpData; -use crate::components::r#type::module_type::ModuleType; -use crate::components::r#type::type_system::TypeSystem; -use crate::processes::parsing::type_token::TypeToken; -use crate::components::r#type::type_printer::verbose; -use crate::components::r#type::vector_type::VecType; use crate::components::r#type::type_printer::format; use crate::components::r#type::type_printer::short; -use crate::components::r#type::alias_type::Alias; -use crate::components::r#type::tchar::Tchar; +use crate::components::r#type::type_printer::verbose; +use crate::components::r#type::type_system::TypeSystem; +use crate::components::r#type::vector_type::VecType; +use crate::processes::parsing::operation_priority::TokenKind; +use crate::processes::parsing::type_token::TypeToken; use crate::processes::parsing::types::ltype; -use crate::components::language::var::Var; -use crate::components::r#type::tint::Tint; -use crate::components::context::Context; -use serde::{Deserialize, Serialize}; -use std::collections::HashSet; +use crate::processes::type_checking::type_comparison::reduce_type; use crate::utils::builder; +use serde::{Deserialize, Serialize}; use std::cmp::Ordering; +use std::collections::HashSet; +use std::fmt; +use std::hash::Hash; use std::hash::Hasher; use std::str::FromStr; -use std::hash::Hash; -use std::fmt; pub fn generate_arg(num: usize) -> String { match num { @@ -242,14 +242,14 @@ impl Type { pub fn is_alias(&self) -> bool { match self { Type::Alias(_, _, _, _) => true, - _ => false + _ => false, } } - pub fn to_alias(self, context: &Context) -> Option { + pub fn to_alias(self, _context: &Context) -> Option { match self { Type::Alias(name, params, opacity, hd) => Some(Alias::new(name, params, opacity, hd)), - _ => None + _ => None, } } @@ -342,7 +342,7 @@ impl Type { .collect::>(); sol.push(self.clone()); sol - }, + } Type::Interface(_, _) => vec![], // there is a special push for this typ => vec![typ.clone()], } @@ -674,7 +674,7 @@ impl Type { Type::Operator(TypeOperator::Union, _, _, _) => TypeCategory::Union, Type::Operator(_, _, _, _) => TypeCategory::Operator, _ => { - println!("{:?} return Rest", self); + eprintln!("{:?} return Rest", self); TypeCategory::Rest } } diff --git a/src/components/type/module_type.rs b/crates/typr-core/src/components/type/module_type.rs similarity index 100% rename from src/components/type/module_type.rs rename to crates/typr-core/src/components/type/module_type.rs diff --git a/src/components/type/tchar.rs b/crates/typr-core/src/components/type/tchar.rs similarity index 100% rename from src/components/type/tchar.rs rename to crates/typr-core/src/components/type/tchar.rs diff --git a/src/components/type/tint.rs b/crates/typr-core/src/components/type/tint.rs similarity index 100% rename from src/components/type/tint.rs rename to crates/typr-core/src/components/type/tint.rs diff --git a/src/components/type/type_category.rs b/crates/typr-core/src/components/type/type_category.rs similarity index 100% rename from src/components/type/type_category.rs rename to crates/typr-core/src/components/type/type_category.rs diff --git a/src/components/type/type_operator.rs b/crates/typr-core/src/components/type/type_operator.rs similarity index 100% rename from src/components/type/type_operator.rs rename to crates/typr-core/src/components/type/type_operator.rs diff --git a/src/components/type/type_printer.rs b/crates/typr-core/src/components/type/type_printer.rs similarity index 100% rename from src/components/type/type_printer.rs rename to crates/typr-core/src/components/type/type_printer.rs diff --git a/src/components/type/type_system.rs b/crates/typr-core/src/components/type/type_system.rs similarity index 100% rename from src/components/type/type_system.rs rename to crates/typr-core/src/components/type/type_system.rs diff --git a/src/components/type/typer.rs b/crates/typr-core/src/components/type/typer.rs similarity index 100% rename from src/components/type/typer.rs rename to crates/typr-core/src/components/type/typer.rs diff --git a/src/components/type/union_type.rs b/crates/typr-core/src/components/type/union_type.rs similarity index 100% rename from src/components/type/union_type.rs rename to crates/typr-core/src/components/type/union_type.rs diff --git a/src/components/type/vector_type.rs b/crates/typr-core/src/components/type/vector_type.rs similarity index 100% rename from src/components/type/vector_type.rs rename to crates/typr-core/src/components/type/vector_type.rs diff --git a/crates/typr-core/src/lib.rs b/crates/typr-core/src/lib.rs new file mode 100644 index 0000000..b6a711f --- /dev/null +++ b/crates/typr-core/src/lib.rs @@ -0,0 +1,202 @@ +//! # TypR Core +//! +//! Pure logic for TypR - a typed superset of R. +//! +//! This crate contains the core functionality that can be compiled to WebAssembly: +//! - Parsing TypR source code +//! - Type checking +//! - Transpilation to R +//! +//! ## Architecture +//! +//! The crate is designed to be platform-agnostic by using trait abstractions +//! for all I/O operations: +//! +//! - [`SourceProvider`]: Provides source code content (replaces `std::fs::read_to_string`) +//! - [`OutputHandler`]: Handles transpilation output (replaces file writes) +//! - [`PackageChecker`]: Checks/installs R packages (replaces `Command` execution) +//! +//! ## Usage +//! +//! ```rust,ignore +//! use typr_core::{Compiler, InMemorySourceProvider}; +//! +//! let mut sources = InMemorySourceProvider::new(); +//! sources.add_source("main.ty", "let x: Number = 42;"); +//! +//! let compiler = Compiler::new(sources); +//! let result = compiler.compile("main.ty")?; +//! println!("R code: {}", result.r_code); +//! ``` + +pub mod abstractions; +pub mod components; +pub mod processes; +pub mod utils; + +// Re-export main types +pub use abstractions::*; +pub use components::context::config::Environment; +pub use components::context::Context; +pub use components::error_message::typr_error::TypRError; +pub use components::language::Lang; +pub use components::r#type::Type; + +// Re-export main functions +pub use processes::parsing; +pub use processes::transpiling; +pub use processes::type_checking::{typing, typing_with_errors, TypingResult}; + +/// Main compiler interface for TypR +pub struct Compiler { + source_provider: S, + context: Context, +} + +impl Compiler { + /// Create a new compiler with the given source provider + pub fn new(source_provider: S) -> Self { + Self { + source_provider, + context: Context::default(), + } + } + + /// Create a compiler configured for WASM environment + /// + /// In WASM mode: + /// - All external modules are inlined + /// - No source() calls are generated + /// - Generated files are collected and can be retrieved + pub fn new_wasm(source_provider: S) -> Self { + use components::context::config::Config; + + let config = Config::default().set_environment(Environment::Wasm); + Self { + source_provider, + context: config.to_context(), + } + } + + /// Create a compiler with a custom context + pub fn with_context(source_provider: S, context: Context) -> Self { + Self { + source_provider, + context, + } + } + + /// Get the current context + pub fn get_context(&self) -> Context { + self.context.clone() + } + + /// Parse source code and return the AST + pub fn parse(&self, filename: &str) -> Result { + let source = self + .source_provider + .get_source(filename) + .ok_or_else(|| CompileError::FileNotFound(filename.to_string()))?; + + Ok(parsing::parse_from_string(&source, filename)) + } + + /// Type check the given AST + pub fn type_check(&self, ast: &Lang) -> TypingResult { + typing_with_errors(&self.context, ast) + } + + /// Transpile AST to R code + pub fn transpile(&self, ast: &Lang) -> TranspileResult { + use processes::type_checking::type_checker::TypeChecker; + + let type_checker = TypeChecker::new(self.context.clone()).typing(ast); + let r_code = type_checker.clone().transpile(); + let context = type_checker.get_context(); + + TranspileResult { + r_code, + type_annotations: context.get_type_anotations(), + generic_functions: context + .get_all_generic_functions() + .iter() + .map(|(var, _)| var.get_name()) + .filter(|x| !x.contains("<-")) + .collect(), + } + } + + /// Full compilation: parse, type check, and transpile + pub fn compile(&self, filename: &str) -> Result { + let ast = self.parse(filename)?; + let typing_result = self.type_check(&ast); + + if typing_result.has_errors() { + return Err(CompileError::TypeErrors(typing_result.get_errors().clone())); + } + + let transpile_result = self.transpile(&ast); + + Ok(CompileOutput { + ast, + r_code: transpile_result.r_code, + type_annotations: transpile_result.type_annotations, + generic_functions: transpile_result.generic_functions, + }) + } + + /// Get completions at a position (for LSP support) + pub fn get_completions(&self, _source: &str, _position: usize) -> Vec { + // TODO: Implement completion logic + vec![] + } + + /// Get hover information at a position (for LSP support) + pub fn get_hover(&self, _source: &str, _position: usize) -> Option { + // TODO: Implement hover logic + None + } +} + +/// Result of transpilation +#[derive(Debug, Clone)] +pub struct TranspileResult { + pub r_code: String, + pub type_annotations: String, + pub generic_functions: Vec, +} + +/// Full compilation output +#[derive(Debug, Clone)] +pub struct CompileOutput { + pub ast: Lang, + pub r_code: String, + pub type_annotations: String, + pub generic_functions: Vec, +} + +/// Compilation errors +#[derive(Debug, Clone)] +pub enum CompileError { + FileNotFound(String), + ParseError(String), + TypeErrors(Vec), +} + +impl std::fmt::Display for CompileError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CompileError::FileNotFound(name) => write!(f, "File not found: {}", name), + CompileError::ParseError(msg) => write!(f, "Parse error: {}", msg), + CompileError::TypeErrors(errors) => { + write!(f, "Type errors:\n")?; + for err in errors { + write!(f, " - {:?}\n", err)?; + } + Ok(()) + } + } + } +} + +impl std::error::Error for CompileError {} diff --git a/src/processes/mod.rs b/crates/typr-core/src/processes/mod.rs similarity index 100% rename from src/processes/mod.rs rename to crates/typr-core/src/processes/mod.rs index 27a3067..edd1758 100644 --- a/src/processes/mod.rs +++ b/crates/typr-core/src/processes/mod.rs @@ -1,3 +1,3 @@ pub mod parsing; -pub mod type_checking; pub mod transpiling; +pub mod type_checking; diff --git a/src/processes/parsing/elements.rs b/crates/typr-core/src/processes/parsing/elements.rs similarity index 99% rename from src/processes/parsing/elements.rs rename to crates/typr-core/src/processes/parsing/elements.rs index 461022c..535fff3 100644 --- a/src/processes/parsing/elements.rs +++ b/crates/typr-core/src/processes/parsing/elements.rs @@ -332,7 +332,7 @@ pub fn simple_function(s: Span) -> IResult { exit(1) } Ok((_s, (_, _, _args, _, None, Some(typ), _exp))) => { - println!( + eprintln!( "The type '{}' should be preceded by a ':' :\n 'fn(...): {}'", typ.clone(), typ.clone() @@ -462,7 +462,7 @@ fn record(s: Span) -> IResult { if args.len() == 0 { panic!("Error: the scope shouldn't be empty") } else { - println!("{}", _s); + eprintln!("{}", _s); panic!("You forgot to put a record identifier before the bracket: ':{{...}}'"); } } diff --git a/src/processes/parsing/indexation.rs b/crates/typr-core/src/processes/parsing/indexation.rs similarity index 100% rename from src/processes/parsing/indexation.rs rename to crates/typr-core/src/processes/parsing/indexation.rs diff --git a/src/processes/parsing/lang_token.rs b/crates/typr-core/src/processes/parsing/lang_token.rs similarity index 100% rename from src/processes/parsing/lang_token.rs rename to crates/typr-core/src/processes/parsing/lang_token.rs diff --git a/src/processes/parsing/mod.rs b/crates/typr-core/src/processes/parsing/mod.rs similarity index 96% rename from src/processes/parsing/mod.rs rename to crates/typr-core/src/processes/parsing/mod.rs index e039059..c1a4b45 100644 --- a/src/processes/parsing/mod.rs +++ b/crates/typr-core/src/processes/parsing/mod.rs @@ -906,6 +906,37 @@ pub fn parse2(s: Span) -> Result { } } +/// Parse source code from a string with a filename for error reporting +/// +/// This is the main entry point for parsing source code in the public API. +/// It registers the source for error display and returns the AST. +pub fn parse_from_string(source: &str, filename: &str) -> Lang { + use crate::components::error_message::help_data::register_source; + + // Register source for error display + register_source(filename, source); + + // Create a span with the filename as extra data + let span: Span = LocatedSpan::new_extra(source, filename.to_string()); + + // Parse and return the AST + parse(span).ast +} + +/// Parse source code from a string with a filename, returning full ParseResult +pub fn parse_from_string_with_errors(source: &str, filename: &str) -> ParseResult { + use crate::components::error_message::help_data::register_source; + + // Register source for error display + register_source(filename, source); + + // Create a span with the filename as extra data + let span: Span = LocatedSpan::new_extra(source, filename.to_string()); + + // Parse and return full result + parse(span) +} + // main test #[cfg(test)] mod tesus { diff --git a/src/processes/parsing/operation_priority.rs b/crates/typr-core/src/processes/parsing/operation_priority.rs similarity index 100% rename from src/processes/parsing/operation_priority.rs rename to crates/typr-core/src/processes/parsing/operation_priority.rs diff --git a/src/processes/parsing/type_token.rs b/crates/typr-core/src/processes/parsing/type_token.rs similarity index 100% rename from src/processes/parsing/type_token.rs rename to crates/typr-core/src/processes/parsing/type_token.rs diff --git a/src/processes/parsing/types.rs b/crates/typr-core/src/processes/parsing/types.rs similarity index 100% rename from src/processes/parsing/types.rs rename to crates/typr-core/src/processes/parsing/types.rs diff --git a/src/processes/parsing/vector_priority.rs b/crates/typr-core/src/processes/parsing/vector_priority.rs similarity index 100% rename from src/processes/parsing/vector_priority.rs rename to crates/typr-core/src/processes/parsing/vector_priority.rs diff --git a/crates/typr-core/src/processes/transpiling/mod.rs b/crates/typr-core/src/processes/transpiling/mod.rs new file mode 100644 index 0000000..1f2d2c0 --- /dev/null +++ b/crates/typr-core/src/processes/transpiling/mod.rs @@ -0,0 +1,622 @@ +pub mod translatable; + +use crate::components::context::config::Environment; +use crate::components::context::Context; +use crate::components::error_message::help_data::HelpData; +use crate::components::language::format_backtick; +use crate::components::language::function_lang::Function; +use crate::components::language::operators::Op; +use crate::components::language::set_related_type_if_variable; +use crate::components::language::var::Var; +use crate::components::language::Lang; +use crate::components::language::ModulePosition; +use crate::components::r#type::array_type::ArrayType; +use crate::components::r#type::function_type::FunctionType; +use crate::components::r#type::Type; +use crate::processes::transpiling::translatable::Translatable; +use crate::processes::type_checking::type_comparison::reduce_type; +use crate::processes::type_checking::typing; +use translatable::RTranslatable; + +#[cfg(not(feature = "wasm"))] +use std::fs::File; +#[cfg(not(feature = "wasm"))] +use std::io::Write; +#[cfg(not(feature = "wasm"))] +use std::path::PathBuf; + +use std::cell::RefCell; +use std::collections::HashMap; + +// Thread-local storage for generated files (used in WASM mode) +thread_local! { + static GENERATED_FILES: RefCell> = RefCell::new(HashMap::new()); +} + +/// Register a generated file (used for WASM mode to capture file outputs) +pub fn register_generated_file(path: &str, content: &str) { + GENERATED_FILES.with(|files| { + files + .borrow_mut() + .insert(path.to_string(), content.to_string()); + }); +} + +/// Get all generated files +pub fn get_generated_files() -> HashMap { + GENERATED_FILES.with(|files| files.borrow().clone()) +} + +/// Clear all generated files +pub fn clear_generated_files() { + GENERATED_FILES.with(|files| { + files.borrow_mut().clear(); + }); +} + +/// Write a file - in native mode writes to filesystem, in WASM mode stores in memory +#[cfg(not(feature = "wasm"))] +fn write_output_file(path: &str, content: &str) -> Result<(), String> { + use std::fs; + + // Also register in memory for consistency + register_generated_file(path, content); + + let path_buf = PathBuf::from(path); + if let Some(parent) = path_buf.parent() { + fs::create_dir_all(parent).map_err(|e| e.to_string())?; + } + let mut file = File::create(&path_buf).map_err(|e| e.to_string())?; + file.write_all(content.as_bytes()) + .map_err(|e| e.to_string())?; + Ok(()) +} + +#[cfg(feature = "wasm")] +fn write_output_file(path: &str, content: &str) -> Result<(), String> { + register_generated_file(path, content); + Ok(()) +} + +pub trait ToSome { + fn to_some(self) -> Option + where + Self: Sized; +} + +impl ToSome for T { + fn to_some(self) -> Option { + Some(self) + } +} + +trait AndIf { + fn and_if(self, condition: F) -> Option + where + F: Fn(Self) -> bool, + Self: Sized; +} + +impl AndIf for T { + fn and_if(self, condition: F) -> Option + where + F: Fn(Self) -> bool, + { + if condition(self.clone()) { + Some(self) + } else { + None + } + } +} + +const JS_HEADER: &str = ""; + +fn condition_to_if(var: &Var, typ: &Type, context: &Context) -> String { + format!( + "any(class({}) == c({}))", + var.get_name(), + context.get_class(typ) + ) +} + +fn to_if_statement( + var: Var, + exp: Lang, + branches: &[(Type, Box)], + context: &Context, +) -> String { + let res = branches + .iter() + .map(|(typ, body)| (condition_to_if(&var, typ, context), body)) + .enumerate() + .map(|(id, (cond, body))| { + if id == 0 { + format!("if ({}) {{ \n {} \n }}", cond, body.to_r(context).0) + } else { + format!("else if ({}) {{ \n {} \n }}", cond, body.to_r(context).0) + } + }) + .collect::>() + .join(" "); + format!( + "{{\n {} <- {} \n {}\n}}", + var.get_name(), + exp.to_r(context).0, + res + ) +} + +impl RTranslatable<(String, Context)> for Lang { + fn to_r(&self, cont: &Context) -> (String, Context) { + let result = match self { + Lang::Bool(b, _) => { + let (typ, _, _) = typing(cont, self).to_tuple(); + let anotation = cont.get_type_anotation(&typ); + ( + format!("{} |> {}", b.to_string().to_uppercase(), anotation), + cont.clone(), + ) + } + Lang::Number(n, _) => { + let (typ, _, _) = typing(cont, self).to_tuple(); + let anotation = cont.get_type_anotation(&typ); + (format!("{} |> {}", n, anotation), cont.clone()) + } + Lang::Integer(i, _) => { + let (typ, _, _) = typing(cont, self).to_tuple(); + let anotation = cont.get_type_anotation(&typ); + (format!("{}L |> {}", i, anotation), cont.clone()) + } + Lang::Char(s, _) => { + let (typ, _, _) = typing(cont, self).to_tuple(); + let anotation = cont.get_type_anotation(&typ); + (format!("'{}' |> {}", s, anotation), cont.clone()) + } + Lang::Operator(Op::Dot(_), e1, e2, _) | Lang::Operator(Op::Pipe(_), e1, e2, _) => { + let e1 = (**e1).clone(); + let e2 = (**e2).clone(); + match e2.clone() { + Lang::Variable(_, _, _, _) => Translatable::from(cont.clone()) + .to_r(&e2) + .add("[['") + .to_r(&e1) + .add("']]") + .into(), + Lang::Record(fields, _) => { + let at = fields[0].clone(); + Translatable::from(cont.clone()) + .add("within(") + .to_r(&e2) + .add(", { ") + .add(&at.get_argument()) + .add(" <- ") + .to_r(&at.get_value()) + .add(" })") + .into() + } + Lang::FunctionApp(var, v, h) => { + let v = [e1].iter().chain(v.iter()).cloned().collect(); + Lang::FunctionApp(var, v, h).to_r(cont) + } + _ => Translatable::from(cont.clone()) + .to_r(&e2) + .add("[[") + .add("]]") + .to_r(&e1) + .into(), + } + } + Lang::Operator(Op::Dollar(_), e1, e2, _) => { + let e1 = (**e1).clone(); + let e2 = (**e2).clone(); + let t1 = typing(cont, &e1).value; + let val = match (t1.clone(), e2.clone()) { + (Type::Vec(vtype, _, _, _), Lang::Variable(name, _, _, _)) + if vtype.is_array() => + { + format!("vec_apply(get, {}, typed_vec('{}'))", e1.to_r(cont).0, name) + } + (_, Lang::Variable(name, _, _, _)) => format!("{}${}", e1.to_r(cont).0, name), + _ => format!("{}${}", e1.to_r(cont).0, e2.to_r(cont).0), + //_ => panic!("Dollar operation not yet implemented for {:?}", e2) + }; + (val, cont.clone()) + } + Lang::Operator(op, e1, e2, _) => { + let op_str = format!(" {} ", op.to_string()); + Translatable::from(cont.clone()) + .to_r(e1) + .add(&op_str) + .to_r(e2) + .into() + } + Lang::Scope(exps, _) => Translatable::from(cont.clone()) + .add("{\n") + .join(exps, "\n") + .add("\n}") + .into(), + Lang::Function(args, _, body, _) => { + let fn_type = FunctionType::try_from(typing(cont, self).value.clone()).unwrap(); + let output_conversion = cont.get_type_anotation(&fn_type.get_return_type()); + let res = (output_conversion == "") + .then_some("".to_string()) + .unwrap_or(" |> ".to_owned() + &output_conversion); + ( + format!( + "(function({}) {}{}) |> {}", + args.iter().map(|x| x.to_r()).collect::>().join(", "), + body.to_r(cont).0, + res, + cont.get_type_anotation(&fn_type.into()) + ), + cont.clone(), + ) + } + Lang::Variable(_, _, _, _) => { + //Here we only keep the variable name, the path and the type + let var = Var::from_language(self.clone()).unwrap(); + let name = if var.contains("__") { + var.replace("__", ".").get_name() + } else { + var.display_type(cont).get_name() + }; + ((&name).to_string(), cont.clone()) + } + Lang::FunctionApp(exp, vals, _) => { + let var = Var::try_from(exp.clone()).unwrap(); + + let (exp_str, cont1) = exp.to_r(cont); + let fn_t = FunctionType::try_from( + cont1 + .get_type_from_variable(&var) + .expect(&format!("variable {} don't have a related type", var)), + ) + .map(|ft| ft.adjust_nb_parameters(vals.len())) + .unwrap(); + let new_args = fn_t + .get_param_types() + .into_iter() + .map(|arg| reduce_type(&cont1, &arg)) + .collect::>(); + let new_vals = vals + .into_iter() + .zip(new_args.iter()) + .map(set_related_type_if_variable) + .collect::>(); + let (args, current_cont) = Translatable::from(cont1).join(&new_vals, ", ").into(); + Var::from_language(*exp.clone()) + .map(|var| { + let name = var.get_name(); + let new_name = if &name[0..1] == "%" { + format!("`{}`", name.replace("__", ".")) + } else { + name.replace("__", ".") + }; + (format!("{}({})", new_name, args), current_cont.clone()) + }) + .unwrap_or((format!("{}({})", exp_str, args), current_cont)) + } + Lang::VecFunctionApp(exp, vals, _) => { + let var = Var::try_from(exp.clone()).unwrap(); + let name = var.get_name(); + let str_vals = vals + .iter() + .map(|x| x.to_r(cont).0) + .collect::>() + .join(", "); + if name == "reduce" { + (format!("vec_reduce({})", str_vals), cont.clone()) + } else if cont.is_an_untyped_function(&name) { + let name = name.replace("__", "."); + let new_name = if &name[0..1] == "%" { + format!("`{}`", name) + } else { + name.to_string() + }; + let s = format!("{}({})", new_name, str_vals); + (s, cont.clone()) + } else { + let (exp_str, cont1) = exp.to_r(cont); + let fn_t = FunctionType::try_from( + cont1 + .get_type_from_variable(&var) + .expect(&format!("variable {} don't have a related type", var)), + ) + .unwrap(); + let new_args = fn_t + .get_param_types() + .into_iter() + .map(|arg| reduce_type(&cont1, &arg)) + .collect::>(); + let new_vals = vals + .into_iter() + .zip(new_args.iter()) + .map(set_related_type_if_variable) + .collect::>(); + let (args, current_cont) = + Translatable::from(cont1).join(&new_vals, ", ").into(); + Var::from_language(*exp.clone()) + .map(|var| { + let name = var.get_name(); + let new_name = if &name[0..1] == "%" { + format!("`{}`", name.replace("__", ".")) + } else { + name.replace("__", ".") + }; + ( + format!("vec_apply({}, {})", new_name, args), + current_cont.clone(), + ) + }) + .unwrap_or((format!("vec_apply({}, {})", exp_str, args), current_cont)) + } + } + Lang::ArrayIndexing(exp, val, _) => { + let (exp_str, _) = exp.to_r(cont); + let (val_str, _) = val.to_simple_r(cont); + let (typ, _, _) = typing(&cont, exp).to_tuple(); + let res = match typ { + Type::Vec(_, _, _, _) => format!("{}[[{}]]", exp_str, val_str), + _ => "".to_string(), + }; + (res, cont.clone()) + } + Lang::GenFunc(func, _, _) => ( + format!("function(x, ...) UseMethod('{}')", func.to_string()), + cont.clone(), + ), + Lang::Let(expr, ttype, body, _) => { + let (body_str, new_cont) = body.to_r(cont); + let new_name = format_backtick(expr.clone().to_r(cont).0); + + let (r_code, _new_name2) = Function::try_from((**body).clone()) + .map(|_| { + let related_type = typing(cont, expr).value; + let method = match cont.get_environment() { + Environment::Project => format!( + "#' @method {}\n", + new_name.replace(".", " ").replace("`", "") + ), + _ => "".to_string(), + }; + match related_type { + Type::Empty(_) => { + (format!("{} <- {}", new_name, body_str), new_name.clone()) + } + Type::Any(_) | Type::Generic(_, _) => ( + format!("{}.default <- {}", new_name, body_str), + new_name.clone(), + ), + _ => ( + format!("{}{} <- {}", method, new_name, body_str), + new_name.clone(), + ), + } + }) + .unwrap_or((format!("{} <- {}", new_name, body_str), new_name)); + let code = if !ttype.is_empty() { + let _ = new_cont.get_type_anotation(ttype); + format!("{}\n", r_code) + } else { + r_code + "\n" + }; + (code, new_cont) + } + Lang::Array(_v, _h) => { + let typ = self.typing(cont).value; + + let _dimension = ArrayType::try_from(typ.clone()) + .unwrap() + .get_shape() + .map(|sha| format!("c({})", sha)) + .unwrap_or(format!("c(0)")); + + let array = &self + .linearize_array() + .iter() + .map(|lang| lang.to_r(&cont).0) + .collect::>() + .join(", ") + .and_if(|lin_array| lin_array != "") + //.map(|lin_array| format!("concat({}, dim = {})", lin_array, dimension)) + .map(|lin_array| format!("typed_vec({})", lin_array)) + .unwrap_or("logical(0)".to_string()); + + ( + format!("{} |> {}", array, cont.get_type_anotation(&typ)), + cont.to_owned(), + ) + } + Lang::Record(args, _) => { + let (body, current_cont) = Translatable::from(cont.clone()) + .join_arg_val(args, ",\n ") + .into(); + let (typ, _, _) = typing(cont, self).to_tuple(); + let anotation = cont.get_type_anotation(&typ); + cont.get_classes(&typ) + .map(|_| format!("list({}) |> {}", body, anotation)) + .unwrap_or(format!("list({}) |> {}", body, anotation)) + .to_some() + .map(|s| (s, current_cont)) + .unwrap() + } + Lang::If(cond, exp, els, _) if els == &Box::new(Lang::Empty(HelpData::default())) => { + Translatable::from(cont.clone()) + .add("if(") + .to_r(cond) + .add(") {\n") + .to_r(exp) + .add(" \n}") + .into() + } + Lang::If(cond, exp, els, _) => Translatable::from(cont.clone()) + .add("if(") + .to_r(cond) + .add(") {\n") + .to_r(exp) + .add(" \n} else ") + .to_r(els) + .into(), + Lang::Tuple(vals, _) => Translatable::from(cont.clone()) + .add("struct(list(") + .join(vals, ", ") + .add("), 'Tuple')") + .into(), + Lang::Assign(var, exp, _) => Translatable::from(cont.clone()) + .to_r(var) + .add(" <- ") + .to_r(exp) + .into(), + Lang::Comment(txt, _) => ("#".to_string() + txt, cont.clone()), + Lang::Tag(s, t, _) => { + let (t_str, new_cont) = t.to_r(cont); + let (typ, _, _) = typing(cont, self).to_tuple(); + let class = cont.get_class(&typ); + cont.get_classes(&typ) + .map(|res| { + format!( + "struct(list('{}', {}), c('Tag', {}, {}))", + s, t_str, class, res + ) + }) + .unwrap_or(format!( + "struct(list('{}', {}), c('Tag', {}))", + s, t_str, class + )) + .to_some() + .map(|s| (s, new_cont)) + .unwrap() + } + Lang::Empty(_) => ("NA".to_string(), cont.clone()), + Lang::ModuleDecl(name, _) => (format!("{} <- new.env()", name), cont.clone()), + Lang::Lines(exps, _) => Translatable::from(cont.clone()).join(exps, "\n").into(), + Lang::Return(exp, _) => Translatable::from(cont.clone()) + .add("return ") + .to_r(exp) + .into(), + Lang::Lambda(bloc, _) => ( + format!("function(x) {{ {} }}", bloc.to_r(cont).0), + cont.clone(), + ), + Lang::VecBlock(bloc, _) => (bloc.to_string(), cont.clone()), + Lang::Library(name, _) => (format!("library({})", name), cont.clone()), + Lang::Match(exp, var, branches, _) => ( + to_if_statement(var.clone(), (**exp).clone(), branches, cont), + cont.clone(), + ), + Lang::Exp(exp, _) => (exp.clone(), cont.clone()), + Lang::ForLoop(var, iterator, body, _) => Translatable::from(cont.clone()) + .add("for (") + .to_r_safe(var) + .add(" in ") + .to_r_safe(iterator) + .add(") {\n") + .to_r_safe(body) + .add("\n}") + .into(), + Lang::RFunction(vars, body, _) => Translatable::from(cont.clone()) + .add("function (") + .join(vars, ", ") + .add(") \n") + .add(&body) + .add("\n") + .into(), + Lang::Signature(_, _, _) => ("".to_string(), cont.clone()), + Lang::Alias(_, _, _, _) => ("".to_string(), cont.clone()), + Lang::KeyValue(k, v, _) => (format!("{} = {}", k, v.to_r(cont).0), cont.clone()), + Lang::Vector(vals, _) => { + let res = "c(".to_string() + + &vals + .iter() + .map(|x| x.to_r(cont).0) + .collect::>() + .join(", ") + + ")"; + (res, cont.to_owned()) + } + Lang::Not(exp, _) => (format!("!{}", exp.to_r(cont).0), cont.clone()), + Lang::Sequence(vals, _) => { + let res = if vals.len() > 0 { + "c(".to_string() + + &vals + .iter() + .map(|x| "list(".to_string() + &x.to_r(cont).0 + ")") + .collect::>() + .join(", ") + + ")" + } else { + "c(list())".to_string() + }; + (res, cont.to_owned()) + } + Lang::TestBlock(body, h) => { + let file_name = h + .get_file_data() + .map(|(name, _)| format!("test-{}", name)) + .unwrap_or_else(|| "test-unknown".to_string()) + .replace("TypR/", "") + .replace(".ty", ".R"); + + let file_path = format!("tests/testthat/{}", file_name); + let content = body.to_r(cont).0; + + let _ = write_output_file(&file_path, &content); + ("".to_string(), cont.clone()) + } + Lang::JSBlock(exp, _id, _h) => { + let js_cont = Context::default(); //TODO get js context from memory + let res = exp.to_js(&js_cont).0; + (format!("'{}{}'", JS_HEADER, res), cont.clone()) + } + Lang::WhileLoop(condition, body, _) => ( + format!( + "while ({}) {{\n{}\n}}", + condition.to_r(cont).0, + body.to_r(cont).0 + ), + cont.clone(), + ), + Lang::Break(_) => ("break".to_string(), cont.clone()), + Lang::Module(name, body, position, config, _) => { + let name = if (name == "main") && (config.environment == Environment::Project) { + "a_main" + } else { + name + }; + let content = body + .iter() + .map(|lang| lang.to_r(cont).0) + .collect::>() + .join("\n"); + match (position, config.environment) { + (ModulePosition::Internal, _) => (content, cont.clone()), + // In WASM mode, inline all external modules instead of writing files + (ModulePosition::External, Environment::Wasm) => { + let file_path = format!("{}.R", name); + let _ = write_output_file(&file_path, &content); + // Return the content directly, no source() call + (content, cont.clone()) + } + (ModulePosition::External, Environment::StandAlone) + | (ModulePosition::External, Environment::Repl) => { + let file_path = format!("{}.R", name); + let _ = write_output_file(&file_path, &content); + (format!("source('{}')", file_path), cont.clone()) + } + (ModulePosition::External, Environment::Project) => { + let file_path = format!("R/{}.R", name); + let _ = write_output_file(&file_path, &content); + ("".to_string(), cont.clone()) + } + } + } + _ => { + println!("This language structure won't transpile: {:?}", self); + ("".to_string(), cont.clone()) + } + }; + + result + } +} diff --git a/src/processes/transpiling/translatable.rs b/crates/typr-core/src/processes/transpiling/translatable.rs similarity index 69% rename from src/processes/transpiling/translatable.rs rename to crates/typr-core/src/processes/transpiling/translatable.rs index 67d6fc3..4570947 100644 --- a/src/processes/transpiling/translatable.rs +++ b/crates/typr-core/src/processes/transpiling/translatable.rs @@ -1,5 +1,5 @@ -use crate::components::language::argument_value::ArgumentValue; use crate::components::context::Context; +use crate::components::language::argument_value::ArgumentValue; use crate::components::language::Lang; use std::ops::Add; @@ -13,7 +13,7 @@ pub trait RTranslatable { pub struct Translatable { context: Context, - code: String + code: String, } impl Translatable { @@ -61,70 +61,66 @@ impl Translatable { pub fn join(self, vals: &[Lang], joint: &str) -> Translatable { if vals.len() > 0 { vals.into_iter() - .fold(self, - |trans, val| trans.to_r(val).add(joint)) + .fold(self, |trans, val| trans.to_r(val).add(joint)) .sub(joint.len()) } else { - self + self } } pub fn join_arg_val(self, vals: &[ArgumentValue], joint: &str) -> Translatable { vals.into_iter() - .fold(self, - |trans, val| trans.to_r_arg_val(val, joint)) + .fold(self, |trans, val| trans.to_r_arg_val(val, joint)) .sub(joint.len()) } pub fn sub(self, len: usize) -> Translatable { - let new_code = - if self.code.len() > len { - &self.code[0..(self.code.len()-len)] - } else { - &self.code - }; + let new_code = if self.code.len() > len { + &self.code[0..(self.code.len() - len)] + } else { + &self.code + }; Translatable { code: new_code.to_string(), ..self } } - } impl Add for Translatable { type Output = Translatable; fn add(self, other: Self) -> Self::Output { - let new_context = (other.context == Context::default()) - .then_some(self.context) - .unwrap_or(other.context); - Translatable { - context: new_context, - code: self.code + &other.code + let new_context = (other.context == Context::default()) + .then_some(self.context) + .unwrap_or(other.context); + Translatable { + context: new_context, + code: self.code + &other.code, } } } -impl From for Translatable { - fn from(val: Context) -> Self { - Translatable { - context: val, - code: "".to_string() - } - } +impl From for Translatable { + fn from(val: Context) -> Self { + Translatable { + context: val, + code: "".to_string(), + } + } } -impl From for (String, Context) { - fn from(val: Translatable) -> Self { - (val.code, val.context) - } +impl From for (String, Context) { + fn from(val: Translatable) -> Self { + (val.code, val.context) + } } impl TranslateAppendable for (String, Context) { fn to_translatable(self) -> Translatable { Translatable { context: self.1, - code: self.0 + code: self.0, } } } @@ -133,7 +129,7 @@ impl TranslateAppendable for String { fn to_translatable(self) -> Translatable { Translatable { context: Context::default(), - code: self + code: self, } } } @@ -146,15 +142,15 @@ impl RTranslatable<(String, Context)> for Box { #[cfg(test)] mod tests { - use crate::components::error_message::help_data::HelpData; use super::*; + use crate::components::error_message::help_data::HelpData; #[test] - fn test_simple_trans0(){ - let t = Translatable::from(Context::default()); - let bo = Lang::Bool(true, HelpData::default()); - let v = vec![bo.clone(), bo.clone(), bo]; - let (a, _) = t.join(&v, ", ").into(); - assert_eq!(a, "true"); + fn test_simple_trans0() { + let t = Translatable::from(Context::default()); + let bo = Lang::Bool(true, HelpData::default()); + let v = vec![bo.clone(), bo.clone(), bo]; + let (a, _) = t.join(&v, ", ").into(); + assert_eq!(a, "true"); } } diff --git a/src/processes/type_checking/function_application.rs b/crates/typr-core/src/processes/type_checking/function_application.rs similarity index 100% rename from src/processes/type_checking/function_application.rs rename to crates/typr-core/src/processes/type_checking/function_application.rs diff --git a/src/processes/type_checking/let_expression.rs b/crates/typr-core/src/processes/type_checking/let_expression.rs similarity index 100% rename from src/processes/type_checking/let_expression.rs rename to crates/typr-core/src/processes/type_checking/let_expression.rs diff --git a/src/processes/type_checking/mod.rs b/crates/typr-core/src/processes/type_checking/mod.rs similarity index 98% rename from src/processes/type_checking/mod.rs rename to crates/typr-core/src/processes/type_checking/mod.rs index 2f0cb32..2dac5d7 100644 --- a/src/processes/type_checking/mod.rs +++ b/crates/typr-core/src/processes/type_checking/mod.rs @@ -36,9 +36,10 @@ use crate::processes::type_checking::signature_expression::signature_expression; use crate::processes::type_checking::type_comparison::reduce_type; use crate::processes::type_checking::type_context::TypeContext; use crate::utils::builder; -use crate::utils::package_loader::PackageManager; use std::collections::HashSet; use std::error::Error; + +#[cfg(not(feature = "wasm"))] use std::process::Command; /// Result of type checking, containing the type context and collected errors @@ -115,6 +116,9 @@ pub fn typing_with_errors(context: &Context, expr: &Lang) -> TypingResult { TypingResult::new(tc) } +/// Execute an R function and return the output. +/// Only available in native mode (not in WASM). +#[cfg(not(feature = "wasm"))] pub fn execute_r_function(function_code: &str) -> Result> { let r_script = format!("{}\n", function_code); @@ -129,7 +133,15 @@ pub fn execute_r_function(function_code: &str) -> Result> } } -fn install_package(name: &str) -> () { +/// Stub for WASM mode - R execution is not supported +#[cfg(feature = "wasm")] +pub fn execute_r_function(_function_code: &str) -> Result> { + Err("R execution is not supported in WASM mode".into()) +} + +/// Install an R package. Only available in native mode. +#[cfg(not(feature = "wasm"))] +fn install_package(name: &str) { let _status = Command::new("Rscript") .args([ "-e", @@ -142,6 +154,12 @@ fn install_package(name: &str) -> () { .expect("failed to execute Rscript"); } +/// Stub for WASM mode - package installation is not supported +#[cfg(feature = "wasm")] +fn install_package(_name: &str) { + // No-op in WASM mode - packages must be pre-registered +} + pub fn eval(context: &Context, expr: &Lang) -> TypeContext { match expr { Lang::Let(name, ty, exp, h) => let_expression(context, name, ty, exp, h), @@ -192,16 +210,16 @@ pub fn eval(context: &Context, expr: &Lang) -> TypeContext { } } Lang::Library(name, _h) => { + // In WASM mode, library imports are no-op (types must be pre-registered) + // In native mode, this would install and load the package install_package(name); - let package_manager = PackageManager::to_package(name).unwrap(); - if !package_manager.exists() { - package_manager.clone().save(); - } - let var_type = package_manager.load().unwrap(); + + // For now, just return unknown function type + // The package types should be pre-loaded in the context for WASM ( builder::unknown_function_type(), expr.clone(), - context.clone().extend_typing_context(var_type), + context.clone(), ) .into() } diff --git a/src/processes/type_checking/signature_expression.rs b/crates/typr-core/src/processes/type_checking/signature_expression.rs similarity index 100% rename from src/processes/type_checking/signature_expression.rs rename to crates/typr-core/src/processes/type_checking/signature_expression.rs diff --git a/src/processes/type_checking/type_checker.rs b/crates/typr-core/src/processes/type_checking/type_checker.rs similarity index 90% rename from src/processes/type_checking/type_checker.rs rename to crates/typr-core/src/processes/type_checking/type_checker.rs index c37480d..5a63f97 100644 --- a/src/processes/type_checking/type_checker.rs +++ b/crates/typr-core/src/processes/type_checking/type_checker.rs @@ -36,7 +36,7 @@ impl TypeChecker { pub fn show_errors(&self) { self.errors .iter() - .for_each(|error| println!("{}", error.clone().display())) + .for_each(|error| eprintln!("{}", error.clone().display())) } pub fn typing(self, exp: &Lang) -> Self { @@ -45,7 +45,7 @@ impl TypeChecker { let type_checker = exps .iter() .fold(self.clone(), |acc, lang| acc.typing_helper(lang)); - println!("Typing:\n{}\n", type_checker.last_type.pretty()); + eprintln!("Typing:\n{}\n", type_checker.last_type.pretty()); type_checker } _ => self.clone().typing_helper(exp), @@ -83,6 +83,8 @@ impl TypeChecker { let import = match self.get_environment() { Environment::Project | Environment::Repl => "", Environment::StandAlone => "source('a_std.R', echo = FALSE)", + // In WASM mode, no source() calls - files will be inlined by the compiler + Environment::Wasm => "", }; format!("{}\n\n{}", import, code) diff --git a/src/processes/type_checking/type_comparison.rs b/crates/typr-core/src/processes/type_checking/type_comparison.rs similarity index 100% rename from src/processes/type_checking/type_comparison.rs rename to crates/typr-core/src/processes/type_checking/type_comparison.rs diff --git a/src/processes/type_checking/type_context.rs b/crates/typr-core/src/processes/type_checking/type_context.rs similarity index 78% rename from src/processes/type_checking/type_context.rs rename to crates/typr-core/src/processes/type_checking/type_context.rs index 795b259..33f010e 100644 --- a/src/processes/type_checking/type_context.rs +++ b/crates/typr-core/src/processes/type_checking/type_context.rs @@ -1,9 +1,6 @@ -use crate::components::error_message::typr_error::TypRError; use crate::components::error_message::type_error::TypeError; -<<<<<<< fix/let-typeerror -======= +use crate::components::error_message::typr_error::TypRError; use crate::components::r#type::type_system::TypeSystem; ->>>>>>> main use crate::processes::type_checking::Context; use crate::processes::type_checking::Lang; use crate::processes::type_checking::Type; @@ -62,37 +59,22 @@ impl TypeContext { } } -<<<<<<< fix/let-typeerror pub fn get_covariant_type(self, typ: &Type) -> Self { let new_type = self.value.get_covariant_type(typ, &self.context); let mut errors = self.errors; // If the covariant check failed, the returned type is Any — record a TypeError::Let if let crate::components::r#type::Type::Any(_) = new_type { - errors.push(TypRError::type_error(TypeError::Let(typ.clone(), self.value.clone()))); + errors.push(TypRError::type_error(TypeError::Let( + typ.clone(), + self.value.clone(), + ))); } Self { value: new_type, lang: self.lang, context: self.context, errors, -======= - pub fn get_covariant_type(mut self, typ: &Type) -> Self { - let expected_type = typ.reduce(&self.context); - let actual_type = self.value.reduce(&self.context); - - if !typ.is_empty() { - if actual_type.is_subtype(&expected_type, &self.context).0 { - self.value = typ.clone(); - } else { - self.errors.push(TypRError::Type(TypeError::Let( - expected_type.clone().set_help_data(typ.get_help_data()), - actual_type.clone().set_help_data(self.value.get_help_data()), - ))); - self.value = builder::any_type(); - } ->>>>>>> main } - self } pub fn add_to_context(self, var: Var) -> Self { diff --git a/src/processes/type_checking/unification.rs b/crates/typr-core/src/processes/type_checking/unification.rs similarity index 100% rename from src/processes/type_checking/unification.rs rename to crates/typr-core/src/processes/type_checking/unification.rs diff --git a/src/processes/type_checking/unification_map.rs b/crates/typr-core/src/processes/type_checking/unification_map.rs similarity index 100% rename from src/processes/type_checking/unification_map.rs rename to crates/typr-core/src/processes/type_checking/unification_map.rs diff --git a/src/utils/builder.rs b/crates/typr-core/src/utils/builder.rs similarity index 100% rename from src/utils/builder.rs rename to crates/typr-core/src/utils/builder.rs index 0aeca86..2e6daa5 100644 --- a/src/utils/builder.rs +++ b/crates/typr-core/src/utils/builder.rs @@ -6,15 +6,15 @@ unused_assignments )] -use crate::components::r#type::type_operator::TypeOperator; -use crate::components::r#type::argument_type::ArgumentType; use crate::components::error_message::help_data::HelpData; -use crate::components::r#type::vector_type::VecType; use crate::components::language::operators::Op; -use crate::components::r#type::tchar::Tchar; -use crate::components::r#type::tint::Tint; use crate::components::language::var::Var; use crate::components::language::Lang; +use crate::components::r#type::argument_type::ArgumentType; +use crate::components::r#type::tchar::Tchar; +use crate::components::r#type::tint::Tint; +use crate::components::r#type::type_operator::TypeOperator; +use crate::components::r#type::vector_type::VecType; use crate::components::r#type::Type; use std::collections::HashSet; diff --git a/src/utils/fluent_parser.rs b/crates/typr-core/src/utils/fluent_parser.rs similarity index 66% rename from src/utils/fluent_parser.rs rename to crates/typr-core/src/utils/fluent_parser.rs index 0a135b5..811eca7 100644 --- a/src/utils/fluent_parser.rs +++ b/crates/typr-core/src/utils/fluent_parser.rs @@ -1,12 +1,18 @@ -#![allow(dead_code, unused_variables, unused_imports, unreachable_code, unused_assignments)] -use crate::processes::transpiling::translatable::RTranslatable; -use crate::components::r#type::type_system::TypeSystem; -use crate::processes::type_checking::typing; -use crate::components::language::var::Var; +#![allow( + dead_code, + unused_variables, + unused_imports, + unreachable_code, + unused_assignments +)] use crate::components::context::Context; -use crate::processes::parsing::parse2; +use crate::components::language::var::Var; use crate::components::language::Lang; +use crate::components::r#type::type_system::TypeSystem; use crate::components::r#type::Type; +use crate::processes::parsing::parse2; +use crate::processes::transpiling::translatable::RTranslatable; +use crate::processes::type_checking::typing; use crate::utils::builder; use rpds::Vector; @@ -19,22 +25,21 @@ pub struct FluentParser { logs: Vector, pub context: Context, last_type: Type, - pub saved_r: Vector + pub saved_r: Vector, } impl FluentParser { - pub fn new() -> Self { - FluentParser { - raw_code: Vector::new(), - code: Vector::new(), - new_code: Vector::new(), - r_code: Vector::new(), - logs: Vector::new(), - context: Context::empty(), - last_type: builder::empty_type(), - saved_r: Vector::new() - } + FluentParser { + raw_code: Vector::new(), + code: Vector::new(), + new_code: Vector::new(), + r_code: Vector::new(), + logs: Vector::new(), + context: Context::empty(), + last_type: builder::empty_type(), + saved_r: Vector::new(), + } } pub fn push(self, code: &str) -> Self { @@ -68,20 +73,18 @@ impl FluentParser { fn next_raw_code(self) -> Option<(String, Self)> { match self.clone().raw_code.first() { Some(val) => Some((val.clone(), self.drop_first_raw())), - _ => None + _ => None, } } /// Go from raw_code (String) to code (Lang) pub fn parse_next(self) -> Self { match self.clone().next_raw_code() { - Some((line, rest)) => { - match parse2((&line[..]).into()) { - Ok(code) => rest.push_code(code), - Err(msg) => rest.push_log(&msg), - } + Some((line, rest)) => match parse2((&line[..]).into()) { + Ok(code) => rest.push_code(code), + Err(msg) => rest.push_log(&msg), }, - _ => self.push_log("No more raw line left") + _ => self.push_log("No more raw line left"), } } @@ -93,13 +96,14 @@ impl FluentParser { } pub fn parse_all_lines(self) -> Self { - self.clone().raw_code.iter() - .fold(self, |acc, x| { - match parse2(x[..].into()) { - Ok(code) => acc.push_code(code), - Err(msg) => acc.push_log(&msg) - } - }).clean_raw_code() + self.clone() + .raw_code + .iter() + .fold(self, |acc, x| match parse2(x[..].into()) { + Ok(code) => acc.push_code(code), + Err(msg) => acc.push_log(&msg), + }) + .clean_raw_code() } fn drop_first_code(self) -> Self { @@ -111,17 +115,13 @@ impl FluentParser { pub fn next_code(self) -> Option<(Lang, Self)> { match self.code.first() { - Some(lang) - => Some((lang.clone(), self.drop_first_code())), - _ => None + Some(lang) => Some((lang.clone(), self.drop_first_code())), + _ => None, } } pub fn set_context(self, context: Context) -> Self { - Self { - context, - ..self - } + Self { context, ..self } } fn set_last_type(self, typ: Type) -> Self { @@ -142,34 +142,33 @@ impl FluentParser { pub fn type_next(self) -> Self { match self.clone().next_code() { Some((code, rest)) => { - let (typ, lang, new_context) = typing(&self.context, &code).to_tuple(); + let (typ, lang, new_context) = typing(&self.context, &code).to_tuple(); rest.set_context(new_context) .push_new_code(lang) .set_last_type(typ) - }, - _ => self.push_log("No more Lang code left") + } + _ => self.push_log("No more Lang code left"), } } pub fn type_all(self) -> Self { - let (new_context, new_type) = self.clone().code.iter() - .fold((self.clone().context, builder::empty_type()), - |(cont, typ), x| { - let (new_type, _, new_cont) = typing(&cont, x).to_tuple(); - (new_cont, new_type) - }); + let (new_context, new_type) = self.clone().code.iter().fold( + (self.clone().context, builder::empty_type()), + |(cont, typ), x| { + let (new_type, _, new_cont) = typing(&cont, x).to_tuple(); + (new_cont, new_type) + }, + ); self.set_context(new_context).set_last_type(new_type) } /// Parsing from raw code (String) to new code (Lang) pub fn parse_type_next(self) -> Self { - self.parse_next() - .type_next() + self.parse_next().type_next() } pub fn parse_type_all(self) -> Self { - self.parse_all_lines() - .type_all() + self.parse_all_lines().type_all() } pub fn type_of(&self, symbol: &str) -> Vec { @@ -223,9 +222,8 @@ impl FluentParser { pub fn next_new_code(self) -> Option<(Lang, Self)> { match self.new_code.first() { - Some(lang) - => Some((lang.clone(), self.drop_first_new_code())), - _ => None + Some(lang) => Some((lang.clone(), self.drop_first_new_code())), + _ => None, } } @@ -244,7 +242,8 @@ impl FluentParser { } pub fn get_saved_r_code(&self) -> String { - self.saved_r.iter() + self.saved_r + .iter() .cloned() .reduce(|acc, x| format!("{}\n{}", acc, &x)) .unwrap_or("".to_string()) @@ -252,32 +251,28 @@ impl FluentParser { fn get_let_definitions(v: Vector, context: &Context) -> Vec { v.iter() - .filter(|x| x.save_in_memory()) - .map(|x| x.to_r(context).0) - .collect() + .filter(|x| x.save_in_memory()) + .map(|x| x.to_r(context).0) + .collect() } pub fn transpile_next(self) -> Self { match self.clone().next_new_code() { Some((code, rest)) => { - let (r_code, new_context) = code.to_r(&self.context); - let res = rest.set_context(new_context) - .push_r_code(r_code); + let (r_code, new_context) = code.to_r(&self.context); + let res = rest.set_context(new_context).push_r_code(r_code); Self::get_let_definitions(self.new_code, &self.context) .iter() .fold(res, |acc, x| acc.save_r_code(x)) - }, - _ => self.push_log("No more Lang code left") + } + _ => self.push_log("No more Lang code left"), } } /// from raw code (String) to r code (String) /// Do the same as .run() methode pub fn parse_type_transpile_next(self) -> Self { - self - .parse_next() - .type_next() - .transpile_next() + self.parse_next().type_next().transpile_next() } /// from raw code (String) to r code (String) @@ -295,9 +290,8 @@ impl FluentParser { pub fn next_r_code(self) -> Option<(String, Self)> { match self.r_code.first() { - Some(lang) - => Some((lang.clone(), self.drop_first_r_code())), - _ => None + Some(lang) => Some((lang.clone(), self.drop_first_r_code())), + _ => None, } } @@ -310,36 +304,42 @@ impl FluentParser { } pub fn check_parsing(self, s: &str) -> Vector { - self.push(s) - .parse_next() - .get_code() + self.push(s).parse_next().get_code() } pub fn check_typing(self, s: &str) -> Type { - self.push(s) - .parse_type_next() - .get_last_type() + self.push(s).parse_type_next().get_last_type() } pub fn check_transpiling(self, s: &str) -> Vector { - self.push(s) - .parse_type_transpile_next() - .get_r_code() + self.push(s).parse_type_transpile_next().get_r_code() } - - } use std::fmt; impl fmt::Display for FluentParser { fn fmt(self: &Self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let res = format!("raw_code: {}\ncode: {}\nnew_code: {}\nr_code: {}\nlast_type: {}", - self.raw_code.iter().cloned().collect::>().join(" | "), - self.code.iter().map(|x| x.simple_print()).collect::>().join(" | "), - self.new_code.iter().map(|x| x.simple_print()).collect::>().join(" | "), - self.r_code.iter().cloned().collect::>().join(" | "), - self.last_type.pretty()); - write!(f, "{}", res) + let res = format!( + "raw_code: {}\ncode: {}\nnew_code: {}\nr_code: {}\nlast_type: {}", + self.raw_code + .iter() + .cloned() + .collect::>() + .join(" | "), + self.code + .iter() + .map(|x| x.simple_print()) + .collect::>() + .join(" | "), + self.new_code + .iter() + .map(|x| x.simple_print()) + .collect::>() + .join(" | "), + self.r_code.iter().cloned().collect::>().join(" | "), + self.last_type.pretty() + ); + write!(f, "{}", res) } } @@ -348,7 +348,7 @@ mod tests { use super::*; #[test] - fn test_fluent_parser0(){ + fn test_fluent_parser0() { let typ = FluentParser::new() .push("8") .parse_type_next() @@ -357,20 +357,19 @@ mod tests { } #[test] - fn test_fluent_parser1(){ + fn test_fluent_parser1() { let typ = FluentParser::new() - .push("let df <- 8;").parse_type_next() - .push("9").parse_type_next() + .push("let df <- 8;") + .parse_type_next() + .push("9") + .parse_type_next() .get_last_type(); assert_eq!(typ, builder::integer_type(8)) } #[test] - fn test_fluent_transpiler1(){ - let fp = FluentParser::new() - .push("8") - .run(); + fn test_fluent_transpiler1() { + let fp = FluentParser::new().push("8").run(); assert_eq!(fp.next_r_code().unwrap().0, "8L |> Integer()") } - } diff --git a/crates/typr-core/src/utils/mod.rs b/crates/typr-core/src/utils/mod.rs new file mode 100644 index 0000000..e00a71f --- /dev/null +++ b/crates/typr-core/src/utils/mod.rs @@ -0,0 +1,11 @@ +//! Pure utility modules for TypR core +//! +//! These utilities have no system dependencies and are WASM-compatible. + +pub mod builder; +pub mod fluent_parser; +pub mod path; +pub mod standard_library; + +// Re-export commonly used items +pub use builder::*; diff --git a/src/utils/path.rs b/crates/typr-core/src/utils/path.rs similarity index 73% rename from src/utils/path.rs rename to crates/typr-core/src/utils/path.rs index e6f58d2..cc92574 100644 --- a/src/utils/path.rs +++ b/crates/typr-core/src/utils/path.rs @@ -1,7 +1,6 @@ use serde::Serialize; use std::ops::Add; - // names separated by a "/" #[derive(Debug, Clone, PartialEq, Eq, Serialize, Default, Hash)] pub struct Path(String); @@ -14,7 +13,7 @@ impl Path { pub fn to_r(self) -> String { match &self.0[..] { "" => "".to_string(), - _ => self.0.replace("::", "$") + "$" + _ => self.0.replace("::", "$") + "$", } } @@ -31,7 +30,6 @@ impl Path { } } - impl Add for Path { type Output = Path; @@ -43,28 +41,28 @@ impl Add for Path { use std::fmt; impl fmt::Display for Path { fn fmt(self: &Self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.0 == ""{ - write!(f, "{}", self.0) + if self.0 == "" { + write!(f, "{}", self.0) } else { - write!(f, "{}::", self.0) + write!(f, "{}::", self.0) } } } -impl From for Path { - fn from(val: String) -> Self { +impl From for Path { + fn from(val: String) -> Self { Path(val) - } + } } -impl From<&str> for Path { - fn from(val: &str) -> Self { +impl From<&str> for Path { + fn from(val: &str) -> Self { Path(val.to_string()) - } + } } impl From for String { - fn from(val: Path) -> Self { + fn from(val: Path) -> Self { val.0 - } + } } diff --git a/src/utils/standard_library.rs b/crates/typr-core/src/utils/standard_library.rs similarity index 55% rename from src/utils/standard_library.rs rename to crates/typr-core/src/utils/standard_library.rs index 8fb7c2c..a83ef97 100644 --- a/src/utils/standard_library.rs +++ b/crates/typr-core/src/utils/standard_library.rs @@ -1,21 +1,80 @@ +//! Standard library utilities (pure functions) +//! +//! This module contains pure utility functions for working with the standard library. +//! The actual loading of standard library files is done elsewhere. + use crate::components::r#type::vector_type::VecType; -use crate::utils::package_loader::PackageManager; use crate::components::r#type::Type; -use std::collections::HashSet; use std::collections::HashMap; -use std::fs; - -const R_FUNCTIONS: &str = "../configs/src/functions_R.txt"; -const TYPED_R_FUNCTIONS: &str = "../configs/std/std_R.ty"; -const JS_FUNCTIONS: &str = "../configs/src/functions_JS.txt"; -const TYPED_JS_FUNCTIONS: &str = "../configs/std/std_JS.ty"; +use std::collections::HashSet; -const BLACKLIST: [&str; 59] = ["test_that", "expect_true", "`+`", "`*`", "`-`", "`/`", "while", "repeat", "for", "if", "function", "||", "|", ">=", "<=", "<", ">", "==", "=", "+", "^", "&&", "&", "/", "next", "break", ".POSIXt", "source", "class", "union", "c", "library", "return", "list", "try", "integer", "character", "logical", "UseMethod", "length", "sapply", "inherits", "all", "lapply", "unlist", "array", "cat", "rep", "str", "oldClass", "stop", "invisible", "capture__output", "paste0", "unclass", "exists", "vector", "tags", "paste"]; +const BLACKLIST: [&str; 59] = [ + "test_that", + "expect_true", + "`+`", + "`*`", + "`-`", + "`/`", + "while", + "repeat", + "for", + "if", + "function", + "||", + "|", + ">=", + "<=", + "<", + ">", + "==", + "=", + "+", + "^", + "&&", + "&", + "/", + "next", + "break", + ".POSIXt", + "source", + "class", + "union", + "c", + "library", + "return", + "list", + "try", + "integer", + "character", + "logical", + "UseMethod", + "length", + "sapply", + "inherits", + "all", + "lapply", + "unlist", + "array", + "cat", + "rep", + "str", + "oldClass", + "stop", + "invisible", + "capture__output", + "paste0", + "unclass", + "exists", + "vector", + "tags", + "paste", +]; +/// Check if a function name is not in the blacklist of reserved/special functions pub fn not_in_blacklist(name: &str) -> bool { let hs = BLACKLIST.iter().cloned().collect::>(); - !hs.contains(name) - && !name.contains("$") + !hs.contains(name) + && !name.contains("$") && !name.contains("~") && !name.contains("||") && !name.contains("|") @@ -36,27 +95,37 @@ pub fn not_in_blacklist(name: &str) -> bool { && !name.contains(".") } -pub fn validate_vectorization(set: HashSet<(i32, VecType, Type)>) -> Option> { +/// Validate that vectorization is consistent across arguments +pub fn validate_vectorization( + set: HashSet<(i32, VecType, Type)>, +) -> Option> { // Check there is only one type of Vector - if set.iter().map(|(_, vectyp, _)| vectyp).collect::>().len() == 1 { + if set + .iter() + .map(|(_, vectyp, _)| vectyp) + .collect::>() + .len() + == 1 + { let mut number_by_type: HashMap> = HashMap::new(); - + for (num, _, typ) in &set { - number_by_type.entry((*typ).clone()) + number_by_type + .entry((*typ).clone()) .or_insert_with(HashSet::new) .insert(*num); } - + // Check if each type don't have more than two related number for numeros in number_by_type.values() { if numeros.len() > 2 { return None; } } - + // If there is 2 numbers, must be only 1 or n let mut n_option: Option = None; - + for numeros in number_by_type.values() { if numeros.len() == 2 { if !numeros.contains(&1) { @@ -84,25 +153,18 @@ pub fn validate_vectorization(set: HashSet<(i32, VecType, Type)>) -> Option 1 { + if set + .iter() + .max_by(|x, y| x.0.cmp(&y.0)) + .map(|(i, _, _)| *i) + .unwrap_or(1) + > 1 + { Some(set) } else { None } - - } else { None } -} - -pub fn standard_library() { - let std_r_txt = fs::read_to_string(R_FUNCTIONS).unwrap(); - PackageManager::to_name_list(&std_r_txt) - .unwrap().set_target_path("../configs/bin/").set_name("std_r").save(); - PackageManager::to_header(TYPED_R_FUNCTIONS) - .unwrap().set_target_path("../configs/bin/").set_name("std_r_typed").save(); - - let std_js_txt = fs::read_to_string(JS_FUNCTIONS).unwrap(); - PackageManager::to_name_list(&std_js_txt) - .unwrap().set_target_path("../configs/bin/").set_name("std_js").save(); - PackageManager::to_header(TYPED_JS_FUNCTIONS) - .unwrap().set_target_path("../configs/bin/").set_name("std_js_typed").save(); + } else { + None + } } diff --git a/crates/typr-wasm/Cargo.toml b/crates/typr-wasm/Cargo.toml new file mode 100644 index 0000000..3058dda --- /dev/null +++ b/crates/typr-wasm/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "typr-wasm" +description = "WebAssembly bindings for TypR - compile and type-check TypR code in the browser" +keywords = ["R", "type-checker", "wasm", "webassembly", "playground"] +readme = "README.md" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true + +[lib] +crate-type = ["cdylib"] + +[dependencies] +typr-core = { workspace = true, features = ["wasm"] } +wasm-bindgen = "0.2" +serde.workspace = true +serde_json.workspace = true +serde-wasm-bindgen = "0.6" + +[dependencies.web-sys] +version = "0.3" +features = ["console"] + +[profile.release] +opt-level = "s" +lto = true diff --git a/crates/typr-wasm/src/lib.rs b/crates/typr-wasm/src/lib.rs new file mode 100644 index 0000000..949824c --- /dev/null +++ b/crates/typr-wasm/src/lib.rs @@ -0,0 +1,300 @@ +//! WebAssembly bindings for TypR +//! +//! This crate provides JavaScript/TypeScript bindings for the TypR compiler, +//! allowing TypR code to be compiled and type-checked directly in the browser. +//! +//! ## Usage in JavaScript +//! +//! ```javascript +//! import init, { compile, typeCheck } from 'typr-wasm'; +//! +//! async function main() { +//! await init(); +//! +//! const result = compile("let x: int <- 42\nx"); +//! console.log(result.r_code); +//! } +//! ``` + +use typr_core::processes::transpiling::clear_generated_files; +use typr_core::processes::type_checking::type_checker::TypeChecker; +use typr_core::{Compiler, InMemorySourceProvider}; +use wasm_bindgen::prelude::*; + +/// Standard library R code (embedded at compile time) +const STD_R: &str = include_str!("../../typr-core/configs/src/std.R"); + +/// Compile TypR source code to R +/// +/// This is the main entry point for compilation. +/// Returns a CompileResult with the generated R code, including all +/// support code (std library, type annotations, generic functions) inlined. +#[wasm_bindgen] +pub fn compile(source: &str) -> Result { + // Clear any previously generated files + clear_generated_files(); + + let mut sources = InMemorySourceProvider::new(); + sources.add_source("main.ty", source); + + // Use WASM-mode compiler which inlines everything + let compiler = Compiler::new_wasm(sources); + + // Parse + let ast = compiler + .parse("main.ty") + .map_err(|e| JsValue::from_str(&format!("{}", e)))?; + + // Type check and get context + let type_checker = TypeChecker::new(compiler.get_context()).typing(&ast); + let context = type_checker.get_context(); + + // Get the transpiled main code + let main_code = type_checker.transpile(); + + // Get type annotations (defines Character, Integer, Number, Boolean, etc.) + let type_annotations = context.get_type_anotations(); + + // Get generic function declarations + let generic_functions: String = context + .get_all_generic_functions() + .iter() + .map(|(var, _)| var.get_name()) + .filter(|x| !x.contains("<-")) + .map(|fn_name| { + format!( + "{} <- function(x, ...) UseMethod('{}', x)", + fn_name, + fn_name.replace("`", "") + ) + }) + .collect::>() + .join("\n"); + + // Build the final R code by concatenating in order: + // 1. Standard library (std.R) + // 2. Generic function declarations + // 3. Type annotations (defines Character, Integer, etc.) + // 4. The main transpiled code + let mut final_code = String::new(); + + // 1. Standard library + final_code.push_str("# === TypR Standard Library ===\n"); + final_code.push_str(STD_R); + final_code.push_str("\n\n"); + + // 2. Generic functions + if !generic_functions.trim().is_empty() { + final_code.push_str("# === Generic Functions ===\n"); + final_code.push_str(&generic_functions); + final_code.push_str("\n\n"); + } + + // 3. Type annotations + if !type_annotations.trim().is_empty() { + final_code.push_str("# === Type Annotations ===\n"); + final_code.push_str(&type_annotations); + final_code.push_str("\n\n"); + } + + // 4. Main code + final_code.push_str("# === Main Code ===\n"); + final_code.push_str(&main_code); + + Ok(CompileResult { + r_code: final_code, + type_annotations, + generic_functions, + }) +} + +/// Type check TypR source code without compiling +/// +/// Returns a TypeCheckResult indicating if there are errors. +#[wasm_bindgen(js_name = typeCheck)] +pub fn type_check(source: &str) -> Result { + let mut sources = InMemorySourceProvider::new(); + sources.add_source("main.ty", source); + + let compiler = Compiler::new_wasm(sources); + let ast = match compiler.parse("main.ty") { + Ok(ast) => ast, + Err(e) => { + return Ok(TypeCheckResult { + has_errors: true, + errors: format!("Parse error: {}", e), + }); + } + }; + + let result = compiler.type_check(&ast); + + Ok(TypeCheckResult { + has_errors: result.has_errors(), + errors: result + .get_errors() + .iter() + .map(|e| format!("{:?}", e)) + .collect::>() + .join("\n---\n"), + }) +} + +/// Parse TypR source code and return the AST as JSON +#[wasm_bindgen] +pub fn parse(source: &str) -> Result { + let mut sources = InMemorySourceProvider::new(); + sources.add_source("main.ty", source); + + let compiler = Compiler::new_wasm(sources); + match compiler.parse("main.ty") { + Ok(ast) => serde_json::to_string(&format!("{:?}", ast)) + .map_err(|e| JsValue::from_str(&e.to_string())), + Err(e) => Err(JsValue::from_str(&e.to_string())), + } +} + +/// Transpile TypR source code to R without type checking +#[wasm_bindgen] +pub fn transpile(source: &str) -> Result { + let mut sources = InMemorySourceProvider::new(); + sources.add_source("main.ty", source); + + let compiler = Compiler::new_wasm(sources); + let ast = compiler + .parse("main.ty") + .map_err(|e| JsValue::from_str(&e.to_string()))?; + let result = compiler.transpile(&ast); + Ok(result.r_code) +} + +/// Compile multiple TypR files +/// +/// Takes a JSON object mapping filenames to source code. +/// The main file should be named "main.ty". +#[wasm_bindgen(js_name = compileMultiple)] +pub fn compile_multiple(files_json: &str) -> Result { + clear_generated_files(); + + let files: std::collections::HashMap = serde_json::from_str(files_json) + .map_err(|e| JsValue::from_str(&format!("Invalid JSON: {}", e)))?; + + let mut sources = InMemorySourceProvider::new(); + for (filename, content) in files { + sources.add_source(&filename, &content); + } + + let compiler = Compiler::new_wasm(sources); + + let ast = compiler + .parse("main.ty") + .map_err(|e| JsValue::from_str(&format!("{}", e)))?; + + let type_checker = TypeChecker::new(compiler.get_context()).typing(&ast); + let context = type_checker.get_context(); + let main_code = type_checker.transpile(); + let type_annotations = context.get_type_anotations(); + + let generic_functions: String = context + .get_all_generic_functions() + .iter() + .map(|(var, _)| var.get_name()) + .filter(|x| !x.contains("<-")) + .map(|fn_name| { + format!( + "{} <- function(x, ...) UseMethod('{}', x)", + fn_name, + fn_name.replace("`", "") + ) + }) + .collect::>() + .join("\n"); + + let mut final_code = String::new(); + final_code.push_str(STD_R); + final_code.push_str("\n\n"); + if !generic_functions.trim().is_empty() { + final_code.push_str(&generic_functions); + final_code.push_str("\n\n"); + } + if !type_annotations.trim().is_empty() { + final_code.push_str(&type_annotations); + final_code.push_str("\n\n"); + } + final_code.push_str(&main_code); + + Ok(CompileResult { + r_code: final_code, + type_annotations, + generic_functions, + }) +} + +/// Result of compilation +#[wasm_bindgen] +pub struct CompileResult { + #[wasm_bindgen(getter_with_clone)] + pub r_code: String, + #[wasm_bindgen(getter_with_clone)] + pub type_annotations: String, + #[wasm_bindgen(getter_with_clone)] + pub generic_functions: String, +} + +/// Result of type checking +#[wasm_bindgen] +pub struct TypeCheckResult { + #[wasm_bindgen(getter_with_clone)] + pub has_errors: bool, + #[wasm_bindgen(getter_with_clone)] + pub errors: String, +} + +/// Initialize the WASM module (called automatically by wasm-bindgen) +#[wasm_bindgen(start)] +pub fn init() { + // Module initialization +} + +// Keep the TypRCompiler struct for backwards compatibility +/// TypR compiler instance (for backwards compatibility) +/// +/// Prefer using the standalone functions `compile()` and `typeCheck()` instead. +#[wasm_bindgen] +pub struct TypRCompiler; + +#[wasm_bindgen] +impl TypRCompiler { + /// Create a new TypR compiler instance + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + Self + } + + /// Compile source code + pub fn compile(&self, source: &str) -> Result { + compile(source) + } + + /// Type check source code + #[wasm_bindgen(js_name = typeCheck)] + pub fn type_check(&self, source: &str) -> Result { + type_check(source) + } + + /// Parse source code + pub fn parse(&self, source: &str) -> Result { + parse(source) + } + + /// Transpile source code + pub fn transpile(&self, source: &str) -> Result { + transpile(source) + } +} + +impl Default for TypRCompiler { + fn default() -> Self { + Self::new() + } +} diff --git a/playground b/playground new file mode 160000 index 0000000..defeba8 --- /dev/null +++ b/playground @@ -0,0 +1 @@ +Subproject commit defeba837201f22d4d0e08b555f8289984019062 diff --git a/settings.vim b/settings.vim index 310bd43..958bcf0 100644 --- a/settings.vim +++ b/settings.vim @@ -1,3 +1,4 @@ command! Deploy !nu deploy.nu & command! Release !nu release.nu command! Publish !nu publish.nu + diff --git a/src/components/error_message/help_data.rs b/src/components/error_message/help_data.rs deleted file mode 100644 index d3889da..0000000 --- a/src/components/error_message/help_data.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::components::language::Lang; -use nom_locate::LocatedSpan; -use serde::{Deserialize, Serialize}; -use std::fs; - -#[derive(Debug, PartialEq, Serialize, Deserialize, Eq, Clone, Hash, Default)] -pub struct HelpData { - offset: usize, - file_name: String, -} - -impl HelpData { - pub fn get_offset(&self) -> usize { - self.offset - } - - pub fn get_file_name(&self) -> String { - self.file_name.clone() - } - - pub fn get_file_data(&self) -> Option<(String, String)> { - let file_name = self.get_file_name(); - if file_name == "" { - None - } else { - match fs::read_to_string(&file_name).ok() { - Some(text) => Some((file_name, text)), - None => None, - } - } - } - - pub fn random() -> Self { - HelpData { - offset: 7_usize, - file_name: "asfdlwone".to_string(), - } - } -} - -impl From> for HelpData { - fn from(ls: LocatedSpan<&str, String>) -> Self { - HelpData { - offset: ls.location_offset(), - file_name: ls.extra, - } - } -} - -impl From> for HelpData { - fn from(val: Vec) -> Self { - if val.len() > 0 { - val[0].clone().into() - } else { - HelpData::default() - } - } -} diff --git a/src/interface/cli.rs b/src/interface/cli.rs deleted file mode 100644 index 28e4969..0000000 --- a/src/interface/cli.rs +++ /dev/null @@ -1,145 +0,0 @@ -use clap::{Parser, Subcommand}; -use std::path::PathBuf; -use crate::interface::repl; -use crate::utils::standard_library::standard_library; -use crate::utils::project_management::run_file; -use crate::utils::project_management::new; -use crate::utils::project_management::check_file; -use crate::utils::project_management::check_project; -use crate::utils::project_management::build_file; -use crate::utils::project_management::build_project; -use crate::utils::project_management::run_project; -use crate::utils::project_management::test; -use crate::utils::project_management::pkg_install; -use crate::utils::project_management::pkg_uninstall; -use crate::utils::project_management::document; -use crate::utils::project_management::use_package; -use crate::utils::project_management::load; -use crate::utils::project_management::cran; -use crate::utils::project_management::clean; - -#[derive(Parser)] -#[command(author, version, about, long_about = None)] -struct Cli { - #[arg(value_name = "FILE")] - file: Option, - - #[arg(short, long, value_name = "TARGET", default_value = "r")] - target: Option, - - #[command(subcommand)] - command: Option, -} - -#[derive(Subcommand, Debug)] -enum Commands { - New { - name: String, - }, - Check { - #[arg(value_name = "FILE")] - file: Option, - }, - Build { - #[arg(value_name = "FILE")] - file: Option, - }, - Run { - #[arg(value_name = "FILE")] - file: Option, - }, - Test, - Pkg { - #[command(subcommand)] - pkg_command: PkgCommands, - }, - Document, - Use { - package_name: String, - }, - Load, - Cran, - Std, - Clean, - Repl, - Lsp, -} - -#[derive(Subcommand, Debug)] -enum PkgCommands { - Install, - Uninstall, -} - -pub fn start() { - let cli = Cli::parse(); - if let Some(path) = cli.file { - if cli.command.is_none() { - run_file(&path); - return; - } - } - - match cli.command { - Some(Commands::New { name }) => { - new(&name) - }, - Some(Commands::Check { file }) => { - match file { - Some(path) => check_file(&path), - _ => check_project(), - } - }, - Some(Commands::Build { file }) => { - match file { - Some(path) => build_file(&path), - _ => build_project(), - } - }, - Some(Commands::Run { file }) => { - match file { - Some(path) => run_file(&path), - _ => run_project(), - } - }, - Some(Commands::Test) => { - test() - }, - Some(Commands::Pkg { pkg_command }) => { - match pkg_command { - PkgCommands::Install => pkg_install(), - PkgCommands::Uninstall => pkg_uninstall(), - } - }, - Some(Commands::Document) => { - document() - }, - Some(Commands::Use { package_name }) => { - use_package(&package_name) - }, - Some(Commands::Load) => { - load() - }, - Some(Commands::Cran) => { - cran() - }, - Some(Commands::Std) => { - standard_library() - }, - Some(Commands::Clean) => { - clean() - }, - Some(Commands::Lsp) => { - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(crate::interface::lsp::run_lsp()); - }, - Some(Commands::Repl) => { - repl::start() - }, - _ => { - println!("Please specify a subcommand or file to execute"); - std::process::exit(1); - } - } -} - diff --git a/src/interface/mod.rs b/src/interface/mod.rs deleted file mode 100644 index 377a6ec..0000000 --- a/src/interface/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod repl; -pub mod cli; -pub mod lsp; -mod parser; diff --git a/src/lib.rs b/src/lib.rs index d121a02..433ba4c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,11 @@ -pub mod components; -pub mod processes; -pub mod utils; -pub mod interface; +//! # TypR +//! +//! A typed superset of R with static type checking and transpilation to R. +//! +//! This crate re-exports everything from `typr-cli` which provides +//! the CLI interface, REPL, and LSP server. +//! +//! For core types and compilation API, see `typr-core`. -// Re-export commonly used items for integration tests and external users -pub use crate::components::*; -pub use crate::processes::*; -pub use crate::utils::*; -pub use crate::interface::*; +// Re-export everything from typr-cli +pub use typr_cli::*; diff --git a/src/main.rs b/src/main.rs index 6398172..1bdd732 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,8 @@ -mod components; -mod processes; -mod interface; -mod utils; -use crate::interface::cli; +//! TypR main executable +//! +//! This is a thin wrapper around typr-cli. +//! All CLI functionality is provided by the typr-cli crate. fn main() { - cli::start() + typr_cli::start() } diff --git a/src/processes/transpiling/mod.rs b/src/processes/transpiling/mod.rs deleted file mode 100644 index c5bbfc9..0000000 --- a/src/processes/transpiling/mod.rs +++ /dev/null @@ -1,480 +0,0 @@ -pub mod translatable; - -use crate::processes::type_checking::type_comparison::reduce_type; -use crate::components::language::set_related_type_if_variable; -use crate::processes::transpiling::translatable::Translatable; -use crate::components::r#type::function_type::FunctionType; -use crate::components::error_message::help_data::HelpData; -use crate::components::language::function_lang::Function; -use crate::components::r#type::array_type::ArrayType; -use crate::components::context::config::Environment; -use crate::components::language::format_backtick; -use crate::components::language::operators::Op; -use crate::components::language::ModulePosition; -use crate::processes::type_checking::typing; -use crate::components::language::var::Var; -use crate::components::context::Context; -use crate::components::language::Lang; -use crate::components::r#type::Type; -use translatable::RTranslatable; -use std::path::PathBuf; -use std::io::Write; -use std::fs::File; -use std::fs; - -pub trait ToSome { - fn to_some(self) -> Option where Self: Sized; -} - -impl ToSome for T { - fn to_some(self) -> Option { - Some(self) - } -} - -trait AndIf { - fn and_if(self, condition: F) -> Option - where - F: Fn(Self) -> bool, - Self: Sized; -} - -impl AndIf for T { - fn and_if(self, condition: F) -> Option - where - F: Fn(Self) -> bool, - { - if condition(self.clone()) { - Some(self) - } else { - None - } - } -} - -const JS_HEADER: &str = ""; - -fn condition_to_if(var: &Var, typ: &Type, context: &Context) -> String { - format!("any(class({}) == c({}))", var.get_name(), context.get_class(typ)) -} - -fn to_if_statement(var: Var, exp: Lang, branches: &[(Type, Box)], context: &Context) -> String { - let res = branches.iter() - .map(|(typ, body)| { (condition_to_if(&var, typ, context), body)}) - .enumerate() - .map(|(id, (cond, body))| if id == 0 { - format!("if ({}) {{ \n {} \n }}", cond, body.to_r(context).0) - } else { - format!("else if ({}) {{ \n {} \n }}", cond, body.to_r(context).0) - }).collect::>().join(" "); - format!("{{\n {} <- {} \n {}\n}}", var.get_name(), exp.to_r(context).0, res) -} - -impl RTranslatable<(String, Context)> for Lang { - fn to_r(&self, cont: &Context) -> (String, Context) { - let result = match self { - Lang::Bool(b, _) => { - let (typ, _, _) = typing(cont, self).to_tuple(); - let anotation = cont.get_type_anotation(&typ); - (format!("{} |> {}", b.to_string().to_uppercase(), anotation), cont.clone()) - }, - Lang::Number(n, _) => { - let (typ, _, _) = typing(cont, self).to_tuple(); - let anotation = cont.get_type_anotation(&typ); - (format!("{} |> {}", n, anotation), cont.clone()) - }, - Lang::Integer(i, _) => { - let (typ, _, _) = typing(cont, self).to_tuple(); - let anotation = cont.get_type_anotation(&typ); - (format!("{}L |> {}", i, anotation), cont.clone()) - }, - Lang::Char(s, _) => { - let (typ, _, _) = typing(cont, self).to_tuple(); - let anotation = cont.get_type_anotation(&typ); - (format!("'{}' |> {}", s, anotation), cont.clone()) - }, - Lang::Operator(Op::Dot(_), e1, e2, _) | Lang::Operator(Op::Pipe(_), e1, e2, _) - => { - let e1 = (**e1).clone(); - let e2 = (**e2).clone(); - match e2.clone() { - Lang::Variable(_, _, _, _) => { - Translatable::from(cont.clone()) - .to_r(&e2) - .add("[['").to_r(&e1).add("']]").into() - }, - Lang::Record(fields, _) => { - let at = fields[0].clone(); - Translatable::from(cont.clone()) - .add("within(").to_r(&e2) - .add(", { ").add(&at.get_argument()) - .add(" <- ") - .to_r(&at.get_value()).add(" })") - .into() - } - Lang::FunctionApp(var, v, h) => { - let v = [e1].iter().chain(v.iter()).cloned().collect(); - Lang::FunctionApp(var, v, h).to_r(cont) - } - _ => { - Translatable::from(cont.clone()) - .to_r(&e2).add("[[") - .add("]]").to_r(&e1) - .into() - } - } - }, - Lang::Operator(Op::Dollar(_), e1, e2, _) => { - let e1 = (**e1).clone(); - let e2 = (**e2).clone(); - let t1 = typing(cont, &e1).value; - let val = match (t1.clone(), e2.clone()) { - (Type::Vec(vtype, _, _, _), Lang::Variable(name, _, _, _)) - if vtype.is_array() => - format!("vec_apply(get, {}, typed_vec('{}'))", e1.to_r(cont).0, name), - (_, Lang::Variable(name, _, _, _)) - => format!("{}${}", e1.to_r(cont).0, name), - _ => format!("{}${}", e1.to_r(cont).0, e2.to_r(cont).0), - //_ => panic!("Dollar operation not yet implemented for {:?}", e2) - }; - (val, cont.clone()) - }, - Lang::Operator(op, e1, e2, _) => { - let op_str = format!(" {} ", op.to_string()); - Translatable::from(cont.clone()) - .to_r(e1).add(&op_str).to_r(e2).into() - }, - Lang::Scope(exps, _) => { - Translatable::from(cont.clone()) - .add("{\n") - .join(exps, "\n") - .add("\n}").into() - }, - Lang::Function(args, _, body, _) => { - let fn_type = FunctionType::try_from(typing(cont, self).value.clone()).unwrap(); - let output_conversion = cont.get_type_anotation(&fn_type.get_return_type()); - let res = (output_conversion == "") - .then_some("".to_string()) - .unwrap_or(" |> ".to_owned() + &output_conversion); - (format!("(function({}) {}{}) |> {}", - args.iter().map(|x| x.to_r()).collect::>().join(", "), - body.to_r(cont).0, - res, - cont.get_type_anotation(&fn_type.into())), - cont.clone()) - }, - Lang::Variable(_, _, _, _) => { - //Here we only keep the variable name, the path and the type - let var = Var::from_language(self.clone()).unwrap(); - let name = if var.contains("__") { - var.replace("__", ".").get_name() - } else { - var.display_type(cont).get_name() - }; - ((&name).to_string(), cont.clone()) - }, - Lang::FunctionApp(exp, vals, _) => { - let var = Var::try_from(exp.clone()).unwrap(); - - let (exp_str, cont1) = exp.to_r(cont); - let fn_t = FunctionType::try_from(cont1.get_type_from_variable(&var) - .expect(&format!("variable {} don't have a related type", var))) - .map(|ft| ft.adjust_nb_parameters(vals.len())) - .unwrap(); - let new_args = fn_t.get_param_types().into_iter() - .map(|arg| reduce_type(&cont1, &arg)) - .collect::>(); - let new_vals = vals.into_iter().zip(new_args.iter()) - .map(set_related_type_if_variable) - .collect::>(); - let (args, current_cont) = Translatable::from(cont1) - .join(&new_vals, ", ").into(); - Var::from_language(*exp.clone()) - .map(|var| { - let name = var.get_name(); - let new_name = if &name[0..1] == "%" { - format!("`{}`", name.replace("__", ".")) - } else { name.replace("__", ".") }; - (format!("{}({})", new_name, args), current_cont.clone()) - }).unwrap_or((format!("{}({})", exp_str, args), current_cont)) - }, - Lang::VecFunctionApp(exp, vals, _) => { - let var = Var::try_from(exp.clone()).unwrap(); - let name = var.get_name(); - let str_vals = vals.iter() - .map(|x| x.to_r(cont).0) - .collect::>().join(", "); - if name == "reduce" { - (format!("vec_reduce({})", str_vals), cont.clone()) - } else if cont.is_an_untyped_function(&name) { - let name = name.replace("__", "."); - let new_name = if &name[0..1] == "%" { - format!("`{}`", name) - } else { name.to_string() }; - let s = format!("{}({})", new_name, str_vals); - (s, cont.clone()) - } else { - let (exp_str, cont1) = exp.to_r(cont); - let fn_t = FunctionType::try_from(cont1.get_type_from_variable(&var) - .expect(&format!("variable {} don't have a related type", var))) - .unwrap(); - let new_args = fn_t.get_param_types().into_iter() - .map(|arg| reduce_type(&cont1, &arg)) - .collect::>(); - let new_vals = vals.into_iter().zip(new_args.iter()) - .map(set_related_type_if_variable) - .collect::>(); - let (args, current_cont) = Translatable::from(cont1) - .join(&new_vals, ", ").into(); - Var::from_language(*exp.clone()) - .map(|var| { - let name = var.get_name(); - let new_name = if &name[0..1] == "%" { - format!("`{}`", name.replace("__", ".")) - } else { name.replace("__", ".") }; - (format!("vec_apply({}, {})", new_name, args), current_cont.clone()) - }).unwrap_or((format!("vec_apply({}, {})", exp_str, args), current_cont)) - } - }, - Lang::ArrayIndexing(exp, val, _) => { - let (exp_str, _) = exp.to_r(cont); - let (val_str, _) = val.to_simple_r(cont); - let (typ, _, _) = typing(&cont, exp).to_tuple(); - let res = match typ { - Type::Vec(_, _, _, _) - => format!("{}[[{}]]", exp_str, val_str), - _ => "".to_string() - }; - (res, cont.clone()) - }, - Lang::GenFunc(func, _, _) => - (format!("function(x, ...) UseMethod('{}')", func.to_string()), cont.clone()), - Lang::Let(expr, ttype, body, _) => { - let (body_str, new_cont) = body.to_r(cont); - let new_name = format_backtick(expr.clone().to_r(cont).0); - - let (r_code, _new_name2) = - Function::try_from((**body).clone()) - .map(|_| { - let related_type = typing(cont, expr).value; - let method = match cont.get_environment() { - Environment::Project => - format!("#' @method {}\n", new_name.replace(".", " ").replace("`", "")), - _ => "".to_string() - }; - match related_type { - Type::Empty(_) - => (format!("{} <- {}", new_name, body_str), new_name.clone()), - Type::Any(_) | Type::Generic(_, _) - => (format!("{}.default <- {}", new_name, body_str), new_name.clone()), - _ => { - (format!("{}{} <- {}", method, new_name, body_str), new_name.clone()) - } - } - }).unwrap_or((format!("{} <- {}", new_name, body_str), new_name)); - let code = if !ttype.is_empty() { - let _ = new_cont.get_type_anotation(ttype); - format!("{}\n", r_code) - } else { - r_code + "\n" - }; - (code, new_cont) - }, - Lang::Array(_v, _h) => { - let typ = self.typing(cont).value; - - let _dimension = ArrayType::try_from(typ.clone()).unwrap().get_shape() - .map(|sha| format!("c({})", sha)) - .unwrap_or(format!("c(0)")); - - let array = &self.linearize_array() - .iter().map(|lang| lang.to_r(&cont).0) - .collect::>().join(", ") - .and_if(|lin_array| lin_array != "") - //.map(|lin_array| format!("concat({}, dim = {})", lin_array, dimension)) - .map(|lin_array| format!("typed_vec({})", lin_array)) - .unwrap_or("logical(0)".to_string()); - - (format!("{} |> {}", array, cont.get_type_anotation(&typ)) ,cont.to_owned()) - }, - Lang::Record(args, _) => { - let (body, current_cont) = - Translatable::from(cont.clone()) - .join_arg_val(args, ",\n ").into(); - let (typ, _, _) = typing(cont, self).to_tuple(); - let anotation = cont.get_type_anotation(&typ); - cont.get_classes(&typ) - .map(|_| format!("list({}) |> {}", - body, anotation)) - .unwrap_or(format!("list({}) |> {}", - body, anotation)) - .to_some().map(|s| (s, current_cont)).unwrap() - }, - Lang::If(cond, exp, els, _) if els == &Box::new(Lang::Empty(HelpData::default())) => { - Translatable::from(cont.clone()) - .add("if(").to_r(cond).add(") {\n") - .to_r(exp).add(" \n}").into() - }, - Lang::If(cond, exp, els, _) => { - Translatable::from(cont.clone()) - .add("if(").to_r(cond).add(") {\n") - .to_r(exp).add(" \n} else ") - .to_r(els).into() - }, - Lang::Tuple(vals, _) => { - Translatable::from(cont.clone()) - .add("struct(list(") - .join(vals, ", ") - .add("), 'Tuple')").into() - }, - Lang::Assign(var, exp, _) => { - Translatable::from(cont.clone()) - .to_r(var).add(" <- ").to_r(exp).into() - }, - Lang::Comment(txt, _) => - ("#".to_string() + txt, cont.clone()), - Lang::Tag(s, t, _) => { - let (t_str, new_cont) = t.to_r(cont); - let (typ, _, _) = typing(cont, self).to_tuple(); - let class = cont.get_class(&typ); - cont.get_classes(&typ) - .map(|res| format!("struct(list('{}', {}), c('Tag', {}, {}))", - s, t_str, class, res)) - .unwrap_or(format!("struct(list('{}', {}), c('Tag', {}))", - s, t_str, class)) - .to_some().map(|s| (s, new_cont)).unwrap() - }, - Lang::Empty(_) => - ("NA".to_string(), cont.clone()), - Lang::ModuleDecl(name, _) => - (format!("{} <- new.env()", name), cont.clone()), - Lang::Lines(exps, _) => { - Translatable::from(cont.clone()) - .join(exps, "\n").into() - }, - Lang::Return(exp, _) => { - Translatable::from(cont.clone()) - .add("return ").to_r(exp).into() - }, - Lang::Lambda(bloc, _) - => { - (format!("function(x) {{ {} }}", bloc.to_r(cont).0), cont.clone()) - }, - Lang::VecBlock(bloc, _) => (bloc.to_string(), cont.clone()), - Lang::Library(name, _) => (format!("library({})", name), cont.clone()), - Lang::Match(exp, var, branches, _) - => (to_if_statement(var.clone(), (**exp).clone(), branches, cont), cont.clone()), - Lang::Exp(exp, _) => (exp.clone(), cont.clone()), - Lang::ForLoop(var, iterator, body, _) => { - Translatable::from(cont.clone()) - .add("for (").to_r_safe(var) - .add(" in ").to_r_safe(iterator).add(") {\n") - .to_r_safe(body).add("\n}").into() - }, - Lang::RFunction(vars, body, _) => { - Translatable::from(cont.clone()) - .add("function (").join(vars, ", ") - .add(") \n").add(&body).add("\n") - .into() - } - Lang::Signature(_, _, _) => { - ("".to_string(), cont.clone()) - } - Lang::Alias(_, _, _, _) => ("".to_string(), cont.clone()), - Lang::KeyValue(k, v, _) => { - (format!("{} = {}", k, v.to_r(cont).0), cont.clone()) - }, - Lang::Vector(vals, _) => { - let res = "c(".to_string() + - &vals.iter().map(|x| x.to_r(cont).0) - .collect::>().join(", ") - + ")"; - (res, cont.to_owned()) - }, - Lang::Not(exp, _) => { - (format!("!{}", exp.to_r(cont).0), - cont.clone()) - }, - Lang::Sequence(vals, _) => { - let res = if vals.len() > 0 { - "c(".to_string() + - &vals.iter().map(|x| "list(".to_string() + &x.to_r(cont).0 + ")") - .collect::>().join(", ") - + ")" - } else { - "c(list())".to_string() - }; - (res, cont.to_owned()) - }, - Lang::TestBlock(body, h) => { - let current_dir = match std::env::current_dir() { - Ok(dir) => dir, - Err(e) => { - eprintln!("Erreur lors de l'obtention du répertoire courant: {}", e); - std::process::exit(1); - } - }; - // Définir le chemin du dossier - let dir = current_dir.join("tests/testthat"); - - let file_name = format!("test-{}", h.get_file_data().unwrap().0) - .replace("TypR/", "").replace(".ty", ".R"); - - // Définir le chemin complet du fichier - let file_path = dir.join(&file_name); - - // Écrire le contenu - let mut file = fs::File::create(&file_path).unwrap(); - file.write_all(body.to_r(cont).0.as_bytes()).unwrap(); - ("".to_string(), cont.clone()) - }, - Lang::JSBlock(exp, _id, _h) => { - let js_cont = Context::default(); //TODO get js context from memory - let res = exp.to_js(&js_cont).0; - (format!("'{}{}'", JS_HEADER, res), cont.clone()) - }, - Lang::WhileLoop(condition, body, _) => { - (format!("while ({}) {{\n{}\n}}", - condition.to_r(cont).0, - body.to_r(cont).0), cont.clone()) - }, - Lang::Break(_) => { - ("break".to_string(), cont.clone()) - }, - Lang::Module(name, body, position, config, _) => { - let name = if (name == "main") && (config.environment == Environment::Project) { - "a_main" - } else { name }; - let content = body.iter() - .map(|lang| lang.to_r(cont).0) - .collect::>().join("\n"); - match (position, config.environment) { - (ModulePosition::Internal, _) => { - (content, cont.clone()) - }, - (ModulePosition::External, Environment::StandAlone) | - (ModulePosition::External, Environment::Repl) => { - let output_dir: PathBuf = ".".into(); - let std_path = output_dir.join(format!("{}.R", name)); - let mut module_file = File::create(std_path.clone()).unwrap(); - module_file.write_all(content.as_bytes()).unwrap(); - (format!("source('{}')", std_path.display()), cont.clone()) - }, - (ModulePosition::External, Environment::Project) => { - let output_dir: PathBuf = ".".into(); - let std_path = output_dir.join(format!("R/{}.R", name)); - let mut module_file = File::create(std_path.clone()).unwrap(); - module_file.write_all(content.as_bytes()).unwrap(); - ("".to_string(), cont.clone()) - }, - } - }, - _ => { - println!("This language structure won't transpile: {:?}", self); - ("".to_string(), cont.clone()) - }, - }; - - result - } -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs deleted file mode 100644 index c85d9e5..0000000 --- a/src/utils/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod builder; -pub mod engine; -pub mod fluent_parser; -pub mod metaprogramming; -pub mod my_io; -pub mod package_loader; -pub mod path; -pub mod standard_library; -pub mod project_management; diff --git a/src/utils/package_loader.rs b/src/utils/package_loader.rs deleted file mode 100644 index 76ab8d5..0000000 --- a/src/utils/package_loader.rs +++ /dev/null @@ -1,229 +0,0 @@ -#![allow(dead_code, unused_variables, unused_imports, unreachable_code, unused_assignments)] -use crate::processes::type_checking::type_checker::TypeChecker; -use crate::processes::type_checking::execute_r_function; -use crate::components::context::vartype::VarType; -use crate::components::language::var::Var; -use crate::components::context::Context; -use crate::utils::my_io::get_os_file; -use crate::utils::my_io::read_file; -use crate::utils::engine::TypRFile; -use crate::utils::builder; -use std::path::PathBuf; -use std::path::Path; -use std::fs; - -#[derive(Debug, Default, Clone)] -pub enum Source { - PackageName, - #[default] - NameList, - Header -} - -// Manage the loading and saving of packages -#[derive(Debug, Clone)] -pub struct PackageManager { - name: String, - kind: Source, - content: String, - target_path: String -} - -impl PackageManager { - pub fn save(self) -> Self { - match self.kind { - Source::NameList => { - let unknown_function = builder::unknown_function_type(); - let any_type = builder::any_type(); - let res = self.content.lines() - .map(|line| (Var::from_name(line).set_type(any_type.clone()), unknown_function.clone())) - .collect::>(); - let _ = VarType::from(res).save(&self.get_bin_name()); - }, - Source::Header => { - let file_content = read_file(&PathBuf::from(self.content.clone())).expect("Path not found"); - let base_file = TypRFile::new(&file_content, self.content.clone()); - let lang = base_file.parse(); - let _ = TypeChecker::new(Context::empty()) - .typing(&lang) - .get_context() - .get_vartype() - .save(&self.get_bin_name()); - } - Source::PackageName => { - let function_list = execute_r_function(&format!("library({})\n\npaste(ls('package:{}', all = FALSE), collapse =';')", self.content, self.content)) - .expect("The R command didn't work"); - //Remove extra character at the beginning and at the end - let function_list = function_list[..(function_list.len()-1)][5..] - .to_string().replace("<-", ""); - let unknown_function = builder::unknown_function_type(); - let var_types = function_list.split(";") - .map(|name| (Var::from_name(name), unknown_function.clone())) - .collect::>(); - let _ = VarType::from(var_types).save(&self.get_bin_name()); - } - } - self - } - - pub fn set_target_path(self, path: &str) -> Self { - Self { - target_path: path.to_string(), - ..self - } - } - - pub fn set_content(self, content: &str) -> Self { - Self { - content: content.to_string(), - ..self - } - } - - pub fn set_name(self, name: &str) -> Self { - Self { - name: name.to_string(), - ..self - } - } - - pub fn load(&self) -> Result { - Self::load_with_path(&self.name, &self.target_path) - } - - fn get_bin_name(&self) -> String { - Self::to_bin_name(&self.name, &self.target_path) - } - - fn to_bin_name(name: &str, path: &str) -> String { - path.to_string() + "." + name + ".bin" - } - - fn load_from_name(name: &str) -> Result { - Self::load_with_path(name, "./") - } - - fn load_with_path(name: &str, path: &str) -> Result { - let var_type = VarType::new(); - let full_name = Self::to_bin_name(name, path); - let res = var_type.load(&full_name); - match res { - Ok(var_type) => Ok(var_type), - _ => Err(format!("File {} not found", full_name)) - } - } - - pub fn to_name_list(content: &str) -> Result { - let package_manager = PackageManager { - content: content.to_string(), - kind: Source::NameList, - ..PackageManager::default() - }; - Ok(package_manager) - } - - pub fn to_header(content: &str) -> Result { - let package_manager = PackageManager { - content: content.to_string(), - kind: Source::Header, - ..PackageManager::default() - }; - Ok(package_manager) - } - - pub fn to_package(content: &str) -> Result { - let package_manager = PackageManager { - content: content.to_string(), - name: content.to_string(), - kind: Source::PackageName, - ..PackageManager::default() - }; - Ok(package_manager) - } - - pub fn remove(&self) { - let _ = fs::remove_file(&self.get_bin_name()); - } - - fn remove_from_path(name: &str, path: &str) { - let _ = fs::remove_file(Self::to_bin_name(name, "./")); - } - - fn remove_from_name(name: &str) { - let _ = fs::remove_file(Self::to_bin_name(name, "./")); - } - - fn does_name_exists(name: &str) -> bool { - Path::new(&Self::to_bin_name(name, "./")).exists() - } - - fn does_name_exists_with_path(name: &str, path: &str) -> bool { - Path::new(&Self::to_bin_name(name, path)).exists() - } - - pub fn exists(&self) -> bool { - Path::new(&self.get_bin_name()).exists() - } - -} - -impl Default for PackageManager { - fn default() -> Self { - PackageManager { - name: String::default(), - kind: Source::default(), - content: String::default(), - target_path: "./".to_string() - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - // We should be able to access a VarType by giving a binary name - // We should be able to save different kind of source and get a binary file - - #[test] - #[should_panic] - fn test_loading_unexisting_file(){ - let var_type = PackageManager::load_from_name("test").unwrap(); - } - - #[test] - fn test_loading_existing_file() { - let var_type = PackageManager::load_with_path("std_r", "configs/bin/"); - assert!(var_type.is_ok(), "The path configs/bin should exist"); - } - - #[test] - fn test_saving_name_list() { - let var_type = PackageManager::to_name_list("name1\nname2\nname3") - .unwrap().set_name("name_list").save(); - assert!(var_type.exists(), "The names should exist as .name_list.bin"); - } - - #[test] - fn test_saving_typr_code() { - let var_type = PackageManager::to_header("configs/std/test.ty") - .unwrap().set_name("header").save(); - assert!(var_type.exists(), "The header should exist as .header.bin"); - } - - #[test] - fn test_saving_package() { - let var_type = PackageManager::to_package("dplyr") - .unwrap().set_name("package").save(); - assert!(var_type.exists(), "The header should exist as .package.bin"); - } - - #[test] - fn test_remove_bin_files() { - PackageManager::remove_from_name("name_list"); - PackageManager::remove_from_name("header"); - PackageManager::remove_from_name("package"); - assert!(!PackageManager::does_name_exists("name_list"), - "The file .name_list.bin shouldn't exist"); - } - -} diff --git a/tests/let_assignment.rs b/tests/let_assignment.rs index 74f768c..9e4fc53 100644 --- a/tests/let_assignment.rs +++ b/tests/let_assignment.rs @@ -1,19 +1,19 @@ -use typr::components::context::config::Environment; -use typr::components::error_message::type_error::TypeError; +use typr_core::components::context::config::Environment; +use typr_core::components::error_message::type_error::TypeError; #[test] fn test_let_assignment_type_error() { let code = "let a: char <- 5;"; - let res = typr::utils::engine::compile_string_with_errors( - code, - "test.ty", - Environment::StandAlone, - ); + let res = + typr_cli::engine::compile_string_with_errors(code, "test.ty", Environment::StandAlone); let type_errors = res.type_errors(); - assert!(type_errors.iter().any(|err| matches!(err, TypeError::Let(_, _))), + assert!( + type_errors + .iter() + .any(|err| matches!(err, TypeError::Let(_, _))), "Expected a TypeError::Let for mismatched let assignment, got: {:?}", type_errors );