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 (135 lines) | stat: -rw-r--r-- 2,765 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
128
129
130
131
132
133
134
135
package main

import (
	"fmt"
	"strconv"
	"strings"
	"text/scanner"

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

type operatorPrec struct{ Left, Right int }

var operatorPrecs = map[string]operatorPrec{
	"+": {1, 1},
	"-": {1, 1},
	"*": {3, 2},
	"/": {5, 4},
	"%": {7, 6},
}

type (
	Expr interface{ expr() }

	ExprIdent  struct{ Name string }
	ExprString struct{ Value string }
	ExprNumber struct{ Value float64 }
	ExprParens struct{ Sub Expr }

	ExprUnary struct {
		Op  string
		Sub Expr
	}

	ExprBinary struct {
		Lhs Expr
		Op  string
		Rhs Expr
	}
)

func (ExprIdent) expr()  {}
func (ExprString) expr() {}
func (ExprNumber) expr() {}
func (ExprParens) expr() {}
func (ExprUnary) expr()  {}
func (ExprBinary) expr() {}

func parseExprAny(lex *lexer.PeekingLexer) (Expr, error) { return parseExprPrec(lex, 0) }

func parseExprAtom(lex *lexer.PeekingLexer) (Expr, error) {
	switch peek := lex.Peek(); {
	case peek.Type == scanner.Ident:
		return ExprIdent{lex.Next().Value}, nil
	case peek.Type == scanner.String:
		val, err := strconv.Unquote(lex.Next().Value)
		if err != nil {
			return nil, err
		}
		return ExprString{val}, nil
	case peek.Type == scanner.Int || peek.Type == scanner.Float:
		val, err := strconv.ParseFloat(lex.Next().Value, 64)
		if err != nil {
			return nil, err
		}
		return ExprNumber{val}, nil
	case peek.Value == "(":
		_ = lex.Next()
		inner, err := parseExprAny(lex)
		if err != nil {
			return nil, err
		}
		if lex.Peek().Value != ")" {
			return nil, fmt.Errorf("expected closing ')'")
		}
		_ = lex.Next()
		return ExprParens{inner}, nil
	default:
		return nil, participle.NextMatch
	}
}

func parseExprPrec(lex *lexer.PeekingLexer, minPrec int) (Expr, error) {
	var lhs Expr
	if peeked := lex.Peek(); peeked.Value == "-" || peeked.Value == "!" {
		op := lex.Next().Value
		atom, err := parseExprAtom(lex)
		if err != nil {
			return nil, err
		}
		lhs = ExprUnary{op, atom}
	} else {
		atom, err := parseExprAtom(lex)
		if err != nil {
			return nil, err
		}
		lhs = atom
	}

	for {
		peek := lex.Peek()
		prec, isOp := operatorPrecs[peek.Value]
		if !isOp || prec.Left < minPrec {
			break
		}
		op := lex.Next().Value
		rhs, err := parseExprPrec(lex, prec.Right)
		if err != nil {
			return nil, err
		}
		lhs = ExprBinary{lhs, op, rhs}
	}
	return lhs, nil
}

type Expression struct {
	X Expr `@@`
}

var parser = participle.MustBuild[Expression](participle.ParseTypeWith(parseExprAny))

func main() {
	var cli struct {
		Expr []string `arg required help:"Expression to parse."`
	}
	ctx := kong.Parse(&cli)

	expr, err := parser.ParseString("", strings.Join(cli.Expr, " "))
	ctx.FatalIfErrorf(err)

	repr.Println(expr)
}