327 lines
8.3 KiB
Go
327 lines
8.3 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"strconv"
|
|
)
|
|
|
|
type CharComp interface {
|
|
includes(byte) bool
|
|
}
|
|
|
|
//A single character
|
|
type Char byte
|
|
func (c Char) includes(comp byte) bool { return byte(c) == comp }
|
|
|
|
//The starting and ending chars (inclusive) in a range of chars
|
|
type CharRange struct {
|
|
start byte
|
|
end byte
|
|
}
|
|
func (cr CharRange) includes (comp byte) bool {
|
|
return byte(cr.start) <= comp && byte(cr.end) >= comp
|
|
}
|
|
|
|
//A set of char comparables. Could contain other CharSets
|
|
type CharSet []CharComp
|
|
func (cs CharSet) includes (comp byte) bool {
|
|
for _,c := range cs {
|
|
if c.includes(comp) { return true }
|
|
}
|
|
return false
|
|
}
|
|
|
|
var ASCII = CharRange{0,127}
|
|
|
|
var Whitespace = CharSet{
|
|
CharRange{9,13}, //tab -> carriage return
|
|
Char(' '),
|
|
}
|
|
|
|
var Letters = CharSet{
|
|
CharRange{65,90}, //upper
|
|
CharRange{97,122}, //lower
|
|
}
|
|
|
|
var Numbers = CharRange{48,57}
|
|
|
|
//For the simple string syntax (no d-quotes)
|
|
var SimpleChars = CharSet{
|
|
Letters,
|
|
Numbers,
|
|
Char('-'),
|
|
Char('_'),
|
|
Char('!'),
|
|
Char('?'),
|
|
}
|
|
|
|
//For translating the special characters
|
|
var SpecialCharsMap = map[byte]byte{
|
|
'\\': '\\',
|
|
'"' : '"',
|
|
'\'': '\'',
|
|
'n' : '\n',
|
|
'r' : '\r',
|
|
't' : '\t',
|
|
's' : ' ',
|
|
'0' : 0,
|
|
}
|
|
|
|
//For knowing what the other end of the seq should be
|
|
var SeqMap = map[byte]byte{
|
|
'(': ')',
|
|
'[': ']',
|
|
'{': '}',
|
|
}
|
|
|
|
//For simple unsigned integers
|
|
var UintChars = CharSet{
|
|
Numbers,
|
|
Char(','),
|
|
}
|
|
|
|
//Looks at (but doesn't consume) the first byte in the buffer
|
|
func FirstByte(rbuf *bufio.Reader) (byte,error) {
|
|
b,err := rbuf.Peek(1)
|
|
if err != nil {
|
|
return 0,err
|
|
} else if b[0] == ';' {
|
|
rbuf.ReadLine()
|
|
return FirstByte(rbuf)
|
|
}
|
|
|
|
return b[0],err
|
|
}
|
|
|
|
func PrettyChar(c byte) string {
|
|
switch c {
|
|
case '\n': return "newline"
|
|
case '\t': return "tab"
|
|
case '\r': return "carriage return"
|
|
case 0: return "null byte"
|
|
default: return string(c)
|
|
}
|
|
}
|
|
|
|
func ExpectedPanic(rbuf *bufio.Reader,givenErr error,expected string) {
|
|
if givenErr != nil {
|
|
panic("Expected "+expected+", but found:"+givenErr.Error())
|
|
}
|
|
|
|
b,err := FirstByte(rbuf)
|
|
if err != nil {
|
|
panic("Expected "+expected+", but found:"+err.Error())
|
|
} else {
|
|
panic("Expected "+expected+", but found:"+PrettyChar(b))
|
|
}
|
|
}
|
|
|
|
func ConcatByteSlices(a,b []byte) []byte {
|
|
c := make([]byte,len(a)+len(b))
|
|
copy(c,a)
|
|
copy(c[len(a):],b)
|
|
return c
|
|
}
|
|
|
|
func PullWhitespace(rbuf *bufio.Reader) (int,error) {
|
|
var i int
|
|
for i=0;;i++{
|
|
b,err := FirstByte(rbuf)
|
|
if err != nil { return i,err }
|
|
if Whitespace.includes(b) != true { return i,nil }
|
|
rbuf.ReadByte()
|
|
}
|
|
return i,nil
|
|
}
|
|
|
|
func PullUint(rbuf *bufio.Reader) ([]byte,error) {
|
|
r := make([]byte,0,16)
|
|
for {
|
|
b,err := FirstByte(rbuf)
|
|
if err != nil { return r,err }
|
|
if UintChars.includes(b) != true { return r,nil }
|
|
if b != ',' { r = append(r,b) }
|
|
rbuf.ReadByte()
|
|
}
|
|
}
|
|
|
|
func PullInteger(rbuf *bufio.Reader) ([]byte,error) {
|
|
neg := false
|
|
nb,err := FirstByte(rbuf)
|
|
if err != nil { return nil,err }
|
|
if nb == '-' {
|
|
neg = true
|
|
rbuf.ReadByte()
|
|
}
|
|
|
|
ui,err := PullUint(rbuf)
|
|
if len(ui) == 0 || err != nil { return ui,err }
|
|
|
|
var r []byte
|
|
if neg {
|
|
r = ConcatByteSlices([]byte{'-'},ui)
|
|
} else {
|
|
r = ui
|
|
}
|
|
|
|
return r,nil
|
|
|
|
}
|
|
|
|
func PullFullString(rbuf *bufio.Reader) ([]byte,error) {
|
|
r := make([]byte,0,256)
|
|
|
|
//Make sure string starts with dquote
|
|
dq,err := FirstByte(rbuf)
|
|
if err != nil { return nil,err }
|
|
if dq != '"' { return r,nil }
|
|
rbuf.ReadByte()
|
|
|
|
for {
|
|
b,err := FirstByte(rbuf)
|
|
if err != nil { return r,err }
|
|
if b == '"' {
|
|
rbuf.ReadByte()
|
|
return r,nil
|
|
} else if b == '\\' {
|
|
rbuf.ReadByte()
|
|
ec,err := FirstByte(rbuf)
|
|
if err != nil { return r,err }
|
|
rbuf.ReadByte()
|
|
|
|
if c,ok := SpecialCharsMap[ec]; ok {
|
|
r = append(r,c)
|
|
} else {
|
|
r = append(r,ec)
|
|
}
|
|
} else {
|
|
rbuf.ReadByte()
|
|
r = append(r,b)
|
|
}
|
|
}
|
|
}
|
|
|
|
func PullSimpleString(rbuf *bufio.Reader) ([]byte,error) {
|
|
r := make([]byte,0,16)
|
|
|
|
for {
|
|
b,err := FirstByte(rbuf)
|
|
if err != nil { return r,err }
|
|
if SimpleChars.includes(b) {
|
|
rbuf.ReadByte()
|
|
r = append(r,b)
|
|
} else {
|
|
return r,nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func PullByte(rbuf *bufio.Reader) ([]byte,error) {
|
|
sq,err := FirstByte(rbuf)
|
|
if err != nil { return nil,err }
|
|
if sq != '\'' { return []byte{},nil }
|
|
rbuf.ReadByte()
|
|
|
|
var rb byte
|
|
b,err := FirstByte(rbuf)
|
|
if err != nil { return nil,err }
|
|
if b == '\\' {
|
|
rbuf.ReadByte()
|
|
ec,err := FirstByte(rbuf)
|
|
if err != nil { return nil,err }
|
|
|
|
if c,ok := SpecialCharsMap[ec]; ok {
|
|
rb = c
|
|
} else {
|
|
return []byte{},nil
|
|
}
|
|
} else if ASCII.includes(b) {
|
|
rb = b
|
|
} else {
|
|
return []byte{},nil
|
|
}
|
|
rbuf.ReadByte()
|
|
rstr := strconv.Itoa(int(rb))
|
|
return []byte(rstr),nil
|
|
}
|
|
|
|
func PullSeq(rbuf *bufio.Reader) ([]GngType,error) {
|
|
d,err := FirstByte(rbuf)
|
|
if err != nil { return nil,err }
|
|
od,ok := SeqMap[d]
|
|
if !ok { return nil,fmt.Errorf("Unknown seq type") }
|
|
rbuf.ReadByte()
|
|
|
|
r := make([]GngType,0,16)
|
|
for {
|
|
b,err := FirstByte(rbuf)
|
|
if err != nil {
|
|
return r,err
|
|
} else if (b == od) {
|
|
rbuf.ReadByte()
|
|
return r,nil
|
|
} else {
|
|
el,err := PullElement(rbuf)
|
|
if err != nil { return r,err }
|
|
r = append(r,el)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func PullElement(rbuf *bufio.Reader) (GngType,error) {
|
|
for {
|
|
b,err := FirstByte(rbuf)
|
|
if err != nil {
|
|
return nil,err
|
|
} else if Whitespace.includes(b) {
|
|
rbuf.ReadByte() //ignore
|
|
} else if Numbers.includes(b) || b == '-' {
|
|
n,err := PullInteger(rbuf)
|
|
if len(n) == 0 || err != nil { ExpectedPanic(rbuf,err,"number") }
|
|
|
|
fb,err := FirstByte(rbuf)
|
|
if err != nil { break }
|
|
if fb == '.' {
|
|
rbuf.ReadByte()
|
|
ui,err := PullUint(rbuf)
|
|
if len(ui) == 0 || err != nil { ExpectedPanic(rbuf,err,"number") }
|
|
ui = ConcatByteSlices([]byte{'.'},ui)
|
|
n = ConcatByteSlices(n,ui)
|
|
return NewGngFloat(n),nil
|
|
} else if fb == 'b' {
|
|
rbuf.ReadByte()
|
|
return NewGngByte(n),nil
|
|
} else {
|
|
return NewGngInteger(n),nil
|
|
}
|
|
|
|
} else if b == '"' {
|
|
s,err := PullFullString(rbuf)
|
|
if len(s) == 0 || err != nil { ExpectedPanic(rbuf,err,"end of string") }
|
|
return NewGngString(s),nil
|
|
} else if b == '\'' {
|
|
b,err := PullByte(rbuf)
|
|
if len(b) == 0 || err != nil { ExpectedPanic(rbuf,err,"character") }
|
|
return NewGngByte(b),nil
|
|
} else if Letters.includes(b) {
|
|
s,err := PullSimpleString(rbuf)
|
|
if len(s) == 0 || err != nil { ExpectedPanic(rbuf,err,"end of string") }
|
|
return NewGngString(s),nil
|
|
} else if _,ok := SeqMap[b]; ok {
|
|
s,err := PullSeq(rbuf)
|
|
if err != nil { ExpectedPanic(rbuf,err,"end of seq") }
|
|
switch(b) {
|
|
case '(': return NewGngList(s)
|
|
case '[': return NewGngVector(s)
|
|
case '{': return NewGngMap(s)
|
|
default: panic("Unknown sequence type:"+string(b))
|
|
}
|
|
} else {
|
|
ExpectedPanic(rbuf,nil,"start of valid data structure")
|
|
}
|
|
}
|
|
return nil,nil
|
|
}
|