Skip to content

Commit

Permalink
Add backtracking parser for arithmetic example
Browse files Browse the repository at this point in the history
  • Loading branch information
shivamMg committed Nov 6, 2018
1 parent afb5f48 commit c1cdd5d
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 59 deletions.
88 changes: 88 additions & 0 deletions examples/arithmetic/backtrackingparser/parser.go
@@ -0,0 +1,88 @@
package backtrackingparser

import (
"fmt"
"regexp"

"github.com/shivamMg/rd"
. "github.com/shivamMg/rd/examples/arithmetic/tokens"
)

const Grammar = `
Expr = Term "+" Expr | Term "-" Expr | Term
Term = Factor "*" Term | Factor "/" Term | Factor
Factor = "(" Expr ")" | "-" Factor | Number
`

var (
numberRegex = regexp.MustCompile(`^(\d*\.\d+|\d+)$`)
b *rd.Builder
)

func Expr() (ok bool) {
b.Enter("Expr")
defer b.Exit(&ok)

if Term() && b.Match(Plus) && Expr() {
return true
}
b.Reset()
if Term() && b.Match(Minus) && Expr() {
return true
}
b.Reset()
return Term()
}

func Term() (ok bool) {
b.Enter("Term")
defer b.Exit(&ok)

if Factor() && b.Match(Star) && Term() {
return true
}
b.Reset()
if Factor() && b.Match(Slash) && Term() {
return true
}
b.Reset()
return Factor()
}

func Factor() (ok bool) {
b.Enter("Factor")
defer b.Exit(&ok)

if b.Match(OpenParen) && Expr() && b.Match(CloseParen) {
return true
}
b.Reset()
if b.Match(Minus) && Factor() {
return true
}
return Number()
}

func Number() (ok bool) {
b.Enter("Number")
defer b.Exit(&ok)

token, ok := b.Next()
if !ok {
return false
}
if numberRegex.MatchString(fmt.Sprint(token)) {
b.Add(token)
return true
}
b.Reset()
return false
}

func Parse(tokens []rd.Token) (parseTree *rd.Tree, debugTree string, err error) {
b = rd.NewBuilder(tokens)
if ok := Expr(); !ok {
return nil, b.DebugTree().Sprint(), b.Err()
}
return b.Tree(), b.DebugTree().Sprint(), nil
}
16 changes: 4 additions & 12 deletions examples/arithmetic/main.go
Expand Up @@ -6,17 +6,9 @@ import (
"strings"

"github.com/shivamMg/rd"
"github.com/shivamMg/rd/examples/arithmetic/parser"
)

// Grammar without recursion. Left-factored. Needs single lookahead. Suitable for R.D. parsing.
const Grammar = `
Expr = Term Expr'
Expr' = "+" Expr | "-" Expr | ε
Term = Factor Term'
Term' = "*" Term | "/" Term | ε
Factor = "(" Expr ")" | "-" Factor | Number
`

