File: node_utils.go

package info (click to toggle)
elvish 0.21.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 6,372 kB
  • sloc: javascript: 236; sh: 130; python: 104; makefile: 88; xml: 9
file content (149 lines) | stat: -rw-r--r-- 3,498 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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package eval

import (
	"src.elv.sh/pkg/diag"
	"src.elv.sh/pkg/parse"
	"src.elv.sh/pkg/parse/cmpd"
)

// Utilities for working with nodes.

func stringLiteralOrError(cp *compiler, n *parse.Compound, what string) string {
	s, err := cmpd.StringLiteralOrError(n, what)
	if err != nil {
		if len(n.Indexings) == 0 {
			cp.errorpfPartial(n, "%v", err)
		} else {
			cp.errorpf(n, "%v", err)
		}
	}
	return s
}

type argsGetter struct {
	cp *compiler
	fn *parse.Form
	ok bool
	n  int
}

func getArgs(cp *compiler, fn *parse.Form) *argsGetter {
	return &argsGetter{cp, fn, true, 0}
}

func (ag *argsGetter) errorpf(r diag.Ranger, format string, args ...any) {
	if ag.ok {
		ag.cp.errorpf(r, format, args...)
		ag.ok = false
	}
}

func (ag *argsGetter) errorpfPartial(r diag.Ranger, format string, args ...any) {
	if ag.ok {
		ag.cp.errorpfPartial(r, format, args...)
		ag.ok = false
	}
}

func (ag *argsGetter) get(i int, what string) *argAsserter {
	if ag.n < i+1 {
		ag.n = i + 1
	}
	if i >= len(ag.fn.Args) {
		ag.errorpfPartial(diag.PointRanging(ag.fn.To), "need %s", what)
		return &argAsserter{ag, what, nil}
	}
	return &argAsserter{ag, what, ag.fn.Args[i]}
}

func (ag *argsGetter) has(i int) bool { return i < len(ag.fn.Args) }

func (ag *argsGetter) hasKeyword(i int, kw string) bool {
	if i < len(ag.fn.Args) {
		s, ok := cmpd.StringLiteral(ag.fn.Args[i])
		return ok && s == kw
	}
	return false
}

func (ag *argsGetter) optionalKeywordBody(i int, kw string) *parse.Primary {
	if ag.has(i+1) && ag.hasKeyword(i, kw) {
		return ag.get(i+1, kw+" body").thunk()
	}
	return nil
}

func (ag *argsGetter) finish() bool {
	if ag.n < len(ag.fn.Args) {
		// In general, the "superfluous" argument may actually be incomplete
		// optional keywords (like "el" in an "if" form), hence this calls
		// errorpfPartial instead of errorpf.
		//
		// TODO: Make this more accurate. We should have all the information we
		// need to say that whether this is partial or not, but in order to
		// retain that information we'll probably need a more declarative API
		// for parsing special forms.
		ag.errorpfPartial(
			diag.Ranging{From: ag.fn.Args[ag.n].Range().From, To: ag.fn.To},
			"superfluous arguments")
	}
	return ag.ok
}

type argAsserter struct {
	ag   *argsGetter
	what string
	node *parse.Compound
}

func (aa *argAsserter) any() *parse.Compound {
	return aa.node
}

func (aa *argAsserter) stringLiteral() string {
	if aa.node == nil {
		return ""
	}
	s, err := cmpd.StringLiteralOrError(aa.node, aa.what)
	if err != nil {
		aa.ag.errorpf(aa.node, "%v", err)
		return ""
	}
	return s
}

func (aa *argAsserter) lambda() *parse.Primary {
	if aa.node == nil {
		return nil
	}
	lambda, ok := cmpd.Lambda(aa.node)
	if !ok {
		if p, ok := cmpd.Primary(aa.node); ok && p.Type == parse.Braced {
			// If we have seen just a "{", the parser will parse a braced
			// expression, but this could as well be the start of a lambda.
			aa.ag.errorpfPartial(aa.node,
				"%s must be lambda, found %s", aa.what, cmpd.Shape(aa.node))
		} else {
			aa.ag.errorpf(aa.node,
				"%s must be lambda, found %s", aa.what, cmpd.Shape(aa.node))
		}
		return nil
	}
	return lambda
}

func (aa *argAsserter) thunk() *parse.Primary {
	lambda := aa.lambda()
	if lambda == nil {
		return nil
	}
	if len(lambda.Elements) > 0 {
		aa.ag.errorpf(lambda, "%s must not have arguments", aa.what)
		return nil
	}
	if len(lambda.MapPairs) > 0 {
		aa.ag.errorpf(lambda, "%s must not have options", aa.what)
		return nil
	}
	return lambda
}