mal-go/read/read.go
2025-11-05 06:43:14 -10:00

121 lines
2.4 KiB
Go

package read
import (
"fmt"
"mal-go/hash_map"
"mal-go/symbol"
"mal-go/vector"
"strconv"
"strings"
)
func ReadString(s string) (any, any) {
res, err, _ := readForm(s, 0, "user")
return res, err
}
func isDigitChar(c byte) bool {
return c >= '0' && c <= '9'
}
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 == '*'
}
func readForm(s string, pos int, ns string) (any, any, int) {
if pos >= len(s) {
return nil, "unterminated input", pos
}
c := s[pos]
var res, err any
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)
default:
return nil, "unexpected char: '" + fmt.Sprint(c) + "'", pos
}
}
return res, err, pos
}
func readMap(s string, pos int, ns string) (any, any, int) {
m := hash_map.New()
if s[pos+1] == '}' {
return m, nil, pos + 2
}
return nil, "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) {
startingPos := pos
c := s[pos]
for isSymbolChar(c) {
pos += 1
if pos >= len(s) {
break
}
c = s[pos]
}
sym := s[startingPos:pos]
symNs := ns
if strings.Contains(sym, "/") {
splitIdx := strings.Index(s, "/")
symNs = s[startingPos:splitIdx]
sym = s[splitIdx+1 : pos]
}
return symbol.Intern(symNs, sym), nil, pos
}