cemc 0.1.2

Cem language compiler - A concatenative language with green threads and linear types
Documentation
# prelude_test.cem - Tests for prelude.cem utilities
#
# These tests validate Option, Result, and List utilities.
# Run with: cem test stdlib/prelude_test.cem

# ============================================================================
# TEST FRAMEWORK (minimal)
# ============================================================================

: assert-eq ( A A String -- )
  # Assert two values are equal
  rot rot = not
  [ "Assertion failed: " swap concat panic ]
  [ drop ]
  if ;

: assert-true ( Bool String -- )
  # Assert value is true
  swap not
  [ "Assertion failed: " swap concat panic ]
  [ drop ]
  if ;

# ============================================================================
# OPTION TESTS
# ============================================================================

: test-is-some ( -- )
  Some(42) is-some true = "Some should be is-some" assert-true
  None is-some false = "None should not be is-some" assert-true ;

: test-is-none ( -- )
  None is-none true = "None should be is-none" assert-true
  Some(42) is-none false = "Some should not be is-none" assert-true ;

: test-unwrap-or-some ( -- )
  Some(42) 0 unwrap-or 42 = "unwrap-or Some should return value" assert-true ;

: test-unwrap-or-none ( -- )
  None 99 unwrap-or 99 = "unwrap-or None should return default" assert-true ;

: test-map-option-some ( -- )
  Some(5) [ 2 * ] map-option
  # Should be Some(10)
  dup is-some true = "map-option Some should return Some" assert-true
  10 unwrap-or 10 = "map-option Some should transform value" assert-true ;

: test-map-option-none ( -- )
  None [ 2 * ] map-option
  is-none true = "map-option None should return None" assert-true ;

: test-and-then-some ( -- )
  # Chain: Some(5) -> check if < 10 -> Some(5)
  Some(5) [ dup 10 < [ Some ] [ drop None ] if ] and-then
  is-some true = "and-then with passing predicate" assert-true ;

: test-and-then-none ( -- )
  None [ dup 10 < [ Some ] [ drop None ] if ] and-then
  is-none true = "and-then None should return None" assert-true ;

: test-filter-option-pass ( -- )
  Some(5) [ 10 < ] filter-option
  is-some true = "filter-option should keep passing value" assert-true ;

: test-filter-option-fail ( -- )
  Some(15) [ 10 < ] filter-option
  is-none true = "filter-option should reject failing value" assert-true ;

: test-option-or-first ( -- )
  Some(5) Some(10) option-or
  10 unwrap-or 5 = "option-or should prefer first Some" assert-true ;

: test-option-or-second ( -- )
  None Some(10) option-or
  0 unwrap-or 10 = "option-or should fallback to second" assert-true ;

# ============================================================================
# RESULT TESTS
# ============================================================================

: test-is-ok ( -- )
  Ok(42) is-ok true = "Ok should be is-ok" assert-true
  Err("bad") is-ok false = "Err should not be is-ok" assert-true ;

: test-is-err ( -- )
  Err("bad") is-err true = "Err should be is-err" assert-true
  Ok(42) is-err false = "Ok should not be is-err" assert-true ;

: test-unwrap-or-result-ok ( -- )
  Ok(42) 0 unwrap-or-result 42 = "unwrap-or-result Ok" assert-true ;

: test-unwrap-or-result-err ( -- )
  Err("bad") 99 unwrap-or-result 99 = "unwrap-or-result Err" assert-true ;

: test-map-ok-ok ( -- )
  Ok(5) [ 2 * ] map-ok
  dup is-ok true = "map-ok Ok should return Ok" assert-true
  0 unwrap-or-result 10 = "map-ok Ok should transform value" assert-true ;

: test-map-ok-err ( -- )
  Err("bad") [ 2 * ] map-ok
  is-err true = "map-ok Err should return Err" assert-true ;

: test-map-err-ok ( -- )
  Ok(42) [ "ERROR: " swap concat ] map-err
  is-ok true = "map-err Ok should return Ok" assert-true ;

: test-map-err-err ( -- )
  Err("bad") [ "ERROR: " swap concat ] map-err
  # Should be Err("ERROR: bad")
  is-err true = "map-err Err should return Err" assert-true ;

: test-bind-result-ok ( -- )
  Ok(5) [ dup 0 > [ Ok ] [ drop Err("negative") ] if ] bind-result
  is-ok true = "bind-result Ok with passing predicate" assert-true ;

: test-bind-result-err ( -- )
  Err("bad") [ dup 0 > [ Ok ] [ drop Err("negative") ] if ] bind-result
  is-err true = "bind-result Err should propagate" assert-true ;

# ============================================================================
# CONVERSION TESTS
# ============================================================================

: test-option-to-result-some ( -- )
  Some(42) "missing" option-to-result
  is-ok true = "option-to-result Some should be Ok" assert-true ;

: test-option-to-result-none ( -- )
  None "missing" option-to-result
  is-err true = "option-to-result None should be Err" assert-true ;

: test-result-to-option-ok ( -- )
  Ok(42) result-to-option
  is-some true = "result-to-option Ok should be Some" assert-true ;

: test-result-to-option-err ( -- )
  Err("bad") result-to-option
  is-none true = "result-to-option Err should be None" assert-true ;

# ============================================================================
# LIST TESTS
# ============================================================================

: test-is-empty-nil ( -- )
  Nil is-empty true = "Nil should be empty" assert-true ;

: test-is-empty-cons ( -- )
  Cons(1, Nil) is-empty false = "Cons should not be empty" assert-true ;

: test-head-cons ( -- )
  Cons(42, Nil) head
  is-some true = "head of Cons should be Some" assert-true ;

: test-head-nil ( -- )
  Nil head
  is-none true = "head of Nil should be None" assert-true ;

: test-tail-cons ( -- )
  Cons(1, Cons(2, Nil)) tail
  is-some true = "tail of Cons should be Some" assert-true ;

: test-tail-nil ( -- )
  Nil tail
  is-none true = "tail of Nil should be None" assert-true ;

: test-length-nil ( -- )
  Nil length 0 = "length of Nil" assert-true ;

: test-length-cons ( -- )
  Cons(1, Cons(2, Cons(3, Nil))) length
  3 = "length of 3-element list" assert-true ;

: test-map-list ( -- )
  # [1, 2, 3] map (*2) -> [2, 4, 6]
  Cons(1, Cons(2, Cons(3, Nil))) [ 2 * ] map
  # Verify it's still a list with same length
  length 3 = "map should preserve list length" assert-true ;

: test-filter-list ( -- )
  # [1, 2, 3, 4, 5] filter (< 3) -> [1, 2]
  Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Nil)))))
  [ 3 < ] filter
  length 2 = "filter should remove non-matching elements" assert-true ;

: test-fold-sum ( -- )
  # [1, 2, 3, 4] fold(0, +) -> 10
  Cons(1, Cons(2, Cons(3, Cons(4, Nil))))
  0 [ + ] fold
  10 = "fold should sum list" assert-true ;

: test-fold-product ( -- )
  # [2, 3, 4] fold(1, *) -> 24
  Cons(2, Cons(3, Cons(4, Nil)))
  1 [ * ] fold
  24 = "fold should compute product" assert-true ;

# ============================================================================
# RUN ALL TESTS
# ============================================================================

: run-all-tests ( -- )
  "Running prelude.cem tests..." println

  # Option tests
  test-is-some
  test-is-none
  test-unwrap-or-some
  test-unwrap-or-none
  test-map-option-some
  test-map-option-none
  test-and-then-some
  test-and-then-none
  test-filter-option-pass
  test-filter-option-fail
  test-option-or-first
  test-option-or-second

  # Result tests
  test-is-ok
  test-is-err
  test-unwrap-or-result-ok
  test-unwrap-or-result-err
  test-map-ok-ok
  test-map-ok-err
  test-map-err-ok
  test-map-err-err
  test-bind-result-ok
  test-bind-result-err

  # Conversion tests
  test-option-to-result-some
  test-option-to-result-none
  test-result-to-option-ok
  test-result-to-option-err

  # List tests
  test-is-empty-nil
  test-is-empty-cons
  test-head-cons
  test-head-nil
  test-tail-cons
  test-tail-nil
  test-length-nil
  test-length-cons
  test-map-list
  test-filter-list
  test-fold-sum
  test-fold-product

  "All prelude.cem tests passed!" println ;

# Entry point
: main ( -- )
  run-all-tests ;