Merge branch 'main' into get-pointer

This commit is contained in:
HajagosNorbert 2023-11-21 11:15:28 +01:00
commit d71b386715
No known key found for this signature in database
GPG key ID: 807F4444870DB673
121 changed files with 3601 additions and 17076 deletions

View file

@ -7,9 +7,10 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
# use .tar.gz for quick testing
env: env:
ARCHIVE_FORMAT: .tar.br # use .tar.gz for quick testing
ARCHIVE_FORMAT: .tar.gz
BASIC_CLI_BRANCH: main
jobs: jobs:
fetch-releases: fetch-releases:
@ -18,6 +19,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_x86_64-latest.tar.gz - run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_x86_64-latest.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_arm64-latest.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_x86_64-latest.tar.gz - run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_x86_64-latest.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-latest.tar.gz - run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-latest.tar.gz
@ -45,12 +47,37 @@ jobs:
with: with:
name: linux-x86_64-files name: linux-x86_64-files
path: | path: |
basic-cli/src/metadata_linux-x86_64.rm basic-cli/src/metadata_linux-x64.rm
basic-cli/src/linux-x86_64.rh basic-cli/src/linux-x64.rh
basic-cli/src/linux-x86_64.o basic-cli/src/linux-x64.o
build-linux-arm64-files:
runs-on: [self-hosted, Linux, ARM64]
needs: [fetch-releases]
steps:
- uses: actions/checkout@v3
- name: Download the previously uploaded roc_nightly archives
uses: actions/download-artifact@v3
- name: build basic-cli
env:
CARGO_BUILD_TARGET: aarch64-unknown-linux-musl
CC_aarch64_unknown_linux_musl: clang-16
AR_aarch64_unknown_linux_musl: llvm-ar-16
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS: "-Clink-self-contained=yes -Clinker=rust-lld"
run: ./ci/build_basic_cli.sh linux_arm64
- name: Save .o file
uses: actions/upload-artifact@v3
with:
name: linux-arm64-files
path: |
basic-cli/src/linux-arm64.o
build-macos-x86_64-files: build-macos-x86_64-files:
runs-on: [macos-11] # I expect the generated files to work on macOS 12 runs-on: [macos-11] # I expect the generated files to work on macOS 12 and up
needs: [fetch-releases] needs: [fetch-releases]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -65,7 +92,7 @@ jobs:
with: with:
name: macos-x86_64-files name: macos-x86_64-files
path: | path: |
basic-cli/src/macos-x86_64.o basic-cli/src/macos-x64.o
build-macos-apple-silicon-files: build-macos-apple-silicon-files:
name: build apple silicon .o file name: build apple silicon .o file
@ -87,7 +114,7 @@ jobs:
basic-cli/src/macos-arm64.o basic-cli/src/macos-arm64.o
create-release-archive: create-release-archive:
needs: [build-linux-x86_64-files, build-macos-x86_64-files, build-macos-apple-silicon-files] needs: [build-linux-x86_64-files, build-linux-arm64-files, build-macos-x86_64-files, build-macos-apple-silicon-files]
name: create release archive name: create release archive
runs-on: [ubuntu-20.04] runs-on: [ubuntu-20.04]
steps: steps:
@ -100,7 +127,7 @@ jobs:
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
- name: mv roc nightly and simplify name - name: mv roc nightly and simplify name
run: mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "linux") ./roc_nightly.tar.gz run: mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "linux_x86_64") ./roc_nightly.tar.gz
- name: decompress the tar - name: decompress the tar
run: tar -xzvf roc_nightly.tar.gz run: tar -xzvf roc_nightly.tar.gz
@ -117,6 +144,8 @@ jobs:
- run: cp linux-x86_64-files/* ./basic-cli/src - run: cp linux-x86_64-files/* ./basic-cli/src
- run: cp linux-arm64-files/* ./basic-cli/src
- run: cp macos-x86_64-files/* ./basic-cli/src - run: cp macos-x86_64-files/* ./basic-cli/src
- run: ./roc_nightly/roc build --bundle=${{ env.ARCHIVE_FORMAT }} ./basic-cli/src/main.roc - run: ./roc_nightly/roc build --bundle=${{ env.ARCHIVE_FORMAT }} ./basic-cli/src/main.roc
@ -139,7 +168,7 @@ jobs:
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
- name: mv roc nightly and simplify name - name: mv roc nightly and simplify name
run: mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "linux") ./roc_nightly.tar.gz run: mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "linux_x86_64") ./roc_nightly.tar.gz
- name: decompress the tar - name: decompress the tar
run: tar -xzvf roc_nightly.tar.gz run: tar -xzvf roc_nightly.tar.gz
@ -159,21 +188,31 @@ jobs:
cd basic-cli-platform && ls | grep "tar" | xargs brotli -d cd basic-cli-platform && ls | grep "tar" | xargs brotli -d
ls | grep "tar$" | xargs tar -xf ls | grep "tar$" | xargs tar -xf
- name: prep testing http-get.roc - name: Install expect for tests if we dont have it yet
run: if ! dpkg -l | grep -qw expect; then sudo apt install -y expect; fi
- name: Install ncat for tests if we dont have it yet
run: if ! dpkg -l | grep -qw ncat; then sudo apt install -y ncat; fi
- name: prep testing
run: | run: |
mv roc_nightly basic-cli-platform/. mv roc_nightly basic-cli-platform/.
cd basic-cli-platform cd basic-cli-platform
mkdir examples mkdir src
cd examples find . -maxdepth 1 -type f -exec mv {} src/ \;
curl -fOL https://raw.githubusercontent.com/roc-lang/basic-cli/main/examples/http-get.roc
sed -i 's/pf:\ \"[^"]*/pf:\ \"\.\.\/main.roc/g' http-get.roc
cd ..
curl -fOL https://raw.githubusercontent.com/roc-lang/basic-cli/main/ci/expect_scripts/http-get.exp
- run: sudo apt install -y expect mkdir temp-basic-cli
cd temp-basic-cli
git clone https://github.com/roc-lang/basic-cli.git
cd basic-cli
git checkout ${{ env.BASIC_CLI_BRANCH }}
cp -r examples ../..
cp -r ci ../..
cp -r LICENSE ../..
# LICENSE is necessary for command test
- name: execute test - name: run tests
run: | run: |
cd basic-cli-platform cd basic-cli-platform
expect http-get.exp ROC=./roc_nightly/roc EXAMPLES_DIR=./examples/ ROC_BUILD_FLAGS=--prebuilt-platform ./ci/all_tests.sh

View file

@ -36,7 +36,7 @@ jobs:
- run: expect -v - run: expect -v
# Run all tests # Run all tests
- run: EXAMPLES_DIR=./examples/ ./ci/all_tests.sh - run: ROC=./roc_nightly/roc EXAMPLES_DIR=./examples/ ./ci/all_tests.sh
###### ######
# Now test the latest basic-cli release, not the main branch # Now test the latest basic-cli release, not the main branch
@ -51,4 +51,4 @@ jobs:
- name: Run all tests with latest roc nightly and latest basic-cli release - name: Run all tests with latest roc nightly and latest basic-cli release
run: | run: |
sed -i 's/x86_64/arm64/g' ./ci/test_latest_release.sh sed -i 's/x86_64/arm64/g' ./ci/test_latest_release.sh
EXAMPLES_DIR=./latest-release-examples/ ./ci/test_latest_release.sh ROC=./roc_nightly/roc EXAMPLES_DIR=./latest-release-examples/ ./ci/test_latest_release.sh

View file

