pkg/parse: Turn most public methods of Node into functions.

This make the godoc of the parse package much cleaner and removes some
boilerplate.
This commit is contained in:
Qi Xiao 2020-04-25 18:55:44 +01:00
parent 4b93d4579d
commit 21f5a6f7c3
15 changed files with 67 additions and 87 deletions

View File

@ -8,6 +8,8 @@ import (
"github.com/elves/elvish/pkg/parse"
)
var parent = parse.Parent
var completers = []completer{
completeCommand,
completeIndex,
@ -28,7 +30,7 @@ type context struct {
func completeArg(n parse.Node, cfg Config) (*context, []RawItem, error) {
ev := cfg.PureEvaler
if sep, ok := n.(*parse.Sep); ok {
if form, ok := sep.Parent().(*parse.Form); ok && form.Head != nil {
if form, ok := parent(sep).(*parse.Form); ok && form.Head != nil {
// Case 1: starting a new argument.
ctx := &context{"argument", "", parse.Bareword, range0(n.Range().To)}
args := purelyEvalForm(form, "", n.Range().To, ev)
@ -38,7 +40,7 @@ func completeArg(n parse.Node, cfg Config) (*context, []RawItem, error) {
}
if primary, ok := n.(*parse.Primary); ok {
if compound, seed := primaryInSimpleCompound(primary, ev); compound != nil {
if form, ok := compound.Parent().(*parse.Form); ok {
if form, ok := parent(compound).(*parse.Form); ok {
if form.Head != nil && form.Head != compound {
// Case 2: in an incomplete argument.
ctx := &context{"argument", seed, primary.Type, compound.Range()}
@ -67,7 +69,7 @@ func completeCommand(n parse.Node, cfg Config) (*context, []RawItem, error) {
return generateForEmpty(n.Range().To)
}
if is(n, aSep) {
parent := n.Parent()
parent := parent(n)
switch {
case is(parent, aChunk), is(parent, aPipeline):
// Case 2: Just after a newline, semicolon, or a pipe.
@ -83,7 +85,7 @@ func completeCommand(n parse.Node, cfg Config) (*context, []RawItem, error) {
if primary, ok := n.(*parse.Primary); ok {
if compound, seed := primaryInSimpleCompound(primary, ev); compound != nil {
if form, ok := compound.Parent().(*parse.Form); ok {
if form, ok := parent(compound).(*parse.Form); ok {
if form.Head == compound {
// Case 4: At an already started command.
ctx := &context{
@ -107,20 +109,20 @@ func completeIndex(n parse.Node, cfg Config) (*context, []RawItem, error) {
}
if is(n, aSep) {
if is(n.Parent(), aIndexing) {
if is(parent(n), aIndexing) {
// We are just after an opening bracket.
indexing := n.Parent().(*parse.Indexing)
indexing := parent(n).(*parse.Indexing)
if len(indexing.Indicies) == 1 {
if indexee := ev.PurelyEvalPrimary(indexing.Head); indexee != nil {
return generateForEmpty(indexee, n.Range().To)
}
}
}
if is(n.Parent(), aArray) {
array := n.Parent()
if is(array.Parent(), aIndexing) {
if is(parent(n), aArray) {
array := parent(n)
if is(parent(array), aIndexing) {
// We are after an existing index and spaces.
indexing := array.Parent().(*parse.Indexing)
indexing := parent(array).(*parse.Indexing)
if len(indexing.Indicies) == 1 {
if indexee := ev.PurelyEvalPrimary(indexing.Head); indexee != nil {
return generateForEmpty(indexee, n.Range().To)
@ -134,11 +136,11 @@ func completeIndex(n parse.Node, cfg Config) (*context, []RawItem, error) {
primary := n.(*parse.Primary)
compound, seed := primaryInSimpleCompound(primary, ev)
if compound != nil {
if is(compound.Parent(), aArray) {
array := compound.Parent()
if is(array.Parent(), aIndexing) {
if is(parent(compound), aArray) {
array := parent(compound)
if is(parent(array), aIndexing) {
// We are just after an incomplete index.
indexing := array.Parent().(*parse.Indexing)
indexing := parent(array).(*parse.Indexing)
if len(indexing.Indicies) == 1 {
if indexee := ev.PurelyEvalPrimary(indexing.Head); indexee != nil {
ctx := &context{
@ -156,7 +158,7 @@ func completeIndex(n parse.Node, cfg Config) (*context, []RawItem, error) {
func completeRedir(n parse.Node, cfg Config) (*context, []RawItem, error) {
ev := cfg.PureEvaler
if is(n, aSep) {
if is(n.Parent(), aRedir) {
if is(parent(n), aRedir) {
// Empty redirection target.
ctx := &context{"redir", "", parse.Bareword, range0(n.Range().To)}
items, err := generateFileNames("", false)
@ -165,7 +167,7 @@ func completeRedir(n parse.Node, cfg Config) (*context, []RawItem, error) {
}
if primary, ok := n.(*parse.Primary); ok {
if compound, seed := primaryInSimpleCompound(primary, ev); compound != nil {
if is(compound.Parent(), &parse.Redir{}) {
if is(parent(compound), &parse.Redir{}) {
// Non-empty redirection target.
ctx := &context{
"redir", seed, primary.Type, compound.Range()}

View File

@ -25,11 +25,11 @@ var (
)
func primaryInSimpleCompound(pn *parse.Primary, ev PureEvaler) (*parse.Compound, string) {
indexing, ok := pn.Parent().(*parse.Indexing)
indexing, ok := parent(pn).(*parse.Indexing)
if !ok {
return nil, ""
}
compound, ok := indexing.Parent().(*parse.Compound)
compound, ok := parent(indexing).(*parse.Compound)
if !ok {
return nil, ""
}

View File

@ -19,7 +19,7 @@ func initHighlighter(appSpec *cli.AppSpec, ev *eval.Evaler) {
}
func check(ev *eval.Evaler, n *parse.Chunk) error {
src := &parse.Source{Name: "[tty]", Code: n.SourceText()}
src := &parse.Source{Name: "[tty]", Code: parse.SourceText(n)}
_, err := ev.Compile(n, src)
return err
}

View File

@ -7,6 +7,8 @@ import (
"github.com/elves/elvish/pkg/parse"
)
var sourceText = parse.SourceText
// Represents a region to be highlighted.
type region struct {
begin int
@ -106,7 +108,7 @@ func emitRegions(n parse.Node, f func(parse.Node, regionKind, string)) {
case *parse.Sep:
emitRegionsInSep(n, f)
}
for _, child := range n.Children() {
for _, child := range parse.Children(n) {
emitRegions(child, f)
}
}
@ -137,7 +139,7 @@ func emitRegionsInForm(n *parse.Form, f func(parse.Node, regionKind, string)) {
// TODO: This only highlights bareword special commands, however currently
// quoted special commands are also possible (e.g `"if" $true { }` is
// accepted).
switch n.Head.SourceText() {
switch sourceText(n.Head) {
case "if":
emitRegionsInIf(n, f)
case "for":
@ -155,7 +157,7 @@ func emitRegionsInIf(n *parse.Form, f func(parse.Node, regionKind, string)) {
// Highlight all "elif" and "else".
for i := 2; i < len(n.Args); i += 2 {
arg := n.Args[i]
if arg.SourceText() == "elif" || arg.SourceText() == "else" {
if s := sourceText(arg); s == "elif" || s == "else" {
f(arg, semanticRegion, keywordRegion)
}
}
@ -167,7 +169,7 @@ func emitRegionsInFor(n *parse.Form, f func(parse.Node, regionKind, string)) {
f(n.Args[0].Indexings[0].Head, semanticRegion, variableRegion)
}
// Highlight "else".
if 3 < len(n.Args) && n.Args[3].SourceText() == "else" {
if 3 < len(n.Args) && sourceText(n.Args[3]) == "else" {
f(n.Args[3], semanticRegion, keywordRegion)
}
}
@ -177,7 +179,7 @@ func emitRegionsInTry(n *parse.Form, f func(parse.Node, regionKind, string)) {
// "finally".
i := 1
matchKW := func(text string) bool {
if i < len(n.Args) && n.Args[i].SourceText() == text {
if i < len(n.Args) && sourceText(n.Args[i]) == text {
f(n.Args[i], semanticRegion, keywordRegion)
return true
}
@ -213,7 +215,7 @@ func emitRegionsInPrimary(n *parse.Primary, f func(parse.Node, regionKind, strin
}
func emitRegionsInSep(n *parse.Sep, f func(parse.Node, regionKind, string)) {
text := n.SourceText()
text := sourceText(n)
switch {
case strings.TrimSpace(text) == "":
// Don't do anything; whitespaces do not get highlighted.

View File

@ -40,7 +40,7 @@ func (aw *argsWalker) next() *parse.Compound {
// nextIs returns whether the next argument's source matches the given text. It
// also consumes the argument if it is.
func (aw *argsWalker) nextIs(text string) bool {
if aw.more() && aw.form.Args[aw.idx].SourceText() == text {
if aw.more() && parse.SourceText(aw.form.Args[aw.idx]) == text {
aw.idx++
return true
}

View File

@ -564,7 +564,7 @@ func compileTry(cp *compiler, fn *parse.Form) effectOpBody {
logger.Println("compiling try")
args := cp.walkArgs(fn)
bodyNode := args.nextMustLambda()
logger.Printf("body is %q", bodyNode.SourceText())
logger.Printf("body is %q", parse.SourceText(bodyNode))
var exceptVarNode *parse.Indexing
var exceptNode *parse.Primary
if args.nextIs("except") {

View File

@ -50,7 +50,7 @@ func (cp *compiler) pipelineOp(n *parse.Pipeline) effectOp {
cp.newLocals = saveNewLocals
return makeEffectOp(n,
&pipelineOp{n.Background, n.SourceText(), formOps, newLocals})
&pipelineOp{n.Background, parse.SourceText(n), formOps, newLocals})
}
func (cp *compiler) pipelineOps(ns []*parse.Pipeline) []effectOp {

View File

@ -255,7 +255,7 @@ func (cp *compiler) primaryOp(n *parse.Primary) valuesOp {
}
body = &variableOp{sigil != "", qname}
case parse.Wildcard:
seg, err := wildcardToSegment(n.SourceText())
seg, err := wildcardToSegment(parse.SourceText(n))
if err != nil {
cp.errorpf(n, "%s", err)
}
@ -279,7 +279,7 @@ func (cp *compiler) primaryOp(n *parse.Primary) valuesOp {
body = cp.braced(n)
default:
cp.errorpf(n, "bad PrimaryType; parser bug")
body = literalStr(n.SourceText())
body = literalStr(parse.SourceText(n))
}
return makeValuesOp(n, body)
}

View File

@ -59,7 +59,7 @@ func checkAST(n Node, want ast) error {
if i == len(wantnames)-1 {
break
}
fields := n.Children()
fields := Children(n)
if len(fields) != 1 {
return fmt.Errorf("want exactly 1 child, got %d (%s)", len(fields), summary(n))
}
@ -134,7 +134,7 @@ func checkField(got interface{}, want interface{}, ctx string) error {
func checkNodeInField(got Node, want interface{}) error {
switch want := want.(type) {
case string:
text := got.SourceText()
text := SourceText(got)
if want != text {
return fmt.Errorf("want %q, got %q (%s)", want, text, summary(got))
}

View File

@ -4,14 +4,14 @@ import "fmt"
// checkParseTree checks whether the parse tree part of a Node is well-formed.
func checkParseTree(n Node) error {
children := n.Children()
children := Children(n)
if len(children) == 0 {
return nil
}
// Parent pointers of all children should point to me.
for i, ch := range children {
if ch.Parent() != n {
if Parent(ch) != n {
return fmt.Errorf("parent of child %d (%s) is wrong: %s", i, summary(ch), summary(n))
}
}
@ -33,7 +33,7 @@ func checkParseTree(n Node) error {
}
// Check children recursively.
for _, ch := range n.Children() {
for _, ch := range Children(n) {
err := checkParseTree(ch)
if err != nil {
return err

View File

@ -4,18 +4,9 @@ import "github.com/elves/elvish/pkg/diag"
// Node represents a parse tree as well as an AST.
type Node interface {
parse(*parser)
diag.Ranger
SourceText() string
Parent() Node
Children() []Node
setFrom(int)
setTo(int)
setSourceText(string)
setParent(Node)
addChild(Node)
parse(*parser)
n() *node
}
type node struct {
@ -25,34 +16,19 @@ type node struct {
children []Node
}
func (n *node) setFrom(begin int) { n.From = begin }
func (n *node) setTo(end int) { n.To = end }
func (n *node) setSourceText(source string) { n.sourceText = source }
func (n *node) setParent(p Node) { n.parent = p }
func (n *node) n() *node { return n }
func (n *node) addChild(ch Node) { n.children = append(n.children, ch) }
// Parent returns the parent node. If the node is the root of the syntax tree,
// the parent is nil.
func (n *node) Parent() Node {
return n.parent
}
// Range returns the range within the full source text that parses to the node.
func (n *node) Range() diag.Ranging { return n.Ranging }
// Range returns the range within the original (full) source text that parses
// to the node.
func (n *node) Range() diag.Ranging {
return diag.Ranging{n.From, n.To}
}
// Parent returns the parent of a node. It returns nil if the node is the root
// of the parse tree.
func Parent(n Node) Node { return n.n().parent }
// SourceText returns the part of the source text that parses to the node.
func (n *node) SourceText() string {
return n.sourceText
}
func SourceText(n Node) string { return n.n().sourceText }
// Children returns all children of the node in the parse tree.
func (n *node) Children() []Node {
return n.children
}
func Children(n Node) []Node { return n.n().children }

View File

@ -905,7 +905,7 @@ func (*Sep) parse(*parser) {
func addSep(n Node, ps *parser) {
var begin int
ch := n.Children()
ch := Children(n)
if len(ch) > 0 {
begin = ch[len(ch)-1].Range().To
} else {
@ -971,6 +971,6 @@ func IsWhitespace(r rune) bool {
}
func addChild(p Node, ch Node) {
p.addChild(ch)
ch.setParent(p)
p.n().addChild(ch)
ch.n().parent = p
}

View File

@ -29,10 +29,10 @@ func newParser(srcname, src string) *parser {
func (ps *parser) parse(n Node) parsed {
begin := ps.pos
n.setFrom(begin)
n.n().From = begin
n.parse(ps)
n.setTo(ps.pos)
n.setSourceText(ps.src[begin:ps.pos])
n.n().To = ps.pos
n.n().sourceText = ps.src[begin:ps.pos]
return parsed{n}
}

View File

@ -11,8 +11,8 @@ import (
// position is out of bound.
func FindLeafNode(n parse.Node, p int) parse.Node {
descend:
for len(n.Children()) > 0 {
for _, ch := range n.Children() {
for len(parse.Children(n)) > 0 {
for _, ch := range parse.Children(n) {
if ch.Range().From <= p && p <= ch.Range().To {
n = ch
continue descend
@ -30,14 +30,14 @@ func Wordify(src string) []string {
}
func wordifyInner(n parse.Node, words []string) []string {
if len(n.Children()) == 0 || isCompound(n) {
text := n.SourceText()
if len(parse.Children(n)) == 0 || isCompound(n) {
text := parse.SourceText(n)
if strings.TrimFunc(text, parse.IsWhitespace) != "" {
return append(words, text)
}
return words
}
for _, ch := range n.Children() {
for _, ch := range parse.Children(n) {
words = wordifyInner(ch, words)
}
return words

View File

@ -63,9 +63,9 @@ func pprintASTRec(n Node, wr io.Writer, indent int, leading string) {
}
// has only one child and nothing more : coalesce
if len(n.Children()) == 1 &&
n.Children()[0].SourceText() == n.SourceText() {
pprintASTRec(n.Children()[0], wr, indent, leading+nt.Name()+"/")
if len(Children(n)) == 1 &&
SourceText(Children(n)[0]) == SourceText(n) {
pprintASTRec(Children(n)[0], wr, indent, leading+nt.Name()+"/")
return
}
// print heading
@ -113,19 +113,19 @@ func pprintParseTree(n Node, w io.Writer) {
func pprintParseTreeRec(n Node, wr io.Writer, indent int) {
leading := ""
for len(n.Children()) == 1 {
for len(Children(n)) == 1 {
leading += reflect.TypeOf(n).Elem().Name() + "/"
n = n.Children()[0]
n = Children(n)[0]
}
fmt.Fprintf(wr, "%*s%s%s\n", indent, "", leading, summary(n))
for _, ch := range n.Children() {
for _, ch := range Children(n) {
pprintParseTreeRec(ch, wr, indent+indentInc)
}
}
func summary(n Node) string {
return fmt.Sprintf("%s %s %d-%d", reflect.TypeOf(n).Elem().Name(),
compactQuote(n.SourceText()), n.Range().From, n.Range().To)
compactQuote(SourceText(n)), n.Range().From, n.Range().To)
}
func compactQuote(text string) string {