Line data Source code
1 : // Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
2 : // of this source code is governed by a BSD-style license that can be found in
3 : // the LICENSE file.
4 :
5 : // Package dsl provides facilities for parsing lisp-like domain-specific
6 : // languages (DSL).
7 : package dsl
8 :
9 : import (
10 : "fmt"
11 : "go/scanner"
12 : "go/token"
13 : "strconv"
14 : "strings"
15 :
16 : "github.com/cockroachdb/errors"
17 : )
18 :
19 : // NewParser constructs a new Parser of a lisp-like DSL.
20 2 : func NewParser[T any]() *Parser[T] {
21 2 : p := new(Parser[T])
22 2 : p.constants = make(map[string]func() T)
23 2 : p.funcs = make(map[string]func(*Parser[T], *Scanner) T)
24 2 : return p
25 2 : }
26 :
27 : // NewPredicateParser constructs a new Parser of a Lisp-like DSL, where the
28 : // resulting type implements Predicate[E]. NewPredicateParser predefines a few
29 : // useful functions: Not, And, Or, OnIndex.
30 2 : func NewPredicateParser[E any]() *Parser[Predicate[E]] {
31 2 : p := NewParser[Predicate[E]]()
32 2 : p.DefineFunc("Not", parseNot[E])
33 2 : p.DefineFunc("And", parseAnd[E])
34 2 : p.DefineFunc("Or", parseOr[E])
35 2 : p.DefineFunc("OnIndex", parseOnIndex[E])
36 2 : return p
37 2 : }
38 :
39 : // A Parser holds the rules and logic for parsing a DSL.
40 : type Parser[T any] struct {
41 : constants map[string]func() T
42 : funcs map[string]func(*Parser[T], *Scanner) T
43 : }
44 :
45 : // DefineConstant adds a new constant to the Parser's supported DSL. Whenever
46 : // the provided identifier is used within a constant context, the provided
47 : // closure is invoked to instantiate an appropriate AST value.
48 2 : func (p *Parser[T]) DefineConstant(identifier string, instantiate func() T) {
49 2 : p.constants[identifier] = instantiate
50 2 : }
51 :
52 : // DefineFunc adds a new func to the Parser's supported DSL. Whenever the
53 : // provided identifier is used within a function invocation context, the
54 : // provided closure is invoked to instantiate an appropriate AST value.
55 2 : func (p *Parser[T]) DefineFunc(identifier string, parseFunc func(*Parser[T], *Scanner) T) {
56 2 : p.funcs[identifier] = parseFunc
57 2 : }
58 :
59 : // Parse parses the provided input string.
60 1 : func (p *Parser[T]) Parse(d string) (ret T, err error) {
61 1 : defer func() {
62 1 : if r := recover(); r != nil {
63 1 : var ok bool
64 1 : err, ok = r.(error)
65 1 : if !ok {
66 0 : panic(r)
67 : }
68 : }
69 : }()
70 :
71 1 : fset := token.NewFileSet()
72 1 : file := fset.AddFile("", -1, len(d))
73 1 : var s Scanner
74 1 : s.Init(file, []byte(strings.TrimSpace(d)), nil /* no error handler */, 0)
75 1 : tok := s.Scan()
76 1 : ret = p.ParseFromPos(&s, tok)
77 1 : tok = s.Scan()
78 1 : if tok.Kind == token.SEMICOLON {
79 1 : tok = s.Scan()
80 1 : }
81 1 : assertTok(tok, token.EOF)
82 1 : return ret, err
83 : }
84 :
85 : // ParseFromPos parses from the provided current position and associated
86 : // scanner. If the parser fails to parse, it panics. This function is intended
87 : // to be used when composing Parsers of various types.
88 1 : func (p *Parser[T]) ParseFromPos(s *Scanner, tok Token) T {
89 1 : switch tok.Kind {
90 1 : case token.IDENT:
91 1 : // A constant without any parens, eg. `Reads`.
92 1 : p, ok := p.constants[tok.Lit]
93 1 : if !ok {
94 1 : panic(errors.Errorf("dsl: unknown constant %q", tok.Lit))
95 : }
96 1 : return p()
97 1 : case token.LPAREN:
98 1 : // Otherwise it's an expression, eg: (OnIndex 1)
99 1 : tok = s.Consume(token.IDENT)
100 1 : fp, ok := p.funcs[tok.Lit]
101 1 : if !ok {
102 1 : panic(errors.Errorf("dsl: unknown func %q", tok.Lit))
103 : }
104 1 : return fp(p, s)
105 0 : default:
106 0 : panic(errors.Errorf("dsl: unexpected token %s; expected IDENT or LPAREN", tok.String()))
107 : }
108 : }
109 :
110 : // A Scanner holds the scanner's internal state while processing a given text.
111 : type Scanner struct {
112 : scanner.Scanner
113 : }
114 :
115 : // Scan scans the next token and returns it.
116 1 : func (s *Scanner) Scan() Token {
117 1 : pos, tok, lit := s.Scanner.Scan()
118 1 : return Token{pos, tok, lit}
119 1 : }
120 :
121 : // Consume scans the next token. If the token is not of the provided token, it
122 : // panics. It returns the token itself.
123 1 : func (s *Scanner) Consume(expect token.Token) Token {
124 1 : t := s.Scan()
125 1 : assertTok(t, expect)
126 1 : return t
127 1 : }
128 :
129 : // ConsumeString scans the next token. It panics if the next token is not a
130 : // string, or if unable to unquote the string. It returns the unquoted string
131 : // contents.
132 1 : func (s *Scanner) ConsumeString() string {
133 1 : lit := s.Consume(token.STRING).Lit
134 1 : str, err := strconv.Unquote(lit)
135 1 : if err != nil {
136 0 : panic(errors.Newf("dsl: unquoting %q: %v", lit, err))
137 : }
138 1 : return str
139 : }
140 :
141 : // Token is a lexical token scanned from an input text.
142 : type Token struct {
143 : pos token.Pos
144 : Kind token.Token
145 : Lit string
146 : }
147 :
148 : // String implements fmt.Stringer.
149 1 : func (t *Token) String() string {
150 1 : if t.Lit != "" {
151 1 : return fmt.Sprintf("(%s, %q) at pos %v", t.Kind, t.Lit, t.pos)
152 1 : }
153 1 : return fmt.Sprintf("%s at pos %v", t.Kind, t.pos)
154 : }
155 :
156 1 : func assertTok(tok Token, expect token.Token) {
157 1 : if tok.Kind != expect {
158 1 : panic(errors.Errorf("dsl: unexpected token %s; expected %s", tok.String(), expect))
159 : }
160 : }
|