diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 054c3f9..8cfa4cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,6 +84,12 @@ jobs: echo "Testing numeric/gamma_fact..." cargo run --quiet --package goth-cli -- "$(pwd)/../examples/numeric/gamma_fact.goth" 5.0 + - name: Run stdlib tests + run: GOTH=./crates/target/release/goth bash tests/stdlib_test.sh + + - name: Run CLI smoke tests + run: GOTH=./crates/target/release/goth bash tests/cli_test.sh + - name: Upload binary artifact uses: actions/upload-artifact@v4 with: diff --git a/crates/Cargo.lock b/crates/Cargo.lock index 03fa664..62a5372 100644 --- a/crates/Cargo.lock +++ b/crates/Cargo.lock @@ -47,7 +47,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -58,7 +58,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -353,7 +353,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -591,11 +591,11 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "home" -version = "0.5.12" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1059,7 +1059,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1350,7 +1350,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1634,7 +1634,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] diff --git a/tests/cli_test.sh b/tests/cli_test.sh new file mode 100644 index 0000000..7aa8abd --- /dev/null +++ b/tests/cli_test.sh @@ -0,0 +1,206 @@ +#!/bin/bash +# CLI smoke test suite for Goth +# Tests all major CLI modes of the goth interpreter binary. + +set -e + +GOTH="${GOTH:-./crates/target/release/goth}" + +# Color output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +NC='\033[0m' + +PASS=0 +FAIL=0 + +pass() { + echo -e "${GREEN}✓ $1${NC}" + PASS=$((PASS + 1)) +} + +fail() { + echo -e "${RED}✗ $1${NC}" + FAIL=$((FAIL + 1)) +} + +# Test exact output match +run_test() { + local name="$1" + local expected="$2" + shift 2 + + local actual + actual=$("$@" 2>&1) || true + + if [ "$actual" = "$expected" ]; then + pass "$name" + else + fail "$name" + echo -e "${YELLOW} Expected:${NC} $(echo "$expected" | head -1)" + echo -e "${YELLOW} Got:${NC} $(echo "$actual" | head -1)" + fi +} + +# Test that output contains a substring (case-sensitive) +run_test_contains() { + local name="$1" + local substring="$2" + shift 2 + + local actual + actual=$("$@" 2>&1) || true + + if echo "$actual" | grep -qF "$substring"; then + pass "$name" + else + fail "$name" + echo -e "${YELLOW} Expected to contain:${NC} $substring" + echo -e "${YELLOW} Got:${NC} $(echo "$actual" | head -3)" + fi +} + +# Test that output contains a substring (case-insensitive) +run_test_contains_i() { + local name="$1" + local substring="$2" + shift 2 + + local actual + actual=$("$@" 2>&1) || true + + if echo "$actual" | grep -qiF "$substring"; then + pass "$name" + else + fail "$name" + echo -e "${YELLOW} Expected to contain (case-insensitive):${NC} $substring" + echo -e "${YELLOW} Got:${NC} $(echo "$actual" | head -3)" + fi +} + +# Test that stdout is empty +run_test_empty() { + local name="$1" + shift + + local actual + actual=$("$@" 2>&1) || true + + if [ -z "$actual" ]; then + pass "$name" + else + fail "$name" + echo -e "${YELLOW} Expected empty output${NC}" + echo -e "${YELLOW} Got:${NC} $(echo "$actual" | head -1)" + fi +} + +echo "=== Goth CLI Smoke Tests ===" +echo "" + +# --- Section 1: Expression evaluation (-e) --- +echo "Section: Expression evaluation (-e)" +run_test "integer arithmetic" "7" $GOTH -e "1 + 2 * 3" +run_test "negative result" "-7" $GOTH -e "3 - 10" +run_test "boolean true" "⊤" $GOTH -e "3 > 2" +run_test "boolean false" "⊥" $GOTH -e "3 < 2" +run_test "lambda application" "6" $GOTH -e "(λ→ ₀ + 1) 5" +run_test "array sum" "15" $GOTH -e "Σ [1, 2, 3, 4, 5]" +run_test "let binding" "15" $GOTH -e "let x ← 10 in x + 5" +echo "" + +# --- Section 2: File execution --- +echo "Section: File execution" +run_test "identity" "42" $GOTH examples/basic/identity.goth 42 +run_test "add_one" "100" $GOTH examples/basic/add_one.goth 99 +run_test "square" "49" $GOTH examples/basic/square.goth 7 +run_test "factorial" "120" $GOTH examples/recursion/factorial.goth 5 +run_test "fibonacci" "55" $GOTH examples/recursion/fibonacci.goth 10 +run_test "isPrime" "⊤" $GOTH examples/algorithms/isPrime.goth 17 +echo "" + +# --- Section 3: Multi-argument programs --- +echo "Section: Multi-argument programs" +run_test "gcd" "4" $GOTH examples/recursion/gcd.goth 12 8 +run_test "compose" "36" $GOTH examples/higher-order/compose.goth 3 +echo "" + +# --- Section 4: Parse-only mode (-p) --- +echo "Section: Parse-only mode (-p)" +run_test_contains "parse file" "Parsed:" $GOTH -p examples/basic/identity.goth +run_test_empty "parse expression" $GOTH -p -e "1 + 2" +echo "" + +# --- Section 5: AST mode (-a) --- +echo "Section: AST mode (-a)" +run_test_contains "ast expression" "AST:" $GOTH -a -e "1 + 2" +run_test_contains "ast file" "Parsed AST:" $GOTH -a examples/basic/identity.goth +echo "" + +# --- Section 6: Type check mode (-c) --- +echo "Section: Type check mode (-c)" +run_test_contains "type check expression" "Type:" $GOTH -c -e "1 + 2" +run_test "type check file with args" "100" $GOTH -c examples/basic/add_one.goth 99 +echo "" + +# --- Section 7: JSON workflow --- +echo "Section: JSON workflow" +TMPDIR=$(mktemp -d) +trap "rm -rf $TMPDIR" EXIT + +run_test_contains "to-json produces JSON" '"decls"' $GOTH --to-json examples/basic/identity.goth + +# Compact JSON should be a single line +COMPACT_OUTPUT=$($GOTH --compact --to-json examples/basic/identity.goth 2>&1) || true +COMPACT_LINES=$(echo "$COMPACT_OUTPUT" | wc -l) +if [ "$COMPACT_LINES" -eq 1 ] && echo "$COMPACT_OUTPUT" | grep -qF '"decls"'; then + pass "compact to-json is single line" +else + fail "compact to-json is single line" + echo -e "${YELLOW} Lines: $COMPACT_LINES${NC}" +fi + +# JSON round-trip +$GOTH --to-json examples/basic/add_one.goth > "$TMPDIR/add_one.json" 2>&1 +ROUNDTRIP=$($GOTH --from-json "$TMPDIR/add_one.json" 99 2>&1) || true +if echo "$ROUNDTRIP" | grep -qF "100"; then + pass "json round-trip preserves semantics" +else + fail "json round-trip preserves semantics" + echo -e "${YELLOW} Expected output to contain: 100${NC}" + echo -e "${YELLOW} Got:${NC} $ROUNDTRIP" +fi +echo "" + +# --- Section 8: No-main mode --- +echo "Section: No-main mode (--no-main)" +run_test_contains "no-main shows declarations" "fn main" $GOTH --no-main examples/basic/identity.goth +echo "" + +# --- Section 9: Error handling --- +echo "Section: Error handling" +run_test_contains_i "nonexistent file" "error" $GOTH nonexistent_file.goth +run_test_contains_i "syntax error" "error" $GOTH -e "1 + + 2" + +# Render from JSON +$GOTH --to-json examples/basic/add_one.goth > "$TMPDIR/render_test.json" 2>&1 +RENDER_OUTPUT=$($GOTH --render --from-json "$TMPDIR/render_test.json" 2>&1) || true +if echo "$RENDER_OUTPUT" | grep -qF "module" && echo "$RENDER_OUTPUT" | grep -qF "main"; then + pass "render from json produces source" +else + fail "render from json produces source" + echo -e "${YELLOW} Expected 'module' and 'main' in output${NC}" + echo -e "${YELLOW} Got:${NC} $(echo "$RENDER_OUTPUT" | head -3)" +fi +echo "" + +# --- Summary --- +TOTAL=$((PASS + FAIL)) +echo "Results: $PASS/$TOTAL passed" +if [ "$FAIL" -gt 0 ]; then + echo -e "${RED}$FAIL test(s) failed${NC}" + exit 1 +else + echo -e "${GREEN}All tests passed!${NC}" +fi diff --git a/tests/stdlib/test_list.goth b/tests/stdlib/test_list.goth new file mode 100644 index 0000000..9357bd4 --- /dev/null +++ b/tests/stdlib/test_list.goth @@ -0,0 +1,45 @@ +# Test suite for stdlib/list.goth + +use "../../stdlib/list.goth" + +╭─ main : () → () +╰─ let xs ← [10, 20, 30] in + # Access operations + let _ ← print (toString (head xs)) in + let _ ← print (toString (last xs)) in + let _ ← print (toString (tail xs)) in + let _ ← print (toString (init xs)) in + let _ ← print (toString (getOr xs 1 99)) in + let _ ← print (toString (getOr xs 5 99)) in + + # Construction + let _ ← print (toString (cons 0 xs)) in + let _ ← print (toString (snoc xs 40)) in + let a ← [1, 2] in + let b ← [3, 4] in + let _ ← print (toString (append a b)) in + let _ ← print (toString (replicate 3 7)) in + let _ ← print (toString (rangeTo 1 5)) in + + # Transformation + let ns ← [1, 2, 3] in + let _ ← print (toString (map ns (λ→ ₀ × 10))) in + let big ← [1, 2, 3, 4, 5] in + let _ ← print (toString (filter big (λ→ ₀ > 3))) in + let _ ← print (toString (rev ns)) in + + # Reduction + let fs ← [1.0, 2.0, 3.0] in + let _ ← print (toString (sum fs)) in + let gs ← [2.0, 3.0, 4.0] in + let _ ← print (toString (product gs)) in + let _ ← print (toString (all big (λ→ ₀ > 0))) in + let _ ← print (toString (all big (λ→ ₀ > 3))) in + let _ ← print (toString (any big (λ→ ₀ > 4))) in + let _ ← print (toString (count big (λ→ ₀ > 3))) in + + # Predicates + let _ ← print (toString (null ns)) in + let _ ← print (toString (size ns)) in + + ⟨⟩ diff --git a/tests/stdlib/test_math.goth b/tests/stdlib/test_math.goth new file mode 100644 index 0000000..c85f084 --- /dev/null +++ b/tests/stdlib/test_math.goth @@ -0,0 +1,124 @@ +# Test suite for stdlib/math.goth +# +# Note: math.goth has a parse error due to `pi` being a reserved keyword. +# Until that is fixed, we test math functions by defining them inline here. + +# === Basic ops === +╭─ absI : ℤ → ℤ +╰─ if ₀ < 0 then 0 - ₀ else ₀ + +╭─ absF : F → F +╰─ if ₀ < 0.0 then 0.0 - ₀ else ₀ + +╭─ signI : ℤ → ℤ +╰─ if ₀ < 0 then 0 - 1 else if ₀ > 0 then 1 else 0 + +╭─ signF : F → F +╰─ if ₀ < 0.0 then 0.0 - 1.0 else if ₀ > 0.0 then 1.0 else 0.0 + +# === Powers/roots === +╭─ sq : F → F +╰─ ₀ × ₀ + +╭─ cb : F → F +╰─ ₀ × ₀ × ₀ + +╭─ mySqrt : F → F +╰─ √₀ + +╭─ hypot : F → F → F +╰─ √(₁ × ₁ + ₀ × ₀) + +# === Number theory === +╭─ isEven : ℤ → Bool +╰─ ₀ % 2 = 0 + +╭─ isOdd : ℤ → Bool +╰─ ₀ % 2 ≠ 0 + +╭─ divides : ℤ → ℤ → Bool +╰─ ₀ % ₁ = 0 + +╭─ floorDiv : ℤ → ℤ → ℤ +╰─ ₁ / ₀ + +╭─ myMod : ℤ → ℤ → ℤ +╰─ let r ← ₁ % ₀ + in if r < 0 then r + ₀ else r + +# === Rounding === +╭─ myFloor : F → F +╰─ ⌊₀⌋ + +╭─ myCeil : F → F +╰─ ⌈₀⌉ + +# === Comparison === +╭─ minF : F → F → F +╰─ if ₁ < ₀ then ₁ else ₀ + +╭─ maxF : F → F → F +╰─ if ₁ > ₀ then ₁ else ₀ + +╭─ clamp : F → F → F → F +╰─ if ₀ < ₂ then ₂ else if ₀ > ₁ then ₁ else ₀ + +# === Interpolation === +╭─ lerp : F → F → F → F +╰─ ₁ + ₂ × (₀ - ₁) + +╭─ main : () → () +╰─ # Constants (using built-in π and 𝕖) + let _ ← print (toString (⌊π × 100.0 + 0.5⌋)) in + let _ ← print (toString (⌊𝕖 × 100.0 + 0.5⌋)) in + let _ ← print (toString (⌊((1.0 + √5.0) / 2.0) × 1000.0 + 0.5⌋)) in + let _ ← print (toString (⌊√2.0 × 1000.0 + 0.5⌋)) in + + # Basic ops + let _ ← print (toString (absI (0 - 7))) in + let _ ← print (toString (absI 3)) in + let _ ← print (toString (absF (0.0 - 2.5))) in + let _ ← print (toString (signI (0 - 4))) in + let _ ← print (toString (signI 0)) in + let _ ← print (toString (signI 6)) in + let _ ← print (toString (signF (0.0 - 1.5))) in + let _ ← print (toString (signF 0.0)) in + let _ ← print (toString (signF 3.0)) in + + # Powers/roots + let _ ← print (toString (sq 5.0)) in + let _ ← print (toString (cb 3.0)) in + let _ ← print (toString (mySqrt 16.0)) in + let _ ← print (toString (hypot 3.0 4.0)) in + + # Exponential/log (using built-ins) + let _ ← print (toString (⌊exp 1.0 × 100.0 + 0.5⌋)) in + let _ ← print (toString (⌊ln 𝕖 × 100.0 + 0.5⌋)) in + let _ ← print (toString (⌊log₁₀ 100.0 × 100.0 + 0.5⌋)) in + let _ ← print (toString (⌊log₂ 8.0 × 100.0 + 0.5⌋)) in + + # Number theory + let _ ← print (toString (isEven 6)) in + let _ ← print (toString (isOdd 7)) in + let _ ← print (toString (divides 5 10)) in + let _ ← print (toString (divides 3 10)) in + let _ ← print (toString (floorDiv 3 10)) in + let _ ← print (toString (floorDiv 10 3)) in + let _ ← print (toString (myMod 3 10)) in + let _ ← print (toString (myMod 10 3)) in + + # Rounding + let _ ← print (toString (myFloor 3.7)) in + let _ ← print (toString (myCeil 3.2)) in + + # Comparison + let _ ← print (toString (minF 3.0 7.0)) in + let _ ← print (toString (maxF 3.0 7.0)) in + let _ ← print (toString (clamp 2.0 8.0 5.0)) in + let _ ← print (toString (clamp 2.0 8.0 1.0)) in + let _ ← print (toString (clamp 2.0 8.0 10.0)) in + + # Interpolation + let _ ← print (toString (lerp 0.5 0.0 10.0)) in + + ⟨⟩ diff --git a/tests/stdlib/test_option.goth b/tests/stdlib/test_option.goth new file mode 100644 index 0000000..43912e3 --- /dev/null +++ b/tests/stdlib/test_option.goth @@ -0,0 +1,47 @@ +# Test suite for stdlib/option.goth + +use "../../stdlib/option.goth" + +╭─ main : () → () +╰─ # Constructors + let _ ← print (toString (some 42)) in + let _ ← print (toString (some 3.14)) in + let _ ← print (toString (none ⟨⟩)) in + + # Predicates + let _ ← print (toString (isSome (some 1))) in + let _ ← print (toString (isSome (none ⟨⟩))) in + let _ ← print (toString (isNone (none ⟨⟩))) in + let _ ← print (toString (isNone (some 1))) in + + # Extractors + let _ ← print (toString (getOrElse 0 (some 42))) in + let _ ← print (toString (getOrElse 0 (none ⟨⟩))) in + + # Transformations + let _ ← print (toString (mapOpt (λ→ ₀ × 2) (some 5))) in + let _ ← print (toString (mapOpt (λ→ ₀ × 2) (none ⟨⟩))) in + + # Filter + let _ ← print (toString (filterOpt (λ→ ₀ > 3) (some 5))) in + let _ ← print (toString (filterOpt (λ→ ₀ > 3) (some 2))) in + + # Combining + let a ← some 1 in + let b ← some 2 in + let _ ← print (toString (orElseOpt a b)) in + + # Safe operations + let _ ← print (toString (safeDiv 10 2)) in + let _ ← print (toString (safeDiv 10 0)) in + + # Boolean checks + let _ ← print (toString (containsOpt 42 (some 42))) in + let _ ← print (toString (containsOpt 42 (some 99))) in + + # Show + let _ ← print (showOpt (some 42)) in + let _ ← print (showOpt (some 3.14)) in + let _ ← print (showOpt (none ⟨⟩)) in + + ⟨⟩ diff --git a/tests/stdlib/test_prelude.goth b/tests/stdlib/test_prelude.goth new file mode 100644 index 0000000..720731a --- /dev/null +++ b/tests/stdlib/test_prelude.goth @@ -0,0 +1,177 @@ +# Test suite for stdlib/prelude.goth +# +# Note: prelude.goth has a parse error due to `round` being a reserved keyword. +# Until that is fixed, we test prelude functions by defining them inline here. + +# === Combinators === +╭─ id : α → α +╰─ ₀ + +╭─ const : α → β → α +╰─ ₁ + +╭─ flip : (α → β → γ) → β → α → γ +╰─ ₂ ₀ ₁ + +╭─ compose : (β → γ) → (α → β) → α → γ +╰─ ₂ (₁ ₀) + +# === Boolean === +╭─ not : Bool → Bool +╰─ if ₀ then ⊥ else ⊤ + +╭─ and : Bool → Bool → Bool +╰─ if ₁ then ₀ else ⊥ + +╭─ or : Bool → Bool → Bool +╰─ if ₁ then ⊤ else ₀ + +╭─ xor : Bool → Bool → Bool +╰─ if ₁ then (if ₀ then ⊥ else ⊤) else ₀ + +╭─ implies : Bool → Bool → Bool +╰─ if ₁ then ₀ else ⊤ + +╭─ iff : Bool → Bool → Bool +╰─ if ₁ then ₀ else (if ₀ then ⊥ else ⊤) + +╭─ boolToInt : Bool → ℤ +╰─ if ₀ then 1 else 0 + +╭─ intToBool : ℤ → Bool +╰─ ₀ ≠ 0 + +# === Numeric predicates === +╭─ isZero : ℤ → Bool +╰─ ₀ = 0 + +╭─ isPositive : ℤ → Bool +╰─ ₀ > 0 + +╭─ isNegative : ℤ → Bool +╰─ ₀ < 0 + +╭─ isEven : ℤ → Bool +╰─ ₀ % 2 = 0 + +╭─ isOdd : ℤ → Bool +╰─ ₀ % 2 ≠ 0 + +# === Integer ops === +╭─ inc : ℤ → ℤ +╰─ ₀ + 1 + +╭─ dec : ℤ → ℤ +╰─ ₀ - 1 + +╭─ absInt : ℤ → ℤ +╰─ if ₀ < 0 then 0 - ₀ else ₀ + +╭─ signInt : ℤ → ℤ +╰─ if ₀ < 0 then 0 - 1 + else if ₀ > 0 then 1 + else 0 + +╭─ divInt : ℤ → ℤ → ℤ +╰─ ₁ / ₀ + +╭─ modInt : ℤ → ℤ → ℤ +╰─ ₁ % ₀ + +# === Float ops === +╭─ double : F → F +╰─ ₀ × 2.0 + +╭─ half : F → F +╰─ ₀ / 2.0 + +╭─ square : F → F +╰─ ₀ × ₀ + +╭─ negate : F → F +╰─ 0.0 - ₀ + +# === Tuple ops === +╭─ fst : ⟨α, β⟩ → α +╰─ ₀.0 + +╭─ snd : ⟨α, β⟩ → β +╰─ ₀.1 + +╭─ swap : ⟨α, β⟩ → ⟨β, α⟩ +╰─ ⟨₀.1, ₀.0⟩ + +# === Conditionals === +╭─ ifThenElse : Bool → α → α → α +╰─ if ₂ then ₁ else ₀ + +# === Rounding === +╭─ myFloor : F → F +╰─ ⌊₀⌋ + +╭─ myCeil : F → F +╰─ ⌈₀⌉ + +╭─ main : () → () +╰─ # Combinators + let _ ← print (toString (id 42)) in + let _ ← print (toString (const 10 99)) in + let _ ← print (toString (flip (λ→ λ→ ₁ - ₀) 3 10)) in + let _ ← print (toString (compose (λ→ ₀ × 2) (λ→ ₀ + 1) 5)) in + + # Boolean + let _ ← print (toString (not ⊤)) in + let _ ← print (toString (not ⊥)) in + let _ ← print (toString (and ⊤ ⊥)) in + let _ ← print (toString (and ⊤ ⊤)) in + let _ ← print (toString (or ⊥ ⊥)) in + let _ ← print (toString (or ⊥ ⊤)) in + let _ ← print (toString (xor ⊤ ⊤)) in + let _ ← print (toString (xor ⊤ ⊥)) in + let _ ← print (toString (implies ⊤ ⊥)) in + let _ ← print (toString (implies ⊥ ⊥)) in + let _ ← print (toString (iff ⊤ ⊤)) in + let _ ← print (toString (iff ⊤ ⊥)) in + let _ ← print (toString (boolToInt ⊤)) in + let _ ← print (toString (boolToInt ⊥)) in + let _ ← print (toString (intToBool 0)) in + let _ ← print (toString (intToBool 5)) in + + # Numeric predicates + let _ ← print (toString (isZero 0)) in + let _ ← print (toString (isZero 1)) in + let _ ← print (toString (isPositive 5)) in + let _ ← print (toString (isNegative (0 - 3))) in + let _ ← print (toString (isEven 4)) in + let _ ← print (toString (isOdd 7)) in + + # Integer ops + let _ ← print (toString (inc 10)) in + let _ ← print (toString (dec 10)) in + let _ ← print (toString (absInt (0 - 5))) in + let _ ← print (toString (signInt (0 - 7))) in + let _ ← print (toString (signInt 3)) in + let _ ← print (toString (signInt 0)) in + let _ ← print (toString (divInt 3 10)) in + let _ ← print (toString (modInt 3 10)) in + + # Float ops + let _ ← print (toString (double 3.5)) in + let _ ← print (toString (half 10.0)) in + let _ ← print (toString (square 4.0)) in + let _ ← print (toString (negate 5.0)) in + + # Tuple ops + let _ ← print (toString (fst ⟨10, 20⟩)) in + let _ ← print (toString (snd ⟨10, 20⟩)) in + let _ ← print (toString (swap ⟨1, 2⟩)) in + + # Conditionals + let _ ← print (toString (ifThenElse ⊤ 1 2)) in + let _ ← print (toString (ifThenElse ⊥ 1 2)) in + + # Rounding + let _ ← print (toString (myFloor 3.7)) in + let _ ← print (toString (myCeil 3.2)) in + + ⟨⟩ diff --git a/tests/stdlib/test_result.goth b/tests/stdlib/test_result.goth new file mode 100644 index 0000000..bb7998f --- /dev/null +++ b/tests/stdlib/test_result.goth @@ -0,0 +1,39 @@ +# Test suite for stdlib/result.goth + +use "../../stdlib/result.goth" + +╭─ main : () → () +╰─ # Constructors + let _ ← print (toString (ok 42)) in + let _ ← print (toString (err 1)) in + let _ ← print (toString (ok 10)) in + let _ ← print (toString (err "bad")) in + + # Predicates + let _ ← print (toString (isOk (ok 42))) in + let _ ← print (toString (isOk (err 1))) in + let _ ← print (toString (isErr (err 1))) in + let _ ← print (toString (isErr (ok 42))) in + + # Extractors + let _ ← print (toString (unwrapOr 0 (ok 42))) in + let _ ← print (toString (unwrapOr 0 (err 1))) in + + # Transformations + let _ ← print (toString (mapRes (λ→ ₀ × 2) (ok 5))) in + let _ ← print (toString (mapRes (λ→ ₀ × 2) (err 1))) in + + # Safe operations + let _ ← print (toString (safeDivRes 10 2)) in + let _ ← print (toString (safeDivRes 10 0)) in + + # Boolean checks + let _ ← print (toString (containsRes 42 (ok 42))) in + let _ ← print (toString (containsRes 42 (ok 99))) in + let _ ← print (toString (containsRes 42 (err 1))) in + + # Show + let _ ← print (showRes (ok 42)) in + let _ ← print (showRes (err 1)) in + + ⟨⟩ diff --git a/tests/stdlib/test_string.goth b/tests/stdlib/test_string.goth new file mode 100644 index 0000000..5135c72 --- /dev/null +++ b/tests/stdlib/test_string.goth @@ -0,0 +1,39 @@ +# Test suite for stdlib/string.goth + +use "../../stdlib/string.goth" + +╭─ main : () → () +╰─ # Properties + let _ ← print (toString (strLen "hello")) in + let _ ← print (toString (strEmpty "")) in + let _ ← print (toString (strEmpty "hi")) in + let _ ← print (toString (strNonEmpty "hi")) in + + # Comparison + let _ ← print (toString (strEqual "abc" "abc")) in + let _ ← print (toString (strEqual "abc" "xyz")) in + let _ ← print (toString (strNotEqual "abc" "xyz")) in + let _ ← print (toString (hasPrefix "hello world" "hello")) in + let _ ← print (toString (hasSuffix "hello world" "world")) in + let _ ← print (toString (hasSubstring "hello world" "lo wo")) in + + # Char predicates + let _ ← print (toString (isDigit '5')) in + let _ ← print (toString (isDigit 'a')) in + let _ ← print (toString (isAlpha 'z')) in + let _ ← print (toString (isSpace ' ')) in + + # Conversion + let _ ← print (intToStr 42) in + let _ ← print (boolToStr ⊤) in + let _ ← print (boolToStr ⊥) in + + # Constants + let _ ← print (toString (strLen emptyStr)) in + + # Word/line operations + let _ ← print (toString (wordCount "hello world foo")) in + let _ ← print (firstWord "hello world foo") in + let _ ← print (lastWord "hello world foo") in + + ⟨⟩ diff --git a/tests/stdlib_test.sh b/tests/stdlib_test.sh new file mode 100755 index 0000000..1264782 --- /dev/null +++ b/tests/stdlib_test.sh @@ -0,0 +1,270 @@ +#!/bin/bash +# Stdlib test suite for Goth +# Tests the standard library modules by running .goth test files +# and comparing stdout against expected output. + +set -e + +GOTH="${GOTH:-./crates/target/release/goth}" + +# Color output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +NC='\033[0m' + +PASS=0 +FAIL=0 + +pass() { + echo -e "${GREEN}✓ $1${NC}" + PASS=$((PASS + 1)) +} + +fail() { + echo -e "${RED}✗ $1${NC}" + FAIL=$((FAIL + 1)) +} + +run_test() { + local name="$1" + local file="$2" + local expected="$3" + + local actual + actual=$($GOTH "$file" 2>&1) || true + + if [ "$actual" = "$expected" ]; then + pass "$name" + else + fail "$name" + echo -e "${YELLOW} Expected:${NC}" + echo "$expected" | head -5 + echo -e "${YELLOW} Got:${NC}" + echo "$actual" | head -5 + if [ "$(echo "$expected" | wc -l)" -gt 5 ]; then + echo " ... (truncated)" + fi + fi +} + +echo "=== Goth Stdlib Tests ===" +echo "" + +# --- Test: prelude --- +echo "Module: prelude" +EXPECTED_PRELUDE=$(cat <<'EOF' +42 +10 +7 +12 +⊥ +⊤ +⊥ +⊤ +⊥ +⊤ +⊥ +⊤ +⊥ +⊤ +⊤ +⊥ +1 +0 +⊥ +⊤ +⊤ +⊥ +⊤ +⊤ +⊤ +⊤ +11 +9 +5 +-1 +1 +0 +0 +3 +7 +5 +16 +-5 +10 +20 +⟨2, 1⟩ +1 +2 +3 +4 +EOF +) +run_test "prelude" "tests/stdlib/test_prelude.goth" "$EXPECTED_PRELUDE" + +# --- Test: list --- +echo "Module: list" +EXPECTED_LIST=$(cat <<'EOF' +10 +30 +[20 30] +[10 20] +20 +99 +[0 10 20 30] +[10 20 30 40] +[1 2 3 4] +[7 7 7] +[1 2 3 4 5] +[10 20 30] +[4 5] +[3 2 1] +6 +24 +⊤ +⊥ +⊤ +2 +⊥ +3 +EOF +) +run_test "list" "tests/stdlib/test_list.goth" "$EXPECTED_LIST" + +# --- Test: math --- +echo "Module: math" +EXPECTED_MATH=$(cat <<'EOF' +314 +272 +1618 +1414 +7 +3 +2.5 +-1 +0 +1 +-1 +0 +1 +25 +27 +4 +5 +272 +100 +200 +300 +⊤ +⊤ +⊤ +⊥ +0 +3 +3 +1 +3 +4 +3 +7 +5 +2 +8 +5 +EOF +) +run_test "math" "tests/stdlib/test_math.goth" "$EXPECTED_MATH" + +# --- Test: option --- +echo "Module: option" +EXPECTED_OPTION=$(cat <<'EOF' +⟨⊤, 42⟩ +⟨⊤, 3.14⟩ +⟨⊥, 0⟩ +⊤ +⊥ +⊤ +⊥ +42 +0 +⟨⊤, 10⟩ +⟨⊥, 0⟩ +⟨⊤, 5⟩ +⟨⊥, 0⟩ +⟨⊤, 1⟩ +⟨⊤, 5⟩ +⟨⊥, 0⟩ +⊤ +⊥ +Some(42) +Some(3.14) +None +EOF +) +run_test "option" "tests/stdlib/test_option.goth" "$EXPECTED_OPTION" + +# --- Test: result --- +echo "Module: result" +EXPECTED_RESULT=$(cat <<'EOF' +⟨⊤, 42, 0⟩ +⟨⊥, 0, 1⟩ +⟨⊤, 10, 0⟩ +⟨⊥, 0, "bad"⟩ +⊤ +⊥ +⊤ +⊥ +42 +0 +⟨⊤, 10, 0⟩ +⟨⊥, 0, 1⟩ +⟨⊤, 5, ""⟩ +⟨⊥, 0, "division by zero"⟩ +⊤ +⊥ +⊥ +Ok(42) +Err(1) +EOF +) +run_test "result" "tests/stdlib/test_result.goth" "$EXPECTED_RESULT" + +# --- Test: string --- +echo "Module: string" +EXPECTED_STRING=$(cat <<'EOF' +5 +⊤ +⊥ +⊤ +⊤ +⊥ +⊤ +⊤ +⊤ +⊤ +⊤ +⊥ +⊤ +⊤ +42 +true +false +0 +3 +hello +foo +EOF +) +run_test "string" "tests/stdlib/test_string.goth" "$EXPECTED_STRING" + +# --- Summary --- +echo "" +TOTAL=$((PASS + FAIL)) +echo "Results: $PASS/$TOTAL passed" +if [ "$FAIL" -gt 0 ]; then + echo -e "${RED}$FAIL test(s) failed${NC}" + exit 1 +else + echo -e "${GREEN}All tests passed!${NC}" +fi