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 tmp
.air.toml .air.toml
*/testdata/*
+1
View File
@@ -7,4 +7,5 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.11.1 // indirect github.com/stretchr/testify v1.11.1 // indirect
gopkg.in/yaml.v3 v3.0.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/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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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) 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)
} }
+33 -50
View File
@@ -5,13 +5,21 @@ import (
"strings" "strings"
) )
type ICollection interface {
type IList 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
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,26 @@ 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 "()"
} }
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 package list
import ( import (
"github.com/stretchr/testify/assert" "regexp"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert"
"pgregory.net/rapid"
) )
func TestReadString(t *testing.T) { // intListGen returns a generator for either empty list or random ints
assert.Equal(t, "()", Empty().String(), "should insert at head") 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) { // descSortedIntListGen returns a generator for list of at least several descending ints
var l IList func descSortedIntListGen() *rapid.Generator[IList] {
assert.Equal(t, "()", Empty().String(), "should insert at head") return rapid.Custom(func(t *rapid.T) IList {
l = New(5) myList := New(0)
assert.Equal(t, "(5)", l.String(), "should insert at head") i := 1
l = Empty().Conj(4) t.Repeat(map[string]func(*rapid.T){
assert.Equal(t, "(4)", l.String(), "should insert at head") "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) { func TestFirstIsNilOnlyWhenEmpty(t *testing.T) {
l := Empty().Conj(5).Conj(6).Conj(7) assert.Equal(t, 1, New(3).Conj(2).Conj(1).First())
assert.Equal(t, 7, l.First(), "should return first element")
assert.Equal(t, nil, Empty().First(), "should return nil") 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) { func TestConj(t *testing.T) {
l := Empty().Conj(5).Conj(6).Conj(7) rapid.Check(t, func(t *rapid.T) {
assert.Equal(t, "(6 5)", l.Rest().String(), "should return rest sublist") myList := intListGen().Draw(t, "myList")
assert.Equal(t, "(5)", l.Rest().Rest().String(), "should return rest sublist") newElem := rapid.Int().Draw(t, "newElem")
assert.Equal(t, "()", l.Rest().Rest().Rest().String(), "should return rest sublist") myList2 := myList.Conj(newElem)
assert.Equal(t, Empty(), Empty().Rest(), "should return rest sublist") assert.Equal(t, myList.Len()+1, myList2.Len(), "conj increases length")
assert.Equal(t, Empty(), Rest(Empty().Rest()), "should return rest sublist") 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
} }