func main() {
if len(os.Args) < 2 {
fmt.Println("invalid arguments. pass arithmetic expression as argument")
Expand All @@ -32,12 +24,12 @@ func main() {
printTokens(tokens)

fmt.Println("Grammar in EBNF:")
fmt.Println(Grammar)
fmt.Println(parser.Grammar)

parseTree, debugTree, err := Parse(tokens)
parseTree, debugTree, err := parser.Parse(tokens)
if err != nil {
fmt.Println("Parsing failed.", err)
fmt.Println("Debug Tree:\n", debugTree)
fmt.Print("Debug Tree:\n", debugTree, "\n")
os.Exit(1)
}
fmt.Print("Parse Tree:\n\n")
Expand Down
21 changes: 10 additions & 11 deletions examples/arithmetic/main_test.go
Expand Up @@ -4,17 +4,17 @@ import (
"testing"

"github.com/shivamMg/rd"
"github.com/shivamMg/rd/examples/arithmetic/parser"
)

func TestArithmeticExpressionsGrammar(t *testing.T) {
tokens := []rd.Token{"2.8", "+", "(", "3", "-", ".733", ")", "/", "23"}
b = rd.NewBuilder(tokens)
ok := Expr()
if !ok {
parseTree, debugTree, err := parser.Parse(tokens)
if err != nil {
t.Error("parsing failed")
}

debugTree := `Expr(true)
expectedDebugTree := `Expr(true)
├─ Term(true)
│ ├─ Factor(true)
│ │ ├─ 2.8 ≠ (
Expand Down Expand Up @@ -69,12 +69,11 @@ func TestArithmeticExpressionsGrammar(t *testing.T) {
├─ <no tokens left> ≠ +
└─ <no tokens left> ≠ -
`
got := b.DebugTree().Sprint()
if got != debugTree {
t.Errorf("invalid debug tree. expected: %s\ngot: %s\n", debugTree, got)
if debugTree != expectedDebugTree {
t.Errorf("invalid debug tree. expected: %s\ngot: %s\n", expectedDebugTree, debugTree)
}

parseTree := `Expr
expectedParseTree := `Expr
├─ Term
│ ├─ Factor
│ │ └─ Number
Expand Down Expand Up @@ -117,8 +116,8 @@ func TestArithmeticExpressionsGrammar(t *testing.T) {
└─ Expr'
└─ ε
`
got = b.Tree().Sprint()
if got != parseTree {
t.Errorf("invalid parse tree. want: %s\ngot: %s\n", parseTree, got)
got := parseTree.Sprint()
if got != expectedParseTree {
t.Errorf("invalid parse tree. want: %s\ngot: %s\n", expectedParseTree, got)
}
}
@@ -1,12 +1,21 @@
package main
package parser

import (
"fmt"
"regexp"

"github.com/shivamMg/rd"
. "github.com/shivamMg/rd/examples/arithmetic/tokens"
)

const Grammar = `
Expr = Term Expr'
Expr' = "+" Expr | "-" Expr | ε
Term = Factor Term'
Term' = "*" Term | "/" Term | ε
Factor = "(" Expr ")" | "-" Factor | Number
`

var (
numberRegex = regexp.MustCompile(`^(\d*\.\d+|\d+)$`)
b *rd.Builder
Expand Down Expand Up @@ -79,7 +88,6 @@ func Number() (ok bool) {
b.Add(token)
return true
}
b.Reset()
return false
}

Expand Down
@@ -1,4 +1,4 @@
package main
package tokens

const (
Plus = "+"
Expand Down
34 changes: 1 addition & 33 deletions examples/pl0/main.go
Expand Up @@ -10,38 +10,6 @@ import (
"github.com/shivamMg/rd/examples/pl0/parser"
)

// Grammar is PL/0's grammar in EBNF. Copied from https://en.wikipedia.org/wiki/PL/0#Grammar
const Grammar = `
program = block "." .
block =
["const" ident "=" number {"," ident "=" number} ";"]
["var" ident {"," ident} ";"]
{"procedure" ident ";" block ";"} statement .
statement =
ident ":=" expression
| "!" expression
| "?" ident
| "call" ident
| "begin" statement {";" statement } "end"
| "if" condition "then" statement
| "while" condition "do" statement .
condition =
"odd" expression
| expression ("="|"#"|"<"|"<="|">"|">=") expression .
expression = ["+"|"-"] term {("+"|"-") term} .
term = factor {("*"|"/") factor} .
factor =
ident
| number
| "(" expression ")" .
`

func main() {
if len(os.Args) != 2 {
fmt.Println("invalid arguments. pass PL/0 program file as an argument")
Expand All @@ -60,7 +28,7 @@ func main() {
}
fmt.Println("Tokens:", tokens)

fmt.Println("\nGrammar:", Grammar)
fmt.Println("\nGrammar:", parser.Grammar)

parseTree, debugTree, err := parser.Parse(tokens)
if err != nil {
Expand Down
32 changes: 32 additions & 0 deletions examples/pl0/parser/parser.go
Expand Up @@ -8,6 +8,38 @@ import (
. "github.com/shivamMg/rd/examples/pl0/tokens"
)

// Grammar is PL/0's grammar in EBNF. Copied from https://en.wikipedia.org/wiki/PL/0#Grammar
const Grammar = `
program = block "." .
block =
["const" ident "=" number {"," ident "=" number} ";"]
["var" ident {"," ident} ";"]
{"procedure" ident ";" block ";"} statement .
statement =
ident ":=" expression
| "!" expression
| "?" ident
| "call" ident
| "begin" statement {";" statement } "end"
| "if" condition "then" statement
| "while" condition "do" statement .
condition =
"odd" expression
| expression ("="|"#"|"<"|"<="|">"|">=") expression .
expression = ["+"|"-"] term {("+"|"-") term} .
term = factor {("*"|"/") factor} .
factor =
ident
| number
| "(" expression ")" .
`

func Parse(tokens []rd.Token) (parseTree *rd.Tree, debugTree string, err error) {
b := rd.NewBuilder(tokens)
if ok := Program(b); !ok {
Expand Down

0 comments on commit c1cdd5d

Please sign in to comment.