275 lines
5.1 KiB
Go
275 lines
5.1 KiB
Go
![]() |
package parse
|
||
|
|
||
|
import "strconv"
|
||
|
|
||
|
func runesContain(rs []rune, r rune) bool {
|
||
|
for _, ri := range rs {
|
||
|
if ri == r {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func unescapeChar(c rune) rune {
|
||
|
switch c {
|
||
|
case 'n':
|
||
|
return '\n'
|
||
|
case 't':
|
||
|
return '\t'
|
||
|
case 'b':
|
||
|
return '\b'
|
||
|
case 'f':
|
||
|
return '\f'
|
||
|
case 'r':
|
||
|
return '\r'
|
||
|
case 'v':
|
||
|
return '\v'
|
||
|
default:
|
||
|
return c
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func unescape(escape rune, banned []rune, chars []rune) ([]rune, error) {
|
||
|
var (
|
||
|
unescaped []rune
|
||
|
escaped bool
|
||
|
)
|
||
|
|
||
|
for _, ci := range chars {
|
||
|
if escaped {
|
||
|
unescaped = append(unescaped, unescapeChar(ci))
|
||
|
escaped = false
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
switch {
|
||
|
case ci == escape:
|
||
|
escaped = true
|
||
|
case runesContain(banned, ci):
|
||
|
return nil, ErrInvalidCharacter
|
||
|
default:
|
||
|
unescaped = append(unescaped, ci)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if escaped {
|
||
|
return nil, ErrInvalidCharacter
|
||
|
}
|
||
|
|
||
|
return unescaped, nil
|
||
|
}
|
||
|
|
||
|
func dropComments(n *Node) *Node {
|
||
|
ncc := *n
|
||
|
nc := &ncc
|
||
|
|
||
|
nc.Nodes = nil
|
||
|
for _, ni := range n.Nodes {
|
||
|
if ni.Name == "comment" {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
nc.Nodes = append(nc.Nodes, dropComments(ni))
|
||
|
}
|
||
|
|
||
|
return nc
|
||
|
}
|
||
|
|
||
|
func flagsToCommitType(n []*Node) CommitType {
|
||
|
var ct CommitType
|
||
|
for _, ni := range n {
|
||
|
switch ni.Name {
|
||
|
case "alias":
|
||
|
ct |= Alias
|
||
|
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 defineMembers(s *Syntax, name string, n ...*Node) ([]string, error) {
|
||
|
var refs []string
|
||
|
for i, ni := range n {
|
||
|
nmi := childName(name, i)
|
||
|
switch ni.Name {
|
||
|
case "symbol":
|
||
|
refs = append(refs, ni.Text())
|
||
|
default:
|
||
|
refs = append(refs, nmi)
|
||
|
if err := defineExpression(s, nmi, Alias, ni); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return refs, nil
|
||
|
}
|
||
|
|
||
|
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 defineQuantifier(s *Syntax, name string, ct CommitType, n *Node, q *Node) error {
|
||
|
refs, err := defineMembers(s, name, n)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
var min, max int
|
||
|
switch q.Name {
|
||
|
case "count-quantifier":
|
||
|
min, err = strconv.Atoi(q.Nodes[0].Text())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
max = min
|
||
|
case "range-quantifier":
|
||
|
min = 0
|
||
|
max = -1
|
||
|
for _, rq := range q.Nodes {
|
||
|
switch rq.Name {
|
||
|
case "range-from":
|
||
|
min, err = strconv.Atoi(rq.Text())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
case "range-to":
|
||
|
max, err = strconv.Atoi(rq.Text())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
default:
|
||
|
return ErrInvalidSyntax
|
||
|
}
|
||
|
}
|
||
|
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 s.Quantifier(name, ct, refs[0], min, max)
|
||
|
}
|
||
|
|
||
|
func defineSequence(s *Syntax, name string, ct CommitType, n ...*Node) error {
|
||
|
refs, err := defineMembers(s, name, n...)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// // TODO: try to make this expressed in the syntax (maybe as sequences need either a quantififer or not
|
||
|
// // one item? or by maintaining the excluded and caching in the sequence in a similar way when there is
|
||
|
// // only one item?) how does this effect the quantifiers?
|
||
|
// if len(refs) == 1 {
|
||
|
// return s.Choice(name, ct, refs[0])
|
||
|
// }
|
||
|
|
||
|
return s.Sequence(name, ct, refs...)
|
||
|
}
|
||
|
|
||
|
func defineChoice(s *Syntax, name string, ct CommitType, n ...*Node) error {
|
||
|
refs, err := defineMembers(s, name, n...)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
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 = defineSequence(s, name, ct, expression)
|
||
|
case "quantifier":
|
||
|
err = defineQuantifier(s, name, ct, expression.Nodes[0], expression.Nodes[1])
|
||
|
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 {
|
||
|
if n.Name != "syntax" {
|
||
|
return ErrInvalidSyntax
|
||
|
}
|
||
|
|
||
|
n = dropComments(n)
|
||
|
|
||
|
for _, ni := range n.Nodes {
|
||
|
if err := defineDefinition(s, ni); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|