File: main.go

package info (click to toggle)
golang-github-alecthomas-participle-v2 2.1.4-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 920 kB
  • sloc: javascript: 1,164; sh: 41; makefile: 7
file content (127 lines) | stat: -rw-r--r-- 2,649 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// Package main shows an example of how to add precedence climbing to a Participle parser.
//
// Precedence climbing is an approach to parsing expressions that efficiently
// produces compact parse trees.
//
// In contrast, naive recursive descent expression parsers produce parse trees proportional in
// complexity to the number of operators supported. This impacts both readability and
// performance.
//
// It is based on https://eli.thegreenplace.net/2012/08/02/parsing-expressions-by-precedence-climbing
package main

import (
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/alecthomas/repr"

	"github.com/alecthomas/participle/v2"
	"github.com/alecthomas/participle/v2/lexer"
)

type opInfo struct {
	RightAssociative bool
	Priority         int
}

var info = map[string]opInfo{
	"+": {Priority: 1},
	"-": {Priority: 1},
	"*": {Priority: 2},
	"/": {Priority: 2},
	"^": {RightAssociative: true, Priority: 3},
}

type Expr struct {
	Terminal *int

	Left  *Expr
	Op    string
	Right *Expr
}

func (e *Expr) String() string {
	if e.Left != nil {
		return fmt.Sprintf("(%s %s %s)", e.Left, e.Op, e.Right)
	}
	return fmt.Sprintf("%d", *e.Terminal)
}

func (e *Expr) Parse(lex *lexer.PeekingLexer) error {
	*e = *parseExpr(lex, 0)
	return nil
}

// (1 + 2) * 3
func parseExpr(lex *lexer.PeekingLexer, minPrec int) *Expr {
	lhs := parseAtom(lex)
	for {
		tok := peek(lex)
		if tok.EOF() || !isOp(rune(tok.Type)) || info[tok.Value].Priority < minPrec {
			break
		}
		op := tok.Value
		nextMinPrec := info[op].Priority
		if !info[op].RightAssociative {
			nextMinPrec++
		}
		lex.Next()
		rhs := parseExpr(lex, nextMinPrec)
		lhs = parseOp(op, lhs, rhs)
	}
	return lhs
}
func parseAtom(lex *lexer.PeekingLexer) *Expr {
	tok := peek(lex)
	if tok.Type == '(' {
		lex.Next()
		val := parseExpr(lex, 1)
		if peek(lex).Value != ")" {
			panic("unmatched (")
		}
		lex.Next()
		return val
	} else if tok.EOF() {
		panic("unexpected EOF")
	} else if isOp(rune(tok.Type)) {
		panic("expected a terminal not " + tok.String())
	} else {
		lex.Next()
		n, err := strconv.ParseInt(tok.Value, 10, 64)
		if err != nil {
			panic("invalid number " + tok.Value)
		}
		in := int(n)
		return &Expr{Terminal: &in}
	}
}

func isOp(rn rune) bool {
	return strings.ContainsRune("+-*/^", rn)
}

func peek(lex *lexer.PeekingLexer) *lexer.Token {
	return lex.Peek()
}

func parseOp(op string, lhs *Expr, rhs *Expr) *Expr {
	return &Expr{
		Op:    op,
		Left:  lhs,
		Right: rhs,
	}
}

var parser = participle.MustBuild[Expr]()

func main() {
	e, err := parser.ParseString("", strings.Join(os.Args[1:], " "))
	fmt.Println(e)
	repr.Println(e)
	if err != nil {
		panic(err)
	}
}