Move String to its own file
This commit is contained in:
parent
7e6f7ce4cf
commit
73b55459ac
@ -31,7 +31,7 @@ var (
|
|||||||
return Node{Result: ret}
|
return Node{Result: ret}
|
||||||
})
|
})
|
||||||
|
|
||||||
attr = And(identifier, "=", String(`"'`))
|
attr = And(identifier, "=", StringLit(`"'`))
|
||||||
attrs = Map(Kleene(attr), func(node Node) Node {
|
attrs = Map(Kleene(attr), func(node Node) Node {
|
||||||
attr := map[string]string{}
|
attr := map[string]string{}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ var (
|
|||||||
}
|
}
|
||||||
return Node{Result: ret}
|
return Node{Result: ret}
|
||||||
})
|
})
|
||||||
properties = Kleene(And(String(`"`), ":", &value), ",")
|
properties = Kleene(And(StringLit(`"`), ":", &value), ",")
|
||||||
_object = Map(And("{", properties, "}"), func(n Node) Node {
|
_object = Map(And("{", properties, "}"), func(n Node) Node {
|
||||||
ret := map[string]interface{}{}
|
ret := map[string]interface{}{}
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ var (
|
|||||||
_true = Bind("true", true)
|
_true = Bind("true", true)
|
||||||
_false = Bind("false", false)
|
_false = Bind("false", false)
|
||||||
|
|
||||||
_string = Map(String(`"`), func(n Node) Node {
|
_string = Map(StringLit(`"`), func(n Node) Node {
|
||||||
return Node{Result: n.Token}
|
return Node{Result: n.Token}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
107
literals.go
Normal file
107
literals.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package goparsify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StringLit(allowedQuotes string) Parser {
|
||||||
|
return NewParser("string", func(ps *State) Node {
|
||||||
|
ps.AutoWS()
|
||||||
|
|
||||||
|
for i := 0; i < len(allowedQuotes); i++ {
|
||||||
|
if ps.Input[ps.Pos] == allowedQuotes[i] {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !stringContainsByte(allowedQuotes, ps.Input[ps.Pos]) {
|
||||||
|
ps.ErrorHere(allowedQuotes)
|
||||||
|
return Node{}
|
||||||
|
}
|
||||||
|
quote := ps.Input[ps.Pos]
|
||||||
|
|
||||||
|
var end int = ps.Pos + 1
|
||||||
|
|
||||||
|
inputLen := len(ps.Input)
|
||||||
|
var buf *bytes.Buffer
|
||||||
|
|
||||||
|
for end < inputLen {
|
||||||
|
switch ps.Input[end] {
|
||||||
|
case '\\':
|
||||||
|
if end+1 >= inputLen {
|
||||||
|
ps.ErrorHere(string(quote))
|
||||||
|
return Node{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf == nil {
|
||||||
|
buf = bytes.NewBufferString(ps.Input[ps.Pos+1 : end])
|
||||||
|
}
|
||||||
|
|
||||||
|
c := ps.Input[end+1]
|
||||||
|
if c == 'u' {
|
||||||
|
if end+6 >= inputLen {
|
||||||
|
ps.Error.Expected = "[a-f0-9]{4}"
|
||||||
|
ps.Error.pos = end + 2
|
||||||
|
return Node{}
|
||||||
|
}
|
||||||
|
|
||||||
|
r, ok := unhex(ps.Input[end+2 : end+6])
|
||||||
|
if !ok {
|
||||||
|
ps.Error.Expected = "[a-f0-9]"
|
||||||
|
ps.Error.pos = end + 2
|
||||||
|
return Node{}
|
||||||
|
}
|
||||||
|
buf.WriteRune(r)
|
||||||
|
end += 6
|
||||||
|
} else {
|
||||||
|
buf.WriteByte(c)
|
||||||
|
end += 2
|
||||||
|
}
|
||||||
|
case quote:
|
||||||
|
if buf == nil {
|
||||||
|
result := ps.Input[ps.Pos+1 : end]
|
||||||
|
ps.Pos = end + 1
|
||||||
|
return Node{Token: result}
|
||||||
|
}
|
||||||
|
ps.Pos = end + 1
|
||||||
|
return Node{Token: buf.String()}
|
||||||
|
default:
|
||||||
|
r, w := utf8.DecodeRuneInString(ps.Input[end:])
|
||||||
|
end += w
|
||||||
|
if buf != nil {
|
||||||
|
buf.WriteRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ps.ErrorHere(string(quote))
|
||||||
|
return Node{}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringContainsByte(s string, b byte) bool {
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if b == s[i] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func unhex(b string) (v rune, ok bool) {
|
||||||
|
for _, c := range b {
|
||||||
|
v <<= 4
|
||||||
|
switch {
|
||||||
|
case '0' <= c && c <= '9':
|
||||||
|
v |= c - '0'
|
||||||
|
case 'a' <= c && c <= 'f':
|
||||||
|
v |= c - 'a' + 10
|
||||||
|
case 'A' <= c && c <= 'F':
|
||||||
|
v |= c - 'A' + 10
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, true
|
||||||
|
}
|
77
literals_test.go
Normal file
77
literals_test.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package goparsify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestString(t *testing.T) {
|
||||||
|
parser := StringLit(`"'`)
|
||||||
|
t.Run("test double match", func(t *testing.T) {
|
||||||
|
result, p := runParser(`"hello"`, parser)
|
||||||
|
require.Equal(t, `hello`, result.Token)
|
||||||
|
require.Equal(t, "", p.Get())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test single match", func(t *testing.T) {
|
||||||
|
result, p := runParser(`"hello"`, parser)
|
||||||
|
require.Equal(t, `hello`, result.Token)
|
||||||
|
require.Equal(t, "", p.Get())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test nested quotes", func(t *testing.T) {
|
||||||
|
result, p := runParser(`"hello 'world'"`, parser)
|
||||||
|
require.Equal(t, `hello 'world'`, result.Token)
|
||||||
|
require.Equal(t, "", p.Get())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test non match", func(t *testing.T) {
|
||||||
|
_, p := runParser(`1`, parser)
|
||||||
|
require.Equal(t, `"'`, p.Error.Expected)
|
||||||
|
require.Equal(t, `1`, p.Get())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test unterminated string", func(t *testing.T) {
|
||||||
|
_, p := runParser(`"hello `, parser)
|
||||||
|
require.Equal(t, `"`, p.Error.Expected)
|
||||||
|
require.Equal(t, `"hello `, p.Get())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test unmatched quotes", func(t *testing.T) {
|
||||||
|
_, p := runParser(`"hello '`, parser)
|
||||||
|
require.Equal(t, `"`, p.Error.Expected)
|
||||||
|
require.Equal(t, 0, p.Pos)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test unterminated escape", func(t *testing.T) {
|
||||||
|
_, p := runParser(`"hello \`, parser)
|
||||||
|
require.Equal(t, `"`, p.Error.Expected)
|
||||||
|
require.Equal(t, 0, p.Pos)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test escaping", func(t *testing.T) {
|
||||||
|
result, p := runParser(`"hello \"world\""`, parser)
|
||||||
|
require.Equal(t, `hello "world"`, result.Token)
|
||||||
|
require.Equal(t, ``, p.Get())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test escaped unicode", func(t *testing.T) {
|
||||||
|
result, p := runParser(`"hello \ubeef cake"`, parser)
|
||||||
|
require.Equal(t, "", p.Error.Expected)
|
||||||
|
require.Equal(t, "hello \uBEEF cake", result.Token)
|
||||||
|
require.Equal(t, ``, p.Get())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test invalid escaped unicode", func(t *testing.T) {
|
||||||
|
_, p := runParser(`"hello \ucake"`, parser)
|
||||||
|
require.Equal(t, "offset 9: Expected [a-f0-9]", p.Error.Error())
|
||||||
|
require.Equal(t, 0, p.Pos)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test incomplete escaped unicode", func(t *testing.T) {
|
||||||
|
_, p := runParser(`"hello \uca"`, parser)
|
||||||
|
require.Equal(t, "offset 9: Expected [a-f0-9]{4}", p.Error.Error())
|
||||||
|
require.Equal(t, 0, p.Pos)
|
||||||
|
})
|
||||||
|
}
|
102
parser.go
102
parser.go
@ -1,7 +1,6 @@
|
|||||||
package goparsify
|
package goparsify
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@ -200,104 +199,3 @@ func charsImpl(matcher string, stopOn bool, repetition ...int) Parser {
|
|||||||
return Node{Token: result}
|
return Node{Token: result}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func String(allowedQuotes string) Parser {
|
|
||||||
return NewParser("string", func(ps *State) Node {
|
|
||||||
ps.AutoWS()
|
|
||||||
|
|
||||||
for i := 0; i < len(allowedQuotes); i++ {
|
|
||||||
if ps.Input[ps.Pos] == allowedQuotes[i] {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !stringContainsByte(allowedQuotes, ps.Input[ps.Pos]) {
|
|
||||||
ps.ErrorHere(allowedQuotes)
|
|
||||||
return Node{}
|
|
||||||
}
|
|
||||||
quote := ps.Input[ps.Pos]
|
|
||||||
|
|
||||||
var end int = ps.Pos + 1
|
|
||||||
|
|
||||||
inputLen := len(ps.Input)
|
|
||||||
var buf *bytes.Buffer
|
|
||||||
|
|
||||||
for end < inputLen {
|
|
||||||
switch ps.Input[end] {
|
|
||||||
case '\\':
|
|
||||||
if end+1 >= inputLen {
|
|
||||||
ps.ErrorHere(string(quote))
|
|
||||||
return Node{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf == nil {
|
|
||||||
buf = bytes.NewBufferString(ps.Input[ps.Pos+1 : end])
|
|
||||||
}
|
|
||||||
|
|
||||||
c := ps.Input[end+1]
|
|
||||||
if c == 'u' {
|
|
||||||
if end+6 >= inputLen {
|
|
||||||
ps.Error.Expected = "[a-f0-9]{4}"
|
|
||||||
ps.Error.pos = end + 2
|
|
||||||
return Node{}
|
|
||||||
}
|
|
||||||
|
|
||||||
r, ok := unhex(ps.Input[end+2 : end+6])
|
|
||||||
if !ok {
|
|
||||||
ps.Error.Expected = "[a-f0-9]"
|
|
||||||
ps.Error.pos = end + 2
|
|
||||||
return Node{}
|
|
||||||
}
|
|
||||||
buf.WriteRune(r)
|
|
||||||
end += 6
|
|
||||||
} else {
|
|
||||||
buf.WriteByte(c)
|
|
||||||
end += 2
|
|
||||||
}
|
|
||||||
case quote:
|
|
||||||
if buf == nil {
|
|
||||||
result := ps.Input[ps.Pos+1 : end]
|
|
||||||
ps.Pos = end + 1
|
|
||||||
return Node{Token: result}
|
|
||||||
}
|
|
||||||
ps.Pos = end + 1
|
|
||||||
return Node{Token: buf.String()}
|
|
||||||
default:
|
|
||||||
r, w := utf8.DecodeRuneInString(ps.Input[end:])
|
|
||||||
end += w
|
|
||||||
if buf != nil {
|
|
||||||
buf.WriteRune(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ps.ErrorHere(string(quote))
|
|
||||||
return Node{}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringContainsByte(s string, b byte) bool {
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
if b == s[i] {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func unhex(b string) (v rune, ok bool) {
|
|
||||||
for _, c := range b {
|
|
||||||
v <<= 4
|
|
||||||
switch {
|
|
||||||
case '0' <= c && c <= '9':
|
|
||||||
v |= c - '0'
|
|
||||||
case 'a' <= c && c <= 'f':
|
|
||||||
v |= c - 'a' + 10
|
|
||||||
case 'A' <= c && c <= 'F':
|
|
||||||
v |= c - 'A' + 10
|
|
||||||
default:
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return v, true
|
|
||||||
}
|
|
||||||
|
@ -151,76 +151,6 @@ func TestParseString(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestString(t *testing.T) {
|
|
||||||
parser := String(`"'`)
|
|
||||||
t.Run("test double match", func(t *testing.T) {
|
|
||||||
result, p := runParser(`"hello"`, parser)
|
|
||||||
require.Equal(t, `hello`, result.Token)
|
|
||||||
require.Equal(t, "", p.Get())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("test single match", func(t *testing.T) {
|
|
||||||
result, p := runParser(`"hello"`, parser)
|
|
||||||
require.Equal(t, `hello`, result.Token)
|
|
||||||
require.Equal(t, "", p.Get())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("test nested quotes", func(t *testing.T) {
|
|
||||||
result, p := runParser(`"hello 'world'"`, parser)
|
|
||||||
require.Equal(t, `hello 'world'`, result.Token)
|
|
||||||
require.Equal(t, "", p.Get())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("test non match", func(t *testing.T) {
|
|
||||||
_, p := runParser(`1`, parser)
|
|
||||||
require.Equal(t, `"'`, p.Error.Expected)
|
|
||||||
require.Equal(t, `1`, p.Get())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("test unterminated string", func(t *testing.T) {
|
|
||||||
_, p := runParser(`"hello `, parser)
|
|
||||||
require.Equal(t, `"`, p.Error.Expected)
|
|
||||||
require.Equal(t, `"hello `, p.Get())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("test unmatched quotes", func(t *testing.T) {
|
|
||||||
_, p := runParser(`"hello '`, parser)
|
|
||||||
require.Equal(t, `"`, p.Error.Expected)
|
|
||||||
require.Equal(t, 0, p.Pos)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("test unterminated escape", func(t *testing.T) {
|
|
||||||
_, p := runParser(`"hello \`, parser)
|
|
||||||
require.Equal(t, `"`, p.Error.Expected)
|
|
||||||
require.Equal(t, 0, p.Pos)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("test escaping", func(t *testing.T) {
|
|
||||||
result, p := runParser(`"hello \"world\""`, parser)
|
|
||||||
require.Equal(t, `hello "world"`, result.Token)
|
|
||||||
require.Equal(t, ``, p.Get())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("test escaped unicode", func(t *testing.T) {
|
|
||||||
result, p := runParser(`"hello \ubeef cake"`, parser)
|
|
||||||
require.Equal(t, "", p.Error.Expected)
|
|
||||||
require.Equal(t, "hello \uBEEF cake", result.Token)
|
|
||||||
require.Equal(t, ``, p.Get())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("test invalid escaped unicode", func(t *testing.T) {
|
|
||||||
_, p := runParser(`"hello \ucake"`, parser)
|
|
||||||
require.Equal(t, "offset 9: Expected [a-f0-9]", p.Error.Error())
|
|
||||||
require.Equal(t, 0, p.Pos)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("test incomplete escaped unicode", func(t *testing.T) {
|
|
||||||
_, p := runParser(`"hello \uca"`, parser)
|
|
||||||
require.Equal(t, "offset 9: Expected [a-f0-9]{4}", p.Error.Error())
|
|
||||||
require.Equal(t, 0, p.Pos)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnhex(t *testing.T) {
|
func TestUnhex(t *testing.T) {
|
||||||
tests := map[int64]string{
|
tests := map[int64]string{
|
||||||
0xF: "F",
|
0xF: "F",
|
||||||
|
Loading…
Reference in New Issue
Block a user