Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,22 @@ jobs:

- uses: extractions/setup-just@v2

- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@master
- uses: actions/setup-node@v4
with:
node-version: '22'

- name: Install Tongues
run: brew install ldayton/tongues/tongues
env:
TONGUES_VERSION: v0.2.4
run: |
mkdir -p "$HOME/.local/lib/tongues/lib" "$HOME/.local/lib/tongues/bin" "$HOME/.local/bin"
curl -sSfL "https://github.com/ldayton/Tongues/releases/download/${TONGUES_VERSION}/tongues.js" \
-o "$HOME/.local/lib/tongues/lib/tongues.js"
curl -sSfL "https://raw.githubusercontent.com/ldayton/Tongues/${TONGUES_VERSION}/tongues/bin/tongues.js" \
-o "$HOME/.local/lib/tongues/bin/tongues.js"
printf '#!/bin/sh\nexec node %s/.local/lib/tongues/bin/tongues.js "$@"\n' "$HOME" > "$HOME/.local/bin/tongues"
chmod +x "$HOME/.local/bin/tongues"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"

- name: Job summary
run: |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ tools/bash-oracle/bin/negation-cmdsub.txt
/bugs.tests
scalene-profile.html
src/cmd/debug-test/
.out/
13 changes: 5 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,14 @@ Parse bash exactly as bash does. One file, zero dependencies, in your language.

**Portable performance.** Hand-written recursive descent—no generators, no native extensions, no imports. [Tongues](https://github.com/ldayton/Tongues), our custom-built transpiler, builds release binaries in many languages. All run the same tests.

## Languages
## Releases

All releases, even Python, run through the transpiler. Current languages:

| Language | Min Version | Status |
| ---------- | ----------------- | ---------- |
| Java | Temurin 21 | Tests pass |
| Javascript | Node.js 21 | Tests pass |
| Perl | Perl 5.38 | Tests pass |
| Python | CPython/Pypy 3.12 | Tests pass |
| Ruby | Ruby 3.2 | Tests pass |
| Language | Min Version |
| ---------- | ----------------- |
| Javascript | Node.js 21 |
| Python | CPython/Pypy 3.12 |

Output code quality is a work in progress. Currently the transpiler prioritizes correctness over readability; generated code may not yet match hand-written idioms, yet.

Expand Down
102 changes: 100 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
set shell := ["bash", "-o", "pipefail", "-cu"]

export VIRTUAL_ENV := ""

# --- Configuration ---
project := "parable"
run_id := `head -c 16 /dev/urandom | xxd -p`
Expand Down Expand Up @@ -36,7 +38,7 @@ src-verify-lock: (_banner "src-verify-lock")
check-tongues: (_banner "check-tongues")
#!/usr/bin/env bash
set -euo pipefail
required="0.2.2"
required="0.2.4"
if ! command -v tongues &>/dev/null; then
echo "FAIL: tongues not found. Install with: brew install ldayton/tap/tongues"
exit 1
Expand All @@ -58,7 +60,7 @@ pycheck: check-tongues (_banner "pycheck")
# Internal: run all parallel checks
[private]
[parallel]
_check-parallel: src-test src-lint src-fmt src-verify-lock check-dump-ast pycheck
_check-parallel: src-test src-lint src-fmt src-verify-lock check-dump-ast pycheck (lang "javascript")

# Run all checks (parallel)
[group: 'ci']
Expand All @@ -68,6 +70,102 @@ check: _check-parallel
[group: 'ci']
check-quick: src-test

# --- Transpiled backends ---

# SHA256 of source file (truncated to 16 chars)
[private]
_src-checksum:
@cat src/parable.py | { sha256sum 2>/dev/null || shasum -a 256; } | cut -c1-16

# Transpile source to target language (skips if unchanged)
[private]
_transpile target:
#!/usr/bin/env bash
set -euo pipefail
declare -A ext=([python]=py [ruby]=rb [perl]=pl [javascript]=js [java]=java)
e=${ext[{{target}}]}
out=".out/parable.$e"
sum_file=".out/.sum-{{target}}"
current=$(just -f {{justfile()}} _src-checksum)
cached=$(cat "$sum_file" 2>/dev/null || echo "")
if [ -f "$out" ] && [ "$current" = "$cached" ]; then
printf '\033[33m[transpile-{{target}}] up to date\033[0m\n'
exit 0
fi
rm -f "$sum_file"
printf '\033[32m[transpile-{{target}}]\033[0m\n'
start=$SECONDS
mkdir -p .out
tongues --target {{target}} -o "$out" src/parable.py
case "{{target}}" in
javascript) echo 'module.exports = { parse, ParseError, MatchedPairError };' >> "$out" ;;
perl) echo '1;' >> "$out" ;;
esac
printf '\033[32m[transpile-{{target}}] %ds\033[0m\n' "$((SECONDS - start))"
echo "$current" > "$sum_file"

# Run transpiled tests for a target language
[private]
_run-lang-tests target:
#!/usr/bin/env bash
set -euo pipefail
printf '\033[32m[lang-{{target}}]\033[0m\n'
start=$SECONDS
case "{{target}}" in
javascript)
PARABLE_PATH="$(pwd)/.out" node tests/transpiled/javascript/run-tests.js tests
;;
python)
PYTHONPATH=.out uv run python tests/transpiled/python/run_tests.py tests
;;
java)
mkdir -p .out/java-classes
cp .out/parable.java .out/java-classes/Main.java
javac -encoding UTF-8 .out/java-classes/Main.java -d .out/java-classes
javac -encoding UTF-8 -cp .out/java-classes tests/transpiled/java/RunTests.java -d .out/java-classes
java -cp .out/java-classes RunTests tests
;;
perl)
perl -I.out tests/transpiled/perl/run_tests.pl tests
;;
ruby)
ruby -I.out tests/transpiled/ruby/run_tests.rb tests
;;
*)
echo "Unknown target: {{target}}"
exit 1
;;
esac
printf '\033[32m[lang-{{target}}] %ds\033[0m\n' "$((SECONDS - start))"

# Transpile and run tests in target language
# Usage: just lang javascript
[group: 'backends']
lang target: check-tongues (_transpile target) (_run-lang-tests target)

# Transpile using local Tongues dev source and run tests
# Usage: just lang-dev javascript
[group: 'backends']
lang-dev target: (_transpile-dev target) (_run-lang-tests target)

# Transpile using local Tongues source (no caching)
[private]
_transpile-dev target:
#!/usr/bin/env bash
set -euo pipefail
declare -A ext=([python]=py [ruby]=rb [perl]=pl [javascript]=js [java]=java)
e=${ext[{{target}}]}
out=".out/parable.$e"
mkdir -p .out
printf '\033[32m[transpile-{{target}} (dev)]\033[0m\n'
start=$SECONDS
python3 ~/source/Tongues/tongues/bin/tongues --target {{target}} -o "$out" src/parable.py
case "{{target}}" in
javascript) echo 'module.exports = { parse, ParseError, MatchedPairError };' >> "$out" ;;
perl) echo '1;' >> "$out" ;;
esac
printf '\033[32m[transpile-{{target}} (dev)] %ds\033[0m\n' "$((SECONDS - start))"

# --- Tools ---

# Run the fuzzer (e.g., just fuzz char --stop-after 10)
Expand Down
Loading
Loading