init reader for lists

This commit is contained in:
Adam Jeniski 2025-11-04 07:04:49 -10:00
parent f279d7c90a
commit 5200dfde00
4 changed files with 123 additions and 16 deletions

View File

@ -29,6 +29,9 @@ func (this *List) Conj(val any) *List {
} }
func Conj(this *List, val any) *List { func Conj(this *List, val any) *List {
if this == nil {
return New(val)
}
new_head := New(val) new_head := New(val)
new_head.next = this new_head.next = this
return new_head return new_head
@ -46,6 +49,9 @@ func (this *List) Rest() *List {
} }
func First(this *List) any { func First(this *List) any {
if this == nil {
return nil
}
return this.Value return this.Value
} }
@ -53,6 +59,10 @@ func (this *List) First() any {
return First(this) return First(this)
} }
func IsEmpty(this *List) bool {
return this == nil
}
func String(this *List) string { func String(this *List) string {
if this == nil { if this == nil {
return "{}" return "{}"

View File

@ -1,39 +1,96 @@
package read package read
import ( import (
"mal-go/list" "mal-go/hash_map"
"mal-go/symbol" "mal-go/symbol"
"mal-go/vector"
"strconv"
"strings" "strings"
) )
func ReadString(s string) (any, any) { func ReadString(s string) (any, any) {
res, err, _ := readInternal(s, 0, nil, "user") res, err, _ := readForm(s, 0, "user")
return res, err return res, err
} }
func isSymbolChar(c byte) bool { func isDigitChar(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '-' || c == '/' || c == '\'' return c >= '0' && c <= '9'
} }
func readInternal(s string, pos int, ctx *list.List, ns string) (any, any, int) { func isSpaceChar(c byte) bool {
return c == ' ' || c == '\n' || c == ','
}
func isSymbolChar(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '-' || c == '/' || c == '\'' || c == '+' || c == '-' || c == '*'
}
func readForm(s string, pos int, ns string) (any, any, int) {
if pos >= len(s) { if pos >= len(s) {
return ctx, nil, pos return nil, "unterminated input", pos
} }
c := s[pos] c := s[pos]
var res, err any var res, err any
if isSymbolChar(c) { if isSpaceChar(c) {
res, err, pos2 := readSymbol(s, pos, ns) res, err, pos = readForm(s, pos+1, ns)
return res, err, pos2 } else if isDigitChar(c) {
res, err, pos = readInt(s, pos, ns)
} else if isSymbolChar(c) {
res, err, pos = readSymbol(s, pos, ns)
} else {
switch c {
case '{':
res, err, pos = readMap(s, pos, ns)
case '[':
res, err, pos = readVector(s, pos, ns)
case '(':
res, err, pos = readList(s, pos, ns)
} }
// switch c { }
// case '{': return res, err, pos
// res, err, _ = readInternal(s, pos+1, list.Conj(ctx, hash_map.New()), ns) }
// case '[':
// case '(': func readMap(s string, pos int, ns string) (any, any, int) {
// } m := hash_map.New()
return res, err, 0 return m, "unimplemented", pos
}
func readList(s string, pos int, ns string) (any, any, int) {
pos += 1
forms := vector.New()
for rune(s[pos]) != ')' {
res, _, pos2 := readForm(s, pos, ns)
forms = forms.Conj(res)
pos = pos2
}
return forms.ToList(), nil, pos + 1
}
func readVector(s string, pos int, ns string) (any, any, int) {
pos += 1
forms := vector.New()
for rune(s[pos]) != ']' {
res, _, pos2 := readForm(s, pos, ns)
forms = forms.Conj(res)
pos = pos2
}
return forms, nil, pos + 1
}
func readInt(s string, pos int, ns string) (any, any, int) {
startingPos := pos
c := s[pos]
for isDigitChar(c) {
pos += 1
if pos >= len(s) {
break
}
c = s[pos]
}
res, _ := strconv.Atoi(s[startingPos:pos])
return res, nil, pos
} }
func readSymbol(s string, pos int, ns string) (any, any, int) { func readSymbol(s string, pos int, ns string) (any, any, int) {

View File

@ -2,10 +2,19 @@ package read
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"mal-go/list"
"mal-go/symbol" "mal-go/symbol"
"testing" "testing"
) )
func TestReadInt(t *testing.T) {
input := "2"
sym, err, end_pos := readInt(input, 0, "user")
assert.Equal(t, 2, sym, "should read ")
assert.Equal(t, nil, err, "should read without error")
assert.Equal(t, len(input), end_pos, "should read the whole string")
}
func TestReadSymbol(t *testing.T) { func TestReadSymbol(t *testing.T) {
input := "foo" input := "foo"
sym, err, end_pos := readSymbol(input, 0, "user") sym, err, end_pos := readSymbol(input, 0, "user")
@ -21,3 +30,20 @@ func TestReadSymbolWithNamespace(t *testing.T) {
assert.Equal(t, err, nil, "should read without error") assert.Equal(t, err, nil, "should read without error")
assert.Equal(t, end_pos, len(input), "should read the whole string") assert.Equal(t, end_pos, len(input), "should read the whole string")
} }
func TestReadListElem(t *testing.T) {
input := "(1223)"
f, err, pos := readForm(input, 1, "user")
assert.Equal(t, f, 1223, "should num list")
assert.Equal(t, err, nil, "should read without error")
assert.Equal(t, pos, len(input)-1, "should read the whole number")
assert.Equal(t, rune(input[pos]), ')', "should end pointing to ending list char")
}
func TestReadList(t *testing.T) {
input := "(1 2 3)"
l, err, pos := readList(input, 0, "user")
assert.Equal(t, list.New(3).Conj(2).Conj(1), l, "should read list")
assert.Equal(t, nil, err, "should read without error")
assert.Equal(t, len(input), pos, "should read the whole string")
}

View File

@ -1,6 +1,7 @@
package vector package vector
import ( import (
"mal-go/list"
"mal-go/utils" "mal-go/utils"
"strings" "strings"
) )
@ -49,3 +50,16 @@ func String(this *Vector) string {
func (this *Vector) String() string { func (this *Vector) String() string {
return String(this) return String(this)
} }
func ToList(this *Vector) *list.List {
var l *list.List
l = nil
for i := len(this._slice) - 1; i >= 0; i-- {
l = list.Conj(l, this._slice[i])
}
return l
}
func (this *Vector) ToList() *list.List {
return ToList(this)
}