diff --git a/parse.go b/parse.go index 2e90250..ec5449f 100644 --- a/parse.go +++ b/parse.go @@ -1,5 +1,9 @@ package jsonast +import ( + "github.com/go-functional/jsonast/parse" +) + type state struct { inObj bool inArr bool @@ -12,10 +16,10 @@ func newState() state { return state{} } -func (s state) addToken(tkn token) error { +func (s state) addToken(tkn parse.Token) error { // char := tkn.char switch tkn { - case quoteToken: + case parse.QuoteToken(): if !s.inStr { // start a new string } else { @@ -33,8 +37,8 @@ func (s state) value() Value { // Parse parses jsonStr from JSON to a Value. Returns nil and an appropriate // error if jsonStr was an invalid JSON string func Parse(jsonStr string) (Value, error) { - tokensCh := make(chan token) - go tokenize(jsonStr, tokensCh) + tokensCh := make(chan parse.Token) + go parse.Tokenize(jsonStr, tokensCh) st := newState() for token := range tokensCh { if err := st.addToken(token); err != nil { diff --git a/parse/pattern.go b/parse/pattern.go new file mode 100644 index 0000000..33f0a35 --- /dev/null +++ b/parse/pattern.go @@ -0,0 +1,33 @@ +package parse + +// Pattern is the interface to match a token or tokens +type Pattern interface { + IsValid(tkn Token) bool + IsOpen() bool +} + +type singleTokenPattern struct { + expected Token + found bool +} + +func (s singleTokenPattern) IsValid(tkn Token) bool { + return tkn == s.expected +} + +func (s singleTokenPattern) IsOpen() bool { + return !s.found +} + +type zeroOrMoreIdenticalTokensPattern struct { + expected Token +} + +func (z zeroOrMoreIdenticalTokensPattern) IsValid(tkn Token) bool { + return tkn == z.expected +} + +func (z zeroOrMoreIdenticalTokensPattern) IsOpen() bool { + return true +} + diff --git a/parse/string_pattern.go b/parse/string_pattern.go new file mode 100644 index 0000000..4b1435e --- /dev/null +++ b/parse/string_pattern.go @@ -0,0 +1,33 @@ +package parse + +type StringPattern struct { + openQuote bool + closeQuote bool + str string +} + +func (s *StringPattern) IsValid(tkn Token) bool { + if !s.openQuote && tkn == QuoteToken() { + // no open quote yet and we found one means string start + s.openQuote = true + s.str += tkn.Char + return true + } + if !s.openQuote && tkn != QuoteToken() { + // no open quote yet and we didn't see one means invalid string + return false + } + if s.openQuote && tkn == QuoteToken() { + // open quote found and we see another means closed quote + s.closeQuote = true + s.str += tkn.Char + return true + } + s.str += tkn.Char + return true +} + +func (s *StringPattern) IsOpen() bool { + // pattern is still open if we haven't found the close quote yet + return !s.closeQuote +} diff --git a/parse/token.go b/parse/token.go new file mode 100644 index 0000000..0820950 --- /dev/null +++ b/parse/token.go @@ -0,0 +1,65 @@ +package parse + +// Token represents a single token in the parser +type Token struct { + Char string +} + +func (t Token) IsDigit() bool { + r := t.Char + return r == "0" || + r == "1" || + r == "2" || + r == "3" || + r == "4" || + r == "5" || + r == "6" || + r == "7" || + r == "8" || + r == "9" +} + +func CommaToken() Token { + return Token{Char: `,`} +} +func QuoteToken() Token { + return Token{Char: `"`} +} + +func ArrayStartToken() Token { + return Token{Char: `[`} +} + +func ArrayEndToken() Token { + return Token{Char: `]`} +} + +func ObjectStartToken() Token { + return Token{Char: `{`} +} +func ObjectEndToken() Token { + return Token{Char: `}`} +} + +func Tokenize(str string, ch chan<- Token) { + for _, char := range str { + var tkn Token + if char == ',' { + tkn = CommaToken() + } else if char == '"' { + tkn = QuoteToken() + } else if char == '[' { + tkn = ArrayStartToken() + } else if char == ']' { + tkn = ArrayEndToken() + } else if char == '{' { + tkn = ObjectStartToken() + } else if char == '}' { + tkn = ObjectEndToken() + } else { + tkn = Token{Char: string(char)} + } + ch <- tkn + } + close(ch) +} diff --git a/string.go b/string.go index 6e9e631..03ff7d1 100644 --- a/string.go +++ b/string.go @@ -28,3 +28,17 @@ func newString(str string) String { str: str, } } + +func appendStringToValue(v Value, s string) (Value, error) { + switch x := v.(type) { + case stringImpl: + x.str += s + return x, nil + default: + return nil, fmt.Errorf("trying to append a string to a %#v", v) + } +} + +// func stringParser() (parsePattern, bool) { +// return newParsePattern(quoteToken, zeroOrMore(charToken), quoteToken) +// } diff --git a/token.go b/token.go deleted file mode 100644 index 48b9bf0..0000000 --- a/token.go +++ /dev/null @@ -1,49 +0,0 @@ -package jsonast - -type token struct { - char string -} - -func (t token) isDigit() bool { - r := t.char - return r == "0" || - r == "1" || - r == "2" || - r == "3" || - r == "4" || - r == "5" || - r == "6" || - r == "7" || - r == "8" || - r == "9" -} - -var commaToken = token{char: `,`} -var quoteToken = token{char: `"`} -var arrayStartToken = token{char: `[`} -var arrayEndToken = token{char: `]`} -var objectStartToken = token{char: `{`} -var objectEndToken = token{char: `}`} - -func tokenize(str string, ch chan<- token) { - for _, char := range str { - var tkn token - if char == ',' { - tkn = commaToken - } else if char == '"' { - tkn = quoteToken - } else if char == '[' { - tkn = arrayStartToken - } else if char == ']' { - tkn = arrayEndToken - } else if char == '{' { - tkn = objectStartToken - } else if char == '}' { - tkn = objectEndToken - } else { - tkn = token{char: string(char)} - } - ch <- tkn - } - close(ch) -}