219 lines
4.2 KiB
Go
219 lines
4.2 KiB
Go
package treerack
|
|
|
|
import "strconv"
|
|
|
|
func dropComments(n *Node) *Node {
|
|
ncc := *n
|
|
nc := &ncc
|
|
nc.Nodes = filterNodes(func(n *Node) bool { return n.Name != "comment" }, n.Nodes)
|
|
nc.Nodes = mapNodes(dropComments, nc.Nodes)
|
|
return nc
|
|
}
|
|
|
|
func flagsToCommitType(n []*Node) CommitType {
|
|
var ct CommitType
|
|
for _, ni := range n {
|
|
switch ni.Name {
|
|
case "alias":
|
|
ct |= Alias
|
|
case "ws":
|
|
ct |= Whitespace
|
|
case "nows":
|
|
ct |= NoWhitespace
|
|
case "doc":
|
|
ct |= Documentation
|
|
case "root":
|
|
ct |= Root
|
|
}
|
|
}
|
|
|
|
return ct
|
|
}
|
|
|
|
func toRune(c string) rune {
|
|
return []rune(c)[0]
|
|
}
|
|
|
|
func nodeChar(n *Node) rune {
|
|
s := n.Text()
|
|
if s[0] == '\\' {
|
|
return unescapeChar(toRune(s[1:]))
|
|
}
|
|
|
|
return toRune(s)
|
|
}
|
|
|
|
func defineMember(s *Syntax, defaultName string, ct CommitType, n *Node) (string, error) {
|
|
switch n.Name {
|
|
case "symbol":
|
|
return n.Text(), nil
|
|
default:
|
|
return defaultName, defineExpression(s, defaultName, ct, n)
|
|
}
|
|
}
|
|
|
|
func defineClass(s *Syntax, name string, ct CommitType, n []*Node) error {
|
|
var (
|
|
not bool
|
|
chars []rune
|
|
ranges [][]rune
|
|
)
|
|
|
|
if len(n) > 0 && n[0].Name == "class-not" {
|
|
not, n = true, n[1:]
|
|
}
|
|
|
|
for _, c := range n {
|
|
switch c.Name {
|
|
case "class-char":
|
|
chars = append(chars, nodeChar(c))
|
|
case "char-range":
|
|
ranges = append(ranges, []rune{nodeChar(c.Nodes[0]), nodeChar(c.Nodes[1])})
|
|
}
|
|
}
|
|
|
|
return s.class(name, ct, not, chars, ranges)
|
|
}
|
|
|
|
func defineCharSequence(s *Syntax, name string, ct CommitType, charNodes []*Node) error {
|
|
var chars []rune
|
|
for _, ci := range charNodes {
|
|
chars = append(chars, nodeChar(ci))
|
|
}
|
|
|
|
return s.charSequence(name, ct, chars)
|
|
}
|
|
|
|
func getQuantity(n *Node) (min int, max int, err error) {
|
|
switch n.Name {
|
|
case "count-quantifier":
|
|
min, err = strconv.Atoi(n.Nodes[0].Text())
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
max = min
|
|
case "range-quantifier":
|
|
min = 0
|
|
max = -1
|
|
for _, rq := range n.Nodes {
|
|
switch rq.Name {
|
|
case "range-from":
|
|
min, err = strconv.Atoi(rq.Text())
|
|
if err != nil {
|
|
return
|
|
}
|
|
case "range-to":
|
|
max, err = strconv.Atoi(rq.Text())
|
|
if err != nil {
|
|
return
|
|
}
|
|
default:
|
|
err = ErrInvalidSyntax
|
|
return
|
|
}
|
|
}
|
|
case "one-or-more":
|
|
min, max = 1, -1
|
|
case "zero-or-more":
|
|
min, max = 0, -1
|
|
case "zero-or-one":
|
|
min, max = 0, 1
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func defineSymbol(s *Syntax, name string, ct CommitType, n *Node) error {
|
|
return s.sequence(name, ct, SequenceItem{Name: n.Text()})
|
|
}
|
|
|
|
func defineSequence(s *Syntax, name string, ct CommitType, n ...*Node) error {
|
|
nows := ct & NoWhitespace
|
|
var items []SequenceItem
|
|
for i, ni := range n {
|
|
if ni.Name != "item" || len(ni.Nodes) == 0 {
|
|
return ErrInvalidSyntax
|
|
}
|
|
|
|
var (
|
|
item SequenceItem
|
|
err error
|
|
)
|
|
|
|
defaultName := childName(name, i)
|
|
item.Name, err = defineMember(s, defaultName, Alias|nows, ni.Nodes[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(ni.Nodes) == 2 {
|
|
item.Min, item.Max, err = getQuantity(ni.Nodes[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
items = append(items, item)
|
|
}
|
|
|
|
return s.sequence(name, ct, items...)
|
|
}
|
|
|
|
func defineChoice(s *Syntax, name string, ct CommitType, n ...*Node) error {
|
|
var refs []string
|
|
memberCT := ct&NoWhitespace | Alias
|
|
for i, ni := range n {
|
|
nmi := childName(name, i)
|
|
ref, err := defineMember(s, nmi, memberCT, ni)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
refs = append(refs, ref)
|
|
}
|
|
|
|
return s.choice(name, ct, refs...)
|
|
}
|
|
|
|
func defineExpression(s *Syntax, name string, ct CommitType, expression *Node) error {
|
|
var err error
|
|
switch expression.Name {
|
|
case "any-char":
|
|
err = s.anyChar(name, ct)
|
|
case "char-class":
|
|
err = defineClass(s, name, ct, expression.Nodes)
|
|
case "char-sequence":
|
|
err = defineCharSequence(s, name, ct, expression.Nodes)
|
|
case "symbol":
|
|
err = defineSymbol(s, name, ct, expression)
|
|
case "sequence":
|
|
err = defineSequence(s, name, ct, expression.Nodes...)
|
|
case "choice":
|
|
err = defineChoice(s, name, ct, expression.Nodes...)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func defineDefinition(s *Syntax, n *Node) error {
|
|
return defineExpression(
|
|
s,
|
|
n.Nodes[0].Text(),
|
|
flagsToCommitType(n.Nodes[1:len(n.Nodes)-1]),
|
|
n.Nodes[len(n.Nodes)-1],
|
|
)
|
|
}
|
|
|
|
func define(s *Syntax, n *Node) error {
|
|
n = dropComments(n)
|
|
|
|
for _, ni := range n.Nodes {
|
|
if err := defineDefinition(s, ni); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|