aboutsummaryrefslogtreecommitdiff
path: root/h
diff options
context:
space:
mode:
authorChristine Dodrill <me@christine.website>2019-06-25 22:59:06 +0000
committerChristine Dodrill <me@christine.website>2019-06-25 22:59:06 +0000
commit96a073ecddf7eee051050347a09786dc38b3a8ac (patch)
tree44644398eaf655e5be51cdbd8bab25330bc0bd58 /h
parentab2f1f58d4de28aa708cdbc1602c5ae1a941fce6 (diff)
downloadx-96a073ecddf7eee051050347a09786dc38b3a8ac.tar.xz
x-96a073ecddf7eee051050347a09786dc38b3a8ac.zip
h
Diffstat (limited to 'h')
-rw-r--r--h/h.peg12
-rw-r--r--h/h_gen.go789
-rw-r--r--h/parser.go42
-rw-r--r--h/simplify.go59
4 files changed, 902 insertions, 0 deletions
diff --git a/h/h.peg b/h/h.peg
new file mode 100644
index 0000000..542a887
--- /dev/null
+++ b/h/h.peg
@@ -0,0 +1,12 @@
+{
+package h
+
+import (
+ "github.com/eaburns/peggy/peg"
+)
+}
+
+sep <- space+h
+space <- ' '
+h <- 'h' / "'"
+H <- h+sep+ / h
diff --git a/h/h_gen.go b/h/h_gen.go
new file mode 100644
index 0000000..365b630
--- /dev/null
+++ b/h/h_gen.go
@@ -0,0 +1,789 @@
+package h
+
+import (
+ "github.com/eaburns/peggy/peg"
+)
+
+type _Parser struct {
+ text string
+ deltaPos []_Rules
+ deltaErr []_Rules
+ node map[_key]*peg.Node
+ fail map[_key]*peg.Fail
+ lastFail int
+ data interface{}
+}
+
+type _key struct {
+ start int
+ name string
+}
+
+func _NewParser(text string) *_Parser {
+ return &_Parser{
+ text: text,
+ deltaPos: make([]_Rules, len(text)+1),
+ deltaErr: make([]_Rules, len(text)+1),
+ node: make(map[_key]*peg.Node),
+ fail: make(map[_key]*peg.Fail),
+ }
+}
+
+type _Rules struct {
+ sep int32
+ space int32
+ h int32
+ H int32
+}
+
+func _max(a, b int) int {
+ if a > b {
+ return a
+ }
+ return b
+}
+
+func _next(parser *_Parser, pos int) (rune, int) {
+ r, w := peg.DecodeRuneInString(parser.text[pos:])
+ return r, w
+}
+
+func _node(name string) *peg.Node {
+ return &peg.Node{Name: name}
+}
+
+func _sub(parser *_Parser, start, end int, kids []*peg.Node) *peg.Node {
+ node := &peg.Node{
+ Text: parser.text[start:end],
+ Kids: make([]*peg.Node, len(kids)),
+ }
+ copy(node.Kids, kids)
+ return node
+}
+
+func _leaf(parser *_Parser, start, end int) *peg.Node {
+ return &peg.Node{Text: parser.text[start:end]}
+}
+
+func _sepAccepts(parser *_Parser, start int) (deltaPos, deltaErr int) {
+ if dp := parser.deltaPos[start].sep; dp != 0 {
+ de := parser.deltaErr[start].sep - 1
+ if dp > 0 {
+ dp--
+ }
+ return int(dp), int(de)
+ }
+ pos, perr := start, -1
+ // space+ h
+ // space+
+ // space
+ if dp, de := _spaceAccepts(parser, pos); dp < 0 {
+ perr = _max(perr, pos+de)
+ goto fail
+ } else {
+ perr = _max(perr, pos+de)
+ pos += dp
+ }
+ for {
+ pos1 := pos
+ // space
+ if dp, de := _spaceAccepts(parser, pos); dp < 0 {
+ perr = _max(perr, pos+de)
+ goto fail2
+ } else {
+ perr = _max(perr, pos+de)
+ pos += dp
+ }
+ continue
+ fail2:
+ pos = pos1
+ break
+ }
+ // h
+ if dp, de := _hAccepts(parser, pos); dp < 0 {
+ perr = _max(perr, pos+de)
+ goto fail
+ } else {
+ perr = _max(perr, pos+de)
+ pos += dp
+ }
+ parser.deltaPos[start].sep = int32(pos-start) + 1
+ parser.deltaErr[start].sep = int32(perr-start) + 1
+ parser.lastFail = perr
+ return pos - start, perr - start
+fail:
+ parser.deltaPos[start].sep = -1
+ parser.deltaErr[start].sep = int32(perr-start) + 1
+ parser.lastFail = perr
+ return -1, perr - start
+}
+
+func _sepNode(parser *_Parser, start int) (int, *peg.Node) {
+ dp := parser.deltaPos[start].sep
+ if dp < 0 {
+ return -1, nil
+ }
+ key := _key{start: start, name: "sep"}
+ node := parser.node[key]
+ if node != nil {
+ return start + int(dp-1), node
+ }
+ pos := start
+ node = _node("sep")
+ // space+ h
+ // space+
+ // space
+ if p, kid := _spaceNode(parser, pos); kid == nil {
+ goto fail
+ } else {
+ node.Kids = append(node.Kids, kid)
+ pos = p
+ }
+ for {
+ nkids0 := len(node.Kids)
+ pos1 := pos
+ // space
+ if p, kid := _spaceNode(parser, pos); kid == nil {
+ goto fail2
+ } else {
+ node.Kids = append(node.Kids, kid)
+ pos = p
+ }
+ continue
+ fail2:
+ node.Kids = node.Kids[:nkids0]
+ pos = pos1
+ break
+ }
+ // h
+ if p, kid := _hNode(parser, pos); kid == nil {
+ goto fail
+ } else {
+ node.Kids = append(node.Kids, kid)
+ pos = p
+ }
+ node.Text = parser.text[start:pos]
+ parser.node[key] = node
+ return pos, node
+fail:
+ return -1, nil
+}
+
+func _sepFail(parser *_Parser, start, errPos int) (int, *peg.Fail) {
+ if start > parser.lastFail {
+ return -1, &peg.Fail{}
+ }
+ dp := parser.deltaPos[start].sep
+ de := parser.deltaErr[start].sep
+ if start+int(de-1) < errPos {
+ if dp > 0 {
+ return start + int(dp-1), &peg.Fail{}
+ }
+ return -1, &peg.Fail{}
+ }
+ key := _key{start: start, name: "sep"}
+ failure := parser.fail[key]
+ if dp < 0 && failure != nil {
+ return -1, failure
+ }
+ if dp > 0 && failure != nil {
+ return start + int(dp-1), failure
+ }
+ pos := start
+ failure = &peg.Fail{
+ Name: "sep",
+ Pos: int(start),
+ }
+ // space+ h
+ // space+
+ // space
+ {
+ p, kid := _spaceFail(parser, pos, errPos)
+ if kid.Want != "" || len(kid.Kids) > 0 {
+ failure.Kids = append(failure.Kids, kid)
+ }
+ if p < 0 {
+ goto fail
+ }
+ pos = p
+ }
+ for {
+ pos1 := pos
+ // space
+ {
+ p, kid := _spaceFail(parser, pos, errPos)
+ if kid.Want != "" || len(kid.Kids) > 0 {
+ failure.Kids = append(failure.Kids, kid)
+ }
+ if p < 0 {
+ goto fail2
+ }
+ pos = p
+ }
+ continue
+ fail2:
+ pos = pos1
+ break
+ }
+ // h
+ {
+ p, kid := _hFail(parser, pos, errPos)
+ if kid.Want != "" || len(kid.Kids) > 0 {
+ failure.Kids = append(failure.Kids, kid)
+ }
+ if p < 0 {
+ goto fail
+ }
+ pos = p
+ }
+ parser.fail[key] = failure
+ return pos, failure
+fail:
+ parser.fail[key] = failure
+ return -1, failure
+}
+
+func _spaceAccepts(parser *_Parser, start int) (deltaPos, deltaErr int) {
+ if dp := parser.deltaPos[start].space; dp != 0 {
+ de := parser.deltaErr[start].space - 1
+ if dp > 0 {
+ dp--
+ }
+ return int(dp), int(de)
+ }
+ pos, perr := start, -1
+ // " "
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != " " {
+ perr = _max(perr, pos)
+ goto fail
+ }
+ pos++
+ parser.deltaPos[start].space = int32(pos-start) + 1
+ parser.deltaErr[start].space = int32(perr-start) + 1
+ parser.lastFail = perr
+ return pos - start, perr - start
+fail:
+ parser.deltaPos[start].space = -1
+ parser.deltaErr[start].space = int32(perr-start) + 1
+ parser.lastFail = perr
+ return -1, perr - start
+}
+
+func _spaceNode(parser *_Parser, start int) (int, *peg.Node) {
+ dp := parser.deltaPos[start].space
+ if dp < 0 {
+ return -1, nil
+ }
+ key := _key{start: start, name: "space"}
+ node := parser.node[key]
+ if node != nil {
+ return start + int(dp-1), node
+ }
+ pos := start
+ node = _node("space")
+ // " "
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != " " {
+ goto fail
+ }
+ node.Kids = append(node.Kids, _leaf(parser, pos, pos+1))
+ pos++
+ node.Text = parser.text[start:pos]
+ parser.node[key] = node
+ return pos, node
+fail:
+ return -1, nil
+}
+
+func _spaceFail(parser *_Parser, start, errPos int) (int, *peg.Fail) {
+ if start > parser.lastFail {
+ return -1, &peg.Fail{}
+ }
+ dp := parser.deltaPos[start].space
+ de := parser.deltaErr[start].space
+ if start+int(de-1) < errPos {
+ if dp > 0 {
+ return start + int(dp-1), &peg.Fail{}
+ }
+ return -1, &peg.Fail{}
+ }
+ key := _key{start: start, name: "space"}
+ failure := parser.fail[key]
+ if dp < 0 && failure != nil {
+ return -1, failure
+ }
+ if dp > 0 && failure != nil {
+ return start + int(dp-1), failure
+ }
+ pos := start
+ failure = &peg.Fail{
+ Name: "space",
+ Pos: int(start),
+ }
+ // " "
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != " " {
+ if pos >= errPos {
+ failure.Kids = append(failure.Kids, &peg.Fail{
+ Pos: int(pos),
+ Want: "\" \"",
+ })
+ }
+ goto fail
+ }
+ pos++
+ parser.fail[key] = failure
+ return pos, failure
+fail:
+ parser.fail[key] = failure
+ return -1, failure
+}
+
+func _hAccepts(parser *_Parser, start int) (deltaPos, deltaErr int) {
+ if dp := parser.deltaPos[start].h; dp != 0 {
+ de := parser.deltaErr[start].h - 1
+ if dp > 0 {
+ dp--
+ }
+ return int(dp), int(de)
+ }
+ pos, perr := start, -1
+ // "h"/"'"
+ {
+ pos2 := pos
+ // "h"
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != "h" {
+ perr = _max(perr, pos)
+ goto fail3
+ }
+ pos++
+ goto ok0
+ fail3:
+ pos = pos2
+ // "'"
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != "'" {
+ perr = _max(perr, pos)
+ goto fail4
+ }
+ pos++
+ goto ok0
+ fail4:
+ pos = pos2
+ goto fail
+ ok0:
+ }
+ parser.deltaPos[start].h = int32(pos-start) + 1
+ parser.deltaErr[start].h = int32(perr-start) + 1
+ parser.lastFail = perr
+ return pos - start, perr - start
+fail:
+ parser.deltaPos[start].h = -1
+ parser.deltaErr[start].h = int32(perr-start) + 1
+ parser.lastFail = perr
+ return -1, perr - start
+}
+
+func _hNode(parser *_Parser, start int) (int, *peg.Node) {
+ dp := parser.deltaPos[start].h
+ if dp < 0 {
+ return -1, nil
+ }
+ key := _key{start: start, name: "h"}
+ node := parser.node[key]
+ if node != nil {
+ return start + int(dp-1), node
+ }
+ pos := start
+ node = _node("h")
+ // "h"/"'"
+ {
+ pos2 := pos
+ nkids1 := len(node.Kids)
+ // "h"
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != "h" {
+ goto fail3
+ }
+ node.Kids = append(node.Kids, _leaf(parser, pos, pos+1))
+ pos++
+ goto ok0
+ fail3:
+ node.Kids = node.Kids[:nkids1]
+ pos = pos2
+ // "'"
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != "'" {
+ goto fail4
+ }
+ node.Kids = append(node.Kids, _leaf(parser, pos, pos+1))
+ pos++
+ goto ok0
+ fail4:
+ node.Kids = node.Kids[:nkids1]
+ pos = pos2
+ goto fail
+ ok0:
+ }
+ node.Text = parser.text[start:pos]
+ parser.node[key] = node
+ return pos, node
+fail:
+ return -1, nil
+}
+
+func _hFail(parser *_Parser, start, errPos int) (int, *peg.Fail) {
+ if start > parser.lastFail {
+ return -1, &peg.Fail{}
+ }
+ dp := parser.deltaPos[start].h
+ de := parser.deltaErr[start].h
+ if start+int(de-1) < errPos {
+ if dp > 0 {
+ return start + int(dp-1), &peg.Fail{}
+ }
+ return -1, &peg.Fail{}
+ }
+ key := _key{start: start, name: "h"}
+ failure := parser.fail[key]
+ if dp < 0 && failure != nil {
+ return -1, failure
+ }
+ if dp > 0 && failure != nil {
+ return start + int(dp-1), failure
+ }
+ pos := start
+ failure = &peg.Fail{
+ Name: "h",
+ Pos: int(start),
+ }
+ // "h"/"'"
+ {
+ pos2 := pos
+ // "h"
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != "h" {
+ if pos >= errPos {
+ failure.Kids = append(failure.Kids, &peg.Fail{
+ Pos: int(pos),
+ Want: "\"h\"",
+ })
+ }
+ goto fail3
+ }
+ pos++
+ goto ok0
+ fail3:
+ pos = pos2
+ // "'"
+ if len(parser.text[pos:]) < 1 || parser.text[pos:pos+1] != "'" {
+ if pos >= errPos {
+ failure.Kids = append(failure.Kids, &peg.Fail{
+ Pos: int(pos),
+ Want: "\"'\"",
+ })
+ }
+ goto fail4
+ }
+ pos++
+ goto ok0
+ fail4:
+ pos = pos2
+ goto fail
+ ok0:
+ }
+ parser.fail[key] = failure
+ return pos, failure
+fail:
+ parser.fail[key] = failure
+ return -1, failure
+}
+
+func _HAccepts(parser *_Parser, start int) (deltaPos, deltaErr int) {
+ if dp := parser.deltaPos[start].H; dp != 0 {
+ de := parser.deltaErr[start].H - 1
+ if dp > 0 {
+ dp--
+ }
+ return int(dp), int(de)
+ }
+ pos, perr := start, -1
+ // h+ sep+/h
+ {
+ pos2 := pos
+ // h+ sep+
+ // h+
+ // h
+ if dp, de := _hAccepts(parser, pos); dp < 0 {
+ perr = _max(perr, pos+de)
+ goto fail3
+ } else {
+ perr = _max(perr, pos+de)
+ pos += dp
+ }
+ for {
+ pos5 := pos
+ // h
+ if dp, de := _hAccepts(parser, pos); dp < 0 {
+ perr = _max(perr, pos+de)
+ goto fail6
+ } else {
+ perr = _max(perr, pos+de)
+ pos += dp
+ }
+ continue
+ fail6:
+ pos = pos5
+ break
+ }
+ // sep+
+ // sep
+ if dp, de := _sepAccepts(parser, pos); dp < 0 {
+ perr = _max(perr, pos+de)
+ goto fail3
+ } else {
+ perr = _max(perr, pos+de)
+ pos += dp
+ }
+ for {
+ pos8 := pos
+ // sep
+ if dp, de := _sepAccepts(parser, pos); dp < 0 {
+ perr = _max(perr, pos+de)
+ goto fail9
+ } else {
+ perr = _max(perr, pos+de)
+ pos += dp
+ }
+ continue
+ fail9:
+ pos = pos8
+ break
+ }
+ goto ok0
+ fail3:
+ pos = pos2
+ // h
+ if dp, de := _hAccepts(parser, pos); dp < 0 {
+ perr = _max(perr, pos+de)
+ goto fail10
+ } else {
+ perr = _max(perr, pos+de)
+ pos += dp
+ }
+ goto ok0
+ fail10:
+ pos = pos2
+ goto fail
+ ok0:
+ }
+ parser.deltaPos[start].H = int32(pos-start) + 1
+ parser.deltaErr[start].H = int32(perr-start) + 1
+ parser.lastFail = perr
+ return pos - start, perr - start
+fail:
+ parser.deltaPos[start].H = -1
+ parser.deltaErr[start].H = int32(perr-start) + 1
+ parser.lastFail = perr
+ return -1, perr - start
+}
+
+func _HNode(parser *_Parser, start int) (int, *peg.Node) {
+ dp := parser.deltaPos[start].H
+ if dp < 0 {
+ return -1, nil
+ }
+ key := _key{start: start, name: "H"}
+ node := parser.node[key]
+ if node != nil {
+ return start + int(dp-1), node
+ }
+ pos := start
+ node = _node("H")
+ // h+ sep+/h
+ {
+ pos2 := pos
+ nkids1 := len(node.Kids)
+ // h+ sep+
+ // h+
+ // h
+ if p, kid := _hNode(parser, pos); kid == nil {
+ goto fail3
+ } else {
+ node.Kids = append(node.Kids, kid)
+ pos = p
+ }
+ for {
+ nkids4 := len(node.Kids)
+ pos5 := pos
+ // h
+ if p, kid := _hNode(parser, pos); kid == nil {
+ goto fail6
+ } else {
+ node.Kids = append(node.Kids, kid)
+ pos = p
+ }
+ continue
+ fail6:
+ node.Kids = node.Kids[:nkids4]
+ pos = pos5
+ break
+ }
+ // sep+
+ // sep
+ if p, kid := _sepNode(parser, pos); kid == nil {
+ goto fail3
+ } else {
+ node.Kids = append(node.Kids, kid)
+ pos = p
+ }
+ for {
+ nkids7 := len(node.Kids)
+ pos8 := pos
+ // sep
+ if p, kid := _sepNode(parser, pos); kid == nil {
+ goto fail9
+ } else {
+ node.Kids = append(node.Kids, kid)
+ pos = p
+ }
+ continue
+ fail9:
+ node.Kids = node.Kids[:nkids7]
+ pos = pos8
+ break
+ }
+ goto ok0
+ fail3:
+ node.Kids = node.Kids[:nkids1]
+ pos = pos2
+ // h
+ if p, kid := _hNode(parser, pos); kid == nil {
+ goto fail10
+ } else {
+ node.Kids = append(node.Kids, kid)
+ pos = p
+ }
+ goto ok0
+ fail10:
+ node.Kids = node.Kids[:nkids1]
+ pos = pos2
+ goto fail
+ ok0:
+ }
+ node.Text = parser.text[start:pos]
+ parser.node[key] = node
+ return pos, node
+fail:
+ return -1, nil
+}
+
+func _HFail(parser *_Parser, start, errPos int) (int, *peg.Fail) {
+ if start > parser.lastFail {
+ return -1, &peg.Fail{}
+ }
+ dp := parser.deltaPos[start].H
+ de := parser.deltaErr[start].H
+ if start+int(de-1) < errPos {
+ if dp > 0 {
+ return start + int(dp-1), &peg.Fail{}
+ }
+ return -1, &peg.Fail{}
+ }
+ key := _key{start: start, name: "H"}
+ failure := parser.fail[key]
+ if dp < 0 && failure != nil {
+ return -1, failure
+ }
+ if dp > 0 && failure != nil {
+ return start + int(dp-1), failure
+ }
+ pos := start
+ failure = &peg.Fail{
+ Name: "H",
+ Pos: int(start),
+ }
+ // h+ sep+/h
+ {
+ pos2 := pos
+ // h+ sep+
+ // h+
+ // h
+ {
+ p, kid := _hFail(parser, pos, errPos)
+ if kid.Want != "" || len(kid.Kids) > 0 {
+ failure.Kids = append(failure.Kids, kid)
+ }
+ if p < 0 {
+ goto fail3
+ }
+ pos = p
+ }
+ for {
+ pos5 := pos
+ // h
+ {
+ p, kid := _hFail(parser, pos, errPos)
+ if kid.Want != "" || len(kid.Kids) > 0 {
+ failure.Kids = append(failure.Kids, kid)
+ }
+ if p < 0 {
+ goto fail6
+ }
+ pos = p
+ }
+ continue
+ fail6:
+ pos = pos5
+ break
+ }
+ // sep+
+ // sep
+ {
+ p, kid := _sepFail(parser, pos, errPos)
+ if kid.Want != "" || len(kid.Kids) > 0 {
+ failure.Kids = append(failure.Kids, kid)
+ }
+ if p < 0 {
+ goto fail3
+ }
+ pos = p
+ }
+ for {
+ pos8 := pos
+ // sep
+ {
+ p, kid := _sepFail(parser, pos, errPos)
+ if kid.Want != "" || len(kid.Kids) > 0 {
+ failure.Kids = append(failure.Kids, kid)
+ }
+ if p < 0 {
+ goto fail9
+ }
+ pos = p
+ }
+ continue
+ fail9:
+ pos = pos8
+ break
+ }
+ goto ok0
+ fail3:
+ pos = pos2
+ // h
+ {
+ p, kid := _hFail(parser, pos, errPos)
+ if kid.Want != "" || len(kid.Kids) > 0 {
+ failure.Kids = append(failure.Kids, kid)
+ }
+ if p < 0 {
+ goto fail10
+ }
+ pos = p
+ }
+ goto ok0
+ fail10:
+ pos = pos2
+ goto fail
+ ok0:
+ }
+ parser.fail[key] = failure
+ return pos, failure
+fail:
+ parser.fail[key] = failure
+ return -1, failure
+}
diff --git a/h/parser.go b/h/parser.go
new file mode 100644
index 0000000..f061ec7
--- /dev/null
+++ b/h/parser.go
@@ -0,0 +1,42 @@
+package h
+
+//go:generate peggy -o h_gen.go h.peg
+
+import (
+ "fmt"
+
+ "github.com/eaburns/peggy/peg"
+ "within.website/x/jbo/namcu"
+)
+
+func (p *_Parser) Parse() (int, bool) {
+ pos, perr := _HAccepts(p, 0)
+ return perr, pos >= 0
+}
+
+func (p *_Parser) ErrorTree(minPos int) *peg.Fail {
+ p.fail = make(map[_key]*peg.Fail) // reset fail memo table
+ _, tree := _HFail(p, 0, minPos)
+ return tree
+}
+
+func (p *_Parser) ParseTree() *peg.Node {
+ _, tree := _HNode(p, 0)
+ return tree
+}
+
+// Parse parses h.
+// On success, the parseTree is returned.
+// On failure, both the word-level and the raw, morphological errors are returned.
+func Parse(text string) (*peg.Node, error) {
+ p := _NewParser(text)
+ if perr, ok := p.Parse(); !ok {
+ return nil, fmt.Errorf("h: gentoldra fi'o zvati zo %s", namcu.Lerfu(perr))
+ }
+
+ tree := p.ParseTree()
+ RemoveSpace(tree)
+ CollapseLists(tree)
+
+ return tree, nil
+}
diff --git a/h/simplify.go b/h/simplify.go
new file mode 100644
index 0000000..36013df
--- /dev/null
+++ b/h/simplify.go
@@ -0,0 +1,59 @@
+package h
+
+import (
+ "strings"
+
+ "github.com/eaburns/peggy/peg"
+)
+
+// RemoveSpace removes whitespace-only nodes.
+func RemoveSpace(n *peg.Node) { removeSpace(n) }
+
+func removeSpace(n *peg.Node) bool {
+ if whitespace(n.Text) {
+ return false
+ }
+ if len(n.Kids) == 0 {
+ return true
+ }
+ var kids []*peg.Node
+ for _, k := range n.Kids {
+ if removeSpace(k) {
+ kids = append(kids, k)
+ }
+ }
+ n.Kids = kids
+ return len(n.Kids) > 0
+}
+
+// SpaceChars is the string of all whitespace characters.
+const SpaceChars = "\x20"
+
+func whitespace(s string) bool {
+ for _, r := range s {
+ if !strings.ContainsRune(SpaceChars, r) {
+ return false
+ }
+ }
+ return true
+}
+
+// CollapseLists collapses chains of single-kid nodes.
+func CollapseLists(n *peg.Node) {
+ if collapseLists(n) == 1 {
+ n.Kids = n.Kids[0].Kids
+ }
+}
+
+func collapseLists(n *peg.Node) int {
+ var kids []*peg.Node
+ for _, k := range n.Kids {
+ if gk := collapseLists(k); gk == 1 {
+ kids = append(kids, k.Kids[0])
+ } else {
+ kids = append(kids, k)
+ }
+ }
+ n.Kids = kids
+ return len(n.Kids)
+}