@ -0,0 +1,164 @@
on:
# pull_request:
workflow_dispatch:
# this cancels workflows currently in progress if you start a new one
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
# use .tar.gz for quick testing
ARCHIVE_FORMAT: .tar.br
BASIC_WEBSERVER_BRANCH: main
jobs:
fetch-releases:
runs-on: [ubuntu-20.04]
steps:
- uses: actions/checkout@v3
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_x86_64-latest.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_arm64-latest.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_x86_64-latest.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-latest.tar.gz
- name: Save roc_nightly archives
uses: actions/upload-artifact@v3
with:
path: roc_nightly-*
build-linux-x86_64-files:
runs-on: [ubuntu-20.04]
needs: [fetch-releases]
steps:
- uses: actions/checkout@v3
- name: Download the previously uploaded roc_nightly archives
uses: actions/download-artifact@v3
- name: build basic-webserver with surgical linker and also with legacy linker
env:
CARGO_BUILD_TARGET: x86_64-unknown-linux-musl
run: ./ci/build_basic_webserver.sh linux_x86_64 "--linker legacy"
- name: Save .rh, .rm and .o file
uses: actions/upload-artifact@v3
with:
name: linux-x86_64-files
path: |
basic-webserver/platform/metadata_linux-x64.rm
basic-webserver/platform/linux-x64.rh
basic-webserver/platform/linux-x64.o
build-linux-arm64-files:
runs-on: [self-hosted, Linux, ARM64]
needs: [fetch-releases]
steps:
- uses: actions/checkout@v3
- name: Download the previously uploaded roc_nightly archives
uses: actions/download-artifact@v3
- name: build basic-webserver
env:
CARGO_BUILD_TARGET: aarch64-unknown-linux-musl
CC_aarch64_unknown_linux_musl: clang-16
AR_aarch64_unknown_linux_musl: llvm-ar-16
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS: "-Clink-self-contained=yes -Clinker=rust-lld"
run: ./ci/build_basic_webserver.sh linux_arm64
- name: Save .o file
uses: actions/upload-artifact@v3
with:
name: linux-arm64-files
path: |
basic-webserver/platform/linux-arm64.o
build-macos-x86_64-files:
runs-on: [macos-11] # I expect the generated files to work on macOS 12 and 13
needs: [fetch-releases]
steps:
- uses: actions/checkout@v3
- name: Download the previously uploaded roc_nightly archives
uses: actions/download-artifact@v3
- run: ./ci/build_basic_webserver.sh macos_x86_64
- name: Save .o files
uses: actions/upload-artifact@v3
with:
name: macos-x86_64-files
path: |
basic-webserver/platform/macos-x64.o
build-macos-apple-silicon-files:
name: build apple silicon .o file
runs-on: [self-hosted, macOS, ARM64]
needs: [fetch-releases]
steps:
- uses: actions/checkout@v3
- name: Download the previously uploaded roc_nightly archives
uses: actions/download-artifact@v3
- run: ./ci/build_basic_webserver.sh macos_apple_silicon
- name: Save macos-arm64.o file
uses: actions/upload-artifact@v3
with:
name: macos-apple-silicon-files
path: |
basic-webserver/platform/macos-arm64.o
create-release-archive:
needs: [build-linux-x86_64-files, build-linux-arm64-files, build-macos-x86_64-files, build-macos-apple-silicon-files]
name: create release archive
runs-on: [ubuntu-20.04]
steps:
- uses: actions/checkout@v3
- name: remove all folders except the ci folder
run: ls | grep -v ci | xargs rm -rf
- name: Download the previously uploaded files
uses: actions/download-artifact@v3
- name: mv roc nightly and simplify name
run: mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "linux_x86_64") ./roc_nightly.tar.gz
- name: decompress the tar
run: tar -xzvf roc_nightly.tar.gz
- name: delete tar
run: rm roc_nightly.tar.gz
- name: rename nightly folder
run: mv roc_nightly* roc_nightly
- run: |
git clone https://github.com/roc-lang/basic-webserver.git
cd basic-webserver
git checkout ${{ env.BASIC_WEBSERVER_BRANCH }}
cd ..
- run: cp macos-apple-silicon-files/* ./basic-webserver/platform
- run: cp linux-x86_64-files/* ./basic-webserver/platform
- run: cp linux-arm64-files/* ./basic-webserver/platform
- run: cp macos-x86_64-files/* ./basic-webserver/platform
- run: ./roc_nightly/roc build --bundle=${{ env.ARCHIVE_FORMAT }} ./basic-webserver/platform/main.roc
- run: echo "TAR_FILENAME=$(ls -d basic-webserver/platform/* | grep ${{ env.ARCHIVE_FORMAT }})" >> $GITHUB_ENV
- name: Upload platform archive
uses: actions/upload-artifact@v3
with:
name: basic-webserver-platform
path: |
${{ env.TAR_FILENAME }}

View file

@ -5,44 +5,51 @@ on:
name: Garbage collect nix store name: Garbage collect nix store
jobs: jobs:
clean-nix-store-big-ci: clean-big-ci:
runs-on: [self-hosted, i7-6700K] runs-on: [self-hosted, i7-6700K]
timeout-minutes: 120 timeout-minutes: 120
steps: steps:
- name: Clean up nix store - name: Clean up nix store
run: nix-store --gc run: nix-store --gc
clean-nix-store-small-ci: clean-small-ci:
runs-on: [self-hosted, i5-4690K] runs-on: [self-hosted, i5-4690K]
timeout-minutes: 120 timeout-minutes: 120
steps: steps:
- name: Clean up nix store - name: Clean up nix store
run: nix-store --gc run: nix-store --gc
clean-nix-store-mac-mini-arm64: clean-mac-mini-arm64:
runs-on: [self-hosted, macOS, ARM64] runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 120 timeout-minutes: 120
steps: steps:
- name: Clean up nix store - name: Clean up nix store
run: nix-store --gc run: nix-store --gc
clean-nix-store-rpi-1: - name: Clean up nix shells
runs-on: [self-hosted, pi-4-8GB-aarch64] run: rm -rf /private/tmp/nix-shell.*
clean-rpi-1:
runs-on: [self-hosted, Linux, ARM64]
timeout-minutes: 120 timeout-minutes: 120
steps: steps:
- name: Clean up nix store - name: Clean up nix store
run: nix-store --gc run: nix-store --gc
clean-nix-store-rpi-2: clean-rpi-2:
runs-on: [self-hosted, rpiGreen] runs-on: [self-hosted, Linux, ARM64]
timeout-minutes: 120 timeout-minutes: 120
steps: steps:
- name: Clean up nix store - name: Clean up nix store
run: nix-store --gc run: nix-store --gc
clean-nix-store-mac-mini-x86-64: clean-mac-mini-x86-64:
runs-on: [self-hosted, macOS, X64] runs-on: [self-hosted, macOS, X64]
timeout-minutes: 120 timeout-minutes: 120
steps: steps:
- name: Clean up nix store - name: Clean up nix store
run: nix-store --gc run: nix-store --gc
- name: Clean up temp roc binaries
run: find /private/var/folders/hq -type f -name "roc_app_binary" -exec rm {} \; || true

View file

@ -15,25 +15,25 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Only run all steps if flake.lock or flake.nix changed - name: Only run all steps if a nix file changed
id: checklock
run: | run: |
if git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep 'flake'; then git fetch origin ${{ github.base_ref }}
echo "A flake file was changed. Testing devtools nix files..." if git diff --name-only origin/${{ github.base_ref }} HEAD | grep 'nix'; then
echo "flake_changed=true" >> $GITHUB_ENV echo "A nix file was changed. Testing devtools nix files..."
echo "nix_changed=true" >> $GITHUB_ENV
else else
echo "No flake file was changed. No need to run tests." echo "A nix file was changed. No need to run tests."
echo "flake_changed=false" >> $GITHUB_ENV echo "nix_changed=false" >> $GITHUB_ENV
fi fi
- uses: cachix/install-nix-action@v23 - uses: cachix/install-nix-action@v23
if: env.flake_changed == 'true' if: env.nix_changed == 'true'
with: with:
nix_path: nixpkgs=channel:nixos-unstable nix_path: nixpkgs=channel:nixos-unstable
- name: test devtools/flake.nix - name: test devtools/flake.nix
if: env.flake_changed == 'true' if: env.nix_changed == 'true'
id: devtools_test_step id: devtools_test_step
run: | run: |
sed -i "s|/home/username/gitrepos/roc|$(realpath .)|g" devtools/flake.nix sed -i "s|/home/username/gitrepos/roc|$(realpath .)|g" devtools/flake.nix

View file

@ -15,19 +15,19 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Only run all steps if flake.lock or flake.nix changed - name: Only run all steps if a nix file changed
id: checklock
run: | run: |
if git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep 'flake'; then git fetch origin ${{ github.base_ref }}
echo "A flake file was changed. Testing devtools nix files..." if git diff --name-only origin/${{ github.base_ref }} HEAD | grep 'nix'; then
echo "flake_changed=true" >> $GITHUB_ENV echo "A nix file was changed. Testing devtools nix files..."
echo "nix_changed=true" >> $GITHUB_ENV
else else
echo "No flake file was changed. No need to run tests." echo "A nix file was changed. No need to run tests."
echo "flake_changed=false" >> $GITHUB_ENV echo "nix_changed=false" >> $GITHUB_ENV
fi fi
- name: test devtools/flake.nix - name: test devtools/flake.nix
if: env.flake_changed == 'true' if: env.nix_changed == 'true'
id: devtools_test_step id: devtools_test_step
run: | run: |
sed -i '' "s|/home/username/gitrepos/roc|$(realpath .)|g" devtools/flake.nix sed -i '' "s|/home/username/gitrepos/roc|$(realpath .)|g" devtools/flake.nix
@ -41,7 +41,7 @@ jobs:
nix develop --show-trace nix develop --show-trace
- name: Print tip on fail - name: Print tip on fail
if: env.flake_changed == 'true' if: steps.devtools_test_step.outcome == 'failure'
run: | run: |
echo "The devtools test failed, this can likely be fixed by" echo "The devtools test failed, this can likely be fixed by"
echo "locally deleting devtools/flake.lock and following the" echo "locally deleting devtools/flake.lock and following the"

9
.gitignore vendored
View file

@ -61,9 +61,6 @@ roc_linux_x86_64.tar.gz
# nix # nix
result result
# tutorial
www/src/roc-tutorial
# Only keep Cargo.lock dependencies for the main compiler. # Only keep Cargo.lock dependencies for the main compiler.
# Examples and test only crates should be fine to be unlocked. # Examples and test only crates should be fine to be unlocked.
# This remove unneccessary lock file versioning. # This remove unneccessary lock file versioning.
@ -77,7 +74,11 @@ www/src/roc-tutorial
# checkmate # checkmate
checkmate_*.json checkmate_*.json
www/build/
www/main
www/dist
# ignore the examples folder in the WIP website, this is copied from roc-lang/examples in when building the site # ignore the examples folder in the WIP website, this is copied from roc-lang/examples in when building the site
www/wip_new_website/content/examples www/content/examples
www/examples-main.zip www/examples-main.zip
www/examples-main www/examples-main

View file

@ -27,7 +27,7 @@ We are very grateful for our corporate sponsors [Vendr](https://www.vendr.com/),
If you would like your company to become a corporate sponsor of Roc's development, please [DM Richard Feldman on Zulip](https://roc.zulipchat.com/#narrow/pm-with/281383-user281383)! If you would like your company to become a corporate sponsor of Roc's development, please [DM Richard Feldman on Zulip](https://roc.zulipchat.com/#narrow/pm-with/281383-user281383)!
We'd also like to express our gratitude to each and every one of our fantastic [GitHub sponsors](https://github.com/sponsors/roc-lang/)! A special thanks to those sponsoring $25/month or more: We'd also like to express our gratitude to our generous [individual sponsors](https://github.com/sponsors/roc-lang/)! A special thanks to those sponsoring $25/month or more:
* [James Birtles](https://github.com/jamesbirtles) * [James Birtles](https://github.com/jamesbirtles)
* [Ivo Balbaert](https://github.com/Ivo-Balbaert) * [Ivo Balbaert](https://github.com/Ivo-Balbaert)

View file

@ -5,10 +5,20 @@ set -euxo pipefail
git clone https://github.com/roc-lang/basic-cli.git git clone https://github.com/roc-lang/basic-cli.git
if [ "$(uname -m)" == "x86_64" ] && [ "$(uname -s)" == "Linux" ]; then if [ "$(uname -s)" == "Linux" ]; then
sudo apt-get install musl-tools
# check if musl-tools is installed
if ! dpkg -l | grep -q musl-tools; then
# install musl-tools with timeout for sudo problems with CI
timeout 300s sudo apt-get install -y musl-tools
fi
cd basic-cli/src # we cd to install the target for the right rust version cd basic-cli/src # we cd to install the target for the right rust version
if [ "$(uname -m)" == "x86_64" ]; then
rustup target add x86_64-unknown-linux-musl rustup target add x86_64-unknown-linux-musl
elif [ "$(uname -m)" == "aarch64" ]; then
rustup target add aarch64-unknown-linux-musl
fi
cd ../.. cd ../..
fi fi

56
ci/build_basic_webserver.sh Executable file
View file

@ -0,0 +1,56 @@
#!/usr/bin/env bash
# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
set -euxo pipefail
git clone https://github.com/roc-lang/basic-webserver.git
OS=$(uname -s)
ARCH=$(uname -m)
if [ "$OS" == "Linux" ]; then
# check if musl-tools is installed
if ! dpkg -l | grep -q musl-tools; then
# install musl-tools with timeout for sudo problems with CI
timeout 300s sudo apt-get install -y musl-tools
fi
cd basic-webserver/platform # we cd to install the target for the right rust version
if [ "$ARCH" == "x86_64" ]; then
rustup target add x86_64-unknown-linux-musl
elif [ "$ARCH" == "aarch64" ]; then
rustup target add aarch64-unknown-linux-musl
fi
cd ../..
fi
# simplify tar name
mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "$1") ./roc_nightly.tar.gz
# decompress the tar
tar -xzvf roc_nightly.tar.gz
# delete tar
rm roc_nightly.tar.gz
# simplify dir name
mv roc_nightly* roc_nightly
cd roc_nightly
# prevent https://github.com/roc-lang/basic-webserver/issues/9
if [ "$OS" != "Linux" ] || [ "$ARCH" != "x86_64" ]; then
# build the basic-webserver platform
./roc build ../basic-webserver/examples/echo.roc
fi
# We need this extra variable so we can safely check if $2 is empty later
EXTRA_ARGS=${2:-}
# In some rare cases it's nice to be able to use the legacy linker, so we produce the .o file to be able to do that
if [ -n "${EXTRA_ARGS}" ];
then ./roc build $EXTRA_ARGS ../basic-webserver/examples/echo.roc
fi
cd ..

View file

@ -474,18 +474,6 @@ mod cli_run {
) )
} }
#[test]
#[serial(cli_platform)]
#[cfg_attr(windows, ignore)]
fn hello_world_no_url() {
test_roc_app_slim(
"examples",
"helloWorldNoURL.roc",
"Hello, World!\n",
UseValgrind::Yes,
)
}
#[cfg(windows)] #[cfg(windows)]
const LINE_ENDING: &str = "\r\n"; const LINE_ENDING: &str = "\r\n";
#[cfg(not(windows))] #[cfg(not(windows))]
@ -687,12 +675,13 @@ mod cli_run {
} }
#[test] #[test]
#[ignore = "currently broken in basic-cli platform"]
#[cfg_attr(windows, ignore = "missing __udivdi3 and some other symbols")] #[cfg_attr(windows, ignore = "missing __udivdi3 and some other symbols")]
#[serial(cli_platform)] #[serial(cli_platform)]
fn cli_args() { fn cli_args() {
test_roc_app( test_roc_app(
"examples/cli", "examples/cli",
"args.roc", "argsBROKEN.roc",
&[], &[],
&[ &[
Arg::PlainText("log"), Arg::PlainText("log"),
@ -713,7 +702,7 @@ mod cli_run {
#[cfg_attr(windows, ignore = "missing __udivdi3 and some other symbols")] #[cfg_attr(windows, ignore = "missing __udivdi3 and some other symbols")]
#[serial(cli_platform)] #[serial(cli_platform)]
fn cli_args_check() { fn cli_args_check() {
let path = file_path_from_root("examples/cli", "args.roc"); let path = file_path_from_root("examples/cli", "argsBROKEN.roc");
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
assert!(out.status.success()); assert!(out.status.success());
} }
@ -736,6 +725,51 @@ mod cli_run {
assert!(out.status.success()); assert!(out.status.success());
} }
#[test]
#[cfg_attr(windows, ignore)]
#[serial(cli_platform)]
fn cli_countdown_check() {
let path = file_path_from_root("examples/cli", "countdown.roc");
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
assert!(out.status.success());
}
#[test]
#[cfg_attr(windows, ignore)]
#[serial(cli_platform)]
fn cli_echo_check() {
let path = file_path_from_root("examples/cli", "echo.roc");
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
assert!(out.status.success());
}
#[test]
#[cfg_attr(windows, ignore)]
#[serial(cli_platform)]
fn cli_file_check() {
let path = file_path_from_root("examples/cli", "fileBROKEN.roc");
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
assert!(out.status.success());
}
#[test]
#[cfg_attr(windows, ignore)]
#[serial(cli_platform)]
fn cli_form_check() {
let path = file_path_from_root("examples/cli", "form.roc");
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
assert!(out.status.success());
}
#[test]
#[cfg_attr(windows, ignore)]
#[serial(cli_platform)]
fn cli_http_get_check() {
let path = file_path_from_root("examples/cli", "http-get.roc");
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
assert!(out.status.success());
}
#[test] #[test]
#[cfg_attr(windows, ignore)] #[cfg_attr(windows, ignore)]
fn interactive_effects() { fn interactive_effects() {
@ -839,7 +873,7 @@ mod cli_run {
This roc file can print it's own source code. The source is: This roc file can print it's own source code. The source is:
app "ingested-file" app "ingested-file"
packages { pf: "cli-platform/main.roc" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" }
imports [ imports [
pf.Stdout, pf.Stdout,
"ingested-file.roc" as ownCode : Str, "ingested-file.roc" as ownCode : Str,
@ -866,7 +900,7 @@ mod cli_run {
&[], &[],
&[], &[],
&[], &[],
"22424\n", "30256\n",
UseValgrind::No, UseValgrind::No,
TestCliCommands::Run, TestCliCommands::Run,
) )

View file

@ -1,3 +1,3 @@
package "builtins" package "builtins"
exposes [Str, Num, Bool, Result, List, Dict, Set, Decode, Encode, Hash, Set, Box, TotallyNotJson] exposes [Str, Num, Bool, Result, List, Dict, Set, Decode, Encode, Hash, Box, TotallyNotJson]
packages {} packages {}

View file

@ -502,6 +502,7 @@ pub fn constrain_expr(
let reason = Reason::FnArg { let reason = Reason::FnArg {
name: opt_symbol, name: opt_symbol,
arg_index: HumanIndex::zero_based(index), arg_index: HumanIndex::zero_based(index),
called_via: *called_via,
}; };
let expected_arg = let expected_arg =
constraints.push_expected_type(ForReason(reason, arg_type_index, region)); constraints.push_expected_type(ForReason(reason, arg_type_index, region));

View file

@ -2447,7 +2447,7 @@ mod test_reporting {
@r###" @r###"
TYPE MISMATCH /code/proj/Main.roc TYPE MISMATCH /code/proj/Main.roc
This 2nd argument to `add` has an unexpected type: This 2nd argument to + has an unexpected type:
4 0x4 + "foo" 4 0x4 + "foo"
^^^^^ ^^^^^
@ -2456,7 +2456,7 @@ mod test_reporting {
Str Str
But `add` needs its 2nd argument to be: But + needs its 2nd argument to be:
Int * Int *
"### "###
@ -2472,7 +2472,7 @@ mod test_reporting {
@r###" @r###"
TYPE MISMATCH /code/proj/Main.roc TYPE MISMATCH /code/proj/Main.roc
This 2nd argument to `add` has an unexpected type: This 2nd argument to + has an unexpected type:
4 0x4 + 3.14 4 0x4 + 3.14
^^^^ ^^^^
@ -2481,7 +2481,7 @@ mod test_reporting {
Frac * Frac *
But `add` needs its 2nd argument to be: But + needs its 2nd argument to be:
Int * Int *
@ -2500,7 +2500,7 @@ mod test_reporting {
@r###" @r###"
TYPE MISMATCH /code/proj/Main.roc TYPE MISMATCH /code/proj/Main.roc
This 2nd argument to `add` has an unexpected type: This 2nd argument to + has an unexpected type:
4 42 + True 4 42 + True
^^^^ ^^^^
@ -2509,7 +2509,7 @@ mod test_reporting {
[True] [True]
But `add` needs its 2nd argument to be: But + needs its 2nd argument to be:
Num * Num *
"### "###
@ -3556,7 +3556,7 @@ mod test_reporting {
TYPE MISMATCH /code/proj/Main.roc TYPE MISMATCH /code/proj/Main.roc
This 2nd argument to `add` has an unexpected type: This 2nd argument to + has an unexpected type:
14 x + y + h + l + minlit + maxlit 14 x + y + h + l + minlit + maxlit
^^^^^^ ^^^^^^
@ -3565,7 +3565,7 @@ mod test_reporting {
U128 U128
But `add` needs its 2nd argument to be: But + needs its 2nd argument to be:
I128 or Dec I128 or Dec
"### "###
@ -3843,7 +3843,7 @@ mod test_reporting {
@r###" @r###"
TYPE MISMATCH /code/proj/Main.roc TYPE MISMATCH /code/proj/Main.roc
This 2nd argument to `add` has an unexpected type: This 2nd argument to + has an unexpected type:
4 \{ x, y ? True } -> x + y 4 \{ x, y ? True } -> x + y
^ ^
@ -3852,7 +3852,7 @@ mod test_reporting {
[True] [True]
But `add` needs its 2nd argument to be: But + needs its 2nd argument to be:
Num a Num a
"### "###
@ -6377,7 +6377,7 @@ In roc, functions are always written as a lambda, like{}
@r###" @r###"
TYPE MISMATCH /code/proj/Main.roc TYPE MISMATCH /code/proj/Main.roc
This 2nd argument to `mul` has an unexpected type: This 2nd argument to * has an unexpected type:
5 mult = \a, b -> a * b 5 mult = \a, b -> a * b
^ ^
@ -6386,7 +6386,7 @@ In roc, functions are always written as a lambda, like{}
F64 F64
But `mul` needs its 2nd argument to be: But * needs its 2nd argument to be:
Num * Num *
@ -6421,7 +6421,7 @@ In roc, functions are always written as a lambda, like{}
@r###" @r###"
TYPE MISMATCH /code/proj/Main.roc TYPE MISMATCH /code/proj/Main.roc
This 2nd argument to `mul` has an unexpected type: This 2nd argument to * has an unexpected type:
5 mult = \a, b -> a * b 5 mult = \a, b -> a * b
^ ^
@ -6430,7 +6430,7 @@ In roc, functions are always written as a lambda, like{}
F64 F64
But `mul` needs its 2nd argument to be: But * needs its 2nd argument to be:
Num a Num a
@ -9234,7 +9234,7 @@ In roc, functions are always written as a lambda, like{}
@r###" @r###"
TYPE MISMATCH /code/proj/Main.roc TYPE MISMATCH /code/proj/Main.roc
This 2nd argument to `isEq` has an unexpected type: This 2nd argument to == has an unexpected type:
9 Job lst -> lst == "" 9 Job lst -> lst == ""
^^ ^^
@ -9243,7 +9243,7 @@ In roc, functions are always written as a lambda, like{}
Str Str
But `isEq` needs its 2nd argument to be: But == needs its 2nd argument to be:
List [Job ] as List [Job ] as
"### "###
@ -10013,7 +10013,7 @@ In roc, functions are always written as a lambda, like{}
@r###" @r###"
TYPE MISMATCH /code/proj/Main.roc TYPE MISMATCH /code/proj/Main.roc
This 2nd argument to `isEq` has an unexpected type: This 2nd argument to == has an unexpected type:
4 0x80000000000000000000000000000000 == -0x80000000000000000000000000000000 4 0x80000000000000000000000000000000 == -0x80000000000000000000000000000000
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -10022,7 +10022,7 @@ In roc, functions are always written as a lambda, like{}
I128 I128
But `isEq` needs its 2nd argument to be: But == needs its 2nd argument to be:
U128 U128
"### "###
@ -10038,7 +10038,7 @@ In roc, functions are always written as a lambda, like{}
@r###" @r###"
TYPE MISMATCH /code/proj/Main.roc TYPE MISMATCH /code/proj/Main.roc
This 2nd argument to `isEq` has an unexpected type: This 2nd argument to == has an unexpected type:
4 170141183460469231731687303715884105728 == -170141183460469231731687303715884105728 4 170141183460469231731687303715884105728 == -170141183460469231731687303715884105728
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -10047,7 +10047,7 @@ In roc, functions are always written as a lambda, like{}
I128 or Dec I128 or Dec
But `isEq` needs its 2nd argument to be: But == needs its 2nd argument to be:
U128 U128
"### "###

View file

@ -3291,7 +3291,7 @@ fn finish(
exposed_types_storage: ExposedTypesStorageSubs, exposed_types_storage: ExposedTypesStorageSubs,
resolved_implementations: ResolvedImplementations, resolved_implementations: ResolvedImplementations,
dep_idents: IdentIdsByModule, dep_idents: IdentIdsByModule,
documentation: VecMap<ModuleId, ModuleDocumentation>, mut documentation: VecMap<ModuleId, ModuleDocumentation>,
abilities_store: AbilitiesStore, abilities_store: AbilitiesStore,
// //
#[cfg(debug_assertions)] checkmate: Option<roc_checkmate::Collector>, #[cfg(debug_assertions)] checkmate: Option<roc_checkmate::Collector>,
@ -3330,6 +3330,18 @@ fn finish(
roc_checkmate::dump_checkmate!(checkmate); roc_checkmate::dump_checkmate!(checkmate);
let mut docs_by_module = Vec::with_capacity(state.exposed_modules.len());
for module_id in state.exposed_modules.iter() {
let docs = documentation.remove(module_id).unwrap_or_else(|| {
panic!("A module was exposed but didn't have an entry in `documentation` somehow: {module_id:?}");
});
docs_by_module.push(docs);
}
debug_assert_eq!(documentation.len(), 0);
LoadedModule { LoadedModule {
module_id: state.root_id, module_id: state.root_id,
interns, interns,
@ -3346,7 +3358,7 @@ fn finish(
resolved_implementations, resolved_implementations,
sources, sources,
timings: state.timings, timings: state.timings,
docs_by_module: documentation, docs_by_module,
abilities_store, abilities_store,
} }
} }

View file

@ -43,7 +43,7 @@ pub struct LoadedModule {
pub resolved_implementations: ResolvedImplementations, pub resolved_implementations: ResolvedImplementations,
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>, pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub timings: MutMap<ModuleId, ModuleTiming>, pub timings: MutMap<ModuleId, ModuleTiming>,
pub docs_by_module: VecMap<ModuleId, ModuleDocumentation>, pub docs_by_module: Vec<(ModuleId, ModuleDocumentation)>,
pub abilities_store: AbilitiesStore, pub abilities_store: AbilitiesStore,
pub typechecked: MutMap<ModuleId, CheckedModule>, pub typechecked: MutMap<ModuleId, CheckedModule>,
} }

View file

@ -102,6 +102,15 @@ pub enum UnaryOp {
Not, Not,
} }
impl std::fmt::Display for UnaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UnaryOp::Negate => write!(f, "-"),
UnaryOp::Not => write!(f, "!"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BinOp { pub enum BinOp {
// highest precedence // highest precedence

View file

@ -4270,7 +4270,8 @@ pub fn with_hole<'a>(
let arena = env.arena; let arena = env.arena;
match can_expr { match can_expr {
Int(_, _, int_str, int, _bound) => assign_num_literal_expr( Int(_, _, int_str, int, _bound) => {
match assign_num_literal_expr(
env, env,
layout_cache, layout_cache,
assigned, assigned,
@ -4278,9 +4279,14 @@ pub fn with_hole<'a>(
&int_str, &int_str,
IntOrFloatValue::Int(int), IntOrFloatValue::Int(int),
hole, hole,
), ) {
Ok(stmt) => stmt,
Err(_) => hole.clone(),
}
}
Float(_, _, float_str, float, _bound) => assign_num_literal_expr( Float(_, _, float_str, float, _bound) => {
match assign_num_literal_expr(
env, env,
layout_cache, layout_cache,
assigned, assigned,
@ -4288,9 +4294,14 @@ pub fn with_hole<'a>(
&float_str, &float_str,
IntOrFloatValue::Float(float), IntOrFloatValue::Float(float),
hole, hole,
), ) {
Ok(stmt) => stmt,
Err(_) => hole.clone(),
}
}
Num(_, num_str, num, _bound) => assign_num_literal_expr( Num(_, num_str, num, _bound) => {
match assign_num_literal_expr(
env, env,
layout_cache, layout_cache,
assigned, assigned,
@ -4298,7 +4309,11 @@ pub fn with_hole<'a>(
&num_str, &num_str,
IntOrFloatValue::Int(num), IntOrFloatValue::Int(num),
hole, hole,
), ) {
Ok(stmt) => stmt,
Err(_) => hole.clone(),
}
}
Str(string) => Stmt::Let( Str(string) => Stmt::Let(
assigned, assigned,
@ -9267,14 +9282,12 @@ fn assign_num_literal_expr<'a>(
num_str: &str, num_str: &str,
num_value: IntOrFloatValue, num_value: IntOrFloatValue,
hole: &'a Stmt<'a>, hole: &'a Stmt<'a>,
) -> Stmt<'a> { ) -> Result<Stmt<'a>, RuntimeError> {
let layout = layout_cache let layout = layout_cache.from_var(env.arena, variable, env.subs)?;
.from_var(env.arena, variable, env.subs)
.unwrap();
let literal = let literal =
make_num_literal(&layout_cache.interner, layout, num_str, num_value).to_expr_literal(); make_num_literal(&layout_cache.interner, layout, num_str, num_value).to_expr_literal();
Stmt::Let(assigned, Expr::Literal(literal), layout, hole) Ok(Stmt::Let(assigned, Expr::Literal(literal), layout, hole))
} }
type ToLowLevelCallArguments<'a> = ( type ToLowLevelCallArguments<'a> = (

View file

@ -3370,6 +3370,7 @@ pub enum Reason {
FnArg { FnArg {
name: Option<Symbol>, name: Option<Symbol>,
arg_index: HumanIndex, arg_index: HumanIndex,
called_via: CalledVia,
}, },
TypedArg { TypedArg {
name: Option<Symbol>, name: Option<Symbol>,

View file

@ -110,13 +110,13 @@ pub fn generate_docs_html(root_file: PathBuf, build_dir: &Path) {
.replace("<!-- base -->", &base_url()) .replace("<!-- base -->", &base_url())
.replace( .replace(
"<!-- Module links -->", "<!-- Module links -->",
render_sidebar(loaded_module.docs_by_module.values()).as_str(), render_sidebar(loaded_module.docs_by_module.iter().map(|(_, docs)| docs)).as_str(),
); );
let all_exposed_symbols = { let all_exposed_symbols = {
let mut set = VecSet::default(); let mut set = VecSet::default();
for docs in loaded_module.docs_by_module.values() { for (_, docs) in loaded_module.docs_by_module.iter() {
set.insert_all(docs.exposed_symbols.iter().copied()); set.insert_all(docs.exposed_symbols.iter().copied());
} }
@ -146,7 +146,7 @@ pub fn generate_docs_html(root_file: PathBuf, build_dir: &Path) {
} }
// Write each package module's index.html file // Write each package module's index.html file
for module_docs in loaded_module.docs_by_module.values() { for (_, module_docs) in loaded_module.docs_by_module.iter() {
let module_name = module_docs.name.as_str(); let module_name = module_docs.name.as_str();
let module_dir = build_dir.join(module_name.replace('.', "/").as_str()); let module_dir = build_dir.join(module_name.replace('.', "/").as_str());
@ -183,7 +183,7 @@ fn render_package_index(root_module: &LoadedModule) -> String {
// The list items containing module links // The list items containing module links
let mut module_list_buf = String::new(); let mut module_list_buf = String::new();
for module in root_module.docs_by_module.values() { for (_, module) in root_module.docs_by_module.iter() {
// The anchor tag containing the module link // The anchor tag containing the module link
let mut link_buf = String::new(); let mut link_buf = String::new();
@ -200,7 +200,12 @@ fn render_package_index(root_module: &LoadedModule) -> String {
// The HTML for the index page // The HTML for the index page
let mut index_buf = String::new(); let mut index_buf = String::new();
push_html(&mut index_buf, "h2", vec![], "Exposed Modules"); push_html(
&mut index_buf,
"h2",
vec![("class", "module-name")],
"Exposed Modules",
);
push_html( push_html(
&mut index_buf, &mut index_buf,
"ul", "ul",
@ -222,7 +227,7 @@ fn render_module_documentation(
push_html(&mut buf, "h2", vec![("class", "module-name")], { push_html(&mut buf, "h2", vec![("class", "module-name")], {
let mut link_buf = String::new(); let mut link_buf = String::new();
push_html(&mut link_buf, "a", vec![("href", "/#")], module_name); push_html(&mut link_buf, "a", vec![("href", "/")], module_name);
link_buf link_buf
}); });

View file

@ -2,27 +2,39 @@
let sidebar = document.getElementById("sidebar-nav"); let sidebar = document.getElementById("sidebar-nav");
let searchBox = document.getElementById("module-search"); let searchBox = document.getElementById("module-search");
if (searchBox != null) {
function search() { function search() {
let text = searchBox.value.toLowerCase(); // Search is case-insensitive. let text = searchBox.value.toLowerCase(); // Search is case-insensitive.
if (text === "") { if (text === "") {
// Un-hide everything // Un-hide everything
sidebar.querySelectorAll(".sidebar-entry a").forEach((entry) => entry.classList.remove("hidden")); sidebar
.querySelectorAll(".sidebar-entry a")
.forEach((entry) => entry.classList.remove("hidden"));
// Re-hide all the sub-entries except for those of the current module // Re-hide all the sub-entries except for those of the current module
let currentModuleName = document.querySelector('.module-name').textContent; let currentModuleName =
document.querySelector(".module-name").textContent;
sidebar.querySelectorAll(".sidebar-entry").forEach((entry) => { sidebar.querySelectorAll(".sidebar-entry").forEach((entry) => {
let entryName = entry.querySelector('.sidebar-module-link').textContent; let entryName = entry.querySelector(
".sidebar-module-link"
).textContent;
if (currentModuleName === entryName) { if (currentModuleName === entryName) {
entry.firstChild.classList.add("active"); entry.firstChild.classList.add("active");
return; return;
}; }
entry.querySelectorAll(".sidebar-sub-entries a").forEach((subEntry) => subEntry.classList.add("hidden")); entry
}) .querySelectorAll(".sidebar-sub-entries a")
.forEach((subEntry) =>
subEntry.classList.add("hidden")
);
});
} else { } else {
// First, show/hide all the sub-entries within each module (top-level functions etc.) // First, show/hide all the sub-entries within each module (top-level functions etc.)
sidebar.querySelectorAll(".sidebar-sub-entries a").forEach((entry) => { sidebar
.querySelectorAll(".sidebar-sub-entries a")
.forEach((entry) => {
if (entry.textContent.toLowerCase().includes(text)) { if (entry.textContent.toLowerCase().includes(text)) {
entry.classList.remove("hidden"); entry.classList.remove("hidden");
} else { } else {
@ -31,8 +43,15 @@
}); });
// Then, show/hide modules based on whether they match, or any of their sub-entries matched // Then, show/hide modules based on whether they match, or any of their sub-entries matched
sidebar.querySelectorAll(".sidebar-module-link").forEach((entry) => { sidebar
if (entry.textContent.toLowerCase().includes(text) || entry.parentNode.querySelectorAll(".sidebar-sub-entries a:not(.hidden)").length > 0) { .querySelectorAll(".sidebar-module-link")
.forEach((entry) => {
if (
entry.textContent.toLowerCase().includes(text) ||
entry.parentNode.querySelectorAll(
".sidebar-sub-entries a:not(.hidden)"
).length > 0
) {
entry.classList.remove("hidden"); entry.classList.remove("hidden");
} else { } else {
entry.classList.add("hidden"); entry.classList.add("hidden");
@ -61,14 +80,18 @@
// Reset sidebar state // Reset sidebar state
search(); search();
} }
}); });
}
const isTouchSupported = () => { const isTouchSupported = () => {
try{ document.createEvent("TouchEvent"); return true; } try {
catch(e){ return false; } document.createEvent("TouchEvent");
return true;
} catch (e) {
return false;
} }
};
// Select all <samp> elements that are children of <pre> elements // Select all <samp> elements that are children of <pre> elements
const codeBlocks = document.querySelectorAll("pre > samp"); const codeBlocks = document.querySelectorAll("pre > samp");
@ -88,7 +111,7 @@
copyButton.classList.add("copy-button-copied"); copyButton.classList.add("copy-button-copied");
copyButton.addEventListener("mouseleave", () => { copyButton.addEventListener("mouseleave", () => {
copyButton.textContent = "Copy"; copyButton.textContent = "Copy";
copyButton.classList.remove('copy-button-copied'); copyButton.classList.remove("copy-button-copied");
}); });
}); });

View file

@ -9,6 +9,7 @@
--violet: #7c38f5; --violet: #7c38f5;
--violet-bg: #ece2fd; --violet-bg: #ece2fd;
--magenta: #a20031; --magenta: #a20031;
--link-hover-color: #333;
--link-color: var(--violet); --link-color: var(--violet);
--code-link-color: var(--violet); --code-link-color: var(--violet);
@ -53,7 +54,7 @@ table tr td {
} }
.logo svg:hover { .logo svg:hover {
fill: var(--green); fill: var(--link-hover-color);
} }
.pkg-full-name { .pkg-full-name {
@ -130,7 +131,7 @@ a:hover code {
} }
.pkg-and-logo a:hover { .pkg-and-logo a:hover {
color: var(--green); color: var(--link-hover-color);
text-decoration: none; text-decoration: none;
} }
@ -173,8 +174,9 @@ main {
line-height: 1.85em; line-height: 1.85em;
margin-top: 2px; margin-top: 2px;
padding: 48px; padding: 48px;
max-width: 740px;
min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */ /* necessary for text-overflow: ellipsis to work in descendants */
min-width: 0;
} }
/* Module links on the package index page (/index.html) */ /* Module links on the package index page (/index.html) */
@ -254,7 +256,8 @@ padding: 0px 16px;
font-family: var(--font-sans); font-family: var(--font-sans);
font-size: 24px; font-size: 24px;
height: 100%; height: 100%;
min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */ /* min-width must be set to something (even 0) for text-overflow: ellipsis to work in descendants, but we want this anyway. */
min-width: 1024px;
} }
.top-header-triangle { .top-header-triangle {
@ -340,7 +343,7 @@ color: inherit;
} }
.module-name a:hover { .module-name a:hover {
color: var(--green); color: var(--link-hover-color);
} }
.sidebar-module-link { .sidebar-module-link {
@ -519,6 +522,7 @@ pre>samp {
--violet: #CAADFB; --violet: #CAADFB;
--violet-bg: #332944; --violet-bg: #332944;
--magenta: #f39bac; --magenta: #f39bac;
--link-hover-color: #fff;
--link-color: var(--violet); --link-color: var(--violet);
--code-link-color: var(--violet); --code-link-color: var(--violet);
@ -548,6 +552,8 @@ pre>samp {
.top-header { .top-header {
justify-content: space-between; justify-content: space-between;
width: auto; width: auto;
/* min-width must be set to something (even 0) for text-overflow: ellipsis to work in descendants. */
min-width: 0;
} }
.pkg-full-name { .pkg-full-name {
@ -591,6 +597,7 @@ pre>samp {
grid-row-end: above-footer; grid-row-end: above-footer;
padding: 18px; padding: 18px;
font-size: 16px; font-size: 16px;
max-width: none;
} }
#sidebar-nav { #sidebar-nav {
@ -739,6 +746,6 @@ code .dim {
} }
.copy-button:hover { .copy-button:hover {
border-color: var(--green); border-color: var(--link-hover-color);
color: var(--green); color: var(--link-hover-color);
} }

View file

@ -20,7 +20,7 @@ use target_lexicon::Triple;
use crate::cli_gen::eval_llvm; use crate::cli_gen::eval_llvm;
pub const WELCOME_MESSAGE: &str = concatcp!( pub const WELCOME_MESSAGE: &str = concatcp!(
"\n The rockin ", "\n The rockin' ",
BLUE, BLUE,
"roc repl", "roc repl",
END_COL, END_COL,

View file

@ -658,17 +658,17 @@ fn too_few_args() {
#[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes! #[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes!
#[test] #[test]
fn type_problem() { fn type_problem_function() {
expect_failure( expect_failure(
"1 + \"\"", "Num.add 1 \"not a num\"",
indoc!( indoc!(
r#" r#"
TYPE MISMATCH TYPE MISMATCH
This 2nd argument to add has an unexpected type: This 2nd argument to add has an unexpected type:
4 1 + "" 4 Num.add 1 "not a num"
^^ ^^^^^^^^^^^
The argument is a string of type: The argument is a string of type:
@ -682,6 +682,84 @@ fn type_problem() {
); );
} }
#[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes!
#[test]
fn type_problem_binary_operator() {
expect_failure(
"1 + \"\"",
indoc!(
r#"
TYPE MISMATCH
This 2nd argument to + has an unexpected type:
4 1 + ""
^^
The argument is a string of type:
Str
But + needs its 2nd argument to be:
Num *
"#
),
);
}
#[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes!
#[test]
fn type_problem_unary_operator() {
expect_failure(
"!\"not a bool\"",
indoc!(
r#"
TYPE MISMATCH
This 1st argument to ! has an unexpected type:
4 !"not a bool"
^^^^^^^^^^^^
The argument is a string of type:
Str
But ! needs its 1st argument to be:
Bool
"#
),
);
}
#[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes!
#[test]
fn type_problem_string_interpolation() {
expect_failure(
"\"This is not a string -> \\(1)\"",
indoc!(
r#"
TYPE MISMATCH
This argument to this string interpolation has an unexpected type:
4 "This is not a string -> \(1)"
^
The argument is a number of type:
Num *
But this string interpolation needs its argument to be:
Str
"#
),
);
}
#[test] #[test]
fn issue_2149_i8_ok() { fn issue_2149_i8_ok() {
expect_success(r#"Str.toI8 "127""#, "Ok 127 : Result I8 [InvalidNumStr]"); expect_success(r#"Str.toI8 "127""#, "Ok 127 : Result I8 [InvalidNumStr]");

View file

@ -3205,10 +3205,21 @@ fn to_header_report<'a>(
let surroundings = Region::new(start, *pos); let surroundings = Region::new(start, *pos);
let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); let region = LineColumnRegion::from_pos(lines.convert_pos(*pos));
let doc = alloc.stack([ let is_utf8 = alloc
.src_lines
.iter()
.all(|line| std::str::from_utf8(line.as_bytes()).is_ok());
let preamble = if is_utf8 {
vec![
alloc.reflow(r"I am expecting a header, but got stuck here:"), alloc.reflow(r"I am expecting a header, but got stuck here:"),
alloc.region_with_subregion(lines.convert_region(surroundings), region), alloc.region_with_subregion(lines.convert_region(surroundings), region),
alloc.concat([ ]
} else {
vec![alloc.reflow(r"I am expecting a header, but the file is not UTF-8 encoded.")]
};
let doc = alloc.stack(preamble.into_iter().chain([alloc.concat([
alloc.reflow("I am expecting a module keyword next, one of "), alloc.reflow("I am expecting a module keyword next, one of "),
alloc.keyword("interface"), alloc.keyword("interface"),
alloc.reflow(", "), alloc.reflow(", "),
@ -3218,8 +3229,7 @@ fn to_header_report<'a>(
alloc.reflow(" or "), alloc.reflow(" or "),
alloc.keyword("platform"), alloc.keyword("platform"),
alloc.reflow("."), alloc.reflow("."),
]), ])]));
]);
Report { Report {
filename, filename,

View file

@ -1273,12 +1273,24 @@ fn to_expr_report<'b>(
} }
} }
}, },
Reason::FnArg { name, arg_index } => { Reason::FnArg {
name,
arg_index,
called_via,
} => {
let ith = arg_index.ordinal(); let ith = arg_index.ordinal();
let this_function = match name { let this_function = match (called_via, name) {
None => alloc.text("this function"), (CalledVia::Space, Some(symbole)) => alloc.symbol_unqualified(symbole),
Some(symbol) => alloc.symbol_unqualified(symbol), (CalledVia::BinOp(op), _) => alloc.binop(op),
(CalledVia::UnaryOp(op), _) => alloc.unop(op),
(CalledVia::StringInterpolation, _) => alloc.text("this string interpolation"),
_ => alloc.text("this function"),
};
let argument = match called_via {
CalledVia::StringInterpolation => "argument".to_string(),
_ => format!("{ith} argument"),
}; };
report_mismatch( report_mismatch(
@ -1292,7 +1304,7 @@ fn to_expr_report<'b>(
region, region,
Some(expr_region), Some(expr_region),
alloc.concat([ alloc.concat([
alloc.string(format!("This {ith} argument to ")), alloc.string(format!("This {argument} to ")),
this_function.clone(), this_function.clone(),
alloc.text(" has an unexpected type:"), alloc.text(" has an unexpected type:"),
]), ]),
@ -1300,7 +1312,7 @@ fn to_expr_report<'b>(
alloc.concat([ alloc.concat([
alloc.text("But "), alloc.text("But "),
this_function, this_function,
alloc.string(format!(" needs its {ith} argument to be:")), alloc.string(format!(" needs its {argument} to be:")),
]), ]),
None, None,
) )

View file

@ -490,6 +490,13 @@ impl<'a> RocDocAllocator<'a> {
self.text(content.to_string()).annotate(Annotation::BinOp) self.text(content.to_string()).annotate(Annotation::BinOp)
} }
pub fn unop(
&'a self,
content: roc_module::called_via::UnaryOp,
) -> DocBuilder<'a, Self, Annotation> {
self.text(content.to_string()).annotate(Annotation::UnaryOp)
}
/// Turns off backticks/colors in a block /// Turns off backticks/colors in a block
pub fn type_block( pub fn type_block(
&'a self, &'a self,
@ -843,6 +850,7 @@ pub enum Annotation {
Structure, Structure,
Symbol, Symbol,
BinOp, BinOp,
UnaryOp,
Error, Error,
GutterBar, GutterBar,
LineNumber, LineNumber,
@ -1027,6 +1035,9 @@ where
BinOp => { BinOp => {
self.write_str(self.palette.alias)?; self.write_str(self.palette.alias)?;
} }
UnaryOp => {
self.write_str(self.palette.alias)?;
}
Symbol => { Symbol => {
self.write_str(self.palette.variable)?; self.write_str(self.palette.variable)?;
} }
@ -1075,9 +1086,9 @@ where
match self.style_stack.pop() { match self.style_stack.pop() {
None => {} None => {}
Some(annotation) => match annotation { Some(annotation) => match annotation {
Emphasized | Url | TypeVariable | Alias | Symbol | BinOp | Error | GutterBar Emphasized | Url | TypeVariable | Alias | Symbol | BinOp | UnaryOp | Error
| Ellipsis | Typo | TypoSuggestion | ParserSuggestion | Structure | CodeBlock | GutterBar | Ellipsis | Typo | TypoSuggestion | ParserSuggestion | Structure
| PlainText | LineNumber | Tip | Module | Header | Keyword => { | CodeBlock | PlainText | LineNumber | Tip | Module | Header | Keyword => {
self.write_str(self.palette.reset)?; self.write_str(self.palette.reset)?;
} }

View file

@ -101,6 +101,13 @@ where
} }
} }
impl<T, E> Eq for RocResult<T, E>
where
T: Eq,
E: Eq,
{
}
impl<T, E> PartialEq for RocResult<T, E> impl<T, E> PartialEq for RocResult<T, E>
where where
T: PartialEq, T: PartialEq,
@ -111,6 +118,37 @@ where
} }
} }
impl<T, E> Ord for RocResult<T, E>
where
T: Ord,
E: Ord,
{
fn cmp(&self, other: &Self) -> Ordering {
self.as_result_of_refs().cmp(&other.as_result_of_refs())
}
}
impl<T, E> PartialOrd for RocResult<T, E>
where
T: PartialOrd,
E: PartialOrd,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.as_result_of_refs()
.partial_cmp(&other.as_result_of_refs())
}
}
impl<T, E> Hash for RocResult<T, E>
where
T: Hash,
E: Hash,
{
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_result_of_refs().hash(state)
}
}
impl<T, E> Clone for RocResult<T, E> impl<T, E> Clone for RocResult<T, E>
where where
T: Clone, T: Clone,

View file

@ -1,111 +1,10 @@
{ rev ? (builtins.fromJSON (builtins.readFile ./flake.lock)).nodes.nixpkgs.locked.rev (import
, nixpkgsSource ? builtins.fetchTarball { (
url = "https://github.com/nixos/nixpkgs/tarball/${rev}"; let lock = builtins.fromJSON (builtins.readFile ./flake.lock);
sha256 = (builtins.fromJSON (builtins.readFile ./flake.lock)).nodes.nixpkgs.locked.narHash; in fetchTarball {
} url =
, pkgs ? import nixpkgsSource { } "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
, sha256 = lock.nodes.flake-compat.locked.narHash;
}:
# we only use this file to release a nix package, use flake.nix for development
let
desiredRustVersion = (builtins.fromTOML (builtins.readFile (./rust-toolchain.toml))).toolchain.channel;
actualRustVersion = pkgs.rustc;
rustVersionsMatch = pkgs.lib.strings.hasSuffix desiredRustVersion actualRustVersion;
# When updating the zig or llvm version, make sure they stay in sync.
# Also update in flake.nix (TODO: maybe we can use nix code to sync this)
zigPkg = pkgs.zig_0_11;
llvmPkgs = pkgs.llvmPackages_16;
llvmVersion = builtins.splitVersion llvmPkgs.release_version;
llvmMajorMinorStr = builtins.elemAt llvmVersion 0 + builtins.elemAt llvmVersion 1;
# nix does not store libs in /usr/lib or /lib
glibcPath =
if pkgs.stdenv.isLinux then "${pkgs.glibc.out}/lib" else "";
libGccSPath =
if pkgs.stdenv.isLinux then "${pkgs.stdenv.cc.cc.lib}/lib" else "";
in
assert pkgs.lib.assertMsg rustVersionsMatch ''
The rust version changed in rust-toolchain.toml but the rev(commit) in nixpkgs.url in flake.nix was not updated.
1. clone the nixpkgs repo: `git clone --depth 60000 git@github.com:NixOS/nixpkgs.git`
2. `cd nixpkgs`
3. `git log --oneline | rg -A 1 "rustc: <RUST_VERSION_IN_RUST_TOOLCHAIN_TOML>"`
4. Copy the short SHA from the line **after** the commit with the message of for example `rustc: 1.67.1 -> 1.68.0`
5. Find the long SHA by executing `git rev-parse <PASTE>`
6. Copy the long SHA
7. Paste it in place of the old SHA(rev) in flake.nix:inputs:nixpkgs.url
8. execute `nix flake lock --update-input rust-overlay`
'';
pkgs.rustPlatform.buildRustPackage {
pname = "roc";
version = "0.0.1";
src = pkgs.nix-gitignore.gitignoreSource [ ] ./.;
cargoLock = {
lockFile = ./Cargo.lock;
outputHashes = {
"criterion-0.3.5" = "sha256-+FibPQGiR45g28xCHcM0pMN+C+Q8gO8206Wb5fiTy+k=";
"inkwell-0.2.0" = "sha256-VhTapYGonoSQ4hnDoLl4AAgj0BppAhPNA+UPuAJSuAU=";
"plotters-0.3.1" = "sha256-noy/RSjoEPZZbOJTZw1yxGcX5S+2q/7mxnUrzDyxOFw=";
"rustyline-9.1.1" = "sha256-aqQqz6nSp+Qn44gm3jXmmQUO6/fYTx7iLph2tbA24Bs=";
};
};
shellHook = ''
export LLVM_SYS_${llvmMajorMinorStr}_PREFIX="${llvmPkgs.llvm.dev}"
'';
# required for zig
XDG_CACHE_HOME =
"xdg_cache"; # prevents zig AccessDenied error github.com/ziglang/zig/issues/6810
# want to see backtrace in case of failure
RUST_BACKTRACE = 1;
# skip running rust tests, problems:
# building of example platforms requires network: Could not resolve host
# zig AccessDenied error github.com/ziglang/zig/issues/6810
# Once instance has previously been poisoned ??
doCheck = false;
nativeBuildInputs = (with pkgs; [
cmake
git
pkg-config
python3
llvmPkgs.clang
llvmPkgs.llvm.dev
llvmPkgs.bintools-unwrapped # contains lld
zigPkg
]);
buildInputs = (with pkgs;
[
libffi
libxml2
ncurses
zlib
cargo
makeWrapper # necessary for postBuild wrapProgram
] ++ lib.optionals pkgs.stdenv.isDarwin [
pkgs.darwin.apple_sdk.frameworks.AppKit
pkgs.darwin.apple_sdk.frameworks.CoreFoundation
pkgs.darwin.apple_sdk.frameworks.CoreServices
pkgs.darwin.apple_sdk.frameworks.Foundation
pkgs.darwin.apple_sdk.frameworks.Security
]);
# cp: to copy str.zig,list.zig...
# wrapProgram pkgs.stdenv.cc: to make ld available for compiler/build/src/link.rs
postInstall =
if pkgs.stdenv.isLinux then ''
wrapProgram $out/bin/roc --set NIX_GLIBC_PATH ${glibcPath} --set NIX_LIBGCC_S_PATH ${libGccSPath} --prefix PATH : ${
pkgs.lib.makeBinPath [ pkgs.stdenv.cc ]
}
'' else ''
wrapProgram $out/bin/roc --prefix PATH : ${
pkgs.lib.makeBinPath [ pkgs.stdenv.cc ]
}
'';
} }
)
{ src = ./.; }).defaultNix

120
devtools/flake.lock generated Executable file → Normal file
View file

@ -1,21 +1,36 @@
{ {
"nodes": { "nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": { "flake-utils": {
"inputs": { "inputs": {
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1692799911, "lastModified": 1694529238,
"narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=", "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44", "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "numtide", "id": "flake-utils",
"repo": "flake-utils", "type": "indirect"
"type": "github"
} }
}, },
"flake-utils_2": { "flake-utils_2": {
@ -23,44 +38,11 @@
"systems": "systems_2" "systems": "systems_2"
}, },
"locked": { "locked": {
"lastModified": 1681202837, "lastModified": 1694529238,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401", "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_3": {
"locked": {
"lastModified": 1659877975,
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_4": {
"inputs": {
"systems": "systems_3"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -71,18 +53,21 @@
}, },
"nixgl": { "nixgl": {
"inputs": { "inputs": {
"flake-utils": "flake-utils_3", "flake-utils": [
"roc",
"flake-utils"
],
"nixpkgs": [ "nixpkgs": [
"roc", "roc",
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1676383589, "lastModified": 1685908677,
"narHash": "sha256-KCkWZXCjH+C4Kn7fUGSrEl5btk+sERHhZueSsvVbPWc=", "narHash": "sha256-E4zUPEUFyVWjVm45zICaHRpfGepfkE9Z2OECV9HXfA4=",
"owner": "guibou", "owner": "guibou",
"repo": "nixGL", "repo": "nixGL",
"rev": "c917918ab9ebeee27b0dd657263d3f57ba6bb8ad", "rev": "489d6b095ab9d289fe11af0219a9ff00fe87c7c5",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -93,35 +78,36 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1690279121, "lastModified": 1693140250,
"narHash": "sha256-XoPGhV1UJQPue6RiehAu7lQwKss3J1B/K0QtVOMD83A=", "narHash": "sha256-URyIDETtu1bbxcSl83xp7irEV04dPEgj7O3LjHcD1Sk=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "821c72743ceae44bdd09718d47cab98fd5fd90af", "rev": "676fe5e01b9a41fa14aaa48d87685677664104b1",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "821c72743ceae44bdd09718d47cab98fd5fd90af", "rev": "676fe5e01b9a41fa14aaa48d87685677664104b1",
"type": "github" "type": "github"
} }
}, },
"roc": { "roc": {
"inputs": { "inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils_2", "flake-utils": "flake-utils_2",
"nixgl": "nixgl", "nixgl": "nixgl",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay"
}, },
"locked": { "locked": {
"lastModified": 1694000770, "lastModified": 1700241573,
"narHash": "sha256-92bAbPmwXxD6rwaAViG5O9r91ZBh9bqaZhM3egPCjuw=", "narHash": "sha256-+hjY1FieVbF8jvRE3Cvo8GBWh1OlFrF+QDFF8OlWM/s=",
"path": "/home/anton/gitrepos/roc", "path": "/home/username/gitrepos/JRMurr/roc",
"type": "path" "type": "path"
}, },
"original": { "original": {
"path": "/home/anton/gitrepos/roc", "path": "/home/username/gitrepos/JRMurr/roc",
"type": "path" "type": "path"
} }
}, },
@ -133,18 +119,21 @@
}, },
"rust-overlay": { "rust-overlay": {
"inputs": { "inputs": {
"flake-utils": "flake-utils_4", "flake-utils": [
"roc",
"flake-utils"
],
"nixpkgs": [ "nixpkgs": [
"roc", "roc",
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1690252178, "lastModified": 1695694299,
"narHash": "sha256-9oEz822bvbHobfCUjJLDor2BqW3I5tycIauzDlzOALY=", "narHash": "sha256-0CucEiOZzOVHwmGDJKNXLj7aDYOqbRtqChp9nbGrh18=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "8d64353ca827002fb8459e44d49116c78d868eba", "rev": "c89a55d2d91cf55234466934b25deeffa365188a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -182,21 +171,6 @@
"repo": "default", "repo": "default",
"type": "github" "type": "github"
} }
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View file

@ -2,6 +2,7 @@ args
countdown countdown
echo echo
effects effects
file
form form
tui tui
http-get http-get

View file

@ -1,9 +1,9 @@
app "args" app "args"
packages { pf: "cli-platform/main.roc" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" }
imports [pf.Stdout, pf.Arg, pf.Task.{ Task }, pf.Process] imports [pf.Stdout, pf.Arg, pf.Task.{ Task }]
provides [main] to pf provides [main] to pf
main : Task {} [] main : Task {} I32
main = main =
args <- Arg.list |> Task.await args <- Arg.list |> Task.await
parser = parser =
@ -57,7 +57,7 @@ main =
Err helpMenu -> Err helpMenu ->
{} <- Stdout.line helpMenu |> Task.await {} <- Stdout.line helpMenu |> Task.await
Process.exit 1 Task.err 1
runCmd = \cmd -> runCmd = \cmd ->
when cmd is when cmd is

File diff suppressed because it is too large Load diff

View file

@ -1,25 +0,0 @@
[package]
name = "host"
authors = ["The Roc Contributors"]
edition = "2021"
license = "UPL-1.0"
version = "0.0.1"
links = "app"
[lib]
name = "host"
path = "src/lib.rs"
crate-type = ["staticlib", "rlib"]
[[bin]]
name = "host"
path = "src/main.rs"
[dependencies]
backtrace = "0.3"
libc = "0.2"
reqwest = { version = "=0.11.20", default-features = false, features = ["blocking", "rustls-tls"] }
roc_std = { path = "../../../crates/roc_std" }
[workspace]

View file

@ -1,25 +0,0 @@
interface Dir
exposes [ReadErr, DeleteErr, DirEntry, deleteEmptyDir, deleteRecursive, list]
imports [Effect, Task.{ Task }, InternalTask, Path.{ Path }, InternalPath, InternalDir]
ReadErr : InternalDir.ReadErr
DeleteErr : InternalDir.DeleteErr
DirEntry : InternalDir.DirEntry
## Lists the files and directories inside the directory.
list : Path -> Task (List Path) [DirReadErr Path ReadErr]
list = \path ->
effect = Effect.map (Effect.dirList (InternalPath.toBytes path)) \result ->
when result is
Ok entries -> Ok (List.map entries InternalPath.fromOsBytes)
Err err -> Err (DirReadErr path err)
InternalTask.fromEffect effect
## Deletes a directory if it's empty.
deleteEmptyDir : Path -> Task {} [DirDeleteErr Path DeleteErr]
## Recursively deletes the directory as well as all files and directories inside it.
deleteRecursive : Path -> Task {} [DirDeleteErr Path DeleteErr]

View file

@ -1,55 +0,0 @@
hosted Effect
exposes [
Effect,
after,
args,
map,
always,
forever,
loop,
dirList,
envDict,
envVar,
cwd,
setCwd,
exePath,
stdoutLine,
stdoutWrite,
stderrLine,
stderrWrite,
stdinLine,
sendRequest,
fileReadBytes,
fileDelete,
fileWriteUtf8,
fileWriteBytes,
processExit,
]
imports [InternalHttp.{ Request, Response }, InternalFile, InternalDir]
generates Effect with [after, map, always, forever, loop]
stdoutLine : Str -> Effect {}
stdoutWrite : Str -> Effect {}
stderrLine : Str -> Effect {}
stderrWrite : Str -> Effect {}
stdinLine : Effect Str
fileWriteBytes : List U8, List U8 -> Effect (Result {} InternalFile.WriteErr)
fileWriteUtf8 : List U8, Str -> Effect (Result {} InternalFile.WriteErr)
fileDelete : List U8 -> Effect (Result {} InternalFile.WriteErr)
fileReadBytes : List U8 -> Effect (Result (List U8) InternalFile.ReadErr)
dirList : List U8 -> Effect (Result (List (List U8)) InternalDir.ReadErr)
envDict : Effect (Dict Str Str)
envVar : Str -> Effect (Result Str {})
exePath : Effect (Result (List U8) {})
setCwd : List U8 -> Effect (Result {} {})
processExit : U8 -> Effect {}
# If we encounter a Unicode error in any of the args, it will be replaced with
# the Unicode replacement char where necessary.
args : Effect (List Str)
cwd : Effect (List U8)
sendRequest : Box Request -> Effect Response

View file

@ -1,123 +0,0 @@
interface Env
exposes [cwd, dict, var, decode, exePath, setCwd]
imports [Task.{ Task }, Path.{ Path }, InternalPath, Effect, InternalTask, EnvDecoding]
## Reads the [current working directory](https://en.wikipedia.org/wiki/Working_directory)
## from the environment. File operations on relative [Path]s are relative to this directory.
cwd : Task Path [CwdUnavailable]
cwd =
effect = Effect.map Effect.cwd \bytes ->
if List.isEmpty bytes then
Err CwdUnavailable
else
Ok (InternalPath.fromArbitraryBytes bytes)
InternalTask.fromEffect effect
## Sets the [current working directory](https://en.wikipedia.org/wiki/Working_directory)
## in the environment. After changing it, file operations on relative [Path]s will be relative
## to this directory.
setCwd : Path -> Task {} [InvalidCwd]
setCwd = \path ->
Effect.setCwd (InternalPath.toBytes path)
|> Effect.map (\result -> Result.mapErr result \{} -> InvalidCwd)
|> InternalTask.fromEffect
## Gets the path to the currently-running executable.
exePath : Task Path [ExePathUnavailable]
exePath =
effect =
Effect.map Effect.exePath \result ->
when result is
Ok bytes -> Ok (InternalPath.fromOsBytes bytes)
Err {} -> Err ExePathUnavailable
InternalTask.fromEffect effect
## Reads the given environment variable.
##
## If the value is invalid Unicode, the invalid parts will be replaced with the
## [Unicode replacement character](https://unicode.org/glossary/#replacement_character)
## (`<60>`).
var : Str -> Task Str [VarNotFound]
var = \name ->
Effect.envVar name
|> Effect.map (\result -> Result.mapErr result \{} -> VarNotFound)
|> InternalTask.fromEffect
## Reads the given environment variable and attempts to decode it.
##
## The type being decoded into will be determined by type inference. For example,
## if this ends up being used like a `Task U16 …` then the environment variable
## will be decoded as a string representation of a `U16`.
##
## getU16Var : Str -> Task U16 [VarNotFound, DecodeErr DecodeError] [Read [Env]]
## getU16Var = \var -> Env.decode var
## # If the environment contains a variable NUM_THINGS=123, then calling
## # (getU16Var "NUM_THINGS") would return a task which succeeds with the U16 number 123.
## #
## # However, if the NUM_THINGS environment variable was set to 1234567, then
## # (getU16Var "NUM_THINGS") would fail because that number is too big to fit in a U16.
##
## Supported types:
## - strings
## - numbers, as long as they contain only numeric digits, up to one `.`, and an optional `-` at the front for negative numbers
## - comma-separated lists (of either strings or numbers), as long as there are no spaces after the commas
##
## Trying to decode into any other types will always fail with a `DecodeErr`.
decode : Str -> Task val [VarNotFound, DecodeErr DecodeError] where val implements Decoding
decode = \name ->
Effect.envVar name
|> Effect.map
(
\result ->
result
|> Result.mapErr (\{} -> VarNotFound)
|> Result.try
(\varStr ->
Decode.fromBytes (Str.toUtf8 varStr) (EnvDecoding.format {})
|> Result.mapErr (\_ -> DecodeErr TooShort)))
|> InternalTask.fromEffect
## Reads all the process's environment variables into a [Dict].
##
## If any key or value contains invalid Unicode, the [Unicode replacement character](https://unicode.org/glossary/#replacement_character)
## (`<60>`) will be used in place of any parts of keys or values that are invalid Unicode.
dict : Task (Dict Str Str) *
dict =
Effect.envDict
|> Effect.map Ok
|> InternalTask.fromEffect
# ## Walks over the process's environment variables as key-value arguments to the walking function.
# ##
# ## Env.walk "Vars:\n" \state, key, value ->
# ## "- \(key): \(value)\n"
# ## # This might produce a string such as:
# ## #
# ## # """
# ## # Vars:
# ## # - FIRST_VAR: first value
# ## # - SECOND_VAR: second value
# ## # - THIRD_VAR: third value
# ## #
# ## # """
# ##
# ## If any key or value contains invalid Unicode, the [Unicode replacement character](https://unicode.org/glossary/#replacement_character)
# ## (`<60>`) will be used in place of any parts of keys or values that are invalid Unicode.
# walk : state, (state, Str, Str -> state) -> Task state [NonUnicodeEnv state] [Read [Env]]
# walk = \state, walker ->
# Effect.envWalk state walker
# |> InternalTask.fromEffect
# TODO could potentially offer something like walkNonUnicode which takes (state, Result Str Str, Result Str Str) so it
# tells you when there's invalid Unicode. This is both faster than (and would give you more accurate info than)
# using regular `walk` and searching for the presence of the replacement character in the resulting
# strings. However, it's unclear whether anyone would use it. What would the use case be? Reporting
# an error that the provided command-line args weren't valid Unicode? Does that still happen these days?
# TODO need to figure out clear rules for how to convert from camelCase to SCREAMING_SNAKE_CASE.
# Note that all the env vars decoded in this way become effectively *required* vars, since if any
# of them are missing, decoding will fail. For this reason, it might make sense to use this to
# decode all the required vars only, and then decode the optional ones separately some other way.
# Alternatively, it could make sense to have some sort of tag union convention here, e.g.
# if decoding into a tag union of [Present val, Missing], then it knows what to do.
# decodeAll : Task val [] [EnvDecodingFailed Str] [Env] where val implements Decoding

View file

@ -1,103 +0,0 @@
interface EnvDecoding exposes [EnvFormat, format] imports []
EnvFormat := {} implements [
DecoderFormatting {
u8: envU8,
u16: envU16,
u32: envU32,
u64: envU64,
u128: envU128,
i8: envI8,
i16: envI16,
i32: envI32,
i64: envI64,
i128: envI128,
f32: envF32,
f64: envF64,
dec: envDec,
bool: envBool,
string: envString,
list: envList,
record: envRecord,
tuple: envTuple,
},
]
format : {} -> EnvFormat
format = \{} -> @EnvFormat {}
decodeBytesToNum = \bytes, transformer ->
when Str.fromUtf8 bytes is
Ok s ->
when transformer s is
Ok n -> { result: Ok n, rest: [] }
Err _ -> { result: Err TooShort, rest: bytes }
Err _ -> { result: Err TooShort, rest: bytes }
envU8 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU8
envU16 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU16
envU32 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU32
envU64 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU64
envU128 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toU128
envI8 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI8
envI16 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI16
envI32 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI32
envI64 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI64
envI128 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toI128
envF32 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toF32
envF64 = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toF64
envDec = Decode.custom \bytes, @EnvFormat {} -> decodeBytesToNum bytes Str.toDec
envBool = Decode.custom \bytes, @EnvFormat {} ->
when Str.fromUtf8 bytes is
Ok "true" -> { result: Ok Bool.true, rest: [] }
Ok "false" -> { result: Ok Bool.false, rest: [] }
_ -> { result: Err TooShort, rest: bytes }
envString = Decode.custom \bytes, @EnvFormat {} ->
when Str.fromUtf8 bytes is
Ok s -> { result: Ok s, rest: [] }
Err _ -> { result: Err TooShort, rest: bytes }
envList = \decodeElem -> Decode.custom \bytes, @EnvFormat {} ->
# Per our supported methods of decoding, this is either a list of strings or
# a list of numbers; in either case, the list of bytes must be Utf-8
# decodable. So just parse it as a list of strings and pass each chunk to
# the element decoder. By construction, our element decoders expect to parse
# a whole list of bytes anyway.
decodeElems = \allBytes, accum ->
{ toParse, remainder } =
when List.splitFirst allBytes (Num.toU8 ',') is
Ok { before, after } ->
{ toParse: before, remainder: Some after }
Err NotFound ->
{ toParse: allBytes, remainder: None }
when Decode.decodeWith toParse decodeElem (@EnvFormat {}) is
{ result, rest } ->
when result is
Ok val ->
when remainder is
Some restBytes -> decodeElems restBytes (List.append accum val)
None -> Done (List.append accum val)
Err e -> Errored e rest
when decodeElems bytes [] is
Errored e rest -> { result: Err e, rest }
Done vals ->
{ result: Ok vals, rest: [] }
# TODO: we must currently annotate the arrows here so that the lambda sets are
# exercised, and the solver can find an ambient lambda set for the
# specialization.
envRecord : _, (_, _ -> [Keep (Decoder _ _), Skip]), (_ -> _) -> Decoder _ _
envRecord = \_initialState, _stepField, _finalizer -> Decode.custom \bytes, @EnvFormat {} ->
{ result: Err TooShort, rest: bytes }
# TODO: we must currently annotate the arrows here so that the lambda sets are
# exercised, and the solver can find an ambient lambda set for the
# specialization.
envTuple : _, (_, _ -> [Next (Decoder _ _), TooLong]), (_ -> _) -> Decoder _ _
envTuple = \_initialState, _stepElem, _finalizer -> Decode.custom \bytes, @EnvFormat {} ->
{ result: Err TooShort, rest: bytes }

View file

@ -1,144 +0,0 @@
interface File
exposes [ReadErr, WriteErr, write, writeUtf8, writeBytes, readUtf8, readBytes, delete]
imports [Task.{ Task }, InternalTask, InternalFile, Path.{ Path }, InternalPath, Effect.{ Effect }]
ReadErr : InternalFile.ReadErr
WriteErr : InternalFile.WriteErr
## Encodes a value using the given `EncodingFormat` and writes it to a file.
##
## For example, suppose you have a [JSON](https://en.wikipedia.org/wiki/JSON)
## `EncodingFormat` named `Json.toCompactUtf8`. Then you can use that format
## to write some encodable data to a file as JSON, like so:
##
## File.write
## (Path.fromStr "output.json")
## { some: "json stuff" }
## Json.toCompactUtf8
## # Writes the following to the file `output.json`:
## #
## # {"some":"json stuff"}
##
## If writing to the file fails, for example because of a file permissions issue,
## the task fails with [WriteErr].
##
## This opens the file first and closes it after writing to it.
##
## To write unformatted bytes to a file, you can use [File.writeBytes] instead.
write : Path, val, fmt -> Task {} [FileWriteErr Path WriteErr] where val implements Encode.Encoding, fmt implements Encode.EncoderFormatting
write = \path, val, fmt ->
bytes = Encode.toBytes val fmt
# TODO handle encoding errors here, once they exist
writeBytes path bytes
## Writes bytes to a file.
##
## # Writes the bytes 1, 2, 3 to the file `myfile.dat`.
## File.writeBytes (Path.fromStr "myfile.dat") [1, 2, 3]
##
## This opens the file first and closes it after writing to it.
##
## To format data before writing it to a file, you can use [File.write] instead.
writeBytes : Path, List U8 -> Task {} [FileWriteErr Path WriteErr]
writeBytes = \path, bytes ->
toWriteTask path \pathBytes -> Effect.fileWriteBytes pathBytes bytes
## Writes a [Str] to a file, encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8).
##
## # Writes "Hello!" encoded as UTF-8 to the file `myfile.txt`.
## File.writeUtf8 (Path.fromStr "myfile.txt") "Hello!"
##
## This opens the file first and closes it after writing to it.
##
## To write unformatted bytes to a file, you can use [File.writeBytes] instead.
writeUtf8 : Path, Str -> Task {} [FileWriteErr Path WriteErr]
writeUtf8 = \path, str ->
toWriteTask path \bytes -> Effect.fileWriteUtf8 bytes str
## Deletes a file from the filesystem.
##
## # Deletes the file named
## File.delete (Path.fromStr "myfile.dat") [1, 2, 3]
##
## Note that this does not securely erase the file's contents from disk; instead, the operating
## system marks the space it was occupying as safe to write over in the future. Also, the operating
## system may not immediately mark the space as free; for example, on Windows it will wait until
## the last file handle to it is closed, and on UNIX, it will not remove it until the last
## [hard link](https://en.wikipedia.org/wiki/Hard_link) to it has been deleted.
##
## This performs a [`DeleteFile`](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-deletefile)
## on Windows and [`unlink`](https://en.wikipedia.org/wiki/Unlink_(Unix)) on UNIX systems.
##
## On Windows, this will fail when attempting to delete a readonly file; the file's
## readonly permission must be disabled before it can be successfully deleted.
delete : Path -> Task {} [FileWriteErr Path WriteErr]
delete = \path ->
toWriteTask path \bytes -> Effect.fileDelete bytes
## Reads all the bytes in a file.
##
## # Read all the bytes in `myfile.txt`.
## File.readBytes (Path.fromStr "myfile.txt")
##
## This opens the file first and closes it after reading its contents.
##
## To read and decode data from a file, you can use `File.read` instead.
readBytes : Path -> Task (List U8) [FileReadErr Path ReadErr]
readBytes = \path ->
toReadTask path \bytes -> Effect.fileReadBytes bytes
## Reads a [Str] from a file containing [UTF-8](https://en.wikipedia.org/wiki/UTF-8)-encoded text.
##
## # Reads UTF-8 encoded text into a `Str` from the file `myfile.txt`.
## File.readUtf8 (Path.fromStr "myfile.txt")
##
## This opens the file first and closes it after writing to it.
## The task will fail with `FileReadUtf8Err` if the given file contains invalid UTF-8.
##
## To read unformatted bytes from a file, you can use [File.readBytes] instead.
readUtf8 :
Path
-> Task Str [FileReadErr Path ReadErr, FileReadUtf8Err Path _]
readUtf8 = \path ->
effect = Effect.map (Effect.fileReadBytes (InternalPath.toBytes path)) \result ->
when result is
Ok bytes ->
Str.fromUtf8 bytes
|> Result.mapErr \err -> FileReadUtf8Err path err
Err readErr -> Err (FileReadErr path readErr)
InternalTask.fromEffect effect
# read :
# Path,
# fmt
# -> Task
# Str
# [FileReadErr Path ReadErr, FileReadDecodeErr Path [Leftover (List U8)]Decode.DecodeError ]
# [Read [File]]
# where val implements Decode.Decoding, fmt implements Decode.DecoderFormatting
# read = \path, fmt ->
# effect = Effect.map (Effect.fileReadBytes (InternalPath.toBytes path)) \result ->
# when result is
# Ok bytes ->
# when Decode.fromBytes bytes fmt is
# Ok val -> Ok val
# Err decodingErr -> Err (FileReadDecodeErr decodingErr)
# Err readErr -> Err (FileReadErr readErr)
# InternalTask.fromEffect effect
toWriteTask : Path, (List U8 -> Effect (Result ok err)) -> Task ok [FileWriteErr Path err]
toWriteTask = \path, toEffect ->
InternalPath.toBytes path
|> toEffect
|> InternalTask.fromEffect
|> Task.mapFail \err -> FileWriteErr path err
toReadTask : Path, (List U8 -> Effect (Result ok err)) -> Task ok [FileReadErr Path err]
toReadTask = \path, toEffect ->
InternalPath.toBytes path
|> toEffect
|> InternalTask.fromEffect
|> Task.mapFail \err -> FileReadErr path err

View file

@ -1,32 +0,0 @@
interface FileMetadata
exposes [FileMetadata, bytes, type, isReadonly, mode]
imports []
# Design note: this is an opaque type rather than a type alias so that
# we can add new operating system info if new OS releases introduce them,
# as a backwards-compatible change.
FileMetadata := {
bytes : U64,
type : [File, Dir, Symlink],
isReadonly : Bool,
mode : [Unix U32, NonUnix],
}
bytes : FileMetadata -> U64
bytes = \@FileMetadata info -> info.bytes
isReadonly : FileMetadata -> Bool
isReadonly = \@FileMetadata info -> info.isReadonly
type : FileMetadata -> [File, Dir, Symlink]
type = \@FileMetadata info -> info.type
mode : FileMetadata -> [Unix U32, NonUnix]
mode = \@FileMetadata info -> info.mode
# TODO need to create a Time module and return something like Time.Utc here.
# lastModified : FileMetadata -> Utc
# TODO need to create a Time module and return something like Time.Utc here.
# lastAccessed : FileMetadata -> Utc
# TODO need to create a Time module and return something like Time.Utc here.
# created : FileMetadata -> Utc

View file

@ -1,111 +0,0 @@
interface Http
exposes [
Request,
Method,
Header,
TimeoutConfig,
Body,
Response,
Metadata,
Error,
header,
emptyBody,
bytesBody,
stringBody,
handleStringResponse,
defaultRequest,
errorToString,
send,
]
imports [Effect, InternalTask, Task.{ Task }, InternalHttp]
Request : InternalHttp.Request
Method : InternalHttp.Method
Header : InternalHttp.Header
TimeoutConfig : InternalHttp.TimeoutConfig
Body : InternalHttp.Body
Response : InternalHttp.Response
Metadata : InternalHttp.Metadata
Error : InternalHttp.Error
defaultRequest : Request
defaultRequest = {
method: Get,
headers: [],
url: "",
body: Http.emptyBody,
timeout: NoTimeout,
}
## An HTTP header for configuring requests. See a bunch of common headers
## [here](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields).
##
header : Str, Str -> Header
header =
Header
emptyBody : Body
emptyBody =
EmptyBody
bytesBody : [MimeType Str], List U8 -> Body
bytesBody =
Body
stringBody : [MimeType Str], Str -> Body
stringBody = \mimeType, str ->
Body mimeType (Str.toUtf8 str)
# jsonBody : a -> Body where a implements Encoding
# jsonBody = \val ->
# Body (MimeType "application/json") (Encode.toBytes val TotallyNotJson.format)
#
# multiPartBody : List Part -> Body
# multiPartBody = \parts ->
# boundary = "7MA4YWxkTrZu0gW" # TODO: what's this exactly? a hash of all the part bodies?
# beforeName = Str.toUtf8 "-- \(boundary)\r\nContent-Disposition: form-data; name=\""
# afterName = Str.toUtf8 "\"\r\n"
# appendPart = \buffer, Part name partBytes ->
# buffer
# |> List.concat beforeName
# |> List.concat (Str.toUtf8 name)
# |> List.concat afterName
# |> List.concat partBytes
# bodyBytes = List.walk parts [] appendPart
# Body (MimeType "multipart/form-data;boundary=\"\(boundary)\"") bodyBytes
# bytesPart : Str, List U8 -> Part
# bytesPart =
# Part
# stringPart : Str, Str -> Part
# stringPart = \name, str ->
# Part name (Str.toUtf8 str)
handleStringResponse : Response -> Result Str Error
handleStringResponse = \response ->
when response is
BadRequest err -> Err (BadRequest err)
Timeout -> Err Timeout
NetworkError -> Err NetworkError
BadStatus metadata _ -> Err (BadStatus metadata.statusCode)
GoodStatus _ bodyBytes ->
Str.fromUtf8 bodyBytes
|> Result.mapErr
\BadUtf8 _ pos ->
position = Num.toStr pos
BadBody "Invalid UTF-8 at byte offset \(position)"
errorToString : Error -> Str
errorToString = \err ->
when err is
BadRequest e -> "Invalid Request: \(e)"
Timeout -> "Request timed out"
NetworkError -> "Network error"
BadStatus code -> Str.concat "Request failed with status " (Num.toStr code)
BadBody details -> Str.concat "Request failed. Invalid body. " details
send : Request -> Task Str Error
send = \req ->
# TODO: Fix our C ABI codegen so that we don't this Box.box heap allocation
Effect.sendRequest (Box.box req)
|> Effect.map handleStringResponse
|> InternalTask.fromEffect

View file

@ -1,41 +0,0 @@
interface InternalDir
exposes [ReadErr, DeleteErr, DirEntry]
imports [FileMetadata.{ FileMetadata }, Path.{ Path }]
DirEntry : {
path : Path,
type : [File, Dir, Symlink],
metadata : FileMetadata,
}
ReadErr : [
NotFound,
Interrupted,
InvalidFilename,
PermissionDenied,
TooManySymlinks, # aka FilesystemLoop
TooManyHardlinks,
TimedOut,
StaleNetworkFileHandle,
NotADirectory,
OutOfMemory,
Unsupported,
Unrecognized I32 Str,
]
DeleteErr : [
NotFound,
Interrupted,
InvalidFilename,
PermissionDenied,
TooManySymlinks, # aka FilesystemLoop
TooManyHardlinks,
TimedOut,
StaleNetworkFileHandle,
NotADirectory,
ReadOnlyFilesystem,
DirectoryNotEmpty,
OutOfMemory,
Unsupported,
Unrecognized I32 Str,
]

View file

@ -1,71 +0,0 @@
interface InternalFile
exposes [ReadErr, WriteErr]
imports []
ReadErr : [
NotFound,
Interrupted,
InvalidFilename,
PermissionDenied,
TooManySymlinks, # aka FilesystemLoop
TooManyHardlinks,
TimedOut,
StaleNetworkFileHandle,
OutOfMemory,
Unsupported,
Unrecognized I32 Str,
]
WriteErr : [
NotFound,
Interrupted,
InvalidFilename,
PermissionDenied,
TooManySymlinks, # aka FilesystemLoop
TooManyHardlinks,
TimedOut,
StaleNetworkFileHandle,
ReadOnlyFilesystem,
AlreadyExists, # can this happen here?
WasADirectory,
WriteZero, # TODO come up with a better name for this, or roll it into another error tag
StorageFull,
FilesystemQuotaExceeded, # can this be combined with StorageFull?
FileTooLarge,
ResourceBusy,
ExecutableFileBusy,
OutOfMemory,
Unsupported,
Unrecognized I32 Str,
]
# DirReadErr : [
# NotFound,
# Interrupted,
# InvalidFilename,
# PermissionDenied,
# TooManySymlinks, # aka FilesystemLoop
# TooManyHardlinks,
# TimedOut,
# StaleNetworkFileHandle,
# NotADirectory,
# OutOfMemory,
# Unsupported,
# Unrecognized I32 Str,
# ]
# RmDirError : [
# NotFound,
# Interrupted,
# InvalidFilename,
# PermissionDenied,
# TooManySymlinks, # aka FilesystemLoop
# TooManyHardlinks,
# TimedOut,
# StaleNetworkFileHandle,
# NotADirectory,
# ReadOnlyFilesystem,
# DirectoryNotEmpty,
# OutOfMemory,
# Unsupported,
# Unrecognized I32 Str,
# ]

View file

@ -1,48 +0,0 @@
interface InternalHttp
exposes [Request, Method, Header, TimeoutConfig, Part, Body, Response, Metadata, Error]
imports []
Request : {
method : Method,
headers : List Header,
url : Str,
body : Body,
timeout : TimeoutConfig,
}
Method : [Options, Get, Post, Put, Delete, Head, Trace, Connect, Patch]
Header : [Header Str Str]
# Name is distinguished from the Timeout tag used in Response and Error
TimeoutConfig : [TimeoutMilliseconds U64, NoTimeout]
Part : [Part Str (List U8)]
Body : [
Body [MimeType Str] (List U8),
EmptyBody,
]
Response : [
BadRequest Str,
Timeout,
NetworkError,
BadStatus Metadata (List U8),
GoodStatus Metadata (List U8),
]
Metadata : {
url : Str,
statusCode : U16,
statusText : Str,
headers : List Header,
}
Error : [
BadRequest Str,
Timeout,
NetworkError,
BadStatus U16,
BadBody Str,
]

View file

@ -1,73 +0,0 @@
interface InternalPath
exposes [
UnwrappedPath,
InternalPath,
wrap,
unwrap,
toBytes,
fromArbitraryBytes,
fromOsBytes,
]
imports []
InternalPath := UnwrappedPath
UnwrappedPath : [
# We store these separately for two reasons:
# 1. If I'm calling an OS API, passing a path I got from the OS is definitely safe.
# However, passing a Path I got from a RocStr might be unsafe; it may contain \0
# characters, which would result in the operation happening on a totally different
# path. As such, we need to check for \0s and fail without calling the OS API if we
# find one in the path.
# 2. If I'm converting the Path to a Str, doing that conversion on a Path that was
# created from a RocStr needs no further processing. However, if it came from the OS,
# then we need to know what charset to assume it had, in order to decode it properly.
# These come from the OS (e.g. when reading a directory, calling `canonicalize`,
# or reading an environment variable - which, incidentally, are nul-terminated),
# so we know they are both nul-terminated and do not contain interior nuls.
# As such, they can be passed directly to OS APIs.
#
# Note that the nul terminator byte is right after the end of the length (into the
# unused capacity), so this can both be compared directly to other `List U8`s that
# aren't nul-terminated, while also being able to be passed directly to OS APIs.
FromOperatingSystem (List U8),
# These come from userspace (e.g. Path.fromBytes), so they need to be checked for interior
# nuls and then nul-terminated before the host can pass them to OS APIs.
ArbitraryBytes (List U8),
# This was created as a RocStr, so it might have interior nul bytes but it's definitely UTF-8.
# That means we can `toStr` it trivially, but have to validate before sending it to OS
# APIs that expect a nul-terminated `char*`.
#
# Note that both UNIX and Windows APIs will accept UTF-8, because on Windows the host calls
# `_setmbcp(_MB_CP_UTF8);` to set the process's Code Page to UTF-8 before doing anything else.
# See https://docs.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page#-a-vs--w-apis
# and https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setmbcp?view=msvc-170
# for more details on the UTF-8 Code Page in Windows.
FromStr Str,
]
wrap : UnwrappedPath -> InternalPath
wrap = @InternalPath
unwrap : InternalPath -> UnwrappedPath
unwrap = \@InternalPath raw -> raw
## TODO do this in the host, and iterate over the Str
## bytes when possible instead of always converting to
## a heap-allocated List.
toBytes : InternalPath -> List U8
toBytes = \@InternalPath path ->
when path is
FromOperatingSystem bytes -> bytes
ArbitraryBytes bytes -> bytes
FromStr str -> Str.toUtf8 str
fromArbitraryBytes : List U8 -> InternalPath
fromArbitraryBytes = \bytes ->
@InternalPath (ArbitraryBytes bytes)
fromOsBytes : List U8 -> InternalPath
fromOsBytes = \bytes ->
@InternalPath (FromOperatingSystem bytes)

View file

@ -1,17 +0,0 @@
interface InternalTask
exposes [Task, fromEffect, toEffect, succeed, fail]
imports [Effect.{ Effect }]
Task ok err := Effect (Result ok err)
succeed : ok -> Task ok *
succeed = \ok -> @Task (Effect.always (Ok ok))
fail : err -> Task * err
fail = \err -> @Task (Effect.always (Err err))
fromEffect : Effect (Result ok err) -> Task ok err
fromEffect = \effect -> @Task effect
toEffect : Task ok err -> Effect (Result ok err)
toEffect = \@Task effect -> effect

View file

@ -1,323 +0,0 @@
interface Path
exposes [
Path,
PathComponent,
CanonicalizeErr,
WindowsRoot,
# toComponents,
# walkComponents,
display,
fromStr,
fromBytes,
withExtension,
]
imports [InternalPath.{ InternalPath }]
## You can canonicalize a [Path] using [Path.canonicalize].
##
## Comparing canonical paths is often more reliable than comparing raw ones.
## For example, `Path.fromStr "foo/bar/../baz" == Path.fromStr "foo/baz"` will return `Bool.false`,
## because those are different paths even though their canonical equivalents would be equal.
##
## Also note that canonicalization reads from the file system (in order to resolve symbolic
## links, and to convert relative paths into absolute ones). This means that it is not only
## a [Task] (which can fail), but also that running [canonicalize] on the same [Path] twice
## may give different answers. An example of a way this could happen is if a symbolic link
## in the path changed on disk to point somewhere else in between the two [canonicalize] calls.
##
## Similarly, remember that canonical paths are not guaranteed to refer to a valid file. They
## might have referred to one when they were canonicalized, but that file may have moved or
## been deleted since the canonical path was created. So you might [canonicalize] a [Path],
## and then immediately use that [Path] to read a file from disk, and still get back an error
## because something relevant changed on the filesystem between the two operations.
##
## Also note that different filesystems have different rules for syntactically valid paths.
## Suppose you're on a machine with two disks, one formatted as ext4 and another as FAT32.
## It's possible to list the contents of a directory on the ext4 disk, and get a [CanPath] which
## is valid on that disk, but invalid on the other disk. One way this could happen is if the
## directory on the ext4 disk has a filename containing a `:` in it. `:` is allowed in ext4
## paths but is considered invalid in FAT32 paths.
Path : InternalPath
CanonicalizeErr a : [
PathCanonicalizeErr {},
]a
## Note that the path may not be valid depending on the filesystem where it is used.
## For example, paths containing `:` are valid on ext4 and NTFS filesystems, but not
## on FAT ones. So if you have multiple disks on the same machine, but they have
## different filesystems, then this path could be valid on one but invalid on another!
##
## It's safest to assume paths are invalid (even syntactically) until given to an operation
## which uses them to open a file. If that operation succeeds, then the path was valid
## (at the time). Otherwise, error handling can happen for that operation rather than validating
## up front for a false sense of security (given symlinks, parts of a path being renamed, etc.).
fromStr : Str -> Path
fromStr = \str ->
FromStr str
|> InternalPath.wrap
## Not all filesystems use Unicode paths. This function can be used to create a path which
## is not valid Unicode (like a Roc [Str] is), but which is valid for a particular filesystem.
##
## Note that if the list contains any `0` bytes, sending this path to any file operations
## (e.g. `File.read` or `WriteStream.openPath`) will fail.
fromBytes : List U8 -> Path
fromBytes = \bytes ->
ArbitraryBytes bytes
|> InternalPath.wrap
## Note that canonicalization reads from the file system (in order to resolve symbolic
## links, and to convert relative paths into absolute ones). This means that it is not only
## a [Task] (which can fail), but also that running [canonicalize] on the same [Path] twice
## may give different answers. An example of a way this could happen is if a symbolic link
## in the path changed on disk to point somewhere else in between the two [canonicalize] calls.
##
## Returns an effect type of `[Metadata, Cwd]` because it can resolve symbolic links
## and can access the current working directory by turning a relative path into an
## absolute one (which can prepend the absolute path of the current working directory to
## the relative path).
# canonicalize : Path -> Task Path (CanonicalizeErr *) [Metadata, Read [Env]]*
## Unfortunately, operating system paths do not include information about which charset
## they were originally encoded with. It's most common (but not guaranteed) that they will
## have been encoded with the same charset as the operating system's curent locale (which
## typically does not change after it is set during installation of the OS), so
## this should convert a [Path] to a valid string as long as the path was created
## with the given `Charset`. (Use `Env.charset` to get the current system charset.)
##
## For a conversion to [Str] that is lossy but does not return a [Result], see
## [display].
# toInner : Path -> [Str Str, Bytes (List U8)]
## Assumes a path is encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8),
## and converts it to a string using `Str.display`.
##
## This conversion is lossy because the path may contain invalid UTF-8 bytes. If that happens,
## any invalid bytes will be replaced with the [Unicode replacement character](https://unicode.org/glossary/#replacement_character)
## instead of returning an error. As such, it's rarely a good idea to use the [Str] returned
## by this function for any purpose other than displaying it to a user.
##
## When you don't know for sure what a path's encoding is, UTF-8 is a popular guess because
## it's the default on UNIX and also is the encoding used in Roc strings. This platform also
## automatically runs applications under the [UTF-8 code page](https://docs.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page)
## on Windows.
##
## Converting paths to strings can be an unreliable operation, because operating systems
## don't record the paths' encodings. This means it's possible for the path to have been
## encoded with a different character set than UTF-8 even if UTF-8 is the system default,
## which means when [display] converts them to a string, the string may include gibberish.
## [Here is an example.](https://unix.stackexchange.com/questions/667652/can-a-file-path-be-invalid-utf-8/667863#667863)
##
## If you happen to know the `Charset` that was used to encode the path, you can use
## `toStrUsingCharset` instead of [display].
display : Path -> Str
display = \path ->
when InternalPath.unwrap path is
FromStr str -> str
FromOperatingSystem bytes | ArbitraryBytes bytes ->
when Str.fromUtf8 bytes is
Ok str -> str
# TODO: this should use the builtin Str.display to display invalid UTF-8 chars in just the right spots, but that does not exist yet!
Err _ -> "<22>"
# isEq : Path, Path -> Bool
# isEq = \p1, p2 ->
# when InternalPath.unwrap p1 is
# FromOperatingSystem bytes1 | ArbitraryBytes bytes1 ->
# when InternalPath.unwrap p2 is
# FromOperatingSystem bytes2 | ArbitraryBytes bytes2 -> bytes1 == bytes2
# # We can't know the encoding that was originally used in the path, so we convert
# # the string to bytes and see if those bytes are equal to the path's bytes.
# #
# # This may sound unreliable, but it's how all paths are compared; since the OS
# # doesn't record which encoding was used to encode the path name, the only
# # reasonable# definition for path equality is byte-for-byte equality.
# FromStr str2 -> Str.isEqUtf8 str2 bytes1
# FromStr str1 ->
# when InternalPath.unwrap p2 is
# FromOperatingSystem bytes2 | ArbitraryBytes bytes2 -> Str.isEqUtf8 str1 bytes2
# FromStr str2 -> str1 == str2
# compare : Path, Path -> [Lt, Eq, Gt]
# compare = \p1, p2 ->
# when InternalPath.unwrap p1 is
# FromOperatingSystem bytes1 | ArbitraryBytes bytes1 ->
# when InternalPath.unwrap p2 is
# FromOperatingSystem bytes2 | ArbitraryBytes bytes2 -> Ord.compare bytes1 bytes2
# FromStr str2 -> Str.compareUtf8 str2 bytes1 |> Ord.reverse
# FromStr str1 ->
# when InternalPath.unwrap p2 is
# FromOperatingSystem bytes2 | ArbitraryBytes bytes2 -> Str.compareUtf8 str1 bytes2
# FromStr str2 -> Ord.compare str1 str2
## ## Path Components
PathComponent : [
ParentDir, # e.g. ".." on UNIX or Windows
CurrentDir, # e.g. "." on UNIX
Named Str, # e.g. "stuff" on UNIX
DirSep Str, # e.g. "/" on UNIX, "\" or "/" on Windows. Or, sometimes, "¥" on Windows - see
# https://docs.microsoft.com/en-us/windows/win32/intl/character-sets-used-in-file-names
#
# This is included as an option so if you're transforming part of a path,
# you can write back whatever separator was originally used.
]
## Note that a root of Slash (`/`) has different meanings on UNIX and on Windows.
## * On UNIX, `/` at the beginning of the path refers to the filesystem root, and means the path is absolute.
## * On Windows, `/` at the beginning of the path refers to the current disk drive, and means the path is relative.
# PathRoot : [
# WindowsSpecificRoot WindowsRoot, # e.g. "C:" on Windows
# Slash,
# None,
# ]
# TODO see https://doc.rust-lang.org/std/path/enum.Prefix.html
WindowsRoot : []
## Returns the root of the path.
# root : Path -> PathRoot
# components : Path -> { root : PathRoot, components : List PathComponent }
## Walk over the path's [components].
# walk :
# Path,
# # None means it's a relative path
# (PathRoot -> state),
# (state, PathComponent -> state)
# -> state
## Returns the path without its last [`component`](#components).
##
## If the path was empty or contained only a [root](#PathRoot), returns the original path.
# dropLast : Path -> Path
# TODO see https://doc.rust-lang.org/std/path/struct.Path.html#method.join for
# the definition of the term "adjoin" - should we use that term?
# append : Path, Path -> Path
# append = \prefix, suffix ->
# content =
# when InternalPath.unwrap prefix is
# FromOperatingSystem prefixBytes ->
# when InternalPath.unwrap suffix is
# FromOperatingSystem suffixBytes ->
# # Neither prefix nor suffix had interior nuls, so the answer won't either
# List.concat prefixBytes suffixBytes
# |> FromOperatingSystem
# ArbitraryBytes suffixBytes ->
# List.concat prefixBytes suffixBytes
# |> ArbitraryBytes
# FromStr suffixStr ->
# # Append suffixStr by writing it to the end of prefixBytes
# Str.appendToUtf8 suffixStr prefixBytes (List.len prefixBytes)
# |> ArbitraryBytes
# ArbitraryBytes prefixBytes ->
# when InternalPath.unwrap suffix is
# ArbitraryBytes suffixBytes | FromOperatingSystem suffixBytes ->
# List.concat prefixBytes suffixBytes
# |> ArbitraryBytes
# FromStr suffixStr ->
# # Append suffixStr by writing it to the end of prefixBytes
# Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes)
# |> ArbitraryBytes
# FromStr prefixStr ->
# when InternalPath.unwrap suffix is
# ArbitraryBytes suffixBytes | FromOperatingSystem suffixBytes ->
# List.concat suffixBytes (Str.toUtf8 prefixStr)
# |> ArbitraryBytes
# FromStr suffixStr ->
# Str.concat prefixStr suffixStr
# |> FromStr
# InternalPath.wrap content
# appendStr : Path, Str -> Path
# appendStr = \prefix, suffixStr ->
# content =
# when InternalPath.unwrap prefix is
# FromOperatingSystem prefixBytes | ArbitraryBytes prefixBytes ->
# # Append suffixStr by writing it to the end of prefixBytes
# Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes)
# |> ArbitraryBytes
# FromStr prefixStr ->
# Str.concat prefixStr suffixStr
# |> FromStr
# InternalPath.wrap content
## Returns `Bool.true` if the first path begins with the second.
# startsWith : Path, Path -> Bool
# startsWith = \path, prefix ->
# when InternalPath.unwrap path is
# FromOperatingSystem pathBytes | ArbitraryBytes pathBytes ->
# when InternalPath.unwrap prefix is
# FromOperatingSystem prefixBytes | ArbitraryBytes prefixBytes ->
# List.startsWith pathBytes prefixBytes
# FromStr prefixStr ->
# strLen = Str.countUtf8Bytes prefixStr
# if strLen == List.len pathBytes then
# # Grab the first N bytes of the list, where N = byte length of string.
# bytesPrefix = List.takeAt pathBytes 0 strLen
# # Compare the two for equality.
# Str.isEqUtf8 prefixStr bytesPrefix
# else
# Bool.false
# FromStr pathStr ->
# when InternalPath.unwrap prefix is
# FromOperatingSystem prefixBytes | ArbitraryBytes prefixBytes ->
# Str.startsWithUtf8 pathStr prefixBytes
# FromStr prefixStr ->
# Str.startsWith pathStr prefixStr
## Returns `Bool.true` if the first path ends with the second.
# endsWith : Path, Path -> Bool
# endsWith = \path, prefix ->
# when InternalPath.unwrap path is
# FromOperatingSystem pathBytes | ArbitraryBytes pathBytes ->
# when InternalPath.unwrap suffix is
# FromOperatingSystem suffixBytes | ArbitraryBytes suffixBytes ->
# List.endsWith pathBytes suffixBytes
# FromStr suffixStr ->
# strLen = Str.countUtf8Bytes suffixStr
# if strLen == List.len pathBytes then
# # Grab the last N bytes of the list, where N = byte length of string.
# bytesSuffix = List.takeAt pathBytes (strLen - 1) strLen
# # Compare the two for equality.
# Str.startsWithUtf8 suffixStr bytesSuffix
# else
# Bool.false
# FromStr pathStr ->
# when InternalPath.unwrap suffix is
# FromOperatingSystem suffixBytes | ArbitraryBytes suffixBytes ->
# Str.endsWithUtf8 pathStr suffixBytes
# FromStr suffixStr ->
# Str.endsWith pathStr suffixStr
# TODO https://doc.rust-lang.org/std/path/struct.Path.html#method.strip_prefix
# TODO idea: what if it's File.openRead and File.openWrite? And then e.g. File.metadata,
# File.isDir, etc.
## If the last component of this path has no `.`, appends `.` followed by the given string.
## Otherwise, replaces everything after the last `.` with the given string.
##
## Examples:
##
## Path.fromStr "foo/bar/baz" |> Path.withExtension "txt" # foo/bar/baz.txt
## Path.fromStr "foo/bar/baz." |> Path.withExtension "txt" # foo/bar/baz.txt
## Path.fromStr "foo/bar/baz.xz" |> Path.withExtension "txt" # foo/bar/baz.txt
withExtension : Path, Str -> Path
withExtension = \path, extension ->
when InternalPath.unwrap path is
FromOperatingSystem bytes | ArbitraryBytes bytes ->
beforeDot =
when List.splitLast bytes (Num.toU8 '.') is
Ok { before } -> before
Err NotFound -> bytes
beforeDot
|> List.reserve (1 + Str.countUtf8Bytes extension)
|> List.append (Num.toU8 '.')
|> List.concat (Str.toUtf8 extension)
|> ArbitraryBytes
|> InternalPath.wrap
FromStr str ->
beforeDot =
when Str.splitLast str "." is
Ok { before } -> before
Err NotFound -> str
beforeDot
|> Str.reserve (1 + Str.countUtf8Bytes extension)
|> Str.concat "."
|> Str.concat extension
|> FromStr
|> InternalPath.wrap
# NOTE: no withExtensionBytes because it's too narrow. If you really need to get some
# non-Unicode in there, do it with

View file

@ -1,13 +0,0 @@
interface Process
exposes [exit]
imports [Task.{ Task }, InternalTask, Effect]
## Exit the process with
##
## {} <- Stderr.line "Exiting right now!" |> Task.await
## Process.exit 1
exit : U8 -> Task {} *
exit = \code ->
Effect.processExit code
|> Effect.map \_ -> Ok {}
|> InternalTask.fromEffect

View file

@ -1,15 +0,0 @@
interface Stderr
exposes [line, write]
imports [Effect, Task.{ Task }, InternalTask]
line : Str -> Task {} *
line = \str ->
Effect.stderrLine str
|> Effect.map (\_ -> Ok {})
|> InternalTask.fromEffect
write : Str -> Task {} *
write = \str ->
Effect.stderrWrite str
|> Effect.map (\_ -> Ok {})
|> InternalTask.fromEffect

View file

@ -1,9 +0,0 @@
interface Stdin
exposes [line]
imports [Effect, Task.{ Task }, InternalTask]
line : Task Str *
line =
Effect.stdinLine
|> Effect.map Ok
|> InternalTask.fromEffect

View file

@ -1,15 +0,0 @@
interface Stdout
exposes [line, write]
imports [Effect, Task.{ Task }, InternalTask]
line : Str -> Task {} *
line = \str ->
Effect.stdoutLine str
|> Effect.map (\_ -> Ok {})
|> InternalTask.fromEffect
write : Str -> Task {} *
write = \str ->
Effect.stdoutWrite str
|> Effect.map (\_ -> Ok {})
|> InternalTask.fromEffect

View file

@ -1,102 +0,0 @@
interface Task
exposes [Task, succeed, fail, await, map, mapFail, onFail, attempt, forever, loop, fromResult]
imports [Effect, InternalTask]
Task ok err : InternalTask.Task ok err
forever : Task val err -> Task * err
forever = \task ->
looper = \{} ->
task
|> InternalTask.toEffect
|> Effect.map
\res ->
when res is
Ok _ -> Step {}
Err e -> Done (Err e)
Effect.loop {} looper
|> InternalTask.fromEffect
loop : state, (state -> Task [Step state, Done done] err) -> Task done err
loop = \state, step ->
looper = \current ->
step current
|> InternalTask.toEffect
|> Effect.map
\res ->
when res is
Ok (Step newState) -> Step newState
Ok (Done result) -> Done (Ok result)
Err e -> Done (Err e)
Effect.loop state looper
|> InternalTask.fromEffect
succeed : ok -> Task ok *
succeed = \ok -> InternalTask.succeed ok
fail : err -> Task * err
fail = \err -> InternalTask.fail err
attempt : Task a b, (Result a b -> Task c d) -> Task c d
attempt = \task, transform ->
effect = Effect.after
(InternalTask.toEffect task)
\result ->
when result is
Ok ok -> transform (Ok ok) |> InternalTask.toEffect
Err err -> transform (Err err) |> InternalTask.toEffect
InternalTask.fromEffect effect
await : Task a err, (a -> Task b err) -> Task b err
await = \task, transform ->
effect = Effect.after
(InternalTask.toEffect task)
\result ->
when result is
Ok a -> transform a |> InternalTask.toEffect
Err err -> Task.fail err |> InternalTask.toEffect
InternalTask.fromEffect effect
onFail : Task ok a, (a -> Task ok b) -> Task ok b
onFail = \task, transform ->
effect = Effect.after
(InternalTask.toEffect task)
\result ->
when result is
Ok a -> Task.succeed a |> InternalTask.toEffect
Err err -> transform err |> InternalTask.toEffect
InternalTask.fromEffect effect
map : Task a err, (a -> b) -> Task b err
map = \task, transform ->
effect = Effect.after
(InternalTask.toEffect task)
\result ->
when result is
Ok ok -> Task.succeed (transform ok) |> InternalTask.toEffect
Err err -> Task.fail err |> InternalTask.toEffect
InternalTask.fromEffect effect
mapFail : Task ok a, (a -> b) -> Task ok b
mapFail = \task, transform ->
effect = Effect.after
(InternalTask.toEffect task)
\result ->
when result is
Ok ok -> Task.succeed ok |> InternalTask.toEffect
Err err -> Task.fail (transform err) |> InternalTask.toEffect
InternalTask.fromEffect effect
## Use a Result among other Tasks by converting it into a Task.
fromResult : Result ok err -> Task ok err
fromResult = \result ->
when result is
Ok ok -> succeed ok
Err err -> fail err

View file

@ -1,431 +0,0 @@
interface Url
exposes [
Url,
append,
fromStr,
toStr,
appendParam,
hasQuery,
hasFragment,
query,
fragment,
reserve,
withQuery,
withFragment,
]
imports []
## A [Uniform Resource Locator](https://en.wikipedia.org/wiki/URL).
##
## It could be an absolute address, such as `https://roc-lang.org/authors` or
## a relative address, such as `/authors`. You can create one using [Url.fromStr].
Url := Str
## Reserve the given number of bytes as extra capacity. This can avoid reallocation
## when calling multiple functions that increase the length of the URL.
##
## Url.fromStr "https://example.com"
## |> Url.reserve 50 # We're about to add 50 UTF-8 bytes to it
## |> Url.append "stuff"
## |> Url.appendParam "café" "du Monde"
## |> Url.appendParam "email" "hi@example.com"
## # https://example.com/stuff?caf%C3%A9=du%20Monde&email=hi%40example.com
##
## The [Str.countUtf8Bytes] function can be helpful in finding out how many bytes to reserve.
##
## There is no `Url.withCapacity` because it's better to reserve extra capacity
## on a [Str] first, and then pass that string to [Url.fromStr]. This function will make use
## of the extra capacity.
reserve : Url, Nat -> Url
reserve = \@Url str, cap ->
@Url (Str.reserve str cap)
## Create a [Url] without validating or [percent-encoding](https://en.wikipedia.org/wiki/Percent-encoding)
## anything.
##
## Url.fromStr "https://example.com#stuff"
## # https://example.com#stuff
##
## URLs can be absolute, like `https://example.com`, or they can be relative, like `/blah`.
##
## Url.fromStr "/this/is#relative"
## # /this/is#relative
##
## Since nothing is validated, this can return invalid URLs.
##
## Url.fromStr "https://this is not a valid URL, not at all!"
## # https://this is not a valid URL, not at all!
##
## Naturally, passing invalid URLs to functions that need valid ones will tend to result in errors.
fromStr : Str -> Url
fromStr = \str -> @Url str
## Return a [Str] representation of this URL.
##
## Url.fromStr "https://example.com"
## |> Url.append "two words"
## |> Url.toStr
## # "https://example.com/two%20words"
toStr : Url -> Str
toStr = \@Url str -> str
## [Percent-encodes](https://en.wikipedia.org/wiki/Percent-encoding) a
## [path component](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax)
## and appends to the end of the URL's path.
##
## Url.fromStr "https://example.com"
## |> Url.append "some stuff"
## # https://example.com/some%20stuff
##
## This will be appended before any queries and fragments.
##
## Url.fromStr "https://example.com?search=blah#fragment"
## |> Url.append "stuff"
## # https://example.com/stuff?search=blah#fragment
##
## If the given path string begins with `"/"` and the URL already ends with `"/"`, one
## will be ignored. This avoids turning a single slash into a double slash.
##
## Url.fromStr "https://example.com/things/"
## |> Url.append "/stuff/"
## |> Url.append "/more/etc/"
## # https://example.com/things/stuff/more/etc/"
##
## If either the given URL or the given string is empty, no `"/"` will be added.
##
## Url.fromStr "https://example.com/things"
## |> Url.append ""
## # https://example.com/things
append : Url, Str -> Url
append = \@Url urlStr, suffixUnencoded ->
suffix = percentEncode suffixUnencoded
when Str.splitFirst urlStr "?" is
Ok { before, after } ->
bytes =
Str.countUtf8Bytes before
+ 1 # for "/"
+ Str.countUtf8Bytes suffix
+ 1 # for "?"
+ Str.countUtf8Bytes after
before
|> Str.reserve bytes
|> appendHelp suffix
|> Str.concat "?"
|> Str.concat after
|> @Url
Err NotFound ->
# There wasn't a query, but there might still be a fragment
when Str.splitFirst urlStr "#" is
Ok { before, after } ->
bytes =
Str.countUtf8Bytes before
+ 1 # for "/"
+ Str.countUtf8Bytes suffix
+ 1 # for "#"
+ Str.countUtf8Bytes after
before
|> Str.reserve bytes
|> appendHelp suffix
|> Str.concat "#"
|> Str.concat after
|> @Url
Err NotFound ->
# No query and no fragment, so just append it
@Url (appendHelp urlStr suffix)
## Internal helper
appendHelp : Str, Str -> Str
appendHelp = \prefix, suffix ->
if Str.endsWith prefix "/" then
if Str.startsWith suffix "/" then
# Avoid a double-slash by appending only the part of the suffix after the "/"
when Str.splitFirst suffix "/" is
Ok { after } ->
# TODO `expect before == ""`
Str.concat prefix after
Err NotFound ->
# This should never happen, because we already verified
# that the suffix startsWith "/"
# TODO `expect Bool.false` here with a comment
Str.concat prefix suffix
else
# prefix ends with "/" but suffix doesn't start with one, so just append.
Str.concat prefix suffix
else if Str.startsWith suffix "/" then
# Suffix starts with "/" but prefix doesn't end with one, so just append them.
Str.concat prefix suffix
else if Str.isEmpty prefix then
# Prefix is empty; return suffix.
suffix
else if Str.isEmpty suffix then
# Suffix is empty; return prefix.
prefix
else
# Neither is empty, but neither has a "/", so add one in between.
prefix
|> Str.concat "/"
|> Str.concat suffix
## Internal helper. This is intentionally unexposed so that you don't accidentally
## double-encode things. If you really want to percent-encode an arbitrary string,
## you can always do:
##
## Url.fromStr ""
## |> Url.append myStrToEncode
## |> Url.toStr
##
## Note that it's not necessary to situationally encode spaces as `+` instead of `%20` -
## it's apparently always safe to use `%20` (but not always safe to use `+`):
## https://stackoverflow.com/questions/2678551/when-should-space-be-encoded-to-plus-or-20/47188851#47188851
percentEncode : Str -> Str
percentEncode = \input ->
# Optimistically assume we won't need any percent encoding, and can have
# the same capacity as the input string. If we're wrong, it will get doubled.
initialOutput = strWithCapacity (Str.countUtf8Bytes input)
# TODO use Str.walkUtf8 once it exists
Str.walkUtf8WithIndex input initialOutput \output, byte, _index ->
# Spec for percent-encoding: https://www.ietf.org/rfc/rfc3986.txt
if
(byte >= 97 && byte <= 122) # lowercase ASCII
|| (byte >= 65 && byte <= 90) # uppercase ASCII
|| (byte >= 48 && byte <= 57) # digit
then
# This is the most common case: an unreserved character,
# which needs no encoding in a path
Str.appendScalar output (Num.toU32 byte)
|> Result.withDefault "" # this will never fail
else
when byte is
46 # '.'
| 95 # '_'
| 126 # '~'
| 150 -> # '-'
# These special characters can all be unescaped in paths
Str.appendScalar output (Num.toU32 byte)
|> Result.withDefault "" # this will never fail
_ ->
# This needs encoding in a path
suffix =
Str.toUtf8 percentEncoded
|> List.sublist { len: 3, start: 3 * Num.toNat byte }
|> Str.fromUtf8
|> Result.withDefault "" # This will never fail
Str.concat output suffix
## Adds a [Str] query parameter to the end of the [Url]. The key
## and value both get [percent-encoded](https://en.wikipedia.org/wiki/Percent-encoding).
##
## Url.fromStr "https://example.com"
## |> Url.appendParam "email" "someone@example.com"
## # https://example.com?email=someone%40example.com
##
## This can be called multiple times on the same URL.
##
## Url.fromStr "https://example.com"
## |> Url.appendParam "café" "du Monde"
## |> Url.appendParam "email" "hi@example.com"
## # https://example.com?caf%C3%A9=du%20Monde&email=hi%40example.com
appendParam : Url, Str, Str -> Url
appendParam = \@Url urlStr, key, value ->
{ withoutFragment, afterQuery } =
when Str.splitLast urlStr "#" is
Ok { before, after } ->
# The fragment is almost certainly going to be a small string,
# so this interpolation should happen on the stack.
{ withoutFragment: before, afterQuery: "#\(after)" }
Err NotFound ->
{ withoutFragment: urlStr, afterQuery: "" }
encodedKey = percentEncode key
encodedValue = percentEncode value
bytes =
Str.countUtf8Bytes withoutFragment
+ 1 # for "?" or "&"
+ Str.countUtf8Bytes encodedKey
+ 1 # for "="
+ Str.countUtf8Bytes encodedValue
+ Str.countUtf8Bytes afterQuery
withoutFragment
|> Str.reserve bytes
|> Str.concat (if hasQuery (@Url withoutFragment) then "&" else "?")
|> Str.concat encodedKey
|> Str.concat "="
|> Str.concat encodedValue
|> Str.concat afterQuery
|> @Url
## Replaces the URL's [query](https://en.wikipedia.org/wiki/URL#Syntax)—the part after
## the `?`, if it has one, but before any `#` it might have.
##
## Url.fromStr "https://example.com?key1=val1&key2=val2#stuff"
## |> Url.withQuery "newQuery=thisRightHere"
## # https://example.com?newQuery=thisRightHere#stuff
##
## Passing `""` removes the `?` (if there was one).
##
## Url.fromStr "https://example.com?key1=val1&key2=val2#stuff"
## |> Url.withQuery ""
## # https://example.com#stuff
withQuery : Url, Str -> Url
withQuery = \@Url urlStr, queryStr ->
{ withoutFragment, afterQuery } =
when Str.splitLast urlStr "#" is
Ok { before, after } ->
# The fragment is almost certainly going to be a small string,
# so this interpolation should happen on the stack.
{ withoutFragment: before, afterQuery: "#\(after)" }
Err NotFound ->
{ withoutFragment: urlStr, afterQuery: "" }
beforeQuery =
when Str.splitLast withoutFragment "?" is
Ok { before } -> before
Err NotFound -> withoutFragment
if Str.isEmpty queryStr then
@Url (Str.concat beforeQuery afterQuery)
else
bytes =
Str.countUtf8Bytes beforeQuery
+ 1 # for "?"
+ Str.countUtf8Bytes queryStr
+ Str.countUtf8Bytes afterQuery
beforeQuery
|> Str.reserve bytes
|> Str.concat "?"
|> Str.concat queryStr
|> Str.concat afterQuery
|> @Url
## Returns the URL's [query](https://en.wikipedia.org/wiki/URL#Syntax)—the part after
## the `?`, if it has one, but before any `#` it might have.
##
## Url.fromStr "https://example.com?key1=val1&key2=val2&key3=val3#stuff"
## |> Url.query
## # "key1=val1&key2=val2&key3=val3"
##
## Returns `""` if the URL has no query.
##
## Url.fromStr "https://example.com#stuff"
## |> Url.query
## # ""
query : Url -> Str
query = \@Url urlStr ->
withoutFragment =
when Str.splitLast urlStr "#" is
Ok { before } -> before
Err NotFound -> urlStr
when Str.splitLast withoutFragment "?" is
Ok { after } -> after
Err NotFound -> ""
## Returns `Bool.true` if the URL has a `?` in it.
##
## Url.fromStr "https://example.com?key=value#stuff"
## |> Url.hasQuery
## # Bool.true
##
## Url.fromStr "https://example.com#stuff"
## |> Url.hasQuery
## # Bool.false
hasQuery : Url -> Bool
hasQuery = \@Url urlStr ->
# TODO use Str.contains once it exists. It should have a "fast path"
# with SIMD iteration if the string is small enough to fit in a SIMD register.
Str.toUtf8 urlStr
|> List.contains (Num.toU8 '?')
## Returns the URL's [fragment](https://en.wikipedia.org/wiki/URL#Syntax)—the part after
## the `#`, if it has one.
##
## Url.fromStr "https://example.com#stuff"
## |> Url.fragment
## # "stuff"
##
## Returns `""` if the URL has no fragment.
##
## Url.fromStr "https://example.com"
## |> Url.fragment
## # ""
fragment : Url -> Str
fragment = \@Url urlStr ->
when Str.splitLast urlStr "#" is
Ok { after } -> after
Err NotFound -> ""
## Replaces the URL's [fragment](https://en.wikipedia.org/wiki/URL#Syntax).
##
## Url.fromStr "https://example.com#stuff"
## |> Url.withFragment "things"
## # https://example.com#things
##
## If the URL didn't have a fragment, adds one.
##
## Url.fromStr "https://example.com"
## |> Url.withFragment "things"
## # https://example.com#things
##
## Passing `""` removes the fragment.
##
## Url.fromStr "https://example.com#stuff"
## |> Url.withFragment ""
## # https://example.com
withFragment : Url, Str -> Url
withFragment = \@Url urlStr, fragmentStr ->
when Str.splitLast urlStr "#" is
Ok { before } ->
if Str.isEmpty fragmentStr then
# If the given fragment is empty, remove the URL's fragment
@Url before
else
# Replace the URL's old fragment with this one, discarding `after`
@Url "\(before)#\(fragmentStr)"
Err NotFound ->
if Str.isEmpty fragmentStr then
# If the given fragment is empty, leave the URL as having no fragment
@Url urlStr
else
# The URL didn't have a fragment, so give it this one
@Url "\(urlStr)#\(fragmentStr)"
## Returns `Bool.true` if the URL has a `#` in it.
##
## Url.fromStr "https://example.com?key=value#stuff"
## |> Url.hasFragment
## # Bool.true
##
## Url.fromStr "https://example.com?key=value"
## |> Url.hasFragment
## # Bool.false
hasFragment : Url -> Bool
hasFragment = \@Url urlStr ->
# TODO use Str.contains once it exists. It should have a "fast path"
# with SIMD iteration if the string is small enough to fit in a SIMD register.
Str.toUtf8 urlStr
|> List.contains (Num.toU8 '#')
strWithCapacity : Nat -> Str
strWithCapacity = \cap ->
Str.reserve "" cap
# Adapted from the percent-encoding crate, © The rust-url developers, Apache2-licensed
#
# https://github.com/servo/rust-url/blob/e12d76a61add5bc09980599c738099feaacd1d0d/percent_encoding/src/lib.rs#L183
percentEncoded : Str
percentEncoded = "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C%2D%2E%2F%30%31%32%33%34%35%36%37%38%39%3A%3B%3C%3D%3E%3F%40%41%42%43%44%45%46%47%48%49%4A%4B%4C%4D%4E%4F%50%51%52%53%54%55%56%57%58%59%5A%5B%5C%5D%5E%5F%60%61%62%63%64%65%66%67%68%69%6A%6B%6C%6D%6E%6F%70%71%72%73%74%75%76%77%78%79%7A%7B%7C%7D%7E%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF"

View file

@ -1,9 +0,0 @@
fn main() {
#[cfg(not(windows))]
println!("cargo:rustc-link-lib=dylib=app");
#[cfg(windows)]
println!("cargo:rustc-link-lib=dylib=libapp");
println!("cargo:rustc-link-search=.");
}

View file

@ -1,3 +0,0 @@
extern unsigned char rust_main();
int main() { return (int)rust_main(); }

View file

@ -1,9 +0,0 @@
platform "cli"
requires {} { main : Task {} [] }
exposes []
packages {}
imports [Task.{ Task }]
provides [mainForHost]
mainForHost : Task {} []
mainForHost = main

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,564 +0,0 @@
#![allow(non_snake_case)]
mod file_glue;
mod glue;
use core::alloc::Layout;
use core::ffi::c_void;
use core::mem::MaybeUninit;
use glue::Metadata;
use roc_std::{RocDict, RocList, RocResult, RocStr};
use std::borrow::{Borrow, Cow};
use std::ffi::OsStr;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::time::Duration;
use file_glue::ReadErr;
use file_glue::WriteErr;
extern "C" {
#[link_name = "roc__mainForHost_1_exposed_generic"]
fn roc_main(output: *mut u8);
#[link_name = "roc__mainForHost_1_exposed_size"]
fn roc_main_size() -> i64;
#[link_name = "roc__mainForHost_0_caller"]
fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8);
#[allow(dead_code)]
#[link_name = "roc__mainForHost_0_size"]
fn size_Fx() -> i64;
#[link_name = "roc__mainForHost_0_result_size"]
fn size_Fx_result() -> i64;
}
#[no_mangle]
pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
libc::malloc(size)
}
#[no_mangle]
pub unsafe extern "C" fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
libc::realloc(c_ptr, new_size)
}
#[no_mangle]
pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
libc::free(c_ptr)
}
#[no_mangle]
pub unsafe extern "C" fn roc_panic(msg: &RocStr, tag_id: u32) {
match tag_id {
0 => {
eprintln!("Roc crashed with:\n\n\t{}\n", msg.as_str());
print_backtrace();
std::process::exit(1);
}
1 => {
eprintln!("The program crashed with:\n\n\t{}\n", msg.as_str());
print_backtrace();
std::process::exit(1);
}
_ => todo!(),
}
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_getppid() -> libc::pid_t {
libc::getppid()
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_mmap(
addr: *mut libc::c_void,
len: libc::size_t,
prot: libc::c_int,
flags: libc::c_int,
fd: libc::c_int,
offset: libc::off_t,
) -> *mut libc::c_void {
libc::mmap(addr, len, prot, flags, fd, offset)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_shm_open(
name: *const libc::c_char,
oflag: libc::c_int,
mode: libc::mode_t,
) -> libc::c_int {
libc::shm_open(name, oflag, mode as libc::c_uint)
}
fn print_backtrace() {
eprintln!("Here is the call stack that led to the crash:\n");
let mut entries = Vec::new();
#[derive(Default)]
struct Entry {
pub fn_name: String,
pub filename: Option<String>,
pub line: Option<u32>,
pub col: Option<u32>,
}
backtrace::trace(|frame| {
backtrace::resolve_frame(frame, |symbol| {
if let Some(fn_name) = symbol.name() {
let fn_name = fn_name.to_string();
if should_show_in_backtrace(&fn_name) {
let mut entry: Entry = Default::default();
entry.fn_name = format_fn_name(&fn_name);
if let Some(path) = symbol.filename() {
entry.filename = Some(path.to_string_lossy().into_owned());
};
entry.line = symbol.lineno();
entry.col = symbol.colno();
entries.push(entry);
}
} else {
entries.push(Entry {
fn_name: "???".to_string(),
..Default::default()
});
}
});
true // keep going to the next frame
});
for entry in entries {
eprintln!("\t{}", entry.fn_name);
if let Some(filename) = entry.filename {
eprintln!("\t\t{filename}");
}
}
eprintln!("\nOptimizations can make this list inaccurate! If it looks wrong, try running without `--optimize` and with `--linker=legacy`\n");
}
fn should_show_in_backtrace(fn_name: &str) -> bool {
let is_from_rust = fn_name.contains("::");
let is_host_fn = fn_name.starts_with("roc_panic")
|| fn_name.starts_with("_Effect_effect")
|| fn_name.starts_with("_roc__")
|| fn_name.starts_with("rust_main")
|| fn_name == "_main";
!is_from_rust && !is_host_fn
}
fn format_fn_name(fn_name: &str) -> String {
// e.g. convert "_Num_sub_a0c29024d3ec6e3a16e414af99885fbb44fa6182331a70ab4ca0886f93bad5"
// to ["Num", "sub", "a0c29024d3ec6e3a16e414af99885fbb44fa6182331a70ab4ca0886f93bad5"]
let mut pieces_iter = fn_name.split("_");
if let (_, Some(module_name), Some(name)) =
(pieces_iter.next(), pieces_iter.next(), pieces_iter.next())
{
display_roc_fn(module_name, name)
} else {
"???".to_string()
}
}
fn display_roc_fn(module_name: &str, fn_name: &str) -> String {
let module_name = if module_name == "#UserApp" {
"app"
} else {
module_name
};
let fn_name = if fn_name.parse::<u64>().is_ok() {
"(anonymous function)"
} else {
fn_name
};
format!("\u{001B}[36m{module_name}\u{001B}[39m.{fn_name}")
}
#[no_mangle]
pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void {
libc::memset(dst, c, n)
}
#[no_mangle]
pub extern "C" fn rust_main() {
let size = unsafe { roc_main_size() } as usize;
let layout = Layout::array::<u8>(size).unwrap();
unsafe {
// TODO allocate on the stack if it's under a certain size
let buffer = std::alloc::alloc(layout);
roc_main(buffer);
call_the_closure(buffer);
std::alloc::dealloc(buffer, layout);
}
}
unsafe fn call_the_closure(closure_data_ptr: *const u8) -> u8 {
let size = size_Fx_result() as usize;
let layout = Layout::array::<u8>(size).unwrap();
let buffer = std::alloc::alloc(layout) as *mut u8;
call_Fx(
// This flags pointer will never get dereferenced
MaybeUninit::uninit().as_ptr(),
closure_data_ptr as *const u8,
buffer as *mut u8,
);
std::alloc::dealloc(buffer, layout);
// TODO return the u8 exit code returned by the Fx closure
0
}
#[no_mangle]
pub extern "C" fn roc_fx_envDict() -> RocDict<RocStr, RocStr> {
// TODO: can we be more efficient about reusing the String's memory for RocStr?
std::env::vars_os()
.map(|(key, val)| {
(
RocStr::from(key.to_string_lossy().borrow()),
RocStr::from(val.to_string_lossy().borrow()),
)
})
.collect()
}
#[no_mangle]
pub extern "C" fn roc_fx_args() -> RocList<RocStr> {
// TODO: can we be more efficient about reusing the String's memory for RocStr?
std::env::args_os()
.map(|os_str| RocStr::from(os_str.to_string_lossy().borrow()))
.collect()
}
#[no_mangle]
pub extern "C" fn roc_fx_envVar(roc_str: &RocStr) -> RocResult<RocStr, ()> {
// TODO: can we be more efficient about reusing the String's memory for RocStr?
match std::env::var_os(roc_str.as_str()) {
Some(os_str) => RocResult::ok(RocStr::from(os_str.to_string_lossy().borrow())),
None => RocResult::err(()),
}
}
#[no_mangle]
pub extern "C" fn roc_fx_setCwd(roc_path: &RocList<u8>) -> RocResult<(), ()> {
match std::env::set_current_dir(path_from_roc_path(roc_path)) {
Ok(()) => RocResult::ok(()),
Err(_) => RocResult::err(()),
}
}
#[no_mangle]
pub extern "C" fn roc_fx_processExit(exit_code: u8) {
std::process::exit(exit_code as i32);
}
#[no_mangle]
pub extern "C" fn roc_fx_exePath(_roc_str: &RocStr) -> RocResult<RocList<u8>, ()> {
match std::env::current_exe() {
Ok(path_buf) => RocResult::ok(os_str_to_roc_path(path_buf.as_path().as_os_str())),
Err(_) => RocResult::err(()),
}
}
#[no_mangle]
pub extern "C" fn roc_fx_stdinLine() -> RocStr {
use std::io::BufRead;
let stdin = std::io::stdin();
let line1 = stdin.lock().lines().next().unwrap().unwrap();
RocStr::from(line1.as_str())
}
#[no_mangle]
pub extern "C" fn roc_fx_stdoutLine(line: &RocStr) {
let string = line.as_str();
println!("{}", string);
}
#[no_mangle]
pub extern "C" fn roc_fx_stdoutWrite(text: &RocStr) {
let string = text.as_str();
print!("{}", string);
std::io::stdout().flush().unwrap();
}
#[no_mangle]
pub extern "C" fn roc_fx_stderrLine(line: &RocStr) {
let string = line.as_str();
eprintln!("{}", string);
}
#[no_mangle]
pub extern "C" fn roc_fx_stderrWrite(text: &RocStr) {
let string = text.as_str();
eprint!("{}", string);
std::io::stderr().flush().unwrap();
}
// #[no_mangle]
// pub extern "C" fn roc_fx_fileWriteUtf8(
// roc_path: &RocList<u8>,
// roc_string: &RocStr,
// // ) -> RocResult<(), WriteErr> {
// ) -> (u8, u8) {
// let _ = write_slice(roc_path, roc_string.as_str().as_bytes());
// (255, 255)
// }
// #[no_mangle]
// pub extern "C" fn roc_fx_fileWriteUtf8(roc_path: &RocList<u8>, roc_string: &RocStr) -> Fail {
// write_slice2(roc_path, roc_string.as_str().as_bytes())
// }
#[no_mangle]
pub extern "C" fn roc_fx_fileWriteUtf8(
roc_path: &RocList<u8>,
roc_str: &RocStr,
) -> RocResult<(), WriteErr> {
write_slice(roc_path, roc_str.as_str().as_bytes())
}
#[no_mangle]
pub extern "C" fn roc_fx_fileWriteBytes(
roc_path: &RocList<u8>,
roc_bytes: &RocList<u8>,
) -> RocResult<(), WriteErr> {
write_slice(roc_path, roc_bytes.as_slice())
}
fn write_slice(roc_path: &RocList<u8>, bytes: &[u8]) -> RocResult<(), WriteErr> {
match File::create(path_from_roc_path(roc_path)) {
Ok(mut file) => match file.write_all(bytes) {
Ok(()) => RocResult::ok(()),
Err(_) => {
todo!("Report a file write error");
}
},
Err(_) => {
todo!("Report a file open error");
}
}
}
#[cfg(target_family = "unix")]
fn path_from_roc_path(bytes: &RocList<u8>) -> Cow<'_, Path> {
use std::os::unix::ffi::OsStrExt;
let os_str = OsStr::from_bytes(bytes.as_slice());
Cow::Borrowed(Path::new(os_str))
}
#[cfg(target_family = "windows")]
fn path_from_roc_path(bytes: &RocList<u8>) -> Cow<'_, Path> {
use std::os::windows::ffi::OsStringExt;
let bytes = bytes.as_slice();
assert_eq!(bytes.len() % 2, 0);
let characters: &[u16] =
unsafe { std::slice::from_raw_parts(bytes.as_ptr().cast(), bytes.len() / 2) };
let os_string = std::ffi::OsString::from_wide(characters);
Cow::Owned(std::path::PathBuf::from(os_string))
}
#[no_mangle]
pub extern "C" fn roc_fx_fileReadBytes(roc_path: &RocList<u8>) -> RocResult<RocList<u8>, ReadErr> {
use std::io::Read;
let mut bytes = Vec::new();
match File::open(path_from_roc_path(roc_path)) {
Ok(mut file) => match file.read_to_end(&mut bytes) {
Ok(_bytes_read) => RocResult::ok(RocList::from(bytes.as_slice())),
Err(_) => {
todo!("Report a file write error");
}
},
Err(_) => {
todo!("Report a file open error");
}
}
}
#[no_mangle]
pub extern "C" fn roc_fx_fileDelete(roc_path: &RocList<u8>) -> RocResult<(), ReadErr> {
match std::fs::remove_file(path_from_roc_path(roc_path)) {
Ok(()) => RocResult::ok(()),
Err(_) => {
todo!("Report a file write error");
}
}
}
#[no_mangle]
pub extern "C" fn roc_fx_cwd() -> RocList<u8> {
// TODO instead, call getcwd on UNIX and GetCurrentDirectory on Windows
match std::env::current_dir() {
Ok(path_buf) => os_str_to_roc_path(path_buf.into_os_string().as_os_str()),
Err(_) => {
// Default to empty path
RocList::empty()
}
}
}
#[no_mangle]
pub extern "C" fn roc_fx_dirList(
// TODO: this RocResult should use Dir.WriteErr - but right now it's File.WriteErr
// because glue doesn't have Dir.WriteErr yet.
roc_path: &RocList<u8>,
) -> RocResult<RocList<RocList<u8>>, WriteErr> {
println!("Dir.list...");
match std::fs::read_dir(path_from_roc_path(roc_path)) {
Ok(dir_entries) => RocResult::ok(
dir_entries
.map(|opt_dir_entry| match opt_dir_entry {
Ok(entry) => os_str_to_roc_path(entry.path().into_os_string().as_os_str()),
Err(_) => {
todo!("handle dir_entry path didn't resolve")
}
})
.collect::<RocList<RocList<u8>>>(),
),
Err(_) => {
todo!("handle Dir.list error");
}
}
}
#[cfg(target_family = "unix")]
fn os_str_to_roc_path(os_str: &OsStr) -> RocList<u8> {
use std::os::unix::ffi::OsStrExt;
RocList::from(os_str.as_bytes())
}
#[cfg(target_family = "windows")]
fn os_str_to_roc_path(os_str: &OsStr) -> RocList<u8> {
use std::os::windows::ffi::OsStrExt;
let bytes: Vec<_> = os_str.encode_wide().flat_map(|c| c.to_be_bytes()).collect();
RocList::from(bytes.as_slice())
}
#[no_mangle]
pub extern "C" fn roc_fx_sendRequest(roc_request: &glue::Request) -> glue::Response {
let mut builder = reqwest::blocking::ClientBuilder::new();
if roc_request.timeout.discriminant() == glue::discriminant_TimeoutConfig::TimeoutMilliseconds {
let ms: &u64 = unsafe { roc_request.timeout.as_TimeoutMilliseconds() };
builder = builder.timeout(Duration::from_millis(*ms));
}
let client = match builder.build() {
Ok(c) => c,
Err(_) => {
return glue::Response::NetworkError; // TLS backend cannot be initialized
}
};
let method = match roc_request.method {
glue::Method::Connect => reqwest::Method::CONNECT,
glue::Method::Delete => reqwest::Method::DELETE,
glue::Method::Get => reqwest::Method::GET,
glue::Method::Head => reqwest::Method::HEAD,
glue::Method::Options => reqwest::Method::OPTIONS,
glue::Method::Patch => reqwest::Method::PATCH,
glue::Method::Post => reqwest::Method::POST,
glue::Method::Put => reqwest::Method::PUT,
glue::Method::Trace => reqwest::Method::TRACE,
};
let url = roc_request.url.as_str();
let mut req_builder = client.request(method, url);
for header in roc_request.headers.iter() {
let (name, value) = unsafe { header.as_Header() };
req_builder = req_builder.header(name.as_str(), value.as_str());
}
if roc_request.body.discriminant() == glue::discriminant_Body::Body {
let (mime_type_tag, body_byte_list) = unsafe { roc_request.body.as_Body() };
let mime_type_str: &RocStr = unsafe { mime_type_tag.as_MimeType() };
req_builder = req_builder.header("Content-Type", mime_type_str.as_str());
req_builder = req_builder.body(body_byte_list.as_slice().to_vec());
}
let request = match req_builder.build() {
Ok(req) => req,
Err(err) => {
return glue::Response::BadRequest(RocStr::from(err.to_string().as_str()));
}
};
match client.execute(request) {
Ok(response) => {
let status = response.status();
let status_str = status.canonical_reason().unwrap_or_else(|| status.as_str());
let headers_iter = response.headers().iter().map(|(name, value)| {
glue::Header::Header(
RocStr::from(name.as_str()),
RocStr::from(value.to_str().unwrap_or_default()),
)
});
let metadata = Metadata {
headers: RocList::from_iter(headers_iter),
statusText: RocStr::from(status_str),
url: RocStr::from(url),
statusCode: status.as_u16(),
};
let bytes = response.bytes().unwrap_or_default();
let body: RocList<u8> = RocList::from_iter(bytes.into_iter());
if status.is_success() {
glue::Response::GoodStatus(metadata, body)
} else {
glue::Response::BadStatus(metadata, body)
}
}
Err(err) => {
if err.is_timeout() {
glue::Response::Timeout
} else if err.is_request() {
glue::Response::BadRequest(RocStr::from(err.to_string().as_str()))
} else {
glue::Response::NetworkError
}
}
}
}

View file

@ -1,3 +0,0 @@
fn main() {
host::rust_main();
}

View file

@ -1,18 +1,18 @@
app "countdown" app "countdown"
packages { pf: "cli-platform/main.roc" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" }
imports [pf.Stdin, pf.Stdout, pf.Task.{ await, loop, succeed }] imports [pf.Stdin, pf.Stdout, pf.Task.{ await, loop }]
provides [main] to pf provides [main] to pf
main = main =
_ <- await (Stdout.line "\nLet's count down from 10 together - all you have to do is press <ENTER>.") _ <- await (Stdout.line "\nLet's count down from 3 together - all you have to do is press <ENTER>.")
_ <- await Stdin.line _ <- await Stdin.line
loop 10 tick loop 3 tick
tick = \n -> tick = \n ->
if n == 0 then if n == 0 then
_ <- await (Stdout.line "🎉 SURPRISE! Happy Birthday! 🎂") _ <- await (Stdout.line "🎉 SURPRISE! Happy Birthday! 🎂")
succeed (Done {}) Task.ok (Done {})
else else
_ <- await (n |> Num.toStr |> \s -> "\(s)..." |> Stdout.line) _ <- await (n |> Num.toStr |> \s -> "\(s)..." |> Stdout.line)
_ <- await Stdin.line _ <- await Stdin.line
succeed (Step (n - 1)) Task.ok (Step (n - 1))

View file

@ -1,17 +1,21 @@
app "echo" app "echo"
packages { pf: "cli-platform/main.roc" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" }
imports [pf.Stdin, pf.Stdout, pf.Task.{ Task }] imports [pf.Stdin, pf.Stdout, pf.Task.{ Task }]
provides [main] to pf provides [main] to pf
main : Task {} [] main : Task {} I32
main = main =
_ <- Task.await (Stdout.line "🗣 Shout into this cave and hear the echo! 👂👂👂") _ <- Task.await (Stdout.line "🗣 Shout into this cave and hear the echo! 👂👂👂")
Task.loop {} \_ -> Task.map tick Step
tick : Task.Task {} [] Task.loop {} tick
tick =
tick : {} -> Task [Step {}, Done {}] *
tick = \{} ->
shout <- Task.await Stdin.line shout <- Task.await Stdin.line
Stdout.line (echo shout)
when shout is
Input s -> Stdout.line (echo s) |> Task.map Step
End -> Stdout.line (echo "Received end of input (EOF).") |> Task.map Done
echo : Str -> Str echo : Str -> Str
echo = \shout -> echo = \shout ->

View file

@ -1,9 +1,9 @@
app "env" app "env"
packages { pf: "cli-platform/main.roc" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" }
imports [pf.Stdout, pf.Stderr, pf.Env, pf.Task.{ Task }] imports [pf.Stdout, pf.Stderr, pf.Env, pf.Task.{ Task }]
provides [main] to pf provides [main] to pf
main : Task {} [] main : Task {} I32
main = main =
task = task =
Env.decode "EDITOR" Env.decode "EDITOR"

View file

@ -1,7 +1,6 @@
app "file-io" app "file-io"
packages { pf: "cli-platform/main.roc" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" }
imports [ imports [
pf.Process,
pf.Stdout, pf.Stdout,
pf.Stderr, pf.Stderr,
pf.Task.{ Task }, pf.Task.{ Task },
@ -12,7 +11,7 @@ app "file-io"
] ]
provides [main] to pf provides [main] to pf
main : Task {} [] main : Task {} I32
main = main =
path = Path.fromStr "out.txt" path = Path.fromStr "out.txt"
task = task =
@ -42,4 +41,4 @@ main =
_ -> "Uh oh, there was an error!" _ -> "Uh oh, there was an error!"
{} <- Stderr.line msg |> Task.await {} <- Stderr.line msg |> Task.await
Process.exit 1 Task.err 1

View file

@ -1,12 +1,20 @@
app "form" app "form"
packages { pf: "cli-platform/main.roc" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" }
imports [pf.Stdin, pf.Stdout, pf.Task.{ await, Task }] imports [pf.Stdin, pf.Stdout, pf.Task.{ await, Task }]
provides [main] to pf provides [main] to pf
main : Task {} [] main : Task {} I32
main = main =
_ <- await (Stdout.line "What's your first name?") _ <- await (Stdout.line "What's your first name?")
firstName <- await Stdin.line firstName <- await Stdin.line
_ <- await (Stdout.line "What's your last name?") _ <- await (Stdout.line "What's your last name?")
lastName <- await Stdin.line lastName <- await Stdin.line
Stdout.line "Hi, \(firstName) \(lastName)! 👋"
Stdout.line "Hi, \(unwrap firstName) \(unwrap lastName)! 👋"
unwrap : [Input Str, End] -> Str
unwrap = \input ->
when input is
Input line -> line
End -> "Received end of input (EOF)."

View file

@ -1,14 +1,19 @@
app "http-get" app "http-get"
packages { pf: "cli-platform/main.roc" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" }
imports [pf.Http, pf.Task.{ Task }, pf.Stdin, pf.Stdout] imports [pf.Http, pf.Task.{ Task }, pf.Stdin, pf.Stdout]
provides [main] to pf provides [main] to pf
main : Task {} [] main : Task {} I32
main = main =
_ <- Task.await (Stdout.line "Please enter a URL to fetch") _ <- Task.await (Stdout.line "Enter a URL to fetch. It must contain a scheme like \"http://\" or \"https://\".")
url <- Task.await Stdin.line input <- Task.await Stdin.line
when input is
End ->
Stdout.line "I received end-of-input (EOF) instead of a URL."
Input url ->
request = { request = {
method: Get, method: Get,
headers: [], headers: [],
@ -18,7 +23,9 @@ main =
} }
output <- Http.send request output <- Http.send request
|> Task.onFail (\err -> err |> Http.errorToString |> Task.succeed) |> Task.onErr \err -> err
|> Http.errorToString
|> Task.ok
|> Task.await |> Task.await
Stdout.line output Stdout.line output

View file

@ -1,5 +1,5 @@
app "ingested-file-bytes" app "ingested-file-bytes"
packages { pf: "cli-platform/main.roc" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" }
imports [ imports [
pf.Stdout, pf.Stdout,
"ingested-file.roc" as ownCode : _, # A type hole can also be used here. "ingested-file.roc" as ownCode : _, # A type hole can also be used here.

View file

@ -1,5 +1,5 @@
app "ingested-file" app "ingested-file"
packages { pf: "cli-platform/main.roc" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" }
imports [ imports [
pf.Stdout, pf.Stdout,
"ingested-file.roc" as ownCode : Str, "ingested-file.roc" as ownCode : Str,

View file

@ -1,5 +1,5 @@
app "helloWorld" app "helloWorld"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" }
imports [pf.Stdout] imports [pf.Stdout]
provides [main] to pf provides [main] to pf

View file

@ -1,7 +0,0 @@
app "helloWorld"
packages { pf: "cli/cli-platform/main.roc" }
imports [pf.Stdout]
provides [main] to pf
main =
Stdout.line "Hello, World!"

View file

@ -2,7 +2,7 @@
# Shows how Roc values can be logged # Shows how Roc values can be logged
# #
app "inspect-logging" app "inspect-logging"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br" }
imports [ imports [
pf.Stdout, pf.Stdout,
LogFormatter, LogFormatter,

View file

@ -1,6 +1,6 @@
app "example" app "example"
packages { packages {
cli: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br", cli: "https://github.com/roc-lang/basic-cli/releases/download/0.6.0/QOQW08n38nHHrVVkJNiPIjzjvbR3iMjXeFY5w1aT46w.tar.br",
parser: "../package/main.roc", parser: "../package/main.roc",
} }
imports [ imports [

View file

@ -11,6 +11,7 @@ interface Html.Attributes
alt, alt,
ariaLabel, ariaLabel,
ariaLabelledBy, ariaLabelledBy,
ariaHidden,
async, async,
autocapitalize, autocapitalize,
autocomplete, autocomplete,
@ -154,6 +155,7 @@ allow = attribute "allow"
alt = attribute "alt" alt = attribute "alt"
ariaLabel = attribute "aria-label" ariaLabel = attribute "aria-label"
ariaLabelledBy = attribute "aria-labelledby" ariaLabelledBy = attribute "aria-labelledby"
ariaHidden = attribute "aria-label"
async = attribute "async" async = attribute "async"
autocapitalize = attribute "autocapitalize" autocapitalize = attribute "autocapitalize"
autocomplete = attribute "autocomplete" autocomplete = attribute "autocomplete"

17
flake.lock generated
View file

@ -1,5 +1,21 @@
{ {
"nodes": { "nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": { "flake-utils": {
"inputs": { "inputs": {
"systems": "systems" "systems": "systems"
@ -59,6 +75,7 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"nixgl": "nixgl", "nixgl": "nixgl",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",

View file

@ -18,9 +18,15 @@
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
inputs.flake-utils.follows = "flake-utils"; inputs.flake-utils.follows = "flake-utils";
}; };
# for non flake backwards compatibility
flake-compat = {
url = "github:edolstra/flake-compat";
flake = false;
};
}; };
outputs = { self, nixpkgs, rust-overlay, flake-utils, nixgl }: outputs = { self, nixpkgs, rust-overlay, flake-utils, nixgl, ... }@inputs:
let supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" "aarch64-linux" ]; let supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" "aarch64-linux" ];
in flake-utils.lib.eachSystem supportedSystems (system: in flake-utils.lib.eachSystem supportedSystems (system:
let let
@ -28,17 +34,11 @@
++ (if system == "x86_64-linux" then [ nixgl.overlay ] else [ ]); ++ (if system == "x86_64-linux" then [ nixgl.overlay ] else [ ]);
pkgs = import nixpkgs { inherit system overlays; }; pkgs = import nixpkgs { inherit system overlays; };
# When updating the zig or llvm version, make sure they stay in sync. rocBuild = import ./nix { inherit pkgs; };
# Also update in default.nix (TODO: maybe we can use nix code to sync this)
zigPkg = pkgs.zig_0_11;
llvmPkgs = pkgs.llvmPackages_16;
llvmVersion = builtins.splitVersion llvmPkgs.release_version;
llvmMajorMinorStr = builtins.elemAt llvmVersion 0 + builtins.elemAt llvmVersion 1;
# get current working directory compile-deps = rocBuild.compile-deps;
cwd = builtins.toString ./.; inherit (compile-deps) zigPkg llvmPkgs llvmVersion
rust = llvmMajorMinorStr glibcPath libGccSPath darwinInputs;
pkgs.rust-bin.fromRustupToolchainFile "${cwd}/rust-toolchain.toml";
# DevInputs are not necessary to build roc as a user # DevInputs are not necessary to build roc as a user
linuxDevInputs = with pkgs; linuxDevInputs = with pkgs;
@ -55,16 +55,6 @@
xorg.libxcb xorg.libxcb
]; ];
darwinInputs = with pkgs;
lib.optionals stdenv.isDarwin
(with pkgs.darwin.apple_sdk.frameworks; [
AppKit
CoreFoundation
CoreServices
Foundation
Security
]);
# DevInputs are not necessary to build roc as a user # DevInputs are not necessary to build roc as a user
darwinDevInputs = with pkgs; darwinDevInputs = with pkgs;
lib.optionals stdenv.isDarwin lib.optionals stdenv.isDarwin
@ -111,7 +101,7 @@
zlib zlib
# faster builds - see https://github.com/roc-lang/roc/blob/main/BUILDING_FROM_SOURCE.md#use-lld-for-the-linker # faster builds - see https://github.com/roc-lang/roc/blob/main/BUILDING_FROM_SOURCE.md#use-lld-for-the-linker
llvmPkgs.lld llvmPkgs.lld
rust rocBuild.rust-shell
]); ]);
sharedDevInputs = (with pkgs; [ sharedDevInputs = (with pkgs; [
@ -134,7 +124,8 @@
alias fmtc='cargo fmt --all -- --check' alias fmtc='cargo fmt --all -- --check'
''; '';
in { in
{
devShell = pkgs.mkShell { devShell = pkgs.mkShell {
buildInputs = sharedInputs ++ sharedDevInputs ++ darwinInputs ++ darwinDevInputs ++ linuxDevInputs buildInputs = sharedInputs ++ sharedDevInputs ++ darwinInputs ++ darwinDevInputs ++ linuxDevInputs
@ -167,6 +158,14 @@
formatter = pkgs.nixpkgs-fmt; formatter = pkgs.nixpkgs-fmt;
# You can build this package (the roc CLI) with the `nix build` command. # You can build this package (the roc CLI) with the `nix build` command.
packages.default = import ./. { inherit pkgs; }; packages = {
default = rocBuild.roc-cli;
# all rust crates in workspace.members of Cargo.toml
full = rocBuild.roc-full;
# only the CLI crate = executable provided in nightly releases
cli = rocBuild.roc-cli;
lang-server = rocBuild.roc-lang-server;
};
}); });
} }

87
nix/builder.nix Normal file
View file

@ -0,0 +1,87 @@
{ pkgs, lib, rustPlatform, compile-deps, subPackage ? null }:
let
inherit (compile-deps) zigPkg llvmPkgs llvmVersion llvmMajorMinorStr glibcPath libGccSPath;
subPackagePath = if subPackage != null then "crates/${subPackage}" else null;
in
rustPlatform.buildRustPackage {
pname = "roc" + lib.optionalString (subPackage != null) "_${subPackage}";
version = "0.0.1";
buildAndTestSubdir = subPackagePath;
src = pkgs.nix-gitignore.gitignoreSource [ ] ../.;
cargoLock = {
lockFile = ../Cargo.lock;
outputHashes = {
"criterion-0.3.5" = "sha256-+FibPQGiR45g28xCHcM0pMN+C+Q8gO8206Wb5fiTy+k=";
"inkwell-0.2.0" = "sha256-VhTapYGonoSQ4hnDoLl4AAgj0BppAhPNA+UPuAJSuAU=";
"plotters-0.3.1" = "sha256-noy/RSjoEPZZbOJTZw1yxGcX5S+2q/7mxnUrzDyxOFw=";
"rustyline-9.1.1" = "sha256-aqQqz6nSp+Qn44gm3jXmmQUO6/fYTx7iLph2tbA24Bs=";
};
};
shellHook = ''
export LLVM_SYS_${llvmMajorMinorStr}_PREFIX="${llvmPkgs.llvm.dev}"
'';
# required for zig
XDG_CACHE_HOME =
"xdg_cache"; # prevents zig AccessDenied error github.com/ziglang/zig/issues/6810
# want to see backtrace in case of failure
RUST_BACKTRACE = 1;
# skip running rust tests, problems:
# building of example platforms requires network: Could not resolve host
# zig AccessDenied error github.com/ziglang/zig/issues/6810
# Once instance has previously been poisoned ??
doCheck = false;
nativeBuildInputs = (with pkgs; [
cmake
git
pkg-config
python3
llvmPkgs.clang
llvmPkgs.llvm.dev
llvmPkgs.bintools-unwrapped # contains lld
zigPkg
]);
buildInputs = (with pkgs;
[
libffi
libxml2
ncurses
zlib
cargo
makeWrapper # necessary for postBuild wrapProgram
] ++ lib.optionals pkgs.stdenv.isDarwin
(with pkgs.darwin.apple_sdk.frameworks;
[
AppKit
CoreFoundation
CoreServices
Foundation
Security
]));
# cp: to copy str.zig,list.zig...
# wrapProgram pkgs.stdenv.cc: to make ld available for compiler/build/src/link.rs
postInstall =
let
binPath =
lib.makeBinPath [ pkgs.stdenv.cc ];
linuxArgs = lib.optionalString pkgs.stdenv.isLinux
"--set NIX_GLIBC_PATH ${glibcPath} --set NIX_LIBGCC_S_PATH ${libGccSPath}";
rocPath = "$out/bin/roc";
wrapRoc = "wrapProgram ${rocPath} ${linuxArgs} --prefix PATH : ${binPath}";
in
# need to check if roc bin exists since it might not if subPackage is set
''
if test -f ${rocPath}; then
${wrapRoc}
fi
'';
}

25
nix/compile-deps.nix Normal file
View file

@ -0,0 +1,25 @@
{ pkgs }:
let
zigPkg = pkgs.zig_0_11;
llvmPkgs = pkgs.llvmPackages_16;
llvmVersion = builtins.splitVersion llvmPkgs.release_version;
llvmMajorMinorStr = builtins.elemAt llvmVersion 0 + builtins.elemAt llvmVersion 1;
# nix does not store libs in /usr/lib or /lib
glibcPath =
if pkgs.stdenv.isLinux then "${pkgs.glibc.out}/lib" else "";
libGccSPath =
if pkgs.stdenv.isLinux then "${pkgs.stdenv.cc.cc.lib}/lib" else "";
in
{
inherit zigPkg llvmPkgs llvmVersion llvmMajorMinorStr glibcPath libGccSPath;
darwinInputs = with pkgs;
lib.optionals stdenv.isDarwin
(with pkgs.darwin.apple_sdk.frameworks; [
AppKit
CoreFoundation
CoreServices
Foundation
Security
]);
}

27
nix/default.nix Normal file
View file

@ -0,0 +1,27 @@
{ pkgs }:
let
rustVersion = (pkgs.rust-bin.fromRustupToolchainFile ../rust-toolchain.toml);
rustPlatform = pkgs.makeRustPlatform {
cargo = rustVersion;
rustc = rustVersion;
};
# this will allow our callPackage to reference our own packages defined below
# mainly helps with passing compile-deps and rustPlatform to builder automatically
callPackage = pkgs.lib.callPackageWith (pkgs // packages);
packages = {
inherit rustPlatform;
compile-deps = callPackage ./compile-deps.nix { };
rust-shell =
(rustVersion.override { extensions = [ "rust-src" "rust-analyzer" ]; });
# all rust crates in workspace.members of Cargo.toml
roc-full = callPackage ./builder.nix { };
roc-lang-server = callPackage ./builder.nix { subPackage = "lang_srv"; };
# only the CLI crate = executable provided in nightly releases
roc-cli = callPackage ./builder.nix { subPackage = "cli"; };
};
in
packages

View file

@ -9,7 +9,6 @@
# - update nightly-OLD_DATE in .github/workflows/windows_tests.yml # - update nightly-OLD_DATE in .github/workflows/windows_tests.yml
# - update nightly-OLD_DATE in .github/workflows/windows_release_build.yml # - update nightly-OLD_DATE in .github/workflows/windows_release_build.yml
# - update nightly-OLD_DATE in crates/compiler/build/src/link.rs # - update nightly-OLD_DATE in crates/compiler/build/src/link.rs
# - Follow instructions in default.nix: `assert pkgs.lib.assertMsg rustVersionsMatch` ...
channel = "1.71.1" # check ^^^ when changing this channel = "1.71.1" # check ^^^ when changing this
# #

3
www/.gitignore vendored
View file

@ -1,3 +0,0 @@
/build
roc_repl_wasm.js
roc_repl_wasm_bg.wasm

View file

@ -18,23 +18,6 @@ Token : [
view : Html.Node view : Html.Node
view = view =
output = output =
# # Select anything here to see an explanation.
# main =
# Path.fromStr "url.txt"
# |> storeEmail
# |> Task.onErr handleErr
#
# storeEmail = \filename ->
# url <- File.readUtf8 filename |> Task.await
# { name, email } <- Http.get url Json.codec |> Task.await
#
# File.writeUtf8 (Path.fromStr "\(name).txt") email
#
# handleUrl = \err ->
# when err is
# HttpErr url _ -> Stderr.line "Error fetching URL \(url)"
# FileReadErr path _ -> Stderr.line "Error reading \(Path.display path)"
# FileWriteErr path _ -> Stderr.line "Error writing \(Path.display path)"
sectionsToStr [ sectionsToStr [
Desc [Comment "<span class='desktop'>Click anything here to see an explanation.</span><span class='mobile'>Tap anything here to\n# see an explanation.</span>"] "<p><a href=\"/tutorial#comments\">Comments</a> in Roc begin with a <code>#</code> and go to the end of the line.</p>", Desc [Comment "<span class='desktop'>Click anything here to see an explanation.</span><span class='mobile'>Tap anything here to\n# see an explanation.</span>"] "<p><a href=\"/tutorial#comments\">Comments</a> in Roc begin with a <code>#</code> and go to the end of the line.</p>",
Newline, Newline,
@ -42,34 +25,33 @@ view =
Indent, Indent,
Desc [Ident "Path.fromStr", Str "\"url.txt\""] "<p>This converts the string <code>\"url.txt\"</code> into a <code>Path</code> by passing it to <code>Path.fromStr</code>.</p><p>Function arguments are separated with whitespace. Parentheses are only needed in <a href=\"/tutorial#calling-functions\">nested function calls</a>.</p>", Desc [Ident "Path.fromStr", Str "\"url.txt\""] "<p>This converts the string <code>\"url.txt\"</code> into a <code>Path</code> by passing it to <code>Path.fromStr</code>.</p><p>Function arguments are separated with whitespace. Parentheses are only needed in <a href=\"/tutorial#calling-functions\">nested function calls</a>.</p>",
Newline, Newline,
Desc [Kw "|>", Ident "storeEmail"] "<p>The <a href=\"/tutorial#the-pipe-operator\">pipe operator</a> (<code>|></code>) is syntax sugar for passing the previous value to the next function in the “pipeline.”</p><p>This line takes the value that <code>Path.fromStr \"url.txt\"</code> returns and passes it to <code>storeEmail</code>.</p><p>The next <code>|></code> continues the pipeline.</p>", Desc [Kw "|>", Ident "storeEmail"] "<p>The <a href=\"/tutorial#the-pipe-operator\">pipe operator</a> (<code>|></code>) is syntax sugar for passing the previous value to the next function in the \"pipeline.\"</p><p>This line takes the value that <code>Path.fromStr \"url.txt\"</code> returns and passes it to <code>storeEmail</code>.</p><p>The next <code>|></code> continues the pipeline.</p>",
Newline, Newline,
Desc [Kw "|>", Ident "Task.onErr", Ident "handleErr"] "<p>If the task returned by the previous step in the pipeline fails, pass its error to <code>handleErr</code>.</p><p>The pipeline essentially does this:</p><pre><code>val1 = Path.fromStr \"url.txt\"\nval2 = storeEmail val1\n\nTask.onErr val2 handleErr</code></pre><p>It creates a <code>Path</code> from a string, stores an email based on that path, and then does error handling.</p>", Desc [Kw "|>", Ident "Task.onErr", Ident "handleErr"] "<p>If the task returned by the previous step in the pipeline fails, pass its error to <code>handleErr</code>.</p><p>The pipeline essentially does this:</p><pre><code>a = Path.fromStr \"url.txt\"\nb = storeEmail a\n\nTask.onErr b handleErr</code></pre><p>It creates a <code>Path</code> from a string, stores an email based on that path, and then does error handling.</p>",
Outdent, Outdent,
Newline, Newline,
Desc [Ident "storeEmail", Kw "=", Lambda ["filename"]] "<p>This <a href=\"/tutorial#defining-functions\">defines a function</a> named <code>storeEmail</code>.</p><p>In Roc, functions are ordinary values, so we assign names to them using <code>=</code> like with any other value.</p><p>The <code>\\arg1, arg2 -&gt;</code> syntax begins a function, and the part after <code>-&gt;</code> is the function's body.</p>", Desc [Ident "storeEmail", Kw "=", Lambda ["path"]] "<p>This <a href=\"/tutorial#defining-functions\">defines a function</a> named <code>storeEmail</code>. It takes one argument, named <code>path</code>.</p><p>In Roc, functions are ordinary values, so we assign names to them using <code>=</code> like with any other value.</p><p>The <code>\\arg1, arg2 -&gt;</code> syntax begins a function, and the part after <code>-&gt;</code> is the function's body.</p>",
Indent, Indent,
Desc [Ident "url", Kw "&lt;-", Ident "File.readUtf8", Ident "filename", Kw "|>", Ident "Task.await"] "<p>This reads the contents of the file (as a <a href=\"https://en.wikipedia.org/wiki/UTF-8\">UTF-8</a> string) into <code>url</code>.</p><p>The <code>&lt;-</code> does <a href=\"/tutorial#backpassing\">backpassing</a>, which is syntax sugar for defining a function. This line desugars to:</p><pre><code>Task.await\n (File.readUtf8 filename)\n \\url -&gt;</code></pre><p>The lines after this one form the body of the <code>\\url -&gt;</code> <a href=\"https://en.wikipedia.org/wiki/Callback_(computer_programming)\">callback</a>, which runs if the file read succeeds.</p>", Desc [Ident "url", Kw "&lt;-", Ident "File.readUtf8", Ident "path", Kw "|>", Ident "Task.await"] "<p>This reads the contents of the file (as a <a href=\"https://en.wikipedia.org/wiki/UTF-8\">UTF-8</a> string) into <code>url</code>.</p><p>The <code>&lt;-</code> does <a href=\"/tutorial#backpassing\">backpassing</a>, which is syntax sugar for defining a function. This line desugars to:</p><pre><code>Task.await\n (File.readUtf8 path)\n \\url -&gt;</code></pre><p>The lines after this one form the body of the <code>\\url -&gt;</code> <a href=\"https://en.wikipedia.org/wiki/Callback_(computer_programming)\">callback</a>, which runs if the file read succeeds.</p>",
Newline, Newline,
Desc [Ident "user", Kw "&lt;-", Ident "Http.get", Ident "url", Ident "Json.codec", Kw "|>", Ident "Task.await"] "<p>This fetches the contents of the URL and decodes them as <a href=\"https://www.json.org\">JSON</a>.</p><p>If the shape of the JSON isnt compatible with the type of <code>user</code> (based on type inference), this will give a decoding error immediately.</p><p>As with all the other lines ending in <code>|> Task.await</code>, if theres an error, nothing else in <code>storeEmail</code> will be run, and <code>handleErr</code> will end up handling the error.</p>", Desc [Ident "user", Kw "&lt;-", Ident "Http.get", Ident "url", Ident "Json.codec", Kw "|>", Ident "Task.await"] "<p>This fetches the contents of the URL and decodes them as <a href=\"https://www.json.org\">JSON</a>.</p><p>If the shape of the JSON isn't compatible with the type of <code>user</code> (based on type inference), this will give a decoding error immediately.</p><p>As with all the other lines ending in <code>|> Task.await</code>, if there's an error, nothing else in <code>storeEmail</code> will be run, and <code>handleErr</code> will end up handling the error.</p>",
Newline, Newline,
Desc [Ident "dest", Kw "=", StrInterpolation "\"" "user.name" ".txt\""] "<p>The <code>\\(user.name)</code> in this string literal will be replaced with the value in <code>name</code>. This is <a href=\"/tutorial#string-interpolation\">string interpolation</a>.</p><p>Note that this line doesn't end with <code>|> Task.await</code>. Earlier lines needed that because they were I/O <a href=\"/tutorial#tasks\">tasks</a>, but this is a plain old <a href=\"/tutorial#defs\">definition</a>, so there's no task to await.</p>", Desc [Ident "dest", Kw "=", Ident "Path.fromStr", StrInterpolation "\"" "user.name" ".txt\""] "<p>The <code>\\(user.name)</code> in this string literal will be replaced with the value in <code>name</code>. This is <a href=\"/tutorial#string-interpolation\">string interpolation</a>.</p><p>Note that this line doesn't end with <code>|> Task.await</code>. Earlier lines needed that because they were I/O <a href=\"/tutorial#tasks\">tasks</a>, but this is a plain old <a href=\"/tutorial#defs\">definition</a>, so there's no task to await.</p>",
Newline, Newline,
Desc [Literal "_"] "<p>In Roc, if you dont want to bother naming something, you can always choose the name <code>_</code>.</p><p>You can name as many things as you like <code>_</code>, but you can never reference anything named <code>_</code>.</p><p>So its only useful for when you dont want to choose a name.</p>", Desc [Literal "_", Kw "&lt;-", Ident "File.writeUtf8", Ident "dest", Ident "user.email", Kw "|>", Ident "Task.await"] "<p>This writes <code>user.email</code> to the file, encoded as <a href=\"https://en.wikipedia.org/wiki/UTF-8\">UTF-8</a>.</p><p>We won't be using the output of <code>writeUtf8</code>, so we name it <code>_</code>. The special name <code>_</code> is for when you don't want to use something.</p><p>You can name as many things as you like <code>_</code>, but you can never reference anything named <code>_</code>. So it's only useful for when you don't want to choose a name.</p>",
Desc [Kw "&lt;-", Ident "File.writeUtf8", ParensAround [Ident "Path.fromStr dest"], Ident "user.email", Kw "|>", Ident "Task.await"] "<p>This writes the <code>user.email</code> string to the file encoded as <a href=\"https://en.wikipedia.org/wiki/UTF-8\">UTF-8</a>.</p><p>The parentheses here show where the nested call to <code>Path.fromStr</code> begins and ends.</p>",
Newline, Newline,
Desc [Ident "Stdout.line", StrInterpolation "\"Wrote email to " "dest" "\""] "<p>This prints what we did to <a href=\"https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)\">stdout</a>.</p><p>Note that this line doesn't end with <code>|> Task.await</code>. Thats because, although <code>Stdout.line</code> returns a <a href=\"/tutorial#tasks\">task</a>, we dont need to await it because nothing happens after it.</p>", Desc [Ident "Stdout.line", StrInterpolation "\"Wrote email to " "Path.display dest" "\""] "<p>This prints what we did to <a href=\"https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)\">stdout</a>.</p><p>Note that this line doesn't end with <code>|> Task.await</code>. That's because, although <code>Stdout.line</code> returns a <a href=\"/tutorial#tasks\">task</a>, we don't need to await it because nothing happens after it.</p>",
Outdent, Outdent,
Newline, Newline,
Desc [Ident "handleErr", Kw "=", Lambda ["err"]] "<p>Like <code>storeEmail</code>, <code>handleErr</code> is also a function.</p><p>Although type annotations are optional everywhere in Roc—because the language has 100% type inference—you could add type annotations to <code>main</code>, <code>storeEmail</code>, and <code>handleErr</code> if you wanted to.</p>", Desc [Ident "handleErr", Kw "=", Lambda ["err"]] "<p>Like <code>storeEmail</code>, <code>handleErr</code> is also a function.</p><p>Although type annotations are optional everywhere in Roc—because the language has 100% type inference—you could add type annotations to <code>main</code>, <code>storeEmail</code>, and <code>handleErr</code> if you wanted to.</p>",
Indent, Indent,
Desc [Kw "when", Ident "err", Kw "is"] "<p>This will run one of the following lines depending on what value the <code>err</code> argument has.</p><p>Each line does a <a href=\"/tutorial#tags-with-payloads\">pattern match</a> on the shape of the error to decide whether to run, or to move on and try the next line's pattern.</p><p>Roc will do compile-time <a href=\"/tutorial#exhaustiveness\">exhaustiveness checking</a> and tell you if you forgot to handle any error cases here that could have occurred, based on the tasks that were run in <code>storeEmail</code>.</p>", Desc [Kw "when", Ident "err", Kw "is"] "<p>This will run one of the following lines depending on what value the <code>err</code> argument has.</p><p>Each line does a <a href=\"/tutorial#tags-with-payloads\">pattern match</a> on the shape of the error to decide whether to run, or to move on and try the next line's pattern.</p><p>Roc will do compile-time <a href=\"/tutorial#exhaustiveness\">exhaustiveness checking</a> and tell you if you forgot to handle any error cases here that could have occurred, based on the tasks that were run in <code>storeEmail</code>.</p>",
Indent, Indent,
Desc [Literal "HttpErr", Ident "url", Kw "_", Kw "->", Ident "Stderr.line", StrInterpolation "\"Error fetching URL " "url" "\""] "<p>This line will run if the <code>Http.get</code> request from earlier encountered an HTTP error.</p><p>It handles the error by printing an error message to <a href=\"https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)\">stderr</a>.</p><p>The <code>_</code> is where more information about the error is stored in the <code>HttpErr</code>. If we wanted to print more detail about what the error was, wed name that something other than <code>_</code> and actually use it.</p>", Desc [Literal "HttpErr", Ident "url", Kw "_", Kw "->", Ident "Stderr.line", StrInterpolation "\"Error fetching URL " "url" "\""] "<p>This line will run if the <code>Http.get</code> request from earlier encountered an HTTP error.</p><p>It handles the error by printing an error message to <a href=\"https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)\">stderr</a>.</p><p>The <code>_</code> is where more information about the error is stored in the <code>HttpErr</code>. If we wanted to print more detail about what the error was, we'd name that something other than <code>_</code> and actually use it.</p>",
Newline, Newline,
Desc [Literal "FileReadErr", Ident "path", Kw "_", Kw "->", Ident "Stderr.line", StrInterpolation "\"Error reading from " "Path.display path" "\""] "<p>This line will run if the <code>File.readUtf8</code> from earlier encountered a file I/O error.</p><p>It handles the error by printing an error message to <a href=\"https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)\">stderr</a>.</p><p>The <code>_</code> is where more information about the error is stored in the <code>FileReadErr</code>. If we wanted to print more detail about what the error was, wed name that something other than <code>_</code> and actually use it.</p>", Desc [Literal "FileReadErr", Ident "path", Kw "_", Kw "->", Ident "Stderr.line", StrInterpolation "\"Error reading from " "Path.display path" "\""] "<p>This line will run if the <code>File.readUtf8</code> from earlier encountered a file I/O error.</p><p>It handles the error by printing an error message to <a href=\"https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)\">stderr</a>.</p><p>The <code>_</code> is where more information about the error is stored in the <code>FileReadErr</code>. If we wanted to print more detail about what the error was, we'd name that something other than <code>_</code> and actually use it.</p>",
Newline, Newline,
Desc [Literal "FileWriteErr", Ident "path", Kw "_", Kw "->", Ident "Stderr.line", StrInterpolation "\"Error writing to " "Path.display path" "\""] "<p>This line will run if the <code>File.writeUtf8</code> from earlier encountered a file I/O error.</p><p>It handles the error by printing an error message to <a href=\"https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)\">stderr</a>.</p><p>The <code>_</code> is where more information about the error is stored in the <code>FileWriteErr</code>. If we wanted to print more detail about what the error was, wed name that something other than <code>_</code> and actually use it.</p>", Desc [Literal "FileWriteErr", Ident "path", Kw "_", Kw "->", Ident "Stderr.line", StrInterpolation "\"Error writing to " "Path.display path" "\""] "<p>This line will run if the <code>File.writeUtf8</code> from earlier encountered a file I/O error.</p><p>It handles the error by printing an error message to <a href=\"https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)\">stderr</a>.</p><p>The <code>_</code> is where more information about the error is stored in the <code>FileWriteErr</code>. If we wanted to print more detail about what the error was, we'd name that something other than <code>_</code> and actually use it.</p>",
] ]
div [role "presentation"] [ div [role "presentation"] [
@ -77,9 +59,9 @@ view =
samp [] [text output], samp [] [text output],
], ],
p [] [ p [] [
text "To get started learning the language, try the ", text "To get started with the language, try the ",
a [href "/tutorial"] [text "tutorial"], a [href "/tutorial"] [text "tutorial"],
text " next!", text "!",
], ],
p [id "final-tutorial-link"] [ p [id "final-tutorial-link"] [
a [class "btn-small", href "/tutorial"] [text "Start Tutorial"] a [class "btn-small", href "/tutorial"] [text "Start Tutorial"]

View file

@ -1,12 +1,37 @@
# www.roc-lang.org # www.roc-lang.org
## Prerequisites
- Linux or MacOS operating system, Windows users can use linux through WSL.
- Install [git](https://chat.openai.com/share/71fb3ae6-80d7-478c-8a27-a36aaa5ba921)
- Install [nix](https://nixos.org/download.html)
## Building the website from scratch
```bash
git clone https://github.com/roc-lang/roc.git
cd roc
nix develop
./www/build.sh
# make the roc command available
export PATH="$(pwd)/target/release/:$PATH"
bash build-dev-local.sh
```
Open http://0.0.0.0:8080 in your browser.
## After you've made a change locally
In the terminal where the web server is running:
1. kill the server with Ctrl+C
2. re-run the build script
3. refresh the page in your browser
To view the website after you've made a change, execute: To view the website after you've made a change, execute:
```bash ```bash
./www/build.sh bash build-dev-local.sh
cd www/build
simple-http-server --nocache --index # already installed if you're using `nix develop`, otherwise use `cargo install simple-http-server`
``` ```
Open http://0.0.0.0:8000 in your browser. Open http://0.0.0.0:8080 in your browser.

View file

@ -11,9 +11,8 @@ DIR="$(dirname "$0")"
cd "$DIR" || exit cd "$DIR" || exit
rm -rf dist/ rm -rf dist/
cp -r ../build dist/ cp -r build dist/
mkdir -p dist/wip cp -r public/* dist/
roc run main.roc -- content/ dist/wip/ roc run main.roc -- content/ dist/
cp -r static/* dist/wip/
npx http-server dist/ -p 8080 -c-1 --cors npx http-server dist/ -p 8080 -c-1 --cors

View file

@ -17,16 +17,16 @@ cd $SCRIPT_RELATIVE_DIR
rm -rf build/ rm -rf build/
cp -r public/ build/ cp -r public/ build/
mkdir build/wip # for WIP site
# download the latest code for the examples # download the latest code for the examples
echo 'Downloading latest examples...' echo 'Downloading latest examples...'
curl -fLJO https://github.com/roc-lang/examples/archive/refs/heads/main.zip curl -fL -o examples-main.zip https://github.com/roc-lang/examples/archive/refs/heads/main.zip
unzip examples-main.zip rm -rf examples-main/
cp -R examples-main/examples/ wip_new_website/content/examples/ unzip -o examples-main.zip
cp -R examples-main/examples/ content/examples/
# relace links in wip_new_website/content/examples/index.md to work on the WIP site # relace links in content/examples/index.md to work on the WIP site
sed -i 's|](/|](/wip/examples/|g' wip_new_website/content/examples/index.md perl -pi -e 's|\]\(/|\]\(/examples/|g' content/examples/index.md
# clean up examples artifacts # clean up examples artifacts
rm -rf examples-main examples-main.zip rm -rf examples-main examples-main.zip
@ -48,11 +48,10 @@ curl -fLJO https://github.com/roc-lang/roc/archive/www.tar.gz
# Download the latest pre-built Web REPL as a zip file. (Build takes longer than Netlify's timeout.) # Download the latest pre-built Web REPL as a zip file. (Build takes longer than Netlify's timeout.)
REPL_TARFILE="roc_repl_wasm.tar.gz" REPL_TARFILE="roc_repl_wasm.tar.gz"
curl -fLJO https://github.com/roc-lang/roc/releases/download/nightly/$REPL_TARFILE curl -fLJO https://github.com/roc-lang/roc/releases/download/nightly/$REPL_TARFILE
mkdir repl
tar -xzf $REPL_TARFILE -C repl tar -xzf $REPL_TARFILE -C repl
tar -xzf $REPL_TARFILE -C wip # note we also need this for WIP repl
rm $REPL_TARFILE rm $REPL_TARFILE
ls -lh repl ls -lh repl
ls -lh wip
popd popd
@ -77,9 +76,6 @@ rm -rf roc_nightly roc_releases.json
# we use `! [ -v GITHUB_TOKEN_READ_ONLY ];` to check if we're on a netlify server # we use `! [ -v GITHUB_TOKEN_READ_ONLY ];` to check if we're on a netlify server
if ! [ -v GITHUB_TOKEN_READ_ONLY ]; then if ! [ -v GITHUB_TOKEN_READ_ONLY ]; then
echo 'Building tutorial.html from tutorial.md...'
mkdir www/build/tutorial
cargo build --release --bin roc cargo build --release --bin roc
roc=target/release/roc roc=target/release/roc
@ -96,19 +92,12 @@ else
mv roc_nightly* roc_nightly mv roc_nightly* roc_nightly
roc='./roc_nightly/roc' roc='./roc_nightly/roc'
echo 'Building tutorial.html from tutorial.md...'
mkdir www/build/tutorial
fi fi
$roc version $roc version
$roc run www/generate_tutorial/src/tutorial.roc -- www/generate_tutorial/src/input/ www/build/tutorial/
mv www/build/tutorial/tutorial.html www/build/tutorial/index.html
# For WIP site echo 'Building site markdown content'
echo 'Building WIP site...' $roc run www/main.roc -- www/content/ www/build/
$roc run www/wip_new_website/main.roc -- www/wip_new_website/content/ www/build/wip/
cp -r www/wip_new_website/static/* www/build/wip/
# cleanup # cleanup
rm -rf roc_nightly roc_releases.json rm -rf roc_nightly roc_releases.json
@ -153,5 +142,3 @@ if [ -v GITHUB_TOKEN_READ_ONLY ]; then
rm -rf $BASIC_CLI_DIR/generated-docs rm -rf $BASIC_CLI_DIR/generated-docs
done <<< "$VERSION_NUMBERS" done <<< "$VERSION_NUMBERS"
fi fi
popd

View file

@ -10,4 +10,4 @@ Something that needs figuring out between now and then is how to keep the langua
(This would be easy to solve if zero happened to be the optimal number of new features to add over time, but unfortunately the programming landscape will change around the language, so remaining static is unlikely to be best. As such, the goal becomes finding a system which keeps the number of new features low without artificially forcing it to zero.) (This would be easy to solve if zero happened to be the optimal number of new features to add over time, but unfortunately the programming landscape will change around the language, so remaining static is unlikely to be best. As such, the goal becomes finding a system which keeps the number of new features low without artificially forcing it to zero.)
I don't have any plans for when the transition away from BDFN will happen, other than that I certainly imagine it being well after a 1.0 release of the language. I don't have any plans for when the transition away from BDFN will happen, other than that I expect it to be well after a 1.0 release of the language!

View file

@ -3,30 +3,36 @@
[Roc Zulip Chat](https://roc.zulipchat.com/) is the most active community gathering place. [Roc Zulip Chat](https://roc.zulipchat.com/) is the most active community gathering place.
We love it when new people stop by and introduce themselves in [`#introductions`](https://roc.zulipchat.com/#narrow/stream/387892-introductions) so others can welcome you to the community! We love it when new people stop by and introduce themselves in [`#introductions`](https://roc.zulipchat.com/#narrow/stream/387892-introductions) so others can welcome you to the community!
The Roc Online Meetup meets about once per month. The organizer posts a [when2meet](https://when2meet.com) for the next month's meetup, so we can find the best time that works for everyone The Roc Online Meetup meets about once per month. The organizer posts a [when2meet](https://when2meet.com) for the next month's meetup in [`#gatherings` on Zulip](https://roc.zulipchat.com/#narrow/stream/303057-gatherings), so we can find the best time that works for everyone
who's interested in attending. This happens in [`#gatherings` on Zulip](https://roc.zulipchat.com/#narrow/stream/303057-gatherings). The organizer will then make a post on Zulip with a finalized time and a link to where people can join. They usually last between 1-2 hours, and consist of some informal presentations followed by casual conversation. They're fun! who's interested in attending. The organizer then posts a finalized time and a link to where people can join. They usually last between 1-2 hours, and consist of some casual show-and-tell presentations followed by conversation. They're fun!
We have not had a Roc conference yet, although there have been a few Roc talks given at conferences, We have not had a Roc conference yet, although there have been a few Roc talks given at conferences,
and a few times when Roc community members have met up in person. and a few times when Roc community members have met up in person.
## Code of Conduct ### [Code of Conduct](#code-of-conduct) {#code-of-conduct}
The Roc project enforces [a code of conduct](https://github.com/roc-lang/roc/blob/main/CODE_OF_CONDUCT.md). Please read and follow it! The Roc project enforces [a code of conduct](https://github.com/roc-lang/roc/blob/main/CODE_OF_CONDUCT.md). Please read and follow it!
## Contributing ### [Contributing](#contributing) {#contributing}
All the source code to the Roc project is on GitHub in the [roc-lang](https://github.com/roc-lang) organization. The compiler and CLI are at [roc-lang/roc](https://github.com/roc-lang/roc), and there's a tag for [Good First Issues](https://github.com/roc-lang/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). All the source code to the Roc project is on GitHub in the [roc-lang](https://github.com/roc-lang) organization. The compiler and CLI are at [roc-lang/roc](https://github.com/roc-lang/roc), and there's a tag for [Good First Issues](https://github.com/roc-lang/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
The project has [many contributors](https://github.com/roc-lang/roc/graphs/contributors), and we like to invest in helping new contributors get started. If you'd like to become a new contributor (even if you don't know what you'd like that contribution to be yet), just make a post in [the "new contributors" topic](https://roc.zulipchat.com/#narrow/stream/316715-contributing/topic/new.20contributors) and say hello! The project has [many contributors](https://github.com/roc-lang/roc/graphs/contributors), and we like to invest in helping new contributors get started. If you'd like to become a new contributor (even if you don't know what you'd like that contribution to be yet), just make a post in [the "new contributors" topic](https://roc.zulipchat.com/#narrow/stream/316715-contributing/topic/new.20contributors) and say hello!
## Design ideas, feature requests, proposals, etc. ### [Ideas, proposals, and feature requests](#ideas) {#ideas}
Roc doesn't have a formal process for managing design proposals. At the current size of the project, having a formal process (e.g. a [RFC](https://en.wikipedia.org/wiki/Change_request) system) would be more heavyweight than it's worth. Fow now, the guiding principle is that as a community we should all be friendly, supportive, and openly share and discuss ideas without the expectation that they will necessarily be accepted or not. We follow a [BDFN](/bdfn) leadership model today, although this is planned to change someday. Roc doesn't have a formal process for managing design proposals.
At the current size of the project, having a formal process (like a [RFC](https://en.wikipedia.org/wiki/Change_request) system) would be more heavyweight than it's worth. Fow now, the guiding principle is that as a community we should all be friendly, supportive, and openly share and discuss ideas without the expectation that they will necessarily be accepted or not. We follow a [BDFN](/bdfn) leadership model today, although this is planned to change someday.
There are three loose stages that a design proposal can go through in Roc: idea, proposal, and implementation. These are guidelines, not strict requirements; their goal is to prevent the outcome where someone does a lot of implementation work only to have their contribution never make it into the code base because it's determined that we wanted to go in a different design direction. Confirming ahead of time that the design direction is desired can prevent implementing something that ends up not being used. There are three loose stages that a design proposal can go through in Roc: idea, proposal, and implementation. These are guidelines, not strict requirements; their goal is to prevent the outcome where someone does a lot of implementation work only to have their contribution never make it into the code base because it's determined that we wanted to go in a different design direction. Confirming ahead of time that the design direction is desired can prevent implementing something that ends up not being used.
In the idea stage, people are encouraged to describe their idea and explore the problem, potential solutions, and trade-offs. It's a good idea to share the idea in [`#ideas` on Zulip](https://roc.zulipchat.com/#narrow/stream/304641-ideas). There's no prerequisite for sharing an idea (it's only an idea, after all!) and likewise there's also no obligation for any contributor to necessarily act on it. In the idea stage, people are encouraged to describe their idea and explore the problem, potential solutions, and tradeoffs. It's a good idea to share the idea in [`#ideas` on Zulip](https://roc.zulipchat.com/#narrow/stream/304641-ideas). There's no prerequisite for sharing an idea (it's only an idea, after all!) and likewise there's also no obligation for any contributor to necessarily act on it.
If the idea seems promising and worth developing further (as confirmed by a Roc contributor with expertise in the relevant area, doesn't have to be the [BDFN](/bdfn)), usually the next step is to get more specific with a written proposal that details all the necessary information about what the change would involve. A written proposal isn't always necessary (e.g. it may be deemed a simple and uncontroversial enough change that we're comfortable proceeding straight to implementation), but since writing proposals can be time-consuming, it's definitely a good idea to get confirmation at the idea stage from an experienced contributor before taking the time to write one up. If the idea seems promising and worth developing further (as confirmed by a Roc contributor with expertise in the relevant area—not necessarily the [BDFN](/bdfn)), usually the next step is to get more specific with a written proposal that details all the necessary information about what the change would involve.
A written proposal isn't always necessary (for example, it may be deemed a simple and uncontroversial enough change that we're comfortable proceeding straight to implementation), but since writing proposals can be time-consuming, it's definitely a good idea to get confirmation at the idea stage from an experienced contributor before taking the time to write one up.
There's no guarantee that a proposal will be accepted, and even if it is, there's no guarantee that something won't come up during implementation that changes the tradeoffs and means it doesn't end up making it into the language after all. But if it is accepted (again, doesn't have to be by the [BDFN](/bdfn) - although the BDFN does have final say if there's a disagreement at some point), that means a [Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) should be reviewed, and the general understanding is that the PR will be accepted unless some problem is discovered during implementation that wasn't anticipated at the proposal stage. There's no guarantee that a proposal will be accepted, and even if it is, there's no guarantee that something won't come up during implementation that changes the tradeoffs and means it doesn't end up making it into the language after all. But if it is accepted (again, doesn't have to be by the [BDFN](/bdfn) - although the BDFN does have final say if there's a disagreement at some point), that means a [Pull Request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) should be reviewed, and the general understanding is that the PR will be accepted unless some problem is discovered during implementation that wasn't anticipated at the proposal stage.
This is the process we're using for now, but of course it may change in the future!

14
www/content/docs.md Normal file
View file

@ -0,0 +1,14 @@
# Documentation
- [builtins](/builtins) - docs for modules built into the language—`Str`, `Num`, etc.
- [basic-webserver](https://roc-lang.github.io/basic-webserver/) - a platform for making Web servers ([source code](https://github.com/roc-lang/basic-webserver))
- [basic-cli](/packages/basic-cli) - a platform for making command-line interfaces ([source code](https://github.com/roc-lang/basic-cli))
In the future, a language reference will be on this page too.
## [Guides](#guides) {#guides}
- [FAQ (Frequently Asked Questions)](https://github.com/roc-lang/roc/blob/main/FAQ.md)
- [Platforms & Applications](/platforms)
- [Roc for Elm Programmers](https://github.com/roc-lang/roc/blob/main/roc-for-elm-programmers.md)
- [Tutorial](/tutorial)

13
www/content/donate.md Normal file
View file

@ -0,0 +1,13 @@
# Donate
We are a small group trying to do big things, and we are grateful for every donation! Right now we are trying to raise $4,000 USD/month in donations to fund one longtime Roc contributor to continue his work on Roc full-time.
You can donate to Roc's development using:
- [GitHub Sponsors](https://github.com/sponsors/roc-lang)
- [Liberapay](https://liberapay.com/roc_lang)
All donations go through the [Roc Programming Language Foundation](https://foundation.roc-lang.org/), a registered <a href="https://en.wikipedia.org/wiki/501(c)(3)_organization">US <span class="nowrap">501(c)(3)</span> nonprofit organization</a>, which means these donations are tax-exempt in the US.
It also means that the foundation's tax filings are a [matter of public record](https://en.wikipedia.org/wiki/501(c)(3)_organization#Transparency), so you have real transparency into how your donations are advancing Roc!
If you would like your organization to become an official sponsor of Roc's development, please [DM Richard Feldman on Zulip](https://roc.zulipchat.com/#narrow/pm-with/281383-user281383). We'd love to talk to you!

84
www/content/fast.md Normal file
View file

@ -0,0 +1,84 @@
# Fast
Roc code is designed to build fast and run fast...but what does "fast" mean here? And how close is Roc's current implementation to realizing that goal?
## [Fast programs](#fast-programs) {#fast-programs}
What "fast" means in embedded systems is different from what it means in games, which in turn is different from what it means on the Web. To better understand Roc's performance capabilities, let's look at the upper bound of how fast optimized Roc programs are capable of running, and the lower bound of what types of languages Roc should generally outperform.
### [Limiting factors: memory management and async I/O](#limiting-factors) {#limiting-factors}
<span class="nowrap">Roc is a</span> [memory-safe](https://en.wikipedia.org/wiki/Memory_safety) language with [automatic memory management](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)#Reference_counting). Automatic memory management has some unavoidable runtime overhead, and memory safety based on static analysis rules out certain performance optimizations—which is why [unsafe Rust](https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html) can outperform safe Rust. This gives Roc a lower performance ceiling than languages which support memory unsafety and manual memory management, such as C, C++, Zig, and Rust.
Another part of Roc's design is that all I/O operations are done using a lightweight state machine so that they can be asynchronous. This has potential performance benefits compared to synchronous I/O, but it also has some unavoidable overhead.
### [Generally faster than dynamic or gradual languages](#faster-than) {#faster-than}
As a general rule, Roc programs should have almost strictly less runtime overhead than equivalent programs written in languages with dynamic types and automatic memory management. This doesn't mean all Roc programs will outperform all programs in these languages, but it does mean Roc should have a higher ceiling on what performance is achievable.
This is because dynamic typing (and gradual typing) requires tracking types at runtime, which has overhead. Roc tracks types only at compile time, and tends to have [minimal (often zero) runtime overhead](https://vimeo.com/653510682) for language constructs compared to the top performers in industry. For example, Roc's generics, records, functions, numbers, and tag unions have no more runtime overhead than they would in their Rust or C++ equivalents.
When [benchmarking compiled Roc programs](https://www.youtube.com/watch?v=vzfy4EKwG_Y), the goal is to have them normally outperform the fastest mainstream garbage-collected languages (for example, Go, C#, Java, and JavaScript), but it's a non-goal to outperform languages that support memory unsafety or manual memory management. There will always be some individual benchmarks where mainstream garbage-collected languages outperform Roc, but the goal is for these to be uncommon rather than the norm.
### [Domain-specific memory management](#domain-specific-memory-management) {#domain-specific-memory-management}
Roc's ["platforms and applications" design](/platforms) means its automatic memory management can take advantage of domain-specific properties to improve performance.
For example, if you build an application on the [`basic-cli` platform](https://github.com/roc-lang/basic-cli) compared to the [`basic-webserver` platform](https://github.com/roc-lang/basic-webserver), each of those platforms may use a different memory management strategy under the hood that's tailored to their respective use cases. Your application's performance can benefit from this, even though building on either of those platforms feels like using ordinary automatic memory management.
This is because Roc [platforms](/platforms) get to determine how memory gets allocated and deallocated in applications built on them. ([`basic-cli`](https://github.com/roc-lang/basic-cli) and [`basic-webserver`](https://github.com/roc-lang/basic-webserver) are examples of platforms, but anyone can build their own platform.) Here are some examples of how platforms can use this to improve application performance:
- A platform for noninteractive command-line scripts can skip deallocations altogether, since any allocated memory will be cheaply reclaimed by the operating system anyway once the script exits. (This strategy is domain-specific; it would not work well for a long-running, interactive program!)
- A platform for Web servers can put all allocations for each request into a particular [region of memory](https://en.wikipedia.org/wiki/Region-based_memory_management) (this is known as "arena allocation" or "bump allocation") and then deallocate the entire region in one cheap operation after the response has been sent. This would essentially drop memory reclamation times to zero. (This strategy relies on Web servers' request/response architecture, and wouldn't make sense in other use cases. [`nea`](https://github.com/tweedegolf/nea) is a platform in early development that is working towards implementing this.)
- A platform for applications that have very long-lived state could implement [meshing compaction](https://youtu.be/c1UBJbfR-H0?si=D9Gp0cdpjZ_Is5v8) to decrease memory fragmentation. (Compaction would probably be a net negative for performance in the previous two examples.)
[This talk](https://www.youtube.com/watch?v=cpQwtwVKAfU&t=75s) has more information about platforms and applications, including demos and examples of other benefits they unlock besides performance.
### [Current progress](#current-progress) {#current-progress}
Roc's "platforms and applications" design already works, including the domain-specific memory management. Most of Roc's data structures are already close to their theoretical limit in terms of performance, at least without changing their behavior or introducing memory unsafety. [This talk](https://vimeo.com/653510682) explains how they're implemented under the hood.
That said, [the current implementation](https://ayazhafiz.com/articles/23/a-lambda-calculus-with-coroutines-and-heapless-closures) of [defunctionalization](https://blog.sigplan.org/2019/12/30/defunctionalization-everybody-does-it-nobody-talks-about-it/) (based on [this paper](https://dl.acm.org/doi/pdf/10.1145/3591260))—which unlocks stack-allocated closures, among other optimizations—has [significant known gaps](https://github.com/roc-lang/roc/issues/5969), and has a ways to go before it works across the board. (If you're interested in getting involved in that implementation, [we'd love to hear from you](https://roc.zulipchat.com)!)
Current optimizations that are completely implemented (give or take the occasional bug) include [LLVM](https://llvm.org/), [Morphic](https://www.youtube.com/watch?v=F3z39M0gdJU&t=3547s), [Perceus](https://www.microsoft.com/en-us/research/uploads/prod/2020/11/perceus-tr-v1.pdf), and tail recursion optimization (including [modulo cons](https://en.wikipedia.org/wiki/Tail_call#Tail_recursion_modulo_cons)). Promising examples of potential future optimizations include closure-aware [inlining](https://en.wikipedia.org/wiki/Inline_expansion), [automatic deforestation](https://www.cs.tufts.edu/~nr/cs257/archive/duncan-coutts/stream-fusion.pdf), and full [compile-time evaluation](https://en.wikipedia.org/wiki/Constant_folding) of top-level declarations.
We're also interested in improving the performance of the Morphic alias analysis pass itself; if contributing to that project (or any other optimization project) interests you, please [let us know in the `#contributing` channel](https://roc.zulipchat.com/#narrow/stream/316715-contributing)!
## Fast Feedback Loops
One of Roc's goals is to provide fast feedback loops by making builds normally feel "instant" except on truly enormous projects.
It's a concrete goal to have them almost always complete in under 1 second on the median computer being used to write Roc (assuming that system is not bogged down with other programs using up its resources), and ideally under the threshold at which humans typically find latency perceptible (around 100 milliseconds). In the future, hot code loading can make the feedback loop even faster, by letting you see changes without having to restart your program.
Note that although having fast "clean" builds (without the benefit of caching) is a goal, the "normally feels instant" goal refers to builds where caching was involved. After all, the main downside of build latency is that it comes up over and over in a feedback loop; a fast initial "clean" build is valuable too, but it comes up rarely by comparison.
### Current Progress
`roc check` checks your code for errors (such as invalid syntax, naming errors, and type mismatches) and reports problems it finds. On typical development laptops, this usually takes well under 1 second for small projects (for very small projects, it can be around 10 milliseconds on some popular machines). To date, the largest known Roc projects have lines of code numbering in the low thousands, so there's no data yet on `roc check` times for larger projects.
`roc build` does everything `roc check` does, but it additionally builds a runnable binary of your program. You may notice that `roc build` takes much longer to complete! This is because
of two projects that are underway but not completed yet:
- *Development backend* refers to generating machine code directly instead of asking [LLVM](https://llvm.org/) to generate it. LLVM is great at generating optimized machine code, but it takes a long time to generate it—even if you turn off all the optimizations (and `roc` only has LLVM perform optimizations when the `--optimize` flag is set). The dev backend is currently implemented for WebAssembly, which you can see in the [Web REPL](https://www.roc-lang.org/repl), and in `roc repl` except on Windows. Work is underway to implement it for `roc build` and `roc run`, as well as macOS, Windows, and the ARM versions of all of these.
- *Surgical linking* refers to a fast way of combining the platform and application into one binary. Today, this works on x64 Linux, x64 Windows, and WebAssembly. `roc build` on macOS is noticeably slower because it falls back on non-surgical linking.
Here's a table summarizing the current progress:
Target | Dev backend | Surgical linking |
------------|-------------|-------------------|
WebAssembly | yes | yes |
macOS ARM | repl only | |
macOS x64 | repl only | |
Linux ARM | repl only | |
Linux x64 | repl only | yes |
Windows x64 | | yes |
Windows ARM | | |
Once we have full coverage, `roc build` (and `roc run` and `roc test`, which also perform builds) should take only a bit longer than `roc check`.
The next major performance improvement will be caching. Currently, `roc` always builds everything from scratch. Most of the time, it could benefit from caching some of the work it had done in a previous build, but today it doesn't do that. There's a design for the caching system, but essentially none of the implementation has started yet. Hot code loading will be the next major improvement after caching, but it requires full dev backend coverage, and does not have a concrete design yet.
## Friendly
In addition to being fast, Roc also aims to be a friendly programming language.
[What does _friendly_ mean here?](/friendly)

100
www/content/friendly.md Normal file
View file

@ -0,0 +1,100 @@
# Friendly
Besides having a [friendly community](/community), Roc also prioritizes being a user-friendly language. This impacts the syntax, semantics, and tools Roc ships with.
## [Syntax and Formatter](#syntax) {#syntax}
Roc's syntax isn't trivial, but there also isn't much of it to learn. It's designed to be uncluttered and unambiguous. A goal is that you can normally look at a piece of code and quickly get an accurate mental model of what it means, without having to think through several layers of indirection. Here are some examples:
- `user.email` always accesses the `email` field of a record named `user`. <span class="nowrap">(Roc has</span> no inheritance, subclassing, or proxying.)
- `Email.isValid` always refers to something named `isValid` exported by a module named `Email`. (Module names are always capitalized, and variables/constants never are.) Modules are always defined statically and can't be modified at runtime; there's no [monkey patching](https://en.wikipedia.org/wiki/Monkey_patch) to consider either.
- `x = doSomething y z` always declares a new constant `x` (Roc has [no mutable variables, reassignment, or shadowing](/functional)) to be whatever the `doSomething` function returns when passed the arguments `y` and `z`. (Function calls in Roc don't need parentheses or commas.)
- `"Name: \(Str.trim name)"` uses *string interpolation* syntax: a backslash inside a string literal, followed by an expression in parentheses. This code is the same as combining the string `"Name: "` with the string returned by the function call `Str.trim name`.<br><br>Because Roc's string interpolation syntax begins with a backslash (just like other backlash-escapes such as `\n` and `\"`), you can always tell which parts of a string involve special handling: the parts that begin with backslashes. Everything else works as normal.
Roc also ships with a source code formatter that helps you maintain a consistent style with little effort. The `roc format` command neatly formats your source code according to a common style, and it's designed with the time-saving feature of having no configuration options. This feature saves teams all the time they would otherwise spend debating which stylistic tweaks to settle on!
## [Helpful compiler](#helpful-compiler) {#helpful-compiler}
Roc's compiler is designed to help you out. It does complete type inference across all your code, and the type system is [sound](https://en.wikipedia.org/wiki/Type_safety). This means you'll never get a runtime type mismatch if everything type-checked (including null exceptions; Roc doesn't have the [billion-dollar mistake](https://en.wikipedia.org/wiki/Null_pointer#History)), and you also don't have to write any type annotations—the compiler can infer all the types in your program.
If there's a problem at compile time, the compiler is designed to report it in a helpful way. Here's an example:
<pre><samp class="code-snippet"><span class="literal">── TYPE MISMATCH ─────── /home/my-roc-project/main.roc ─</span>
Something is off with the <span class="literal">then</span> branch of this <span class="literal">if</span>:
<span class="literal">4│</span> someInt : I64
<span class="literal">5│</span> someInt =
<span class="literal">6│</span> if someDecimal > 0 then
<span class="literal">7│</span> someDecimal + 1
<span class="error">^^^^^^^^^^^^^^^</span>
This branch is a fraction of type:
<span class="literal">Dec</span>
But the type annotation on `someInt` says it should be:
<span class="literal">I64</span>
<span class="literal">Tip:</span> You can convert between integers and fractions
using functions like `Num.toFrac` and `Num.round`.</samp></pre>
If you like, you can run a program that has compile-time errors like this. (If the program reaches the error at runtime, it will crash.)
This lets you do things like trying out code that's only partially finished, or running tests for one part of your code base while other parts have compile errors. (Note that this feature is only partially completed, and often errors out; it has a ways to go before it works for all compile errors!)
## [Serialization inference](#serialization-inference) {#serialization-inference}
When dealing with [serialized data](https://en.wikipedia.org/wiki/Serialization), an important question is how and when that data will be decoded from a binary format (such as network packets or bytes on disk) into your program's data structures in memory.
A technique used in some popular languages today is to decode without validation. For example, some languages parse [JSON](https://www.json.org) using a function whose return type is unchecked at compile time (commonly called an `any` type). This technique has a low up-front cost, because it does not require specifying the expected shape of the JSON data.
Unfortunately, if there's any mismatch between the way that returned value ends up being used and the runtime shape of the JSON, it can result in errors that are time-consuming to debug because they are distant from (and may appear unrelated to) the JSON decoding where the problem originated. Since Roc has a [sound type system](https://en.wikipedia.org/wiki/Type_safety), it does not have an `any` type, and cannot support this technique.
Another technique is to validate the serialized data against a schema specified at compile time, and give an error during decoding if the data doesn't match this schema. Serialization formats like [protocol buffers](https://protobuf.dev/) require this approach, but some languages encourage (or require) doing it for _all_ serialized data formats, which prevents decoding errors from propagating throughout the program and causing distant errors. Roc supports and encourages using this technique.
In addition to this, Roc also supports serialization _inference_. It has some characteristics of both other approaches:
- Like the first technique, it does not require specifying a schema up front.
- Like the second technique, it reports any errors immediately during decoding rather than letting the problems propagate through the program.
This technique works by using Roc's type inference to infer the expected shape of serialized data based on how it's used in your program. Here's an example, using [`Decode.fromBytes`](https://www.roc-lang.org/builtins/Decode#fromBytes) to decode some JSON:
<pre><samp class="code-snippet"><span class="kw">when</span> Decode<span class="punctuation section">.</span>fromBytes data Json<span class="punctuation section">.</span>codec <span class="kw">is</span>
<span class="literal">Ok</span> decoded <span class="kw">-></span> <span class="comment"># (use the decoded data here)</span>
<span class="literal">Err</span> err <span class="kw">-></span> <span class="comment"># handle the decoding failure</span></samp></pre>
In this example, whether the `Ok` or `Err` branch gets taken at runtime is determined by the way the `decoded` value is used in the source code.
For example, if `decoded` is used like a record with a `username` field and an `email` field, both of which are strings, then this will fail at runtime if the JSON doesn't have fields with those names and those types. No type annotations are needed for this; it relies entirely on Roc's type inference, which by design can correctly infer types for your entire program even without annotations.
Serialization inference has a low up-front cost in the same way that the decode-without-validating technique does, but it doesn't have the downside of decoding failures propagating throughout your program to cause distant errors at runtime. (It also works for encoding; there is an [Encode.toBytes](https://www.roc-lang.org/builtins/Encode#toBytes) function which encodes similarly to how [`Decode.fromBytes`](https://www.roc-lang.org/builtins/Decode#fromBytes) decodes.)
Explicitly writing out a schema has its own benefits that can balance out the extra up-front time investment, but having both techniques available means you can choose whatever will work best for you in a given scenario.
## [Testing](#testing) {#testing}
The `roc test` command runs a Roc program's tests. Each test is declared with the `expect` keyword, and can be as short as one line. For example, this is a complete test:
```roc
## One plus one should equal two.
expect 1 + 1 == 2
```
If the test fails, `roc test` will show you the source code of the `expect`, along with the values of any named variables inside it, so you don't have to separately check what they were.
If you write a documentation comment right before it (like `## One plus one should equal two` here), it will appear in the test output, so you can use that to add some descriptive context to the test if you want to.
## [Inline expectations](#inline-expect) {#inline-expect}
You can also use `expect` in the middle of functions. This lets you verify assumptions that can't reasonably be encoded in types, but which can be checked at runtime. Similarly to [assertions](https://en.wikipedia.org/wiki/Assertion_(software_development)) in other languages, these will run not only during normal program execution, but also during your tests—and they will fail the test if any of them fails.
Unlike assertions (and unlike the `crash` keyword), failed `expect`s do not halt the program; instead, the failure will be reported and the program will continue. This means all `expect`s can be safely removed during `--optimize` builds without affecting program behavior—and so `--optimize` does remove them. This means you can add inline `expect`s without having to weigh each one's helpfulness against the performance cost of its runtime check, because they won't have any runtime cost after `--optimize` removes them.
In the future, there are plans to add built-in support for [benchmarking](https://en.wikipedia.org/wiki/Benchmark_(computing)), [generative tests](https://en.wikipedia.org/wiki/Software_testing#Property_testing), [snapshot tests](https://en.wikipedia.org/wiki/Software_testing#Output_comparison_testing), simulated I/O (so you don't have to actually run the real I/O operations, but also don't have to change your code to accommodate the tests), and "reproduction replays"—tests generated from a recording of what actually happened during a particular run of your program, which deterministically simulate all the I/O that happened.
## Functional
Besides being designed to be [fast](/fast) and friendly, Roc is also a functional programming language.
[What does _functional_ mean here?](/functional)

143
www/content/functional.md Normal file
View file

@ -0,0 +1,143 @@
# Functional
Roc is designed to have a small number of simple language primitives. This goal leads Roc to be a single-paradigm [functional](https://en.wikipedia.org/wiki/Functional_programming) language, while its [performance goals](/fast) lead to some design choices that are uncommon in functional languages.
## [Opportunistic mutation](#opportunistic-mutation) {#opportunistic-mutation}
All Roc values are semantically immutable, but may be opportunistically mutated behind the scenes when it would improve performance (without affecting the program's behavior). For example:
```roc
colors
|> Set.insert "Purple"
|> Set.insert "Orange"
|> Set.insert "Blue"
```
The [`Set.insert`](https://www.roc-lang.org/builtins/Set#insert) function takes a `Set` and returns a `Set` with the given value inserted. It might seem like these three `Set.insert` calls would result in the creation of three brand-new sets, but Roc's *opportunistic mutation* optimizations mean this will be much more efficient.
Opportunistic mutation works by detecting when a semantically immutable value can be safely mutated in-place without changing the behavior of the program. If `colors` is *unique* here—that is, nothing else is currently referencing it—then `Set.insert` will mutate it and then return it. Cloning it first would have no benefit, because nothing in the program could possibly tell the difference!
If `colors` is _not_ unique, however, then the first call to `Set.insert` will not mutate it. Instead, it will clone `colors`, insert `"Purple"` into the clone, and then return that. At that point, since the clone will be unique (nothing else is referencing it, since it was just created), the subsequent `Set.insert` calls will all mutate in-place.
Roc has ways of detecting uniqueness at compile time, so this optimization will often have no runtime cost, but in some cases it instead uses automatic reference counting to tell when something that was previously shared has become unique over the course of the running program.
## [Everything is immutable (semantically)](#everything-is-immutable) {#everything-is-immutable}
Since mutation is only ever done when it wouldn't change the behavior of the program, all Roc values are semantically immutable—even though they can still benefit from the performance of in-place mutation.
In many languages, this is reversed; everything is mutable by default, and it's up to the programmer to "defensively" clone to avoid undesirable modification. Roc's approach means that cloning happens automatically, which can be less error-prone than defensive cloning (which might be forgotten), but <span class="nowrap">which—to be fair—can</span> also increase unintentional cloning. It's a different default with different tradeoffs.
A reliability benefit of semantic immutability everywhere is that it rules out [data races](https://en.wikipedia.org/wiki/Race_condition#Data_race). These concurrency bugs can be difficult to reproduce and time-consuming to debug, and they are only possible through direct mutation. Roc's semantic immutability rules out this category of bugs.
A performance benefit of having no direct mutation is that it lets Roc rule out [reference cycles](https://en.wikipedia.org/wiki/Reference_counting#Dealing_with_reference_cycles). Languages which support direct mutation can have reference cycles, and detecting these cycles automatically at runtime has a cost. (Failing to detect them can result in memory leaks in languages that use reference counting.) Roc's automatic reference counting neither pays for runtime cycle collection nor memory leaks from cycles, because the language's lack of direct mutation primitives lets it rule out reference cycles at language design time.
An ergonomics benefit of having no direct mutation primitives is that functions in Roc tend to be chainable by default. For example, consider the `Set.insert` function. In many languages, this function would be written to accept a `Set` to mutate, and then return nothing. In contrast, in Roc it will necessarily be written to return a (potentially) new `Set`, even if in-place mutation will end up happening anyway if it's unique.
This makes Roc functions naturally amenable to pipelining, as we saw in the earlier example:
```roc
colors
|> Set.insert "Purple"
|> Set.insert "Orange"
|> Set.insert "Blue"
```
To be fair, direct mutation primitives have benefits too. Some algorithms are more concise or otherwise easier to read when written with direct mutation, and direct mutation can make the performance characteristics of some operations clearer.
As such, Roc's opportunistic mutation design means that data races and reference cycles can be ruled out, and that functions will tend to be more amenable for chaining, but also that some algorithms will be harder to express, and that performance optimization will likely tend to involve more profiling. These tradeoffs fit well with the language's overall design goals.
## [No reassignment or shadowing](#no-reassignment) {#no-reassignment}
In some languages, the following is allowed.
<pre><samp class="code-snippet"><span class="literal">x <span class="kw">=</span> <span class="literal">1</span>
x <span class="kw">=</span> <span class="literal">2</span></samp></pre>
In Roc, this will give a compile-time error. Once a name has been assigned to a value, nothing in the same scope can assign it again. (This includes [shadowing](https://en.wikipedia.org/wiki/Variable_shadowing), which is disallowed.)
This can make Roc code easier to read, because the answer to the question "might this have a different value later on in the scope?" is always "no."
That said, this can also make Roc code take longer to write, due to needing to come up with unique names to avoid shadowing—although pipelining (as shown in the previous section) reduces how often intermediate values need names.
### [Avoiding regressions](#avoiding-regressions) {#avoiding-regressions}
A benefit of this design is that it makes Roc code easier to rearrange without causing regressions. Consider this code:
<pre><samp class="code-snippet">func <span class="kw">=</span> <span class="kw">\</span>arg <span class="kw">-&gt;</span>
greeting <span class="kw">=</span> <span class="string">"Hello"</span>
welcome <span class="kw">=</span> <span class="kw">\</span>name <span class="kw">-&gt;</span> <span class="string">"</span><span class="kw">\(</span>greeting<span class="kw">)</span><span class="string">, </span><span class="kw">\(</span>name<span class="kw">)</span><span class="string">!"</span>
<span class="comment"># …</span>
message <span class="kw">=</span> welcome <span class="string">"friend"</span>
<span class="comment"># …</span></samp></pre>
Suppose I decide to extract the `welcome` function to the top level, so I can reuse it elsewhere:
<pre><samp class="code-snippet">func <span class="kw">=</span> <span class="kw">\</span>arg <span class="kw">-&gt;</span>
<span class="comment"># …</span>
message <span class="kw">=</span> welcome <span class="string">"Hello"</span> <span class="string">"friend"</span>
<span class="comment"># …</span>
welcome <span class="kw">=</span> <span class="kw">\</span>prefix<span class="punctuation section">,</span> name <span class="kw">-&gt;</span> <span class="string">"</span><span class="kw">\(</span>prefix<span class="kw">)</span><span class="string">, </span><span class="kw">\(</span>name<span class="kw">)</span><span class="string">!"</span></samp></pre>
Even without knowing the rest of `func`, we can be confident this change will not alter the code's behavior.
In contrast, suppose Roc allowed reassignment. Then it's possible something in the `# …` parts of the code could have modified `greeting` before it was used in the `message =` declaration. For example:
<pre><samp class="code-snippet">func <span class="kw">=</span> <span class="kw">\</span>arg <span class="kw">-&gt;</span>
greeting <span class="kw">=</span> <span class="string">"Hello"</span>
welcome <span class="kw">=</span> <span class="kw">\</span>name <span class="kw">-&gt;</span> <span class="string">"</span><span class="kw">\(</span>greeting<span class="kw">)</span><span class="string">, </span><span class="kw">\(</span>name<span class="kw">)</span><span class="string">!"</span>
<span class="comment"># …</span>
<span class="kw">if</span> someCondition <span class="kw">then</span>
greeting <span class="kw">=</span> <span class="string">"Hi"</span>
<span class="comment"># …</span>
<span class="kw">else</span>
<span class="comment"># …</span>
<span class="comment"># …</span>
message <span class="kw">=</span> welcome <span class="string">"friend"</span>
<span class="comment"># …</span></samp></pre>
If we didn't read the whole function and notice that `greeting` was sometimes (but not always) reassigned from `"Hello"` to `"Hi"`, we might not have known that changing it to `message = welcome "Hello" "friend"` would cause a regression due to having the greeting always be `"Hello"`.
Even if Roc disallowed reassignment but allowed shadowing, a similar regression could happen if the `welcome` function were shadowed between when it was defined here and when `message` later called it in the same scope. Because Roc allows neither shadowing nor reassignment, these regressions can't happen, and rearranging code can be done with more confidence.
In fairness, reassignment has benefits too. For example, using it with [early-exit control flow operations](https://en.wikipedia.org/wiki/Control_flow#Early_exit_from_loops) such as a `break` keyword can be a nice way to represent certain types of logic without incurring extra runtime overhead.
Roc does not have early-exits or loop syntax; looping is done either with convenience functions like [`List.walkUntil`](https://www.roc-lang.org/builtins/List#walkUntil) or with recursion (Roc implements [tail-call optimization](https://en.wikipedia.org/wiki/Tail_call), including [modulo cons](https://en.wikipedia.org/wiki/Tail_call#Tail_recursion_modulo_cons)), but early-exit operators can potentially make some code easier to follow (and potentially even slightly more efficient) when used in scenarios where breaking out of nested loops with a single instruction is desirable.
## [Managed effects over side effects](#managed-effects) {#managed-effects}
Many languages support first-class [asynchronous](https://en.wikipedia.org/wiki/Asynchronous_I/O) effects, which can improve a system's throughput (usually at the cost of some latency) especially in the presence of long-running I/O operations like network requests.
Asynchronous effects are commonly represented by a value such as a [Promise or Future](https://en.wikipedia.org/wiki/Futures_and_promises) (Roc calls these Tasks), which represent an effect to be performed. Tasks can be composed together, potentially while customizing concurrency properties and supporting I/O interruptions like cancellation and timeouts.
Most languages also have a separate system for synchronous effects, namely [side effects](https://en.wikipedia.org/wiki/Side_effect_(computer_science)). Having two different ways to perform every I/O operation—one synchronous and one asynchronous—can lead to a lot of duplication across a language's ecosystem.
Instead of having [side effects](https://en.wikipedia.org/wiki/Side_effect_(computer_science)), Roc functions exclusively use *managed effects* in which they return descriptions of effects to run, in the form of Tasks. Tasks can be composed and chained together, until they are ultimately handed off (usually via a `main` function or something similar) to an effect runner outside the program, which actually performs the effects the tasks describe.
Having only (potentially asynchronous) *managed effects* and no (synchronous) *side effects* both simplifies the language's ecosystem and makes certain guarantees possible. For example, the combination of managed effects and semantically immutable values means all Roc functions are [pure](https://en.wikipedia.org/wiki/Pure_function)—that is, they have no side effects and always return the same answer when called with the same arguments.
## [Pure functions](#pure-functions) {#pure-functions}
Pure functions have some valuable properties, such as [referential transparency](https://en.wikipedia.org/wiki/Referential_transparency) and being trivial to [memoize](https://en.wikipedia.org/wiki/Memoization). They also have testing benefits; for example, all Roc tests which either use simulated effects (or which do not involve Tasks at all) can never flake. They either consistently pass or consistently fail. Because of this, their results can be cached, so `roc test` can skip re-running them unless their source code (including dependencies) changed. (This caching has not yet been implemented, but is planned.)
Roc does support [tracing](https://en.wikipedia.org/wiki/Tracing_(software)) via the `dbg` keyword, an essential [debugging](https://en.wikipedia.org/wiki/Debugging) tool which is unusual among side effects in that—similarly to opportunistic mutation—using it should not affect the behavior of the program. As such, it typically does not impact the guarantees of pure functions in practice.
Pure functions are notably amenable to compiler optimizations, and Roc already takes advantage of them to implement [function-level dead code elimination](https://elm-lang.org/news/small-assets-without-the-headache). Here are some other examples of optimizations that will benefit from this in the future; these are planned, but not yet implemented:
- [Loop fusion](https://en.wikipedia.org/wiki/Loop_fission_and_fusion), which can do things like combining consecutive `List.map` calls (potentially intermingled with other operations that traverse the list) into one pass over the list.
- [Compile-time evaluation](https://en.wikipedia.org/wiki/Compile-time_function_execution), which basically takes [constant folding](https://en.wikipedia.org/wiki/Constant_folding) to its natural limit: anything that can be evaluated at compile time is evaluated then. This saves work at runtime, and is easy to opt out of: if you want evaluation to happen at runtime, you can instead wrap the logic in a function and call it as needed.
- [Hoisting](https://en.wikipedia.org/wiki/Loop-invariant_code_motion), which moves certain operations outside loops to prevent them from being re-evaluated unnecessarily on each step of the loop. It's always safe to hoist calls to pure functions, and in some cases they can be hoisted all the way to the top level, at which point they become eligible for compile-time evaluation.
There are other optimizations (some of which have yet to be considered) that pure functions enable; this is just a sample!
## Get started
If this design sounds interesting to you, you can give Roc a try by heading over to the [tutorial](/tutorial)!

View file

@ -1,50 +1,35 @@
<div role="presentation" id="homepage-intro-outer">
<div role="presentation" id="homepage-intro-box"> <div role="presentation" id="homepage-intro-box">
<h1 id="homepage-h1">Roc</h1> <h1 id="homepage-h1">Roc</h1>
<svg id="homepage-logo" aria-labelledby="logo-svg-title logo-svg-desc" width="240" height="240" viewBox="0 0 51 53" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg id="homepage-logo" aria-labelledby="logo-svg-title logo-svg-desc" width="240" height="240" viewBox="0 0 51 53" fill="none" xmlns="http://www.w3.org/2000/svg"><title id="logo-svg-title">The Roc logo</title><desc id="logo-svg-desc">A purple origami bird made of six triangles</desc><path d="M23.6751 22.7086L17.655 53L27.4527 45.2132L26.4673 39.3424L23.6751 22.7086Z" class="logo-dark"/><path d="M37.2438 19.0101L44.0315 26.3689L45 22L45.9665 16.6324L37.2438 19.0101Z" class="logo-light"/><path d="M23.8834 3.21052L0 0L23.6751 22.7086L23.8834 3.21052Z" class="logo-light"/><path d="M44.0315 26.3689L23.6751 22.7086L26.4673 39.3424L44.0315 26.3689Z" class="logo-light"/><path d="M50.5 22L45.9665 16.6324L45 22H50.5Z" class="logo-dark"/><path d="M23.6751 22.7086L44.0315 26.3689L37.2438 19.0101L23.8834 3.21052L23.6751 22.7086Z" class="logo-dark"/>
<title id="logo-svg-title">The Roc logo</title>
<desc id="logo-svg-desc">A purple origami bird made of six triangles</desc>
<path d="M23.6751 22.7086L17.655 53L27.4527 45.2132L26.4673 39.3424L23.6751 22.7086Z" class="logo-dark"/>
<path d="M37.2438 19.0101L44.0315 26.3689L45 22L45.9665 16.6324L37.2438 19.0101Z" class="logo-light"/>
<path d="M23.8834 3.21052L0 0L23.6751 22.7086L23.8834 3.21052Z" class="logo-light"/>
<path d="M44.0315 26.3689L23.6751 22.7086L26.4673 39.3424L44.0315 26.3689Z" class="logo-light"/>
<path d="M50.5 22L45.9665 16.6324L45 22H50.5Z" class="logo-dark"/>
<path d="M23.6751 22.7086L44.0315 26.3689L37.2438 19.0101L23.8834 3.21052L23.6751 22.7086Z" class="logo-dark"/>
</svg> </svg>
<p id="homepage-tagline">A fast, friendly, functional language.</p> <p id="homepage-tagline">A fast, friendly, functional language.</p>
<!-- This exact sample was chosen for several reasons:
1. It's plausible to figure out what it's doing even if you don't know the language yet.
2. It uses a higher-order function, giving a functional first impression.
3. It shows some things not found in most mainstream languages, e.g. function calls without parens, lambda syntax.
4. It shows some things not found in most FP languages, e.g. string interpolation, passing a lambda without `<|` or `$`
5. It's horizontally small enough that it can be read on mobile without a scroll bar or shrinking the font size.
-->
<pre id="first-code-sample"><samp class="code-snippet">list <span class="kw">=</span> List<span class="punctuation section">.</span>map songs <span class="kw">\</span>song <span class="kw">-></span> <pre id="first-code-sample"><samp class="code-snippet">list <span class="kw">=</span> List<span class="punctuation section">.</span>map songs <span class="kw">\</span>song <span class="kw">-></span>
<span class="string">"Artist: </span><span class="kw">\(</span>song<span class="punctuation section">.</span>artist<span class="kw">)</span><span class="string">"</span></samp></pre> <span class="string">"Artist: </span><span class="kw">\(</span>song<span class="punctuation section">.</span>artist<span class="kw">)</span><span class="string">"</span></samp></pre>
</div> </div>
</div>
<section class="home-goals-container" aria-label="Roc's Design: Fast, Friendly, Functional"> <section class="home-goals-container" aria-label="Roc's Design: Fast, Friendly, Functional">
<div role="presentation" class="home-goals-column"> <div role="presentation" class="home-goals-column">
<a href="/wip/fast.html" class="home-goals-content"> <a href="/fast" class="home-goals-content">
<h3 class="home-goals-title">Fast</h3> <h3 class="home-goals-title">Fast</h3>
<p class="home-goals-description">Roc code is designed to build fast and run fast. <span class="nobreak-on-mobile">It compiles to machine code or WebAssembly.</span></p> <p class="home-goals-description">Roc code is designed to build fast and <span class="nowrap">run fast</span>. It compiles to machine code or WebAssembly.</p>
<p class="home-goals-learn-more">What does <i>fast</i> mean here?</p> <p class="home-goals-learn-more">What does <i>fast</i> mean here?</p>
</a> </a>
</div> </div>
<div role="presentation" class="home-goals-column"> <div role="presentation" class="home-goals-column">
<a href="/wip/friendly.html" class="home-goals-content"> <a href="/friendly" class="home-goals-content">
<h3 class="home-goals-title">Friendly</h3> <h3 class="home-goals-title">Friendly</h3>
<p class="home-goals-description">Rocs syntax, semantics, and included toolset <span class="nobreak-on-mobile">all prioritize user-friendliness.</span></p> <p class="home-goals-description">Roc's syntax, semantics, and included toolset all prioritize user-friendliness.</p>
<p class="home-goals-learn-more">What does <i>friendly</i> mean here?</p> <p class="home-goals-learn-more">What does <i>friendly</i> mean here?</p>
</a> </a>
</div> </div>
<div role="presentation" class="home-goals-column"> <div role="presentation" class="home-goals-column">
<a href="/wip/functional.html" class="home-goals-content"> <a href="/functional" class="home-goals-content">
<h3 class="home-goals-title">Functional</h3> <h3 class="home-goals-title">Functional</h3>
<p class="home-goals-description"> <p class="home-goals-description">
Roc has a small number of simple language primitives. <span class="nobreak-on-mobile">Its a single-paradigm functional language.</span></p> Roc has a small number of simple language primitives. It's a single-paradigm <span class="nowrap">functional language.</span></p>
<p class="home-goals-learn-more">What does <i>functional</i> mean here?</p> <p class="home-goals-learn-more">What does <i>functional</i> mean here?</p>
</a> </a>
</div> </div>
@ -69,50 +54,63 @@
<div id="history-text" aria-live="polite"></div> <div id="history-text" aria-live="polite"></div>
</code> </code>
<div id="repl-prompt" role="presentation">»</div> <div id="repl-prompt" role="presentation">»</div>
<textarea aria-label="Input Roc code here, then press Enter to submit it to the REPL" rows="5" id="source-input" placeholder="Type some Roc code and press Enter."></textarea> <textarea aria-label="Input Roc code here, then press Enter to submit it to the REPL" rows="5" id="source-input" placeholder="Enter some Roc code here."></textarea>
</div> </div>
</div> </div>
<script type="module" src="/wip/site.js"></script> <script type="module" src="/site.js"></script>
</section> </section>
## [Examples](#examples) {#examples} ## [Examples](#examples) {#examples}
Roc is a very young language (it doesnt even have a numbered release yet, just nightly builds!) but it can already be used for several things if youre up for being an early adopter—with all the bugs and missing features which come with that territory. Roc is a young language. It doesn't even have a numbered release yet, just nightly builds!
However, it can already be used for several things if you're up for being an early adopter—<br>
with all the bugs and missing features which come with that territory.
Here are some examples of how it can be used today. Here are some examples of how it can be used today.
<div role="presentation" class="home-examples-container"> <div role="presentation" class="home-examples-container">
<div role="presentation" class="home-examples-column"> <div role="presentation" class="home-examples-column">
<h3 class="home-examples-title">Command-Line Interfaces</h3> <h3 class="home-examples-title">Command-Line Interfaces</h3>
<p>You can use Roc to create command-line interfaces (CLIs) and scripts. For example, the HTML for this website is generated using a simple Roc script. You can see <a href="https://github.com/roc-lang/roc/blob/main/www/wip_new_website/main.roc">the code for it</a> in the main Roc source code repository.</p> <pre><samp class="code-snippet">main <span class="kw">=</span>
<p>For a more flexible starting point, <a href="https://github.com/roc-lang/basic-cli">roc-lang/basic-cli</a> is a popular platform for building your own CLI tools. It's the most mature Roc platform today.</p> Stdout<span class="punctuation section">.</span>line <span class="literal">"Hello!"</span></samp></pre>
<p>You can use Roc to create scripts and command-line interfaces (CLIs). The compiler produces binary executables, so Roc programs can run on devices that don't have Roc itself installed.</p>
<p>As an example, the HTML for this website is generated using a simple Roc script. You can see <a href="https://github.com/roc-lang/roc/blob/main/www/main.roc">the code for it</a> in the main Roc code repository.</p>
<p>If youre looking for a starting point for building a command-line program in Roc, <a href="https://github.com/roc-lang/basic-cli">basic-cli</a> is a popular <a href="/platforms">platform</a> to check out.</p>
</div> </div>
<div role="presentation" class="home-examples-column"> <div role="presentation" class="home-examples-column">
<h3 class="home-examples-title">Web Servers</h3> <h3 class="home-examples-title">Web Servers</h3>
<p>You can also use Roc to build web servers. <a href="https://github.com/roc-lang/basic-webserver">roc-lang/basic-webserver</a> is an upcoming platform for doing this.</> <pre><samp class="code-snippet">handleReq <span class="kw">=</span> <span class="kw">\</span>request <span class="kw">-&gt;</span>
Task<span class="punctuation section">.</span>ok <span class="literal">{</span> body: <span class="comment"></span> <span class="literal">}</span></samp></pre>
<p>You can also build web servers in Roc. <a href="https://github.com/roc-lang/basic-webserver">basic-webserver</a> is a <a href="/platforms">platform</a> with
a simple interface: you write a function which takes a <code>Request</code>, does some I/O, and returns a <code>Response</code>.</p>
<p>Behind the scenes, it uses Rust's high-performance <a href="https://docs.rs/hyper/latest/hyper/">hyper</a> and <a href="https://tokio.rs/">tokio</a> libraries to execute your Roc function on incoming requests.</p>
<p>For database access, <a href="https://github.com/agu-z/roc-pg">roc-pg</a> lets you access a <a href="https://www.postgresql.org/">PostgreSQL</a> database&mdash;with your Roc types checked against the types in your database's schema.</p>
</div> </div>
<div role="presentation" class="home-examples-column"> <div role="presentation" class="home-examples-column">
<h3 class="home-examples-title">Embedding</h3> <h3 class="home-examples-title">Embedding</h3>
<p>You can call Roc functions from other languages. See <a href="https://github.com/roc-lang/roc/tree/main/examples">basic examples</a> of how to call Roc functions from Python, Node.js, Swift, WebAssembly, and JVM languages. Any function that supports C interop can call Roc functions, using similar techniques to the ones these examples use.</p> <pre><samp class="code-snippet">fn <span class="kw">=</span> require(<span class="string">"foo.roc"</span>)<span class="kw">;</span>
<p>Most of those examples are minimal proofs of concept. <a href="https://github.com/vendrinc/roc-esbuild">roc-esbuild</a> is a work in progress thats used at <a href="https://www.vendr.com/careers">Vendr</a> to call Roc functions from Node.js.</p> log(<span class="string">`Roc says </span><span class="kw">${</span>fn()<span class="kw">}</span><span class="string">`</span>)<span class="kw">;</span></samp></pre>
<p>You can call Roc functions from other languages. There are several <a href="https://github.com/roc-lang/roc/tree/main/examples">basic examples</a> of how to call Roc functions from Python, Node.js, Swift, WebAssembly, and JVM languages.</p>
<p>Any language that supports C interop can call Roc functions, using similar techniques to the ones found in these examples.</p>
<p>Most of those are minimal proofs of concept, but <a href="https://github.com/vendrinc/roc-esbuild">roc-esbuild</a> is a work in progress that's used at <a href="https://www.vendr.com/careers">Vendr</a> to call Roc functions from Node.js.</p>
</div> </div>
</div> </div>
### Other Examples ### [Other Examples](#other-examples) {#other-examples}
You can find more use cases and examples on the [examples page](/examples)!
There are a variety of [other examples](/wip/examples) you can check out, if youd like to get a feel for some different ways people write Roc code.
</section> </section>
## [Code Sample with Explanations](#code-sample) {#code-sample} ## [Code Sample with Explanations](#code-sample) {#code-sample}
Heres a code sample that shows a few different aspects of Roc: Here's a code sample that shows a few different aspects of Roc:
* File I/O and HTTP requests * File I/O and HTTP requests
* Pattern matching for error handling * Pattern matching for error handling
* JSON deserialization via type inference * JSON deserialization via type inference
* Common syntax sugar: string interpolation, pipelines, and backpassing * Common syntax sugar: string interpolation, pipelines, and backpassing
The [tutorial](/tutorial) introduces these gradually and in more depth, but this gives you a brief overview. The [tutorial](/tutorial) introduces these gradually and in more depth, but this gives a brief overview.
<!-- THIS COMMENT WILL BE REPLACED BY THE LARGER EXAMPLE --> <!-- THIS COMMENT WILL BE REPLACED BY THE LARGER EXAMPLE -->
@ -140,9 +138,9 @@ We are very grateful for our corporate sponsors [Vendr](https://www.vendr.com/),
<a href="https://tweedegolf.nl/en"><svg class="logo-tweede-golf" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 175.12 40.4"><path class="svg-text" d="M54.42,27.74a4.55,4.55,0,0,1-.73.27,5.47,5.47,0,0,1-1.34.1A3,3,0,0,1,49.83,27a4.44,4.44,0,0,1-.86-2.9V17.26H47.54V14.12H49V11.57l3.11-1.34v3.89h2.36v3.14H52.08v6.48a1.17,1.17,0,0,0,.32.94,1.28,1.28,0,0,0,.89.26,2.15,2.15,0,0,0,.83-.16,2.88,2.88,0,0,0,.78-.45Z"></path><path class="svg-text" d="M59.23,27.88l-3.6-13.75H59l2,8.46,2-8.46h3.27l2,8.46,2-8.46h3.39L69.81,27.88H66.48L64.57,20.6l-2,7.28Z"></path><path class="svg-text" d="M77.7,22.8a2.34,2.34,0,0,0,.35.92,3,3,0,0,0,.62.67,1.87,1.87,0,0,0,.78.4,2.25,2.25,0,0,0,.86.13,3.07,3.07,0,0,0,1.12-.19,2.45,2.45,0,0,0,.89-.77l2.15,2a4.88,4.88,0,0,1-4.24,2.15A5.12,5.12,0,0,1,76,26.37a8,8,0,0,1-1.48-5.15A10.08,10.08,0,0,1,75,18.13a7.38,7.38,0,0,1,1.16-2.31,4.6,4.6,0,0,1,1.82-1.42,4.88,4.88,0,0,1,2.34-.51,5.94,5.94,0,0,1,2.2.43,4.59,4.59,0,0,1,1.72,1.31,6.07,6.07,0,0,1,1.1,2.18,10.35,10.35,0,0,1,.4,3.08c0,.57,0,1,0,1.27s-.06.51-.08.64ZM80.25,17a2.09,2.09,0,0,0-1.72.78,3.3,3.3,0,0,0-.83,2h4.83a4,4,0,0,0-.75-2A1.79,1.79,0,0,0,80.25,17Z"></path><path class="svg-text" d="M91.14,22.8a2.34,2.34,0,0,0,.35.92,3,3,0,0,0,.62.67,2.71,2.71,0,0,0,1.63.53,3.09,3.09,0,0,0,1.13-.19,2.85,2.85,0,0,0,.92-.77l2.12,2a4.88,4.88,0,0,1-4.24,2.15,5.12,5.12,0,0,1-4.22-1.75A8,8,0,0,1,88,21.22a10.08,10.08,0,0,1,.43-3.09,7.38,7.38,0,0,1,1.16-2.31,4.6,4.6,0,0,1,1.82-1.42,4.86,4.86,0,0,1,2.33-.51,5.58,5.58,0,0,1,2.21.43,4.56,4.56,0,0,1,1.71,1.31,5.76,5.76,0,0,1,1.1,2.18,9.74,9.74,0,0,1,.4,3.08c0,.57,0,1,0,1.27a2.8,2.8,0,0,1-.08.64ZM93.69,17a2.09,2.09,0,0,0-1.72.78,3.3,3.3,0,0,0-.83,2H96a4,4,0,0,0-.75-2A1.8,1.8,0,0,0,93.69,17Z"></path><path class="svg-text" d="M109.28,27a7.32,7.32,0,0,1-1.13.78,3.3,3.3,0,0,1-1.74.37,4.57,4.57,0,0,1-2-.48,5.12,5.12,0,0,1-1.58-1.42,6.71,6.71,0,0,1-1-2.2,10.77,10.77,0,0,1-.38-2.82,11.22,11.22,0,0,1,.38-2.88,6.39,6.39,0,0,1,1-2.31,5,5,0,0,1,1.64-1.55,4.34,4.34,0,0,1,2.17-.57,6.29,6.29,0,0,1,1.56.19,4.23,4.23,0,0,1,1.1.56V11l3.17-1.37V27.84h-3.17V27Zm0-7.9a2.55,2.55,0,0,0-.86-1.21,1.85,1.85,0,0,0-1.29-.48,2.13,2.13,0,0,0-2,1,5.73,5.73,0,0,0-.56,2.82,4.52,4.52,0,0,0,.64,2.66,2.16,2.16,0,0,0,1.86,1,2.13,2.13,0,0,0,1.42-.54,3.35,3.35,0,0,0,.78-1.24Z"></path><path class="svg-text" d="M118.34,22.8a2.34,2.34,0,0,0,.35.92,3,3,0,0,0,.62.67,2.22,2.22,0,0,0,.78.4,2.61,2.61,0,0,0,.88.13,2.93,2.93,0,0,0,1.1-.19A2.85,2.85,0,0,0,123,24l2.12,2a4.89,4.89,0,0,1-4.25,2.15,5.11,5.11,0,0,1-4.21-1.75,8,8,0,0,1-1.48-5.15,11.09,11.09,0,0,1,.43-3.09,7.38,7.38,0,0,1,1.16-2.31,4.68,4.68,0,0,1,1.82-1.42,4.88,4.88,0,0,1,2.34-.51,5.57,5.57,0,0,1,2.2.43,4.52,4.52,0,0,1,1.72,1.31,5.91,5.91,0,0,1,1.1,2.18,10,10,0,0,1,.4,3.08c0,.57,0,1,0,1.27a2.8,2.8,0,0,1-.08.64ZM120.89,17a2.09,2.09,0,0,0-1.72.78,3.53,3.53,0,0,0-.83,2h4.83a4,4,0,0,0-.75-2,1.8,1.8,0,0,0-1.53-.78Z"></path><path class="svg-text" d="M140.58,30.34a3.86,3.86,0,0,0,2-.91,3.12,3.12,0,0,0,1-2,2.39,2.39,0,0,1-.91.43,6.71,6.71,0,0,1-1.48.16,4.55,4.55,0,0,1-2.34-.56,4.48,4.48,0,0,1-1.61-1.54,8,8,0,0,1-.94-2.3,12.59,12.59,0,0,1-.32-2.8,8.13,8.13,0,0,1,.43-2.68,7.21,7.21,0,0,1,1.1-2.2,5.86,5.86,0,0,1,1.66-1.48,4.55,4.55,0,0,1,2.1-.54,4,4,0,0,1,2.39.81v-.59h3.14V26.63a7.73,7.73,0,0,1-.35,2.39,7.07,7.07,0,0,1-1.1,2.15,6.16,6.16,0,0,1-1.77,1.64,6.55,6.55,0,0,1-2.39.83Zm3-11.84a1.55,1.55,0,0,0-.7-1.05,1.89,1.89,0,0,0-1.1-.32,2.27,2.27,0,0,0-2,.94,4.94,4.94,0,0,0-.62,2.79,6.65,6.65,0,0,0,.56,3,1.88,1.88,0,0,0,1.8,1,2.08,2.08,0,0,0,2.07-1.13Z"></path><path class="svg-text" d="M155.17,28.12a4.45,4.45,0,0,1-2.29-.54,5.56,5.56,0,0,1-1.77-1.5A7.16,7.16,0,0,1,150,23.8a9.81,9.81,0,0,1,0-5.58A7.06,7.06,0,0,1,151.11,16a5.56,5.56,0,0,1,1.77-1.5,4.92,4.92,0,0,1,4.57,0,5.23,5.23,0,0,1,1.74,1.5,7.13,7.13,0,0,1,1.16,2.26,9.81,9.81,0,0,1,0,5.58,7.23,7.23,0,0,1-1.16,2.28,5.45,5.45,0,0,1-1.74,1.5A4.44,4.44,0,0,1,155.17,28.12Zm0-3.25A2.09,2.09,0,0,0,157,23.8a6.59,6.59,0,0,0,0-5.59,2.12,2.12,0,0,0-2.89-.76h0a2.24,2.24,0,0,0-.75.75,6.38,6.38,0,0,0,0,5.59,2.09,2.09,0,0,0,1.82,1.07Z"></path><path class="svg-text" d="M163.44,11l3.17-1.37V27.87h-3.17Z"></path><path class="svg-text" d="M172.81,17.26V27.87h-3.14V17.26h-1.29V14.12h1.29v-.4a4.57,4.57,0,0,1,1-3.22,3.7,3.7,0,0,1,2.87-1.08,4.1,4.1,0,0,1,.86.06,2.84,2.84,0,0,1,.7.16v2.9l-.56-.16h-.68a1.14,1.14,0,0,0-1,.32,1.63,1.63,0,0,0-.27,1.05v.4h2.47v3.14Z"></path><path class="svg-text" d="M20.26,0A20.2,20.2,0,1,0,40.4,20.26V20.2h0A20.18,20.18,0,0,0,20.26,0Zm-10,29.67H7.5c1.69-1.4,4.16-4.38,5.19-9.85,1.18-6.27,5.82-9.67,10.1-9.1h0c-3.62,1-6.39,4.47-7.25,9.07-1.07,5.66-3.65,8.49-5.1,9.7Zm.73,0c1.5-1.34,4-4.28,5-9.8.86-4.52,3.62-7.89,7.22-8.77a7.09,7.09,0,0,1,7.44,2.74,6.18,6.18,0,0,1,1.4,5.28,5.71,5.71,0,0,1-2.31,3.53,2.18,2.18,0,0,0,.14-.84,2.55,2.55,0,1,0-5.1-.14v.14S24.46,27.47,33,29.7ZM25.9,20.2h0a2.05,2.05,0,0,1,2.87.11h0a6.41,6.41,0,0,1-2.76,3A2.42,2.42,0,0,0,25.9,20.2Z"></path></svg></a> <a href="https://tweedegolf.nl/en"><svg class="logo-tweede-golf" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 175.12 40.4"><path class="svg-text" d="M54.42,27.74a4.55,4.55,0,0,1-.73.27,5.47,5.47,0,0,1-1.34.1A3,3,0,0,1,49.83,27a4.44,4.44,0,0,1-.86-2.9V17.26H47.54V14.12H49V11.57l3.11-1.34v3.89h2.36v3.14H52.08v6.48a1.17,1.17,0,0,0,.32.94,1.28,1.28,0,0,0,.89.26,2.15,2.15,0,0,0,.83-.16,2.88,2.88,0,0,0,.78-.45Z"></path><path class="svg-text" d="M59.23,27.88l-3.6-13.75H59l2,8.46,2-8.46h3.27l2,8.46,2-8.46h3.39L69.81,27.88H66.48L64.57,20.6l-2,7.28Z"></path><path class="svg-text" d="M77.7,22.8a2.34,2.34,0,0,0,.35.92,3,3,0,0,0,.62.67,1.87,1.87,0,0,0,.78.4,2.25,2.25,0,0,0,.86.13,3.07,3.07,0,0,0,1.12-.19,2.45,2.45,0,0,0,.89-.77l2.15,2a4.88,4.88,0,0,1-4.24,2.15A5.12,5.12,0,0,1,76,26.37a8,8,0,0,1-1.48-5.15A10.08,10.08,0,0,1,75,18.13a7.38,7.38,0,0,1,1.16-2.31,4.6,4.6,0,0,1,1.82-1.42,4.88,4.88,0,0,1,2.34-.51,5.94,5.94,0,0,1,2.2.43,4.59,4.59,0,0,1,1.72,1.31,6.07,6.07,0,0,1,1.1,2.18,10.35,10.35,0,0,1,.4,3.08c0,.57,0,1,0,1.27s-.06.51-.08.64ZM80.25,17a2.09,2.09,0,0,0-1.72.78,3.3,3.3,0,0,0-.83,2h4.83a4,4,0,0,0-.75-2A1.79,1.79,0,0,0,80.25,17Z"></path><path class="svg-text" d="M91.14,22.8a2.34,2.34,0,0,0,.35.92,3,3,0,0,0,.62.67,2.71,2.71,0,0,0,1.63.53,3.09,3.09,0,0,0,1.13-.19,2.85,2.85,0,0,0,.92-.77l2.12,2a4.88,4.88,0,0,1-4.24,2.15,5.12,5.12,0,0,1-4.22-1.75A8,8,0,0,1,88,21.22a10.08,10.08,0,0,1,.43-3.09,7.38,7.38,0,0,1,1.16-2.31,4.6,4.6,0,0,1,1.82-1.42,4.86,4.86,0,0,1,2.33-.51,5.58,5.58,0,0,1,2.21.43,4.56,4.56,0,0,1,1.71,1.31,5.76,5.76,0,0,1,1.1,2.18,9.74,9.74,0,0,1,.4,3.08c0,.57,0,1,0,1.27a2.8,2.8,0,0,1-.08.64ZM93.69,17a2.09,2.09,0,0,0-1.72.78,3.3,3.3,0,0,0-.83,2H96a4,4,0,0,0-.75-2A1.8,1.8,0,0,0,93.69,17Z"></path><path class="svg-text" d="M109.28,27a7.32,7.32,0,0,1-1.13.78,3.3,3.3,0,0,1-1.74.37,4.57,4.57,0,0,1-2-.48,5.12,5.12,0,0,1-1.58-1.42,6.71,6.71,0,0,1-1-2.2,10.77,10.77,0,0,1-.38-2.82,11.22,11.22,0,0,1,.38-2.88,6.39,6.39,0,0,1,1-2.31,5,5,0,0,1,1.64-1.55,4.34,4.34,0,0,1,2.17-.57,6.29,6.29,0,0,1,1.56.19,4.23,4.23,0,0,1,1.1.56V11l3.17-1.37V27.84h-3.17V27Zm0-7.9a2.55,2.55,0,0,0-.86-1.21,1.85,1.85,0,0,0-1.29-.48,2.13,2.13,0,0,0-2,1,5.73,5.73,0,0,0-.56,2.82,4.52,4.52,0,0,0,.64,2.66,2.16,2.16,0,0,0,1.86,1,2.13,2.13,0,0,0,1.42-.54,3.35,3.35,0,0,0,.78-1.24Z"></path><path class="svg-text" d="M118.34,22.8a2.34,2.34,0,0,0,.35.92,3,3,0,0,0,.62.67,2.22,2.22,0,0,0,.78.4,2.61,2.61,0,0,0,.88.13,2.93,2.93,0,0,0,1.1-.19A2.85,2.85,0,0,0,123,24l2.12,2a4.89,4.89,0,0,1-4.25,2.15,5.11,5.11,0,0,1-4.21-1.75,8,8,0,0,1-1.48-5.15,11.09,11.09,0,0,1,.43-3.09,7.38,7.38,0,0,1,1.16-2.31,4.68,4.68,0,0,1,1.82-1.42,4.88,4.88,0,0,1,2.34-.51,5.57,5.57,0,0,1,2.2.43,4.52,4.52,0,0,1,1.72,1.31,5.91,5.91,0,0,1,1.1,2.18,10,10,0,0,1,.4,3.08c0,.57,0,1,0,1.27a2.8,2.8,0,0,1-.08.64ZM120.89,17a2.09,2.09,0,0,0-1.72.78,3.53,3.53,0,0,0-.83,2h4.83a4,4,0,0,0-.75-2,1.8,1.8,0,0,0-1.53-.78Z"></path><path class="svg-text" d="M140.58,30.34a3.86,3.86,0,0,0,2-.91,3.12,3.12,0,0,0,1-2,2.39,2.39,0,0,1-.91.43,6.71,6.71,0,0,1-1.48.16,4.55,4.55,0,0,1-2.34-.56,4.48,4.48,0,0,1-1.61-1.54,8,8,0,0,1-.94-2.3,12.59,12.59,0,0,1-.32-2.8,8.13,8.13,0,0,1,.43-2.68,7.21,7.21,0,0,1,1.1-2.2,5.86,5.86,0,0,1,1.66-1.48,4.55,4.55,0,0,1,2.1-.54,4,4,0,0,1,2.39.81v-.59h3.14V26.63a7.73,7.73,0,0,1-.35,2.39,7.07,7.07,0,0,1-1.1,2.15,6.16,6.16,0,0,1-1.77,1.64,6.55,6.55,0,0,1-2.39.83Zm3-11.84a1.55,1.55,0,0,0-.7-1.05,1.89,1.89,0,0,0-1.1-.32,2.27,2.27,0,0,0-2,.94,4.94,4.94,0,0,0-.62,2.79,6.65,6.65,0,0,0,.56,3,1.88,1.88,0,0,0,1.8,1,2.08,2.08,0,0,0,2.07-1.13Z"></path><path class="svg-text" d="M155.17,28.12a4.45,4.45,0,0,1-2.29-.54,5.56,5.56,0,0,1-1.77-1.5A7.16,7.16,0,0,1,150,23.8a9.81,9.81,0,0,1,0-5.58A7.06,7.06,0,0,1,151.11,16a5.56,5.56,0,0,1,1.77-1.5,4.92,4.92,0,0,1,4.57,0,5.23,5.23,0,0,1,1.74,1.5,7.13,7.13,0,0,1,1.16,2.26,9.81,9.81,0,0,1,0,5.58,7.23,7.23,0,0,1-1.16,2.28,5.45,5.45,0,0,1-1.74,1.5A4.44,4.44,0,0,1,155.17,28.12Zm0-3.25A2.09,2.09,0,0,0,157,23.8a6.59,6.59,0,0,0,0-5.59,2.12,2.12,0,0,0-2.89-.76h0a2.24,2.24,0,0,0-.75.75,6.38,6.38,0,0,0,0,5.59,2.09,2.09,0,0,0,1.82,1.07Z"></path><path class="svg-text" d="M163.44,11l3.17-1.37V27.87h-3.17Z"></path><path class="svg-text" d="M172.81,17.26V27.87h-3.14V17.26h-1.29V14.12h1.29v-.4a4.57,4.57,0,0,1,1-3.22,3.7,3.7,0,0,1,2.87-1.08,4.1,4.1,0,0,1,.86.06,2.84,2.84,0,0,1,.7.16v2.9l-.56-.16h-.68a1.14,1.14,0,0,0-1,.32,1.63,1.63,0,0,0-.27,1.05v.4h2.47v3.14Z"></path><path class="svg-text" d="M20.26,0A20.2,20.2,0,1,0,40.4,20.26V20.2h0A20.18,20.18,0,0,0,20.26,0Zm-10,29.67H7.5c1.69-1.4,4.16-4.38,5.19-9.85,1.18-6.27,5.82-9.67,10.1-9.1h0c-3.62,1-6.39,4.47-7.25,9.07-1.07,5.66-3.65,8.49-5.1,9.7Zm.73,0c1.5-1.34,4-4.28,5-9.8.86-4.52,3.62-7.89,7.22-8.77a7.09,7.09,0,0,1,7.44,2.74,6.18,6.18,0,0,1,1.4,5.28,5.71,5.71,0,0,1-2.31,3.53,2.18,2.18,0,0,0,.14-.84,2.55,2.55,0,1,0-5.1-.14v.14S24.46,27.47,33,29.7ZM25.9,20.2h0a2.05,2.05,0,0,1,2.87.11h0a6.41,6.41,0,0,1-2.76,3A2.42,2.42,0,0,0,25.9,20.2Z"></path></svg></a>
</p> </p>
If you would like your company to become a corporate sponsor of Roc's development, please [DM Richard Feldman on Zulip](https://roc.zulipchat.com/#narrow/pm-with/281383-user281383)! If you would like your organization to become an official sponsor of Roc's development, please [DM Richard Feldman on Zulip](https://roc.zulipchat.com/#narrow/pm-with/281383-user281383)!
We'd also like to express our gratitude to each and every one of our fantastic [GitHub sponsors](https://github.com/sponsors/roc-lang/)! A special thanks to those sponsoring $25/month or more: We'd also like to express our gratitude to our generous [individual sponsors](https://github.com/sponsors/roc-lang/)! A special thanks to those sponsoring $25/month or more:
<ul id="individual-sponsors"> <ul id="individual-sponsors">
<li><a href="https://github.com/aaronwhite">Aaron White</a></li> <li><a href="https://github.com/aaronwhite">Aaron White</a></li>
@ -158,10 +156,10 @@ We'd also like to express our gratitude to each and every one of our fantastic [
<li><a href="https://github.com/popara">Zeljko Nesic</a></li> <li><a href="https://github.com/popara">Zeljko Nesic</a></li>
</ul> </ul>
Thank you all so much for helping Roc progress! 💜 Thank you all for your contributions! Roc would not be what it is without your generosity. 💜
We are currently trying to raise $4,000 USD/month in donations to fund one longtime Roc contributor to continue his work on Roc full-time. We are a small group trying to do big things, and every donation helps! You can donate using: We are currently trying to raise $4,000 USD/month in donations to fund one longtime Roc contributor to continue his work on Roc full-time. We are a small group trying to do big things, and every donation helps! You can donate using:
- [GitHub Sponsors](https://github.com/sponsors/roc-lang) - [GitHub Sponsors](https://github.com/sponsors/roc-lang)
- [Liberapay](https://liberapay.com/roc_lang) - [Liberapay](https://liberapay.com/roc_lang)
All donations go through the [Roc Programming Language Foundation](https://foundation.roc-lang.org/), a registered [US 503(c)(3) nonprofit organization](https://en.wikipedia.org/wiki/501(c)(3)_organization), which means these donations are tax-exempt in the US. All donations go through the [Roc Programming Language Foundation](https://foundation.roc-lang.org/), a registered <a href="https://en.wikipedia.org/wiki/501(c)(3)_organization">US <span class="nowrap">501(c)(3)</span> nonprofit organization</a>, which means these donations are tax-exempt in the US.

32
www/content/install.md Normal file
View file

@ -0,0 +1,32 @@
# Install
Roc is a very young language with many incomplete features and known bugs. It doesn't even have a numbered release yet, but it does have [nightly builds](https://github.com/roc-lang/roc/releases) that you can download if you'd like to try it out without [building from source](https://github.com/roc-lang/roc/blob/main/BUILDING_FROM_SOURCE.md)!
There are currently a few known OS-specific issues:
* **macOS:** There are no known compatibility issues, but the compiler doesn't run as fast as it does on Linux or Windows, because we don't (yet) do our own linking like we do on those targets. (Linking works similarly on Linux and Windows, but the way macOS does it is both different and significantly more complicated.)
* **Windows:** There are some known Windows-specific compiler bugs, and probably some other unknown ones because more people have tried out Roc on Mac and Linux than on Windows.
* **Linux:** The nightlies are built with glibc, so they aren't usable on distros that don't use (dynamically linked) glibc, like Alpine or NixOS. In the future we plan to build Linux releases with [musl libc](https://wiki.musl-libc.org/) to address this, but this requires [building LLVM from source with musl](https://wiki.musl-libc.org/building-llvm.html).
* **Other operating systems:** Roc has not been built on any other operating systems. [Building from source](https://github.com/roc-lang/roc/blob/main/BUILDING_FROM_SOURCE.md) on another OS might work, but you might very well be the first person ever to try it!
### [Getting Started](#getting-started) {#getting-started}
Here are some Getting Started guides for different operating systems:
<!-- TODO detect current OS with browser and only show link for that, provide other button for others -->
- [Linux x86-64](https://github.com/roc-lang/roc/blob/main/getting_started/linux_x86_64.md)
- [MacOS Apple Silicon](https://github.com/roc-lang/roc/blob/main/getting_started/macos_apple_silicon.md)
- [MacOS x86-64](https://github.com/roc-lang/roc/blob/main/getting_started/macos_x86_64.md)
- [Windows](https://github.com/roc-lang/roc/blob/main/getting_started/windows.md)
- [Other Systems](https://github.com/roc-lang/roc/blob/main/getting_started/other.md)
### [Editor Extensions](#editor-extensions) {#editor-extensions}
There is currently a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=IvanDemchenko.roc-lang-unofficial) for Roc which includes instructions in its README for how to enable a Roc language server.
Currently that language server has to be built from source; it would be a fantastic contribution if anyone could get it incorporated it into the extension directly. If you'd like to help with this, just make a post in [the "new contributors" topic on Zulip](https://roc.zulipchat.com/#narrow/stream/316715-contributing/topic/new.20contributors) and say hello!
### [Tutorial](#tutorial) {#tutorial}
Once you've installed <code>roc</code>, check out the [tutorial](/tutorial) to learn how to Roc!
<a class="btn-small" href="/tutorial">Start Tutorial</a>

114
www/content/platforms.md Normal file
View file

@ -0,0 +1,114 @@
# Platforms
Something that sets Roc apart from other programming languages is its <span class="nowrap">*platforms and applications*</span> architecture.
## [Applications](#applications) {#applications}
Here is a Roc application that prints `"Hello, World!"` to the command line:
```roc
app "hello"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" }
imports [pf.Stdout]
provides [main] to pf
main =
Stdout.line "Hello, World!"
```
Every Roc application, including this one, is built on a _platform_. This application happens to be built on a platform called [basic-cli](https://github.com/roc-lang/basic-cli), which is a platform for building command-line interfaces.
## [Domain-specific functionality](#domain-specific) {#domain-specific}
Roc platforms provide domain-specific functionality that multiple applications can use as a foundation to build on, much like game engines and Web frameworks do.
Also like many game engines and Web frameworks, Roc platforms have a high-level Roc API which presents a nice interface to a lower-level implementation (written in a different language), which provides the foundational primitives that platform needs to operate—such as a C++ 3D rendering system in a game engine, or a Rust HTTP networking system in a Web framework.
Here are some example Roc platforms, and functionality they might provide:
* A Roc game engine platform might provide functionality for rendering and sound.
* A Roc Web server platform (like [basic-webserver](https://github.com/roc-lang/basic-webserver)) probably would not provide functionality for rendering and sound, but it might provide functionality for responding to incoming HTTP requests—which a game engine platform likely would not.
* A Roc native [GUI](https://en.wikipedia.org/wiki/Graphical_user_interface) platform might provide functionality for defining native operating system UI elements, whereas a game engine platform might focus more on rendering with [shaders](https://en.wikipedia.org/wiki/Shader), and a Web server platform would not have GUI functionality at all.
These are broad domains, but platforms can be much more specific than this. For example, anyone could make a platform for writing [Vim](https://en.wikipedia.org/wiki/Vim_(text_editor)) plugins, or [Postgres](https://en.wikipedia.org/wiki/PostgreSQL) extensions, or robots ([which has already happened](https://roc.zulipchat.com/#narrow/stream/304902-show-and-tell/topic/Roc.20on.20a.20microcontroller/near/286678630)), or even [implementing servo logic for a clock that physically turns panels to simulate an LCD](https://roc.zulipchat.com/#narrow/stream/304641-ideas/topic/Roc.20Clock/near/327939600). You really can get as specific as you like!
Platforms can also be designed to have a single, specific application run on them. For example, you can make a platform that is essentially "your entire existing code base in another language," and then use Roc as an embedded language within that code base. For example, [Vendr](https://www.vendr.com/careers) is using this strategy to call Roc functions from their [Node.js](https://nodejs.org/en) backend using [roc-esbuild](https://github.com/vendrinc/roc-esbuild), as a way to incrementally transition code from Node to Roc.
## [Platform scope](#scope) {#scope}
Roc platforms have a broader scope of responsibility than game engines or Web frameworks. In addition to providing a nice domain-specific interface, platforms are also responsible for:
* Tailoring memory management to that domain (more on this later)
* Providing all I/O primitives
In most languages, I/O primitives come with the standard library. In Roc, the [standard library](https://www.roc-lang.org/builtins/) contains only data structures; an application gets all of its I/O primitives from its platform. For example, in the "Hello, World" application above, the `Stdout.line` function comes from the `basic-cli` platform itself, not from Roc's standard library.
This design has a few benefits.
### [Ecosystem benefits](#ecosystem) {#ecosystem}
Some I/O operations make sense in some use cases but not others.
For example, suppose I'm building an application on a platform for command-line interfaces, and I use a third-party package which sometimes blocks the program while it waits for [standard input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). This might be fine for my command-line application, but it would probably be a very bad fit if I'm using a webserver. Similarly, a package which does some occasional file I/O for caching might work fine on either of those platforms, but might break in surprising ways when used in a platform that's designed to run in a browser on WebAssembly—since browsers don't offer arbitrary file I/O access!
Because Roc's I/O primitives come from platforms, these mismatches can be prevented at build time. The browser-based platform would not expose file I/O primitives, the webserver wouldn't expose a way to block on reading from standard input, and so on. (Note that there's a design in the works for allowing packages which perform I/O to work across multiple platforms—but only platforms which support the I/O primitives it requires—but this design has not yet been implemented.)
### [Security benefits](#security) {#security}
Since platforms have exclusive control over all I/O primitives, one of the things they can do is create domain-specific security guarantees around them. For example, a platform for writing text editor plugins might want to display a prompt to the end user before performing any file I/O operations outside the directory that's currently open in the editor.
[This talk](https://www.youtube.com/watch?v=cpQwtwVKAfU&t=75s) shows an example of taking this idea a step further with a "safe scripting" platform for writing command-line scripts. The idea is that you could download a script from the Internet and run it on this platform without worrying that the script would do bad things to your computer, because the platform would (much like a Web browswer) show you specific prompts before allowing the script to do potentially harmful I/O, such as filesystem operations.
These security guarantees can be relied on because platforms have *exclusive* control over all I/O primitives, including how they are implemented. There are no escape hatches that a malicious program could use to get around these, either; for example, Roc programs that want to call functions in other languages must do so using primitives provided by the platform, which the platform can disallow (or sandbox with end-user prompts) in the same way.
### [Performance benefits](#performance) {#performance}
Many I/O operations can benefit from being run concurrently. Since platforms are in charge of how those I/O operations are implemented, they can also determine how they are scheduled. This means that both applications and packages can describe which operations they want to run concurrently, and then the platform can optimize the scheduling of these operations using its domain-specific knowledge.
For example, a command-line platform might schedule concurrent operations across all available cores (or some lower number specified by a command-line argument). In contrast, a Web server platform might try to balance available cores across multiple request handlers—to prevent undesirable scenarios like one handler getting all the cores (meaning none of the others can progress).
Note that although platform-implemented scheduling of concurrent operations is theoretically possible today, there are currently some missing pieces to make it practical for platform authors to implement. Implementing those missing pieces is already in progress, but is not yet complete.
## [How platforms are implemented](#implementation) {#implementation}
To understand how platforms can tailor automatic memory management to their particular domain, it's helpful to understand how platforms are implemented.
### [The Host and the Roc API](#host-and-roc-api) {#host-and-roc-api}
Each platform consists of two parts:
* **The Roc API** is the part that application authors see. For example, `Stdout.line` is part of basic-cli's Roc API.
* **The Host** is the under-the-hood implementation written in a language other than Roc. For example, basic-cli's host is written in Rust. It has a Rust function which implements the behavior of the `Stdout.line` operation, and all the other I/O operations it supports.
This design means that application authors don't necessarily need to know (or care) about the non-Roc language being used to implement the platform's host. That can be a behind-the-scenes implementation detail that only the platform's author(s) are concerned with. Application authors only interact with the public-facing Roc API.
### [Memory management](#memory) {#memory}
Host authors implement not only the platform's I/O primitives, but also functions for memory allocation and deallocation. In C terms, the host provides [`malloc` and `free`](https://en.wikipedia.org/wiki/C_dynamic_memory_allocation) implementations which the compiled Roc application will automatically call whenever it needs to allocate or deallocate memory.
[The same talk mentioned earlier](https://www.youtube.com/watch?v=cpQwtwVKAfU&t=75s) in the context of [security benefits](#security) demonstrates some benefits of this, such as being able to get accurate diagnostics on how much memory the Roc part (or even specific Roc parts) of a running program are using.
The bigger benefit is tailoring memory management itself based on the domain. For example, [nea](https://github.com/tweedegolf/nea/) is a work-in-progress Web server which performs [arena allocation](https://en.wikipedia.org/wiki/Region-based_memory_management) on each request handler. In Roc terms, this means the host's implementation of `malloc` can allocate into the current handler's arena, and `free` can be a no-op. Instead, the arena can be reset when the response has been sent.
In this design, heap allocations in a Web server running on `nea` are about as cheap as stack allocations, and deallocations are essentially free. This is much better for the server's throughput, latency, and predictability than (for example) having to pay for periodic garbage collection!
### [Program start](#program-start) {#program-start}
When a compiled Roc program runs, it's actually the host—not the Roc application—which starts running first. In C terms, the host implements `main()`, and then at some point it calls a function exposed by the compiled Roc application.
Knowing this, a useful mental model for how Roc platforms and applications interact at the implementation level is: the Roc application compiles down to a C library which the platform can choose to call (or not).
This is essentially what's happening behind the scenes when you run `roc build`. Specifically:
1. The Roc compiler builds the Roc application into a binary [object file](https://en.wikipedia.org/wiki/Object_file)
2. Since that application specified its platform, the compiler then looks up the platform's host implementation (which the platform will have provided as an already-compiled binary)
3. Now that it has a binary for the Roc application and a binary for the host, it links them together into one combined binary in which the host portion calls the application portion as many times as it likes.
This process works for small platforms and large applications (for example, a very large Web server application) as well as for large platforms and small applications (for example, a very large C++ game which serves as a platform for a small amount of Roc application code that the game uses for scripting).
## [Summary](#summary) {#summary}
Every Roc application has exactly one platform. That platform provides all the I/O primitives that the application can use; Roc's standard library provides no I/O operations, and the only way for a Roc application to execute functions in other languages is if the platform offers a way to do that.
This I/O design has [security benefits](#security), [ecosystem benefits](#ecosystem), and [performance benefits](#performance). The [domain-specific memory management](#memory) platforms can implement can offer additional benefits as well.
Applications only interact with the *Roc API* portion of a platform, but there is also a *host* portion (written in a different language) that works behind the scenes. The host determines how the program starts, how memory is allocated and deallocated, and how I/O primitives are implemented.
Anyone can implement their own platform. There isn't yet an official guide about how to do this, so the best way to get help if you'd like to create a platform is to [say hi in the `#beginners` channel](https://roc.zulipchat.com/#narrow/stream/231634-beginners) on [Roc Zulip!](https://roc.zulipchat.com)

15
www/content/repl/index.md Normal file
View file

@ -0,0 +1,15 @@
## The rockin' Roc REPL
<div id="repl-container" role="presentation">
<div id="repl" role="presentation">
<code class="history">
<div id="repl-intro-text">Enter an expression to evaluate, or a definition (like <span class="color-blue">x = 1</span>) to use later.</div>
<div id="history-text" aria-live="polite"></div>
</code>
<div id="repl-prompt" role="presentation">»</div>
<textarea aria-label="Input Roc code here, then press Enter to submit it to the REPL" rows="5" id="source-input" placeholder="Enter some Roc code here."></textarea>
</div>
</div>
<script type="module" src="/site.js"></script>
</section>

View file

@ -1,14 +1,56 @@
<!-- The welcome and installation section are located in tutorial.roc --> <input id="tutorial-toc-toggle" name="tutorial-toc-toggle" type= "checkbox">
<nav id="tutorial-toc" aria-label="Table of Contents">
<label id="close-tutorial-toc" for="tutorial-toc-toggle">close</label>
<!-- TODO fix search: input [id "toc-search", type "text", placeholder "Search"] [] -->
<ol>
<li><a href="#repl">REPL</a></li>
<li><a href="#building-an-application">Building an Application</a></li>
<li><a href="#defining-functions">Defining Functions</a></li>
<li><a href="#if-then-else">if-then-else</a></li>
<li><a href="#debugging">Debugging</a></li>
<li><a href="#records">Records</a></li>
<li><a href="#tags">Tags &amp; Pattern Matching</a></li>
<li><a href="#booleans">Booleans</a></li>
<li><a href="#lists">Lists</a></li>
<li><a href="#types">Types</a></li>
<li><a href="#numeric-types">Numeric Types</a></li>
<li><a href="#crashing">Crashing</a></li>
<li><a href="#tests-and-expectations">Tests and Expectations</a></li>
<li><a href="#modules">Modules</a></li>
<li><a href="#tasks">Tasks</a></li>
<li><a href="#abilities">Abilities</a></li>
<li><a href="#appendix-advanced-concepts">Advanced Concepts</a></li>
<li><a href="#reserved-keywords">Reserved Keywords</a></li>
<li><a href="#operator-desugaring-table">Operator Desugaring Table</a></li>
</ol>
</nav>
<section id="tutorial-body">
<section>
<h1>Tutorial<label id="tutorial-toc-toggle-label" for="tutorial-toc-toggle">contents</label></h1>
<p>Welcome to Roc!</p>
<p>This tutorial will teach you how to build Roc applications. Along the way, you'll learn how to write tests, use the REPL, and more!</p>
</section>
<section>
<h2 id="installation"><a href="#installation">Installation</a></h2>
<p>Roc doesnt have a numbered release or an installer yet, but you can follow the install instructions for your OS<a href="https://github.com/roc-lang/roc/tree/main/getting_started#installation"> here </a>. If you get stuck, friendly people will be happy to help if you open a topic in<a href="https://roc.zulipchat.com/#narrow/stream/231634-beginners"> #beginners </a>on<a href="https://roc.zulipchat.com/"> Roc Zulip Chat </a>and ask for assistance!</p>
</section>
## [Strings and Numbers](#strings-and-numbers) {#strings-and-numbers} ## [REPL](#repl) {#repl}
Let's start by getting acquainted with Roc's [_Read-Eval-Print-Loop_](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop), or **REPL** for short. Run this in a terminal: Let's start by getting acquainted with Roc's [_Read-Eval-Print-Loop_](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop), or **REPL** for short.
<code class="block">roc repl</code> You can use the online REPL at [roc-lang.org/repl](https://www.roc-lang.org/repl).
If Roc is [installed](#installation), you should see this: Or you can run this in a terminal: <code class="block">roc repl</code>, and if Roc is [installed](/install), you should see this:
<pre><samp>The rockin roc repl</samp></pre> <pre>
<samp>
The rockin roc repl
────────────────────────
Enter an expression, or :help, or :q to quit.
</samp></pre>
So far, so good! So far, so good!
@ -28,16 +70,12 @@ Congratulations! You've just written your first Roc code.
When you entered the _expression_ `"Hello, World!"`, the REPL printed it back out. It also printed `: Str`, because `Str` is that expression's type. We'll talk about types later; for now, let's ignore the `:` and whatever comes after it whenever we see them. When you entered the _expression_ `"Hello, World!"`, the REPL printed it back out. It also printed `: Str`, because `Str` is that expression's type. We'll talk about types later; for now, let's ignore the `:` and whatever comes after it whenever we see them.
Let's try that out. Put this into the repl and press Enter: You can assign specific names to expressions. Try entering these lines:
<pre><samp class="repl-prompt">val1</samp></pre> ```
greeting = "Hi"
You should see the same `"Hello, World!"` line as before. audience = "World"
```
You can also assign specific names to expressions. Try entering these lines:
<pre><samp class="repl-prompt">greeting = <span class="literal">"Hi"</span></samp></pre>
<pre><samp class="repl-prompt">audience = <span class="literal">"World"</span></samp></pre>
From now until you exit the REPL, you can refer to either `greeting` or `audience` by those names! We'll use these later on in the tutorial. From now until you exit the REPL, you can refer to either `greeting` or `audience` by those names! We'll use these later on in the tutorial.
@ -49,7 +87,7 @@ Now let's try using an _operator_, specifically the `+` operator. Enter this:
You should see this output: You should see this output:
<pre><samp>2 <span class="colon">:</span> Num * <span class="comment"> # val2</span></samp></pre> <pre><samp>2 <span class="colon">:</span> Num * <span class="comment"></span></samp></pre>
According to the REPL, one plus one equals two. Sounds right! According to the REPL, one plus one equals two. Sounds right!
@ -530,8 +568,6 @@ stoplightStr =
This lets us more concisely handle multiple cases. However, it has the downside that if we add a new case - for example, if we introduce the possibility of `stoplightColor` being `Orange`, the compiler can no longer tell us we forgot to handle that possibility in our `when`. After all, we are handling it - just maybe not in the way we'd decide to if the compiler had drawn our attention to it! This lets us more concisely handle multiple cases. However, it has the downside that if we add a new case - for example, if we introduce the possibility of `stoplightColor` being `Orange`, the compiler can no longer tell us we forgot to handle that possibility in our `when`. After all, we are handling it - just maybe not in the way we'd decide to if the compiler had drawn our attention to it!
## [Exhaustiveness](#exhaustiveness) {#exhaustiveness}
We can make this `when` _exhaustive_ (that is, covering all possibilities) without using `_ ->` by using `|` to specify multiple matching conditions for the same branch: We can make this `when` _exhaustive_ (that is, covering all possibilities) without using `_ ->` by using `|` to specify multiple matching conditions for the same branch:
```roc ```roc
@ -1960,6 +1996,12 @@ For this reason, any time you see a function that only runs a `when` on its only
\[This part of the tutorial has not been written yet. Coming soon!\] \[This part of the tutorial has not been written yet. Coming soon!\]
### [Reserved Keywords](#reserved-keywords) {#reserved-keywords}
These are all the reserved keywords in Roc. You can't choose any of these as names, except as record field names.
`if`, `then`, `else`, `when`, `as`, `is`, `dbg`, `expect`, `expect-fx`, `crash`, `interface`, `app`, `package`, `platform`, `hosted`, `exposes`, `imports`, `with`, `generates`, `packages`, `requires`, `provides`, `to`
### [Operator Desugaring Table](#operator-desugaring-table) {#operator-desugaring-table} ### [Operator Desugaring Table](#operator-desugaring-table) {#operator-desugaring-table}
Here are various Roc expressions involving operators, and what they desugar to. Here are various Roc expressions involving operators, and what they desugar to.
@ -1982,8 +2024,6 @@ Here are various Roc expressions involving operators, and what they desugar to.
| <code>a \|> b</code> | `b a` | | <code>a \|> b</code> | `b a` |
| <code>a b c \|> f x y</code> | `f (a b c) x y` | | <code>a b c \|> f x y</code> | `f (a b c) x y` |
### [Language Keywords](#language-keywords) {#language-keywords}
These are all of the language keywords supported by Roc; </section>
<script type="text/javascript" src="/builtins/search.js" defer></script>
`if`,`then`,`else`,`when`,`as`,`is`,`dbg`,`expect`,`expect-fx`,`crash`,`interface`,`app`,`package`,`platform`,`hosted`,`exposes`,`imports`,`with`,`generates`,`packages`,`requires`,`provides`,`to`

View file

@ -1,2 +0,0 @@
tutorial
src/output/tutorial.html

View file

@ -1,116 +0,0 @@
app "roc-tutorial"
packages { pf: "../../../examples/static-site-gen/platform/main.roc" }
imports [
pf.Html.{ html, head, body, header, footer, script, div, main, p, section, h1, h2, label, ol, input, text, nav, a, li, link, meta },
pf.Html.Attributes.{ content, name, for, id, type, href, rel, lang, title, charset, src },
]
provides [transformFileContent] to pf
transformFileContent : Str, Str -> Str
transformFileContent = \_, htmlContent ->
Html.render (view htmlContent)
view : Str -> Html.Node
view = \htmlContent ->
html [lang "en"] [
head [] [
meta [charset "utf-8"],
Html.title [] [text "Roc Tutorial"],
meta [name "description", content "Learn how to use the Roc programming language."],
meta [name "viewport", content "width=device-width"],
link [rel "stylesheet", href "/site.css"],
link [rel "icon", href "/favicon.svg"],
],
body [] [
viewNavbar,
main [] [
viewTutorialStart,
text htmlContent,
],
footer [] [
text "Made by people who like to make nice things.",
],
script [src "/site.js"] [],
],
]
viewNavbar : Html.Node
viewNavbar =
header [id "top-bar"] [
nav [] [
a [id "nav-home-link", href "/", title "The Roc Programming Language"] [text "roc"],
div [id "header-links"] [
a [href "/tutorial"] [text "tutorial"],
a [href "https://github.com/roc-lang/roc/tree/main/getting_started"] [text "install"],
a [href "/repl"] [text "repl"],
a [href "/builtins"] [text "docs"],
],
],
]
viewTutorialStart : Html.Node
viewTutorialStart =
div [id "tutorial-start"] [
input [id "tutorial-toc-toggle", name "tutorial-toc-toggle", type "checkbox"] [],
nav [id "tutorial-toc", ariaLabel "Table of Contents"] [
label [id "close-tutorial-toc", for "tutorial-toc-toggle"] [text "close"],
# TODO fix search: input [id "toc-search", type "text", placeholder "Search"] [],
ol [] tocLinks,
],
tutorialIntro,
]
tocLinks =
{ tag, value } <- List.map [
{ tag: "#installation", value: "Installation" },
{ tag: "#strings-and-numbers", value: "Strings and Numbers" },
{ tag: "#building-an-application", value: "Building an Application" },
{ tag: "#defining-functions", value: "Defining Functions" },
{ tag: "#if-then-else", value: "if-then-else" },
{ tag: "#debugging", value: "Debugging" },
{ tag: "#records", value: "Records" },
{ tag: "#tags", value: "Tags &amp; Pattern Matching" },
{ tag: "#booleans", value: "Booleans" },
{ tag: "#lists", value: "Lists" },
{ tag: "#types", value: "Types" },
{ tag: "#numeric-types", value: "Numeric Types" },
{ tag: "#crashing", value: "Crashing" },
{ tag: "#tests-and-expectations", value: "Tests and Expectations" },
{ tag: "#modules", value: "Modules" },
{ tag: "#tasks", value: "Tasks" },
{ tag: "#abilities", value: "Abilities" },
{ tag: "#appendix-advanced-concepts", value: "Advanced Concepts" },
{ tag: "#operator-desugaring-table", value: "Operator Desugaring Table" },
]
li [] [
a [href tag] [text value],
]
tutorialIntro =
div [id "tutorial-intro"] [
section [] [
h1 [] [
text "Tutorial",
label [id "tutorial-toc-toggle-label", for "tutorial-toc-toggle"] [text "contents"],
],
p [] [text "Welcome to Roc!"],
p [] [text "This tutorial will teach you how to build Roc applications. Along the way, you'll learn how to write tests, use the REPL, and more!"],
],
section [] [
h2 [id "installation"] [
a [href "#installation"] [text "Installation"],
],
p [] [
text "Roc doesnt have a numbered release or an installer yet, but you can follow the install instructions for your OS",
a [href "https://github.com/roc-lang/roc/tree/main/getting_started#installation"] [text " here "],
text ". If you get stuck, friendly people will be happy to help if you open a topic in",
a [href "https://roc.zulipchat.com/#narrow/stream/231634-beginners"] [text " #beginners "],
text "on",
a [href "https://roc.zulipchat.com/"] [text " Roc Zulip Chat "],
text "and ask for assistance!",
],
],
]
ariaLabel = Html.attribute "aria-label"

View file

@ -1,21 +1,20 @@
app "roc-website" app "roc-website"
packages { pf: "../../examples/static-site-gen/platform/main.roc" } packages { pf: "../examples/static-site-gen/platform/main.roc" }
imports [ imports [
pf.Html.{ Node, html, head, body, header, footer, div, main, text, nav, a, link, meta, script }, pf.Html.{ Node, html, head, body, header, footer, div, span, main, text, nav, a, link, meta, script },
pf.Html.Attributes.{ attribute, content, name, id, href, rel, lang, class, title, charset, color, ariaLabel, type, role }, pf.Html.Attributes.{ attribute, content, name, id, href, rel, lang, class, title, charset, color, ariaLabel, ariaHidden, type },
InteractiveExample, InteractiveExample,
] ]
provides [transformFileContent] to pf provides [transformFileContent] to pf
pageData = pageData =
Dict.empty {} Dict.empty {}
|> Dict.insert "community.html" { title: "Community", description: "The Roc community" } |> Dict.insert "community.html" { title: "Roc Community", description: "Connect with the Roc programming language community" }
|> Dict.insert "design_goals.html" { title: "Design Goals", description: "Roc's design goals" } |> Dict.insert "docs.html" { title: "Roc Docs", description: "Documentation for the Roc programming language, including builtins" }
|> Dict.insert "docs.html" { title: "Documentation", description: "Learn the Roc programming language" } |> Dict.insert "index.html" { title: "The Roc Programming Language", description: "A fast, friendly, functional language" }
|> Dict.insert "index.html" { title: "Roc", description: "The Roc programming language" } |> Dict.insert "install.html" { title: "Install Roc", description: "Install the Roc programming language" }
|> Dict.insert "install.html" { title: "Install", description: "Getting started with the Roc programming language" } |> Dict.insert "donate.html" { title: "Donate to Roc", description: "Support the Roc programming language by donating or sponsoring" }
|> Dict.insert "donate.html" { title: "Donate", description: "Sponsor Roc" } |> Dict.insert "tutorial.html" { title: "Roc Tutorial", description: "Learn the Roc programming language" }
|> Dict.insert "tutorial.html" { title: "Tutorial", description: "The Roc tutorial" }
getPage : Str -> { title : Str, description : Str } getPage : Str -> { title : Str, description : Str }
getPage = \current -> getPage = \current ->
@ -56,6 +55,22 @@ view = \page, htmlContent ->
else else
[text htmlContent] [text htmlContent]
bodyAttrs =
when page is
"index.html" -> [id "homepage-main"]
"tutorial.html" -> [id "tutorial-main", class "article-layout"]
_ ->
if Str.startsWith page "examples/" && page != "examples/index.html" then
# Individual examples should render wider than articles.
# Otherwise the width is unreasonably low for the code blocks,
# and those pages don't tend to have big paragraphs anyway.
# Keep the article width on examples/index.html though,
# because otherwise when you're clicking through the top nav links,
# /examples has a surprisingly different width from the other links.
[id "example-main"]
else
[class "article-layout"]
html [lang "en", class "no-js"] [ html [lang "en", class "no-js"] [
head [] [ head [] [
meta [charset "utf-8"], meta [charset "utf-8"],
@ -67,9 +82,9 @@ view = \page, htmlContent ->
# The homepage doesn't actually use latin-ext # The homepage doesn't actually use latin-ext
preloadWoff2 "/fonts/lato-v23-latin/lato-v23-latin-regular.woff2", preloadWoff2 "/fonts/lato-v23-latin/lato-v23-latin-regular.woff2",
preloadWoff2 "/fonts/source-code-pro-v22-latin/source-code-pro-v22-latin-regular.woff2", preloadWoff2 "/fonts/source-code-pro-v22-latin/source-code-pro-v22-latin-regular.woff2",
preloadWoff2 "/fonts/permanent-marker-v16-latin/permanent-marker-v16-latin-regular.woff2",
link [rel "prefetch", href "/repl/roc_repl_wasm.js"], link [rel "prefetch", href "/repl/roc_repl_wasm.js"],
link [rel "stylesheet", href "/wip/site.css"], link [rel "stylesheet", href "/site.css"],
link [rel "stylesheet", href "/wip/repl.css"],
# Safari ignores rel="icon" and only respects rel="mask-icon". It will render the SVG with # Safari ignores rel="icon" and only respects rel="mask-icon". It will render the SVG with
# fill="#000" unless this `color` attribute here is hardcoded (not a CSS `var()`) to override it. # fill="#000" unless this `color` attribute here is hardcoded (not a CSS `var()`) to override it.
link [rel "mask-icon", href "/favicon.svg", color "#7d59dd"], link [rel "mask-icon", href "/favicon.svg", color "#7d59dd"],
@ -81,7 +96,7 @@ view = \page, htmlContent ->
# Otherwise, this will work locally and then fail in production! # Otherwise, this will work locally and then fail in production!
script [] [text "document.documentElement.className = document.documentElement.className.replace('no-js', '');"], script [] [text "document.documentElement.className = document.documentElement.className.replace('no-js', '');"],
], ],
body [] [ body bodyAttrs [
viewNavbar page, viewNavbar page,
main [] mainBody, main [] mainBody,
footer [] [ footer [] [
@ -97,21 +112,20 @@ viewNavbar : Str -> Html.Node
viewNavbar = \page -> viewNavbar = \page ->
isHomepage = page == "index.html" isHomepage = page == "index.html"
homeLink = homeLinkAttrs =
if isHomepage then [id "nav-home-link", href "/", title "The Roc Programming Language Homepage"]
div [role "presentation"] [] # This is a spacer for the nav bar |> List.concat (if isHomepage then [ariaHidden "true"] else [])
else
a [id "nav-home-link", href "/wip/", title "The Roc Programming Language"] [rocLogo]
header [id "top-bar"] [ header [id "top-bar"] [
nav [ariaLabel "primary"] [ nav [ariaLabel "primary"] [
homeLink, a homeLinkAttrs [rocLogo, span [class "home-link-text"] [text "Roc"]],
div [id "top-bar-links"] [ div [id "top-bar-links"] [
a [href "/wip/tutorial"] [text "tutorial"], a [href "/tutorial"] [text "tutorial"],
a [href "/wip/install"] [text "install"], a [href "/install"] [text "install"],
a [href "/wip/community"] [text "community"], a [href "/examples"] [text "examples"],
a [href "/wip/docs"] [text "docs"], a [href "/community"] [text "community"],
a [href "/wip/donate"] [text "donate"], a [href "/docs"] [text "docs"],
a [href "/donate"] [text "donate"],
], ],
], ],
] ]

View file

@ -43,11 +43,11 @@ done
popd popd
# Minify the CSS file, and let esbuild add a content hash to the file name # Minify the CSS file, and let esbuild add a content hash to the file name
npm exec --yes esbuild -- --minify dist/wip/site.css --outdir=dist/wip/ --entry-names='[name]-[hash]' npm exec --yes esbuild -- --minify dist/site.css --outdir=dist/ --entry-names='[name]-[hash]'
# Remove unused original file # Remove unused original file
rm dist/wip/site.css rm dist/site.css
# Find the new filename # Find the new filename
css_with_hash=$(basename dist/wip/site-*.css) css_with_hash=$(basename dist/site-*.css)
# Replace all occurances in the html # Replace all occurances in the html
sed -i "s:/wip/site.css:/wip/${css_with_hash}:g" dist/wip/*.html sed -i "s:/wip/site.css:/wip/${css_with_hash}:g" dist/*.html

Some files were not shown because too many files have changed in this diff Show more