Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BNF support. #3

Merged
merged 1 commit into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions abnf/abnf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package abnf_test
import (
_ "embed"
"fmt"
"testing"

"github.com/0x51-dev/upeg/abnf"
"github.com/0x51-dev/upeg/abnf/ir"
"github.com/0x51-dev/upeg/ir"
"github.com/0x51-dev/upeg/parser"
"github.com/0x51-dev/upeg/parser/op"
"testing"
)

var (
Expand Down
5 changes: 3 additions & 2 deletions abnf/gen/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import (
"bytes"
"embed"
"fmt"
"github.com/0x51-dev/upeg/abnf"
"github.com/0x51-dev/upeg/abnf/ir"
"io"
"io/fs"
"log"
"sort"
"strings"
"text/template"
"unicode"

"github.com/0x51-dev/upeg/abnf"
"github.com/0x51-dev/upeg/ir"
)

const (
Expand Down
1 change: 1 addition & 0 deletions abnf/gen/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gen_test

import (
"fmt"

"github.com/0x51-dev/upeg/abnf/gen"
)

Expand Down
3 changes: 3 additions & 0 deletions bnf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package upeg

//go:generate go run github.com/0x51-dev/upeg/cmd/abnf --in=bnf/definition.abnf --out=bnf/definition.go --ignore=defined-as,elements,c-wsp,c-nl,element,group,rulename-br,literal-double,literal-single --importCore --package=bnf
20 changes: 20 additions & 0 deletions bnf/definition.abnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
; BNF
rulelist = 1*( rule / (*WSP c-nl) )
rule = rulename-br defined-as elements c-nl
rulename-br = "<" rulename ">"
rulename = ALPHA *(ALPHA / DIGIT / "-")
defined-as = *c-wsp ("::=") *c-wsp
elements = alternation *WSP
c-wsp = WSP / (c-nl WSP)
c-nl = comment / CRLF
comment = ";" *(WSP / VCHAR) CRLF
alternation = concatenation *(*c-wsp "|" *c-wsp concatenation)
concatenation = repetition *(1*c-wsp repetition)
repetition = element [repeat]
repeat = "*" / "+"
element = rulename-br / group / option / char-val
group = "(" *c-wsp alternation *c-wsp ")"
option = "[" *c-wsp alternation *c-wsp "]"
char-val = literal-double / literal-single
literal-double = %x22 *(%x20-21 / %x23-7E) %x22
literal-single = %x27 *(%x20-26 / %x28-7E) %x27
39 changes: 39 additions & 0 deletions bnf/definition.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Package bnf is autogenerated by https://github.com/0x51-dev/upeg. DO NOT EDIT.
package bnf

import (
. "github.com/0x51-dev/upeg/abnf/core"
"github.com/0x51-dev/upeg/parser"
"github.com/0x51-dev/upeg/parser/op"
)

var (
Rulelist = op.Capture{Name: "Rulelist", Value: op.OneOrMore{Value: op.Or{Rule, op.And{op.ZeroOrMore{Value: WSP}, CNl}}}}
Rule = op.Capture{Name: "Rule", Value: op.And{RulenameBr, DefinedAs, Elements, CNl}}
RulenameBr = op.And{'<', Rulename, '>'}
Rulename = op.Capture{Name: "Rulename", Value: op.And{ALPHA, op.ZeroOrMore{Value: op.Or{ALPHA, DIGIT, '-'}}}}
DefinedAs = op.And{op.ZeroOrMore{Value: CWsp}, "::=", op.ZeroOrMore{Value: CWsp}}
Elements = op.And{Alternation, op.ZeroOrMore{Value: WSP}}
CWsp = op.Or{WSP, op.And{CNl, WSP}}
CNl = op.Or{Comment, CRLF}
Comment = op.Capture{Name: "Comment", Value: op.And{';', op.ZeroOrMore{Value: op.Or{WSP, VCHAR}}, CRLF}}
Alternation = op.Capture{Name: "Alternation", Value: op.And{Concatenation, op.ZeroOrMore{Value: op.And{op.ZeroOrMore{Value: CWsp}, '|', op.ZeroOrMore{Value: CWsp}, Concatenation}}}}
Concatenation = op.Capture{Name: "Concatenation", Value: op.And{Repetition, op.ZeroOrMore{Value: op.And{op.OneOrMore{Value: CWsp}, Repetition}}}}
Repetition = op.Capture{Name: "Repetition", Value: op.And{Element, op.Optional{Value: Repeat}}}
Repeat = op.Capture{Name: "Repeat", Value: op.Or{'*', '+'}}
Element = op.Or{RulenameBr, Group, Option, CharVal}
Group = op.And{'(', op.ZeroOrMore{Value: CWsp}, op.Reference{Name: "Alternation"}, op.ZeroOrMore{Value: CWsp}, ')'}
Option = op.Capture{Name: "Option", Value: op.And{'[', op.ZeroOrMore{Value: CWsp}, op.Reference{Name: "Alternation"}, op.ZeroOrMore{Value: CWsp}, ']'}}
CharVal = op.Capture{Name: "CharVal", Value: op.Or{LiteralDouble, LiteralSingle}}
LiteralDouble = op.And{rune(0x22), op.ZeroOrMore{Value: op.Or{op.RuneRange{Min: 0x20, Max: 0x21}, op.RuneRange{Min: 0x23, Max: 0x7E}}}, rune(0x22)}
LiteralSingle = op.And{rune(0x27), op.ZeroOrMore{Value: op.Or{op.RuneRange{Min: 0x20, Max: 0x26}, op.RuneRange{Min: 0x28, Max: 0x7E}}}, rune(0x27)}
)

func NewParser(input []rune) (*parser.Parser, error) {
p, err := parser.New(input)
if err != nil {
return nil, err
}
p.Rules["Alternation"] = Alternation
return p, nil
}
62 changes: 28 additions & 34 deletions abnf/ir/ir.go → ir/ir.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package ir

import (
"fmt"
"github.com/0x51-dev/upeg/parser"
"strings"

"github.com/0x51-dev/upeg/parser"
)

func ParseRulename(n *parser.Node) (string, error) {
Expand Down Expand Up @@ -244,6 +245,16 @@ func ParseRepeat(n *parser.Node) (*Repeat, error) {
return nil, err
}
v := n.Value()
switch v {
case "+":
one := "1"
return &Repeat{
Min: &one,
}, nil
case "*":
return &Repeat{}, nil
}

if !strings.ContainsRune(v, '*') {
return &Repeat{
Min: &v,
Expand Down Expand Up @@ -298,65 +309,48 @@ func ParseRepetition(n *parser.Node) (*Repetition, error) {
if err := checkParent(n, "Repetition"); err != nil {
return nil, err
}
var v Element
var r *Repeat
for i, n := range n.Children() {
for _, n := range n.Children() {
switch n.Name {
case "Repeat":
if i != 0 {
return nil, NewInvalidNodeError("Repeat", n.Name)
}
repeat, err := ParseRepeat(n)
if err != nil {
return nil, err
}
r = repeat
case "Rulename":
v := Rulename(n.Value())
return &Repetition{
Repeat: r,
Value: &v,
}, nil
rn := Rulename(n.Value())
v = &rn
case "Alternation":
a, err := ParseAlternation(n)
if err != nil {
return nil, err
}
return &Repetition{
Repeat: r,
Value: a,
}, nil
v = a
case "Option":
o, err := ParseOption(n)
if err != nil {
return nil, err
}
return &Repetition{
Repeat: r,
Value: o,
}, nil
v = o
case "CharVal":
v := CharVal(n.Value())
return &Repetition{
Repeat: r,
Value: &v,
}, nil
cv := CharVal(n.Value())
v = &cv
case "NumVal":
v := NumVal(n.Children()[0].Value())
return &Repetition{
Repeat: r,
Value: &v,
}, nil
nv := NumVal(n.Children()[0].Value())
v = &nv
case "ProseVal":
v := ProseVal(n.Value())
return &Repetition{
Repeat: r,
Value: &v,
}, nil
pv := ProseVal(n.Value())
v = &pv
default:
return nil, NewInvalidNodeError("Element / Repeat", n.Name)
}
}
return nil, NewInvalidNodeError("Element / Repeat", "")
return &Repetition{
Repeat: r,
Value: v,
}, nil
}

func (r *Repetition) String() string {
Expand Down
50 changes: 48 additions & 2 deletions abnf/ir/ir_test.go → ir/ir_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,58 @@
package ir_test

import (
"testing"

"github.com/0x51-dev/upeg/abnf"
"github.com/0x51-dev/upeg/abnf/ir"
"github.com/0x51-dev/upeg/bnf"
"github.com/0x51-dev/upeg/ir"
"github.com/0x51-dev/upeg/parser"
"testing"
)

func TestBNF_Rulelist(t *testing.T) {
for _, test := range []struct {
raw string
expected string
}{
{
raw: "<X> ::= <Y>",
expected: "X = Y",
},
{
raw: "<X> ::= <Y> <Z>",
expected: "X = ( Y Z )",
},
{
raw: "<X> ::= <Y> | <Z>",
expected: "X = ( Y / Z )",
},
{
raw: "<X> ::= <Y>+",
expected: "X = 1*Y",
},
{
raw: "<X> ::= <Y>*",
expected: "X = *Y",
},
} {
p, err := parser.New([]rune(test.raw + "\n"))
if err != nil {
t.Fatal(err)
}
n, err := p.ParseEOF(bnf.Rulelist)
if err != nil {
t.Fatal(err)
}
l, err := ir.ParseRulelist(n)
if err != nil {
t.Fatal(err)
}
if l.String() != test.expected {
t.Errorf("expected %s, got %s", test.expected, l.String())
}
}
}

func TestParseAlternation(t *testing.T) {
for _, test := range []struct {
raw string
Expand Down
5 changes: 2 additions & 3 deletions parser/op/and_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package op_test

import (
"errors"
"fmt"
"testing"

"github.com/0x51-dev/upeg/parser"
"github.com/0x51-dev/upeg/parser/op"
"testing"
)

var AndTestCases = []AndTestCase{
Expand Down Expand Up @@ -84,7 +84,6 @@ func TestAnd_error(t *testing.T) {
errors.As(err, &stack)
var match *parser.NoMatchError
errors.As(stack.Errors[1], &match)
fmt.Println(stack)
if match.End.Character() != 'b' {
t.Fatalf("expected cursor to be at 'b', got %c", match.End.Character())
}
Expand Down
5 changes: 2 additions & 3 deletions parser/reader_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package parser_test

import (
"fmt"
. "github.com/0x51-dev/upeg/parser"
"testing"

. "github.com/0x51-dev/upeg/parser"
)

func TestReader(t *testing.T) {
Expand Down Expand Up @@ -104,7 +104,6 @@ func TestReader_Cursor(t *testing.T) {
lastNl: 4,
},
} {
fmt.Println(test.input)
r, err := NewReader([]rune(test.input))
if err != nil {
t.Fatal(err)
Expand Down
Loading