Compare commits

..

24 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
6 changed files with 158 additions and 116 deletions
+7 -5
View File
@@ -49,8 +49,13 @@ func (this *HashMap) Conj(key any, val any) *HashMap {
return Conj(this, key, val) return Conj(this, key, val)
} }
func String(this *HashMap) string { func (this *HashMap) IsEmpty() bool {
if this == nil { return len(this._map) == 0
}
func (this *HashMap) String() string {
if this.IsEmpty() {
return "{}" return "{}"
} }
var sb strings.Builder var sb strings.Builder
@@ -63,8 +68,5 @@ func String(this *HashMap) string {
sb.WriteRune(' ') sb.WriteRune(' ')
} }
return sb.String()[:sb.Len()-2] + "}" return sb.String()[:sb.Len()-2] + "}"
}
func (this *HashMap) String() string {
return String(this)
} }
+25 -50
View File
@@ -5,13 +5,21 @@ import (
"strings" "strings"
) )
type IList interface { type ICollection interface {
Conj(data any) IList Conj(data any) IList
IsEmpty() bool
String() string
Len() int
}
type ISeq interface {
First() any First() any
Rest() IList Rest() IList
String() string }
IsEmpty() bool
Len() int type IList interface {
ISeq
ICollection
} }
type EmptyList struct{} type EmptyList struct{}
@@ -27,43 +35,29 @@ func Empty() IList {
var emptyList = Empty() var emptyList = Empty()
func New(val any) *List { func New(val any) IList {
this := new(List) this := new(List)
this.Value = val this.Value = val
this.next = emptyList this.next = emptyList
return this return this
} }
func (this *List) Conj(val any) IList { func Cons(val any, rest IList) IList {
new_head := New(val) this := new(List)
new_head.next = this this.Value = val
return new_head this.next = rest
return this
}
func (this *List) Conj(val any) IList {
return Cons(val, this)
} }
func (this *EmptyList) Conj(val any) IList { func (this *EmptyList) Conj(val any) IList {
return Conj(nil, val) return New(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()
} }
func (this *List) Rest() IList { func (this *List) Rest() IList {
if this == nil {
return emptyList
}
return this.next return this.next
} }
@@ -71,25 +65,14 @@ func (this *EmptyList) Rest() IList {
return emptyList return emptyList
} }
func First(this *List) any {
if this == nil {
return nil
}
return this.Value
}
func (this *List) First() any { func (this *List) First() any {
return First(this) return this.Value
} }
func (this *EmptyList) First() any { func (this *EmptyList) First() any {
return nil return nil
} }
func IsEmpty(this IList) bool {
return this == nil || this.IsEmpty()
}
func (this *List) IsEmpty() bool { func (this *List) IsEmpty() bool {
return false return false
} }
@@ -98,26 +81,18 @@ func (this *EmptyList) IsEmpty() bool {
return true return true
} }
func String(this *List) string { func (this *List) String() string {
if IsEmpty(this) {
return "()"
}
var sb strings.Builder var sb strings.Builder
sb.WriteRune('(') sb.WriteRune('(')
// Iterate and print elements // Iterate and print elements
var e IList 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.WriteString(fmt.Sprint(e.First()))
sb.WriteRune(' ') sb.WriteRune(' ')
} }
return sb.String()[:sb.Len()-1] + ")" return sb.String()[:sb.Len()-1] + ")"
} }
func (this *List) String() string {
return String(this)
}
func (this *EmptyList) String() string { func (this *EmptyList) String() string {
return "()" return "()"
} }
+76 -49
View File
@@ -1,64 +1,91 @@
package list package list
import ( import (
"regexp"
"strings"
"testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"pgregory.net/rapid" "pgregory.net/rapid"
"testing"
) )
func TestListLen(t *testing.T) { // intListGen returns a generator for either empty list or random ints
assert.Equal(t, 0, Empty().Len(), "should insert at head") func intListGen() *rapid.Generator[IList] {
assert.Equal(t, 1, Empty().Conj(1).Len(), "should insert at head") return rapid.OneOf(rapid.Custom(func(t *rapid.T) IList {
assert.Equal(t, 2, Empty().Conj(1).Conj(2).Len(), "should insert at head") myList := Empty()
}
func TestListString(t *testing.T) {
assert.Equal(t, "()", Empty().String(), "should insert at head")
}
func TestListConj(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")
}
func TestListFirst(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 TestListRest(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")
}
// generative tests
func ListLengthProperty(t *testing.T) {
myList := Empty()
expectedSized := 0
rapid.Check(t, func(t *rapid.T) {
t.Repeat(map[string]func(*rapid.T){ t.Repeat(map[string]func(*rapid.T){
"conj": func(t *rapid.T) { "conj": func(t *rapid.T) {
expectedSized += 1
myList = myList.Conj(rapid.Int().Draw(t, "el")) myList = myList.Conj(rapid.Int().Draw(t, "el"))
}, },
"rest": func(t *rapid.T) { })
if !myList.IsEmpty() { return myList
expectedSized -= 1 }), rapid.Just(Empty()))
} }
myList = myList.Rest()
}, // descSortedIntListGen returns a generator for list of at least several descending ints
"": func(t *rapid.T) { func descSortedIntListGen() *rapid.Generator[IList] {
assert.Equal(t, myList.Len(), expectedSized, "must be equal") 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 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 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 package read
import ( import (
"fmt"
"mal-go/hash_map" "mal-go/hash_map"
"mal-go/symbol" "mal-go/symbol"
"mal-go/vector" "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) res, err, pos = readVector(s, pos, ns)
case '(': case '(':
res, err, pos = readList(s, pos, ns) res, err, pos = readList(s, pos, ns)
default:
return nil, "unexpected char: '" + fmt.Sprint(c) + "'", pos
} }
} }
return res, err, 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) { func readMap(s string, pos int, ns string) (any, any, int) {
m := hash_map.New() 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) { func readList(s string, pos int, ns string) (any, any, int) {
+36 -1
View File
@@ -1,10 +1,15 @@
package read package read
import ( import (
"github.com/stretchr/testify/assert" "fmt"
"mal-go/hash_map"
"mal-go/list" "mal-go/list"
"mal-go/symbol" "mal-go/symbol"
"mal-go/vector"
"testing" "testing"
"github.com/stretchr/testify/assert"
"pgregory.net/rapid"
) )
func TestReadInt(t *testing.T) { 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, nil, err, "should read without error")
assert.Equal(t, len(input), pos, "should read the whole string") 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 return this
} }
func Conj(this *Vector, data any) *Vector { func (this *Vector) Conj(data any) *Vector {
newVec := New() newVec := New()
for _, el := range this._slice { for _, el := range this._slice {
newVec._slice = append(newVec._slice, el) newVec._slice = append(newVec._slice, el)
@@ -30,12 +30,8 @@ func Conj(this *Vector, data any) *Vector {
return newVec return newVec
} }
func (this *Vector) Conj(data any) *Vector { func (this *Vector) String() string {
return Conj(this, data) if this.IsEmpty() {
}
func String(this *Vector) string {
if this == nil {
return "[]" return "[]"
} }
var sb strings.Builder var sb strings.Builder
@@ -45,16 +41,17 @@ func String(this *Vector) string {
sb.WriteRune(' ') sb.WriteRune(' ')
} }
return sb.String()[:sb.Len()-1] + "]" return sb.String()[:sb.Len()-1] + "]"
} }
func (this *Vector) String() string { func (this *Vector) IsEmpty() bool {
return String(this) return len(this._slice) == 0
} }
func ToList(this *Vector) list.IList { func ToList(this *Vector) list.IList {
l := list.Empty() l := list.Empty()
for i := len(this._slice) - 1; i >= 0; i-- { for i := len(this._slice) - 1; i >= 0; i-- {
l = list.Conj(l, this._slice[i]) l = l.Conj(this._slice[i])
} }
return l return l
} }