Compare commits
26 Commits
645fd64752
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 0324724fb7 | |||
| 7e5a65e281 | |||
| ca2485b725 | |||
| f816fdc912 | |||
| 4ed27b8c0e | |||
| 656bd7dc27 | |||
| e906b007de | |||
| 465c959414 | |||
| ba00edc4db | |||
| a6f8502fbb | |||
| bfb8a2b0d8 | |||
| d573528f3b | |||
| 0c9e3aa20b | |||
| 72da3ec820 | |||
| df56597332 | |||
| 4deba4be78 | |||
| b7b7f79559 | |||
| 6155074b77 | |||
| 8dd6fd958d | |||
| 3b4c206003 | |||
| cfbddb3f39 | |||
| f5f7691de0 | |||
| b9b88d8fa0 | |||
| 5ac5acd6d1 | |||
| 413ab23904 | |||
| c044f452b5 |
@@ -1,2 +1,3 @@
|
||||
tmp
|
||||
.air.toml
|
||||
*/testdata/*
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user