Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
dsxack committed Mar 17, 2022
0 parents commit 4aa5d4b
Show file tree
Hide file tree
Showing 9 changed files with 552 additions and 0 deletions.
Binary file added .github/images/railroad.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# example-statement-parser

An statement parser with participle.

### Install

```sh
go get github.com/dsxack/example-statement-parser
```

### Usage example

```golang
package main

import (
"github.com/dsxack/example-statement-parser"
"log"
)

func Example() {
stmt := parser.Statement{}
err := parser.Parse(`#{ ${insecure} ? "http" : "https" }://${domain}/${basepath}`, &stmt)
if err != nil {
log.Fatal(err)
}
}
```

### Example of statements

* `some value 5"`
* `${some.variable}"`
* `#{ true ? "yes" : "no" }`
* `#{ ${insecure} ? ${var1} : ${var2} }`
* `#{ true ? true : false }`
* `#{ 5 ? 10 : 0 }`
* `#{ ${cond1} or ${cond2} ? ${var1} : ${var2} }`
* `#{ ${cond1} || ${cond2} ? ${var1} : ${var2} }`
* `#{ ${cond1} and ${cond2} ? ${var1} : ${var2} }`
* `#{ ${cond1} && ${cond2} ? ${var1} : ${var2} }`
* `#{ ${foo} == "bar" ? ${var1} : ${var2} }`
* `#{ (${cond1} or ${cond2}) and ${cond3} ? ${var1} : ${var2} }`
* `#{ ${cond1} and (${cond2} or ${cond3}) ? ${var1} : ${var2} }`
* `#{ ${cond1} and (${cond2} > 5 or ${cond3}) ? ${var1} : ${var2} }`
* `#{ ${insecure} ? "http" : "https" }://${domain}/${basepath}`

### Parser railway diagram

![](.github/images/railroad.png)
109 changes: 109 additions & 0 deletions ast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package parser

import (
"fmt"
"strings"
)

type Statement struct {
Fragments []Fragment `parser:"@@+"`
}

type Fragment struct {
IfStatement *IfStatement `parser:"'#{' @@ '}' "`
Variable *string `parser:"| '${' @Ident '}'"`
Literal *string `parser:"| @!(Ternary Variable)"`
}

type IfStatement struct {
Cond Expr `parser:"@@"`
Then Term `parser:"'?' @@"`
Else Term `parser:"':' @@"`
}

type Expr struct {
Left *Cond `parser:"( @@"`
Sub *Expr `parser:"| '(' @@ ')' )"`
Right []LogicExpr `parser:"@@*"`
}

type LogicExpr struct {
Operator LogicOperator `parser:"@LogicOperator"`
Cond *Cond `parser:"( @@"`
Sub *Expr `parser:"| '(' @@ ')' )"`
}

type Cond struct {
Left Term `parser:"@@"`
Operator ComparisonOperator `parser:"( @ComparisonOperator"`
Right *Term `parser:"@@ )?"`
}

type Term struct {
Variable *string `parser:"'${' @Ident '}'"`
Value *Value `parser:"| @@"`
}

type Value struct {
Number *float64 `parser:"@Number"`
String *string `parser:"| @String"`
Boolean *Boolean `parser:"| @Boolean"`
Array []Value `parser:"| '[' @@ (',' @@)* ']'"`
}

type Boolean bool

func (b *Boolean) Capture(s []string) error {
switch strings.ToUpper(s[0]) {
case "TRUE":
*b = true
case "FALSE":
*b = false
default:
return fmt.Errorf("unexpected string: %s", s[0])
}
return nil
}

type LogicOperator string

const (
AndLogicOperator LogicOperator = "AND"
OrLogicOperator LogicOperator = "OR"
)

func (operator *LogicOperator) Capture(s []string) error {
switch strings.ToUpper(s[0]) {
case "AND", "&&":
*operator = AndLogicOperator
case "OR", "||":
*operator = OrLogicOperator
default:
return fmt.Errorf("unexpected string: %s", s[0])
}
return nil
}

type ComparisonOperator string

const (
EqualComparisonOperator ComparisonOperator = "=="
NotEqualComparisonOperator ComparisonOperator = "!="
GreaterThanComparisonOperator ComparisonOperator = ">"
GreaterThanEqualComparisonOperator ComparisonOperator = ">="
LessThanComparisonOperator ComparisonOperator = "<"
LessThanEqualComparisonOperator ComparisonOperator = "<="
InComparisonOperator ComparisonOperator = "IN"
)

func comparisonOperatorKeywords() []string {
return []string{
string(EqualComparisonOperator),
string(NotEqualComparisonOperator),
string(GreaterThanComparisonOperator),
string(GreaterThanEqualComparisonOperator),
string(LessThanComparisonOperator),
string(LessThanEqualComparisonOperator),
string(InComparisonOperator),
}
}
14 changes: 14 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module github.com/dsxack/example-statement-parser

go 1.17

require (
github.com/alecthomas/participle/v2 v2.0.0-alpha7
github.com/stretchr/testify v1.4.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
)
16 changes: 16 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
github.com/alecthomas/participle/v2 v2.0.0-alpha7 h1:cK4vjj0VSgb3lN1nuKA5F7dw+1s1pWBe5bx7nNCnN+c=
github.com/alecthomas/participle/v2 v2.0.0-alpha7/go.mod h1:NumScqsC42o9x+dGj8/YqsIfhrIQjFEOFovxotbBirA=
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 h1:GDQdwm/gAcJcLAKQQZGOJ4knlw+7rfEQQcmwTbt4p5E=
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
47 changes: 47 additions & 0 deletions lexer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package parser

import (
"github.com/alecthomas/participle/v2/lexer"
"strings"
)

var statementLexer = lexer.MustStateful(lexer.Rules{
"Root": []lexer.Rule{
{Name: "Ternary", Pattern: `\#\{`, Action: lexer.Push("Ternary")},
{Name: "Variable", Pattern: `\$\{`, Action: lexer.Push("Variable")},
{Name: "Literal", Pattern: `[^\$\#]+|[\$\#][^\{][^\$\#]+|[\$\#]`},
lexer.Include("Common"),
},
"Ternary": []lexer.Rule{
{Name: "TernaryThen", Pattern: `\?`},
{Name: "TernaryElse", Pattern: `\:`},
{Name: "TernaryEnd", Pattern: `}`, Action: lexer.Pop()},
lexer.Include("Expr"),
lexer.Include("Common"),
},
"Condition": []lexer.Rule{
{Name: "Variable", Pattern: `\$\{`, Action: lexer.Push("Variable")},
{Name: "ComparisonOperator", Pattern: strings.Join(comparisonOperatorKeywords(), "|")},
{Name: "String", Pattern: `'[^']*'|"[^"]*"`},
lexer.Include("Common"),
},
"Expr": []lexer.Rule{
{Name: "LogicOperator", Pattern: `and|or|AND|OR|&&|\|\|`},
{Name: "SubExpr", Pattern: `\(`, Action: lexer.Push("SubExpr")},
lexer.Include("Condition"),
},
"SubExpr": []lexer.Rule{
lexer.Include("Expr"),
{Name: "SubExprEnd", Pattern: `\)`, Action: lexer.Pop()},
},
"Variable": []lexer.Rule{
{Name: "VariableEnd", Pattern: `}`, Action: lexer.Pop()},
lexer.Include("Common"),
},
"Common": []lexer.Rule{
{Name: "whitespace", Pattern: `\s+`},
{Name: "Number", Pattern: `(?:\d*\.)?\d+`},
{Name: "Boolean", Pattern: `false|true|TRUE|FALSE"`},
{Name: "Ident", Pattern: `[a-zA-Z][a-zA-Z0-9_\.]*`},
},
})
14 changes: 14 additions & 0 deletions parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package parser

import (
"github.com/alecthomas/participle/v2"
)

var statementParser = participle.MustBuild(&Statement{},
participle.Lexer(statementLexer),
participle.Unquote("String"),
)

func Parse(input string, stmt *Statement) error {
return statementParser.ParseString("", input, stmt)
}
Loading

0 comments on commit 4aa5d4b

Please sign in to comment.