Skip to content

Commit

Permalink
Use stack to restore after sub language
Browse files Browse the repository at this point in the history
  • Loading branch information
generikvault committed Dec 13, 2020
1 parent fa27d20 commit bb6a60f
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 100 deletions.
4 changes: 2 additions & 2 deletions evaluable.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ func constant(value interface{}) Evaluable {
// slices and
// map with int or string key.
func (p *Parser) Var(path ...Evaluable) Evaluable {
if p.currentLanguage().selector == nil {
if p.selector == nil {
return variable(path)
}
return p.currentLanguage().selector(path)
return p.selector(path)
}

// Evaluables is a slice of Evaluable.
Expand Down
14 changes: 7 additions & 7 deletions language.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import (

// Language is an expression language
type Language struct {
prefixes map[interface{}]prefix
prefixes map[interface{}]extension
operators map[string]operator
operatorSymbols map[rune]struct{}
init func(context.Context, *Parser) (Evaluable, error)
def func(context.Context, *Parser) (Evaluable, error)
init extension
def extension
selector func(Evaluables) Evaluable
}

Expand Down Expand Up @@ -46,17 +46,17 @@ func NewLanguage(bases ...Language) Language {

func newLanguage() Language {
return Language{
prefixes: map[interface{}]prefix{},
prefixes: map[interface{}]extension{},
operators: map[string]operator{},
operatorSymbols: map[rune]struct{}{},
}
}

// NewEvaluable returns an Evaluable for given expression in the specified language
func (l Language) NewEvaluable(expression string) (Evaluable, error) {
p := newParser(expression)
p := newParser(expression, l)

eval, err := p.ParseSublanguage(context.Background(), l)
eval, err := p.parse(context.Background())
if err == nil && p.isCamouflaged() && p.lastScan != scanner.EOF {
err = p.camouflage
}
Expand Down Expand Up @@ -151,7 +151,7 @@ func PrefixMetaPrefix(r rune, ext func(context.Context, *Parser) (call string, a
if err != nil {
return nil, err
}
if prefix, ok := p.currentLanguage().prefixes[l.makePrefixKey(call)]; ok {
if prefix, ok := p.prefixes[l.makePrefixKey(call)]; ok {
return prefix(c, p)
}
return alternative()
Expand Down
2 changes: 1 addition & 1 deletion operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ func (op directInfix) merge(op2 operator) operator {
return op
}

type prefix func(context.Context, *Parser) (Evaluable, error)
type extension func(context.Context, *Parser) (Evaluable, error)

type postfix struct {
operatorPrecedence
Expand Down
48 changes: 30 additions & 18 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,11 @@ func (p *Parser) ParseExpression(c context.Context) (eval Evaluable, err error)

//ParseNextExpression scans the expression ignoring following operators
func (p *Parser) ParseNextExpression(c context.Context) (eval Evaluable, err error) {
l := p.currentLanguage()

scan := p.Scan()
ex, ok := l.prefixes[scan]
ex, ok := p.prefixes[scan]
if !ok {
if scan != scanner.EOF && l.def != nil {
return l.def(c, p)
if scan != scanner.EOF && p.def != nil {
return p.def(c, p)
}
return nil, p.Expected("extensions")
}
Expand All @@ -47,17 +45,33 @@ func (p *Parser) ParseNextExpression(c context.Context) (eval Evaluable, err err
// ParseSublanguage sets the next language for this parser to parse and calls
// its initialization function, usually ParseExpression.
func (p *Parser) ParseSublanguage(c context.Context, l Language) (Evaluable, error) {
p.pushLanguage(l)
defer p.popLanguage()
if p.isCamouflaged() {
panic("can not ParseSublanguage() on camouflaged Parser")
}
curLang := p.Language
curWhitespace := p.scanner.Whitespace
curMode := p.scanner.Mode
curIsIdentRune := p.scanner.IsIdentRune

init := l.init
if init == nil {
init = func(c context.Context, p *Parser) (Evaluable, error) {
return p.ParseExpression(c)
}
p.Language = l
p.resetScannerProperties()

defer func() {
p.Language = curLang
p.scanner.Whitespace = curWhitespace
p.scanner.Mode = curMode
p.scanner.IsIdentRune = curIsIdentRune
}()

return p.parse(c)
}

func (p *Parser) parse(c context.Context) (Evaluable, error) {
if p.init != nil {
return p.init(c, p)
}

return init(c, p)
return p.ParseExpression(c)
}

func parseString(c context.Context, p *Parser) (Evaluable, error) {
Expand Down Expand Up @@ -90,15 +104,13 @@ func parseParentheses(c context.Context, p *Parser) (Evaluable, error) {
}

func (p *Parser) parseOperator(c context.Context, stack *stageStack, eval Evaluable) (st stage, err error) {
l := p.currentLanguage()

for {
scan := p.Scan()
op := p.TokenText()
mustOp := false
if l.isSymbolOperation(scan) {
if p.isSymbolOperation(scan) {
scan = p.Peek()
for l.isSymbolOperation(scan) {
for p.isSymbolOperation(scan) {
mustOp = true
op += string(scan)
p.Next()
Expand All @@ -108,7 +120,7 @@ func (p *Parser) parseOperator(c context.Context, stack *stageStack, eval Evalua
p.Camouflage("operator")
return stage{Evaluable: eval}, nil
}
operator, _ := l.operators[op]
operator, _ := p.operators[op]
switch operator := operator.(type) {
case *infix:
return stage{
Expand Down
79 changes: 9 additions & 70 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,91 +8,30 @@ import (
"unicode"
)

type parserLang struct {
lang Language
prevWhitespace uint64
prevMode uint
prevIsIdentRune func(ch rune, i int) bool
}

type parserLangStack []parserLang

func (pls *parserLangStack) push(pl parserLang) {
*pls = append(*pls, pl)
}

func (pls parserLangStack) peek() (parserLang, bool) {
if len(pls) == 0 {
return parserLang{}, false
}

return pls[len(pls)-1], true
}

func (pls *parserLangStack) pop() (parserLang, bool) {
pl, ok := pls.peek()
if !ok {
return parserLang{}, false
}

*pls = (*pls)[:len(*pls)-1]
return pl, true
}

//Parser parses expressions in a Language into an Evaluable
type Parser struct {
scanner scanner.Scanner
langs parserLangStack
scanner scanner.Scanner
Language
lastScan rune
camouflage error
}

func newParser(expression string) *Parser {
func newParser(expression string, l Language) *Parser {
sc := scanner.Scanner{}
sc.Init(strings.NewReader(expression))
sc.Error = func(*scanner.Scanner, string) { return }
sc.Filename = expression + "\t"
return &Parser{scanner: sc}
p := &Parser{scanner: sc, Language: l}
p.resetScannerProperties()
return p
}

func (p *Parser) currentLanguage() Language {
pl, ok := p.langs.peek()
if !ok {
return Language{}
}

return pl.lang
}

func (p *Parser) pushLanguage(l Language) {
if p.isCamouflaged() {
panic("can not pushLanguage() on camouflaged Parser")
}

pl := parserLang{
lang: l,
prevWhitespace: p.scanner.Whitespace,
prevMode: p.scanner.Mode,
prevIsIdentRune: p.scanner.IsIdentRune,
}
p.langs.push(pl)

func (p *Parser) resetScannerProperties() {
p.scanner.Whitespace = scanner.GoWhitespace
p.scanner.Mode = scanner.GoTokens
p.scanner.IsIdentRune = func(r rune, pos int) bool { return unicode.IsLetter(r) || r == '_' || (pos > 0 && unicode.IsDigit(r)) }
}

func (p *Parser) popLanguage() error {
pl, ok := p.langs.pop()
if !ok {
return fmt.Errorf("no language to pop")
p.scanner.IsIdentRune = func(r rune, pos int) bool {
return unicode.IsLetter(r) || r == '_' || (pos > 0 && unicode.IsDigit(r))
}

p.scanner.Whitespace = pl.prevWhitespace
p.scanner.Mode = pl.prevMode
p.scanner.IsIdentRune = pl.prevIsIdentRune

return nil
}

// SetWhitespace sets the behavior of the whitespace matcher. The given
Expand Down
3 changes: 1 addition & 2 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,7 @@ func TestParser_Scan(t *testing.T) {
}
}()

p := newParser(tt.input)
p.pushLanguage(tt.Language)
p := newParser(tt.input, tt.Language)
tt.do(p)
if tt.wantPanic {
return
Expand Down

0 comments on commit bb6a60f

Please sign in to comment.