From 71ee6f3d04c0c8dbc01ca31d916d4e5dc46ce3fd Mon Sep 17 00:00:00 2001
From: milarin <milaring@proton.me>
Date: Fri, 30 Jun 2023 20:43:23 +0200
Subject: [PATCH] initial commit

---
 errors.go      |  10 +++++
 go.mod         |   8 ++++
 go.sum         |   4 ++
 reader.go      | 119 +++++++++++++++++++++++++++++++++++++++++++++++++
 reader_test.go |  13 ++++++
 runefunc.go    |  38 ++++++++++++++++
 utils.go       |  33 ++++++++++++++
 7 files changed, 225 insertions(+)
 create mode 100644 errors.go
 create mode 100644 go.mod
 create mode 100644 go.sum
 create mode 100644 reader.go
 create mode 100644 reader_test.go
 create mode 100644 runefunc.go
 create mode 100644 utils.go

diff --git a/errors.go b/errors.go
new file mode 100644
index 0000000..a5a653a
--- /dev/null
+++ b/errors.go
@@ -0,0 +1,10 @@
+package anyreader
+
+import (
+	"git.milar.in/milarin/adverr"
+)
+
+var (
+	ErrNothingToUnread = adverr.NewErrTmpl("ErrNothingToUnread", "Unreading failed because there wasn't any Read yet")
+	ErrNoMoreValues    = adverr.NewErrTmpl("ErrNoMoreValues", "source does not have any more values")
+)
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..e591a7f
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,8 @@
+module git.milar.in/milarin/anyreader
+
+go 1.20
+
+require (
+	git.milar.in/milarin/adverr v1.1.0
+	git.milar.in/milarin/ds v0.0.2
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..bb49ff3
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,4 @@
+git.milar.in/milarin/adverr v1.1.0 h1:jD9WnOvs40lfMhvqQ7cllOaRJNBMWr1f07/s9jAadp0=
+git.milar.in/milarin/adverr v1.1.0/go.mod h1:joU9sBb7ySyNv4SpTXB0Z4o1mjXsArBw4N27wjgzj9E=
+git.milar.in/milarin/ds v0.0.2 h1:vCA3mDxZUNfvHpzrdz7SeBUKiPn74NTopo915IUG7I0=
+git.milar.in/milarin/ds v0.0.2/go.mod h1:HJK7QERcRvV9j7xzEocrKUtW+1q4JB1Ly4Bj54chfwI=
diff --git a/reader.go b/reader.go
new file mode 100644
index 0000000..85888e8
--- /dev/null
+++ b/reader.go
@@ -0,0 +1,119 @@
+package anyreader
+
+import "git.milar.in/milarin/ds"
+
+type Reader[T any] struct {
+	buf ds.Stack[T]
+	src func() (T, error)
+}
+
+func NewReaderFromSlice[T any](s []T) *Reader[T] {
+	return NewReaderFromErrorFunc(sliceToFunc(s))
+}
+
+func NewReaderFromFunc[T any](src func() T) *Reader[T] {
+	return NewReaderFromErrorFunc(func() (T, error) { return src(), nil })
+}
+
+func NewReaderFromErrorFunc[T any](src func() (T, error)) *Reader[T] {
+	return &Reader[T]{
+		src: src,
+		buf: ds.NewArrayStack[T](),
+	}
+}
+
+func (r *Reader[T]) Read() (T, error) {
+	v, err := r.src()
+	if err == nil {
+		r.buf.Push(v)
+	}
+	return v, err
+}
+
+func (r *Reader[T]) Unread() error {
+	if r.buf.Empty() {
+		return ErrNothingToUnread.New()
+	}
+
+	v := r.buf.Pop()
+
+	returned := false
+	oldSrc := r.src
+	r.src = func() (T, error) {
+		if returned {
+			return oldSrc()
+		}
+
+		returned = true
+		return v, nil
+	}
+
+	return nil
+}
+
+func (r *Reader[T]) UnreadN(n int) error {
+	for i := 0; i < n; i++ {
+		err := r.Unread()
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (r *Reader[T]) Peek() (T, error) {
+	value, err := r.Read()
+	if err != nil {
+		return *new(T), err
+	}
+
+	if err := r.Unread(); err != nil {
+		return *new(T), err
+	}
+
+	return value, nil
+}
+
+func (r *Reader[T]) ReadWhile(f ...func(T) bool) ([]T, error) {
+	res := make([]T, 0, 10)
+
+	var value T
+	var err error
+	for value, err = r.Read(); err == nil && findFirstTrue(value, f); value, err = r.Read() {
+		res = append(res, value)
+	}
+
+	return res, err
+}
+
+func (r *Reader[T]) ReadUntil(f ...func(T) bool) ([]T, error) {
+	return r.ReadWhile(func(v T) bool { return !findFirstTrue(v, f) })
+}
+
+func (r *Reader[T]) SkipUntil(f ...func(T) bool) error {
+	_, err := r.ReadUntil(f...)
+	if err != nil {
+		return err
+	}
+	return r.Unread()
+}
+
+func (r *Reader[T]) SkipWhile(f ...func(T) bool) error {
+	_, err := r.ReadWhile(f...)
+	if err != nil {
+		return err
+	}
+	return r.Unread()
+}
+
+func (r *Reader[T]) Expect(f ...func(T) bool) (bool, error) {
+	value, err := r.Read()
+	if err != nil {
+		return false, err
+	}
+	return findFirstTrue(value, f), nil
+}
+
+func (r *Reader[T]) Commit() {
+	r.buf.Clear()
+}
diff --git a/reader_test.go b/reader_test.go
new file mode 100644
index 0000000..805a60f
--- /dev/null
+++ b/reader_test.go
@@ -0,0 +1,13 @@
+package anyreader
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestReader(t *testing.T) {
+	r := NewReaderFromSlice([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
+	fmt.Println(r.Read())
+	r.Unread()
+	fmt.Println(r.Read())
+}
diff --git a/runefunc.go b/runefunc.go
new file mode 100644
index 0000000..1b4cfe7
--- /dev/null
+++ b/runefunc.go
@@ -0,0 +1,38 @@
+package anyreader
+
+import "git.milar.in/milarin/ds"
+
+func And[T any](f ...func(T) bool) func(T) bool {
+	return func(value T) bool {
+		return findFirstFalse(value, f)
+	}
+}
+
+func Or[T any](f ...func(T) bool) func(T) bool {
+	return func(value T) bool {
+		return findFirstTrue(value, f)
+	}
+}
+
+func Not[T any](f func(T) bool) func(T) bool {
+	return func(value T) bool {
+		return !f(value)
+	}
+}
+
+func Is[T comparable](value T) func(T) bool {
+	return func(v T) bool {
+		return value == v
+	}
+}
+
+func OneOf[T comparable](values ...T) func(T) bool {
+	m := ds.NewSet[T]()
+	for _, value := range values {
+		m.Add(value)
+	}
+
+	return func(v T) bool {
+		return m.Has(v)
+	}
+}
diff --git a/utils.go b/utils.go
new file mode 100644
index 0000000..22458a8
--- /dev/null
+++ b/utils.go
@@ -0,0 +1,33 @@
+package anyreader
+
+func sliceToFunc[T any](s []T) func() (T, error) {
+	i := 0
+	return func() (T, error) {
+		c := i
+
+		if c >= len(s) {
+			return *new(T), ErrNoMoreValues.New()
+		}
+
+		i++
+		return s[c], nil
+	}
+}
+
+func findFirstTrue[T any](value T, functions []func(T) bool) bool {
+	for _, f := range functions {
+		if f(value) {
+			return true
+		}
+	}
+	return false
+}
+
+func findFirstFalse[T any](rn T, functions []func(T) bool) bool {
+	for _, f := range functions {
+		if !f(rn) {
+			return false
+		}
+	}
+	return true
+}