Compare commits

...

26 Commits

Author SHA1 Message Date
0324724fb7 assertTrue > assertEqual(true,...)
Go / test (push) Successful in 5s
2025-11-06 11:15:15 -10:00
7e5a65e281 min-max coverage for effort (for now)
Go / test (push) Successful in 3s
2025-11-05 06:53:12 -10:00
ca2485b725 sprinkle in some PBT
Go / test (push) Successful in 3s
2025-11-05 06:43:24 -10:00
f816fdc912 implement read empty map 2025-11-05 06:43:14 -10:00
4ed27b8c0e add IsEmpty 2025-11-05 06:43:02 -10:00
656bd7dc27 add error for unknown chars 2025-11-05 06:28:56 -10:00
e906b007de add IsEmpty 2025-11-05 06:28:42 -10:00
465c959414 strengthen constraint
Go / test (push) Successful in 4s
2025-11-05 04:54:02 -10:00
ba00edc4db PBT all the things
Go / test (push) Successful in 5s
2025-11-05 04:50:07 -10:00
a6f8502fbb franz newline
Go / test (push) Successful in 3s
2025-11-04 18:39:55 -10:00
bfb8a2b0d8 add Cons and IListify all the things
Go / test (push) Successful in 3s
2025-11-04 18:39:07 -10:00
d573528f3b update IColl asbstractions
Go / test (push) Successful in 4s
2025-11-04 18:33:05 -10:00
0c9e3aa20b fix assert comment
Go / test (push) Successful in 3s
2025-11-04 18:11:25 -10:00
72da3ec820 add assert comments
Go / test (push) Successful in 3s
2025-11-04 18:06:17 -10:00
df56597332 rename test
Go / test (push) Successful in 3s
2025-11-04 17:51:07 -10:00
4deba4be78 clean up
Go / test (push) Successful in 3s
2025-11-04 17:50:32 -10:00
b7b7f79559 golf coverage
Go / test (push) Successful in 3s
2025-11-04 17:37:53 -10:00
6155074b77 never go full pbt ;)
Go / test (push) Successful in 3s
2025-11-04 17:36:18 -10:00
8dd6fd958d remove unused func
Go / test (push) Successful in 4s
2025-11-04 17:23:54 -10:00
3b4c206003 generate empty lists
Go / test (push) Successful in 3s
2025-11-04 17:21:52 -10:00
cfbddb3f39 test First()
Go / test (push) Successful in 4s
2025-11-04 17:19:45 -10:00
f5f7691de0 try PBT with coverage #2
Go / test (push) Successful in 3s
2025-11-04 17:17:00 -10:00
b9b88d8fa0 remove unreacahble code
Go / test (push) Successful in 4s
2025-11-04 15:16:50 -10:00
5ac5acd6d1 get rid of extra module level funcs
Go / test (push) Successful in 3s
2025-11-04 15:08:32 -10:00
413ab23904 add PBT for list len
Go / test (push) Successful in 11s
2025-11-04 14:58:08 -10:00
c044f452b5 add PBT infra 2025-11-04 14:50:47 -10:00
9 changed files with 172 additions and 88 deletions
+1
View File
@@ -1,2 +1,3 @@
tmp
.air.toml
*/testdata/*
+1
View File
@@ -7,4 +7,5 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.11.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
pgregory.net/rapid v1.2.0 // indirect
)
+2
View File
@@ -7,3 +7,5 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
+7 -5
View File
@@ -49,8 +49,13 @@ func (this *HashMap) Conj(key any, val any) *HashMap {
return Conj(this, key, val)
}
func String(this *HashMap) string {
if this == nil {
func (this *HashMap) IsEmpty() bool {
return len(this._map) == 0
}
func (this *HashMap) String() string {
if this.IsEmpty() {
return "{}"
}
var sb strings.Builder
@@ -63,8 +68,5 @@ func String(this *HashMap) string {
sb.WriteRune(' ')
}
return sb.String()[:sb.Len()-2] + "}"
}
func (this *HashMap) String() string {
return String(this)
}
+33 -50
View File
@@ -5,13 +5,21 @@ import (
"strings"
)
type IList interface {
type ICollection interface {
Conj(data any) IList
IsEmpty() bool
String() string
Len() int
}
type ISeq interface {
First() any
Rest() IList
String() string
IsEmpty() bool
}
type IList interface {
ISeq
ICollection
}
type EmptyList struct{}
@@ -27,43 +35,29 @@ func Empty() IList {
var emptyList = Empty()
func New(val any) *List {
func New(val any) IList {
this := new(List)
this.Value = val
this.next = emptyList
return this
}
func (this *List) Conj(val any) IList {
new_head := New(val)
new_head.next = this
return new_head
func Cons(val any, rest IList) IList {
this := new(List)
this.Value = val
this.next = rest
return this
}
func (this *List) Conj(val any) IList {
return Cons(val, this)
}
func (this *EmptyList) Conj(val any) IList {
return Conj(nil, val)
}
func Conj(this IList, val any) IList {
if this == nil {
return New(val)
} else {
return this.Conj(val)
}
}
func Rest(this IList) IList {
if this == nil {
return emptyList
}
return this.Rest()
return New(val)
}
func (this *List) Rest() IList {
if this == nil {
return emptyList
}
return this.next
}
@@ -71,25 +65,14 @@ func (this *EmptyList) Rest() IList {
return emptyList
}
func First(this *List) any {
if this == nil {
return nil
}
return this.Value
}
func (this *List) First() any {
return First(this)
return this.Value
}
func (this *EmptyList) First() any {
return nil
}
func IsEmpty(this IList) bool {
return this == nil || this.IsEmpty()
}
func (this *List) IsEmpty() bool {
return false
}
@@ -98,26 +81,26 @@ func (this *EmptyList) IsEmpty() bool {
return true
}
func String(this *List) string {
if IsEmpty(this) {
return "()"
}
func (this *List) String() string {
var sb strings.Builder
sb.WriteRune('(')
// Iterate and print elements
var e IList
for e = this; !IsEmpty(e); e = Rest(e) {
for e = this; !e.IsEmpty(); e = e.Rest() {
sb.WriteString(fmt.Sprint(e.First()))
sb.WriteRune(' ')
}
return sb.String()[:sb.Len()-1] + ")"
}
func (this *List) String() string {
return String(this)
}
func (this *EmptyList) String() string {
return "()"
}
func (this *EmptyList) Len() int {
return 0
}
func (this *List) Len() int {
return 1 + this.Rest().Len()
}
+78 -21
View File
@@ -1,34 +1,91 @@
package list
import (
"github.com/stretchr/testify/assert"
"regexp"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"pgregory.net/rapid"
)
func TestReadString(t *testing.T) {
assert.Equal(t, "()", Empty().String(), "should insert at head")
// intListGen returns a generator for either empty list or random ints
func intListGen() *rapid.Generator[IList] {
return rapid.OneOf(rapid.Custom(func(t *rapid.T) IList {
myList := Empty()
t.Repeat(map[string]func(*rapid.T){
"conj": func(t *rapid.T) {
myList = myList.Conj(rapid.Int().Draw(t, "el"))
},
})
return myList
}), rapid.Just(Empty()))
}
func TestReadConj(t *testing.T) {
var l IList
assert.Equal(t, "()", Empty().String(), "should insert at head")
l = New(5)
assert.Equal(t, "(5)", l.String(), "should insert at head")
l = Empty().Conj(4)
assert.Equal(t, "(4)", l.String(), "should insert at head")
// descSortedIntListGen returns a generator for list of at least several descending ints
func descSortedIntListGen() *rapid.Generator[IList] {
return rapid.Custom(func(t *rapid.T) IList {
myList := New(0)
i := 1
t.Repeat(map[string]func(*rapid.T){
"conj": func(t *rapid.T) {
myList = myList.Conj(i)
i += 1
},
})
return myList
}).Filter(func(l IList) bool {
return l.Len() > 1
})
}
func TestReadFirst(t *testing.T) {
l := Empty().Conj(5).Conj(6).Conj(7)
assert.Equal(t, 7, l.First(), "should return first element")
assert.Equal(t, nil, Empty().First(), "should return nil")
func TestFirstIsNilOnlyWhenEmpty(t *testing.T) {
assert.Equal(t, 1, New(3).Conj(2).Conj(1).First())
rapid.Check(t, func(t *rapid.T) {
myList := intListGen().Draw(t, "myList")
assert.Equal(t, myList.IsEmpty(), myList.First() == nil, "first is nil only when list is empty")
})
}
func TestReadRest(t *testing.T) {
l := Empty().Conj(5).Conj(6).Conj(7)
assert.Equal(t, "(6 5)", l.Rest().String(), "should return rest sublist")
assert.Equal(t, "(5)", l.Rest().Rest().String(), "should return rest sublist")
assert.Equal(t, "()", l.Rest().Rest().Rest().String(), "should return rest sublist")
assert.Equal(t, Empty(), Empty().Rest(), "should return rest sublist")
assert.Equal(t, Empty(), Rest(Empty().Rest()), "should return rest sublist")
func TestConj(t *testing.T) {
rapid.Check(t, func(t *rapid.T) {
myList := intListGen().Draw(t, "myList")
newElem := rapid.Int().Draw(t, "newElem")
myList2 := myList.Conj(newElem)
assert.Equal(t, myList.Len()+1, myList2.Len(), "conj increases length")
assert.Equal(t, newElem, myList2.First(), "first after conj returns inserted number")
})
}
func TestRest(t *testing.T) {
rapid.Check(t, func(t *rapid.T) {
myList := intListGen().Draw(t, "myList")
if myList.IsEmpty() {
assert.Equal(t, myList.Len(), myList.Rest().Len(), "rest does not change length for empty list")
} else {
assert.Equal(t, myList.Len()-1, myList.Rest().Len(), "rest decreases length of non-empty list")
}
})
rapid.Check(t, func(t *rapid.T) {
myList := descSortedIntListGen().Draw(t, "myList")
assert.Greater(t, myList.First(), myList.Rest().First(), "rest decreases value in list head")
})
}
func TestStringifyIntList(t *testing.T) {
assert.Equal(t, "(1 2 3)", New(3).Conj(2).Conj(1).String())
rapid.Check(t, func(t *rapid.T) {
myList := intListGen().Draw(t, "myList")
s := myList.String()
r := regexp.MustCompile(`^\([\d\s-]*\)$`)
assert.True(t, r.Match([]byte(s)), s+" looks like a stringified list")
if !myList.IsEmpty() {
assert.Equal(t, myList.Len(), strings.Count(s, " ")+1, "number of spaces in string should match count of elements")
} else {
assert.Equal(t, 0, strings.Count(s, " "), "no spaces in string for empty list")
}
})
}
+7 -1
View File
@@ -1,6 +1,7 @@
package read
import (
"fmt"
"mal-go/hash_map"
"mal-go/symbol"
"mal-go/vector"
@@ -47,6 +48,8 @@ func readForm(s string, pos int, ns string) (any, any, int) {
res, err, pos = readVector(s, pos, ns)
case '(':
res, err, pos = readList(s, pos, ns)
default:
return nil, "unexpected char: '" + fmt.Sprint(c) + "'", pos
}
}
return res, err, pos
@@ -54,7 +57,10 @@ func readForm(s string, pos int, ns string) (any, any, int) {
func readMap(s string, pos int, ns string) (any, any, int) {
m := hash_map.New()
return m, "unimplemented", pos
if s[pos+1] == '}' {
return m, nil, pos + 2
}
return nil, "unimplemented", pos
}
func readList(s string, pos int, ns string) (any, any, int) {
+36 -1
View File
@@ -1,10 +1,15 @@
package read
import (
"github.com/stretchr/testify/assert"
"fmt"
"mal-go/hash_map"
"mal-go/list"
"mal-go/symbol"
"mal-go/vector"
"testing"
"github.com/stretchr/testify/assert"
"pgregory.net/rapid"
)
func TestReadInt(t *testing.T) {
@@ -47,3 +52,33 @@ func TestReadList(t *testing.T) {
assert.Equal(t, nil, err, "should read without error")
assert.Equal(t, len(input), pos, "should read the whole string")
}
func StringifiedFormGen() *rapid.Generator[string] {
return rapid.OneOf(rapid.Custom(func(t *rapid.T) string {
myList := list.Empty()
i := 0
t.Repeat(map[string]func(*rapid.T){
"conj": func(t *rapid.T) {
if i < 100 {
myList = myList.Conj(rapid.Int().Draw(t, "el"))
}
},
})
return myList.String()
}),
rapid.Just(list.Empty().String()),
rapid.Just(hash_map.New().String()),
rapid.Just(vector.New().String()),
rapid.Custom(func(t *rapid.T) string {
return fmt.Sprint(rapid.Int().Draw(t, "i"))
}),
)
}
func TestRead(t *testing.T) {
rapid.Check(t, func(t *rapid.T) {
input := StringifiedFormGen().Draw(t, "input")
_, err := ReadString(input)
assert.Nil(t, err, err)
})
}
+7 -10
View File
@@ -21,7 +21,7 @@ func (this *Vector) Init() *Vector {
return this
}
func Conj(this *Vector, data any) *Vector {
func (this *Vector) Conj(data any) *Vector {
newVec := New()
for _, el := range this._slice {
newVec._slice = append(newVec._slice, el)
@@ -30,12 +30,8 @@ func Conj(this *Vector, data any) *Vector {
return newVec
}
func (this *Vector) Conj(data any) *Vector {
return Conj(this, data)
}
func String(this *Vector) string {
if this == nil {
func (this *Vector) String() string {
if this.IsEmpty() {
return "[]"
}
var sb strings.Builder
@@ -45,16 +41,17 @@ func String(this *Vector) string {
sb.WriteRune(' ')
}
return sb.String()[:sb.Len()-1] + "]"
}
func (this *Vector) String() string {
return String(this)
func (this *Vector) IsEmpty() bool {
return len(this._slice) == 0
}
func ToList(this *Vector) list.IList {
l := list.Empty()
for i := len(this._slice) - 1; i >= 0; i-- {
l = list.Conj(l, this._slice[i])
l = l.Conj(this._slice[i])
}
return l
}