From 5200dfde000819287b2594d4db864d1092ec9a41 Mon Sep 17 00:00:00 2001 From: ajet Date: Tue, 4 Nov 2025 07:04:49 -1000 Subject: [PATCH] init reader for lists --- list/list.go | 10 ++++++ read/read.go | 89 ++++++++++++++++++++++++++++++++++++++--------- read/read_test.go | 26 ++++++++++++++ vector/vector.go | 14 ++++++++ 4 files changed, 123 insertions(+), 16 deletions(-) diff --git a/list/list.go b/list/list.go index 0b7f7a6..9fc582d 100644 --- a/list/list.go +++ b/list/list.go @@ -29,6 +29,9 @@ func (this *List) Conj(val any) *List { } func Conj(this *List, val any) *List { + if this == nil { + return New(val) + } new_head := New(val) new_head.next = this return new_head @@ -46,6 +49,9 @@ func (this *List) Rest() *List { } func First(this *List) any { + if this == nil { + return nil + } return this.Value } @@ -53,6 +59,10 @@ func (this *List) First() any { return First(this) } +func IsEmpty(this *List) bool { + return this == nil +} + func String(this *List) string { if this == nil { return "{}" diff --git a/read/read.go b/read/read.go index be42e7f..719b351 100644 --- a/read/read.go +++ b/read/read.go @@ -1,39 +1,96 @@ package read import ( - "mal-go/list" + "mal-go/hash_map" "mal-go/symbol" + "mal-go/vector" + "strconv" "strings" ) func ReadString(s string) (any, any) { - res, err, _ := readInternal(s, 0, nil, "user") + res, err, _ := readForm(s, 0, "user") return res, err } -func isSymbolChar(c byte) bool { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '-' || c == '/' || c == '\'' +func isDigitChar(c byte) bool { + 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) { - return ctx, nil, pos + return nil, "unterminated input", pos } c := s[pos] var res, err any - if isSymbolChar(c) { - res, err, pos2 := readSymbol(s, pos, ns) - return res, err, pos2 + if isSpaceChar(c) { + res, err, pos = readForm(s, pos+1, ns) + } 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 '{': - // res, err, _ = readInternal(s, pos+1, list.Conj(ctx, hash_map.New()), ns) - // case '[': - // case '(': - // } - return res, err, 0 + return res, err, pos +} + +func readMap(s string, pos int, ns string) (any, any, int) { + m := hash_map.New() + 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) { diff --git a/read/read_test.go b/read/read_test.go index 69bfcdb..9c54b81 100644 --- a/read/read_test.go +++ b/read/read_test.go @@ -2,10 +2,19 @@ package read import ( "github.com/stretchr/testify/assert" + "mal-go/list" "mal-go/symbol" "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) { input := "foo" 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, 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") +} diff --git a/vector/vector.go b/vector/vector.go index 09afa19..9ea0499 100644 --- a/vector/vector.go +++ b/vector/vector.go @@ -1,6 +1,7 @@ package vector import ( + "mal-go/list" "mal-go/utils" "strings" ) @@ -49,3 +50,16 @@ func String(this *Vector) string { func (this *Vector) String() string { 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) +}