...

Source file src/cuelang.org/go/internal/core/adt/fields_test.go

Documentation: cuelang.org/go/internal/core/adt

     1  // Copyright 2023 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package adt_test
    16  
    17  import (
    18  	"fmt"
    19  	"path/filepath"
    20  	"strings"
    21  	"testing"
    22  
    23  	"cuelang.org/go/cue/format"
    24  	"cuelang.org/go/internal/core/adt"
    25  	"cuelang.org/go/internal/core/debug"
    26  	"cuelang.org/go/internal/core/eval"
    27  	"cuelang.org/go/internal/core/export"
    28  	"cuelang.org/go/internal/core/runtime"
    29  	"cuelang.org/go/internal/cuetest"
    30  )
    31  
    32  // TestCloseContext tests the intricacies of the closedness algorithm.
    33  // Much of this could be tested using the existing testing framework, but the
    34  // code is intricate enough that it is hard to map real-life cases onto
    35  // specific edge cases. Also, changes in the higher-level order of evaluation
    36  // may cause certain test cases to go untested.
    37  //
    38  // This test enables covering such edge cases with confidence by allowing
    39  // fine control over the order of execution of conjuncts
    40  //
    41  // NOTE: much of the code is in export_test.go. This test needs access to
    42  // higher level functionality which prevents it from being defined in
    43  // package adt. The code in export_test.go provides access to the
    44  // low-level functionality that this test needs.
    45  func TestCloseContext(t *testing.T) {
    46  	r := runtime.New()
    47  	ctx := eval.NewContext(r, nil)
    48  
    49  	v := func(s string) adt.Value {
    50  		v, _ := r.Compile(nil, s)
    51  		if err := v.Err(ctx); err != nil {
    52  			t.Fatal(err.Err)
    53  		}
    54  		v.Finalize(ctx)
    55  		return v.Value()
    56  	}
    57  	ref := func(s string) adt.Expr {
    58  		f := r.StrLabel(s)
    59  		return &adt.FieldReference{Label: f}
    60  	}
    61  	// TODO: this may be needed once we optimize inserting scalar values.
    62  	// pe := func(s string) adt.Expr {
    63  	// 	x, err := parser.ParseExpr("", s)
    64  	// 	if err != nil {
    65  	// 		t.Fatal(err)
    66  	// 	}
    67  	// 	c, err := compile.Expr(nil, r, "test", x)
    68  	// 	if err != nil {
    69  	// 		t.Fatal(err)
    70  	// 	}
    71  	// 	return c.Expr()
    72  	// }
    73  	type testCase struct {
    74  		name string
    75  		run  func(*adt.FieldTester)
    76  
    77  		// arcs shows a hierarchical representation of the arcs below the node.
    78  		arcs string
    79  
    80  		// patters shows a list of patterns and associated conjuncts
    81  		patterns string
    82  
    83  		// allowed is the computed allowed expression.
    84  		allowed string
    85  
    86  		// err holds all errors or "" if none.
    87  		err string
    88  	}
    89  	cases := []testCase{{
    90  		name: "one",
    91  		run: func(x *adt.FieldTester) {
    92  			x.Run(x.Field("a", "foo"))
    93  		},
    94  		arcs: `a: {"foo"}`,
    95  	}, {
    96  		name: "two",
    97  		run: func(x *adt.FieldTester) {
    98  			x.Run(
    99  				x.Field("a", "foo"),
   100  				x.Field("a", "bar"),
   101  			)
   102  		},
   103  		arcs: `a: {"foo" "bar"}`,
   104  	}, {
   105  		// This could be optimized as both nestings have a single value.
   106  		// This cause some provenance data to be lost, so this could be an
   107  		// option instead.
   108  		name: "double nested",
   109  		// a: "foo"
   110  		// #A
   111  		//
   112  		// where
   113  		//
   114  		// #A: {
   115  		//    #B
   116  		//    b: "foo"
   117  		// }
   118  		// #B: a: "foo"
   119  		run: func(x *adt.FieldTester) {
   120  			x.Run(
   121  				x.Field("a", "foo"),
   122  				x.EmbedDef(
   123  					x.EmbedDef(x.Field("a", "foo")),
   124  					x.Field("b", "foo"),
   125  				),
   126  			)
   127  		},
   128  		arcs: `
   129  			a: {
   130  				"foo"
   131  				[e]{
   132  					[d]{
   133  						[e]{
   134  							[d]{"foo"}
   135  						}
   136  					}
   137  				}
   138  			}
   139  			b: {
   140  				[e]{
   141  					[d]{"foo"}
   142  				}
   143  			}`,
   144  	}, {
   145  		name: "single pattern",
   146  		run: func(x *adt.FieldTester) {
   147  			x.Run(x.Pat(v(`<="foo"`), v("1")))
   148  		},
   149  		arcs:     "",
   150  		patterns: `<="foo": {1}`,
   151  		allowed:  `<="foo"`,
   152  	}, {
   153  		name: "total patterns",
   154  		run: func(x *adt.FieldTester) {
   155  			x.Run(
   156  				x.Pat(v(`<="foo"`), x.NewString("foo")),
   157  				x.Pat(v(`string`), v("1")),
   158  				x.Pat(v(`string`), v("1")),
   159  				x.Pat(v(`<="foo"`), v("2")),
   160  			)
   161  		},
   162  
   163  		arcs: "",
   164  		patterns: `
   165  			<="foo": {"foo" 2}
   166  			string: {1 1}`,
   167  
   168  		// Should be empty or string only, as all fields match.
   169  		allowed: "",
   170  	}, {
   171  		name: "multi patterns",
   172  		run: func(x *adt.FieldTester) {
   173  			shared := v("100")
   174  			x.Run(
   175  				x.Pat(v(`<="foo"`), x.NewString("foo")),
   176  				x.Pat(v(`>"bar"`), shared),
   177  				x.Pat(v(`>"bar"`), shared),
   178  				x.Pat(v(`<="foo"`), v("1")),
   179  			)
   180  		},
   181  
   182  		// should have only a single 100
   183  		patterns: `
   184  			<="foo": {"foo" 1}
   185  			>"bar": {100}`,
   186  
   187  		// TODO: normalize the output, Greater than first.
   188  		allowed: `|(<="foo", >"bar")`,
   189  	}, {
   190  		name: "pattern defined after matching field",
   191  		run: func(x *adt.FieldTester) {
   192  			x.Run(
   193  				x.Field("a", "foo"),
   194  				x.Pat(v(`string`), v(`string`)),
   195  			)
   196  		},
   197  		arcs:     `a: {"foo" string}`,
   198  		patterns: `string: {string}`,
   199  		allowed:  "", // all fields
   200  	}, {
   201  		name: "pattern defined before matching field",
   202  		run: func(x *adt.FieldTester) {
   203  			x.Run(
   204  				x.Pat(v(`string`), v(`string`)),
   205  				x.Field("a", "foo"),
   206  			)
   207  		},
   208  		arcs:     `a: {"foo" string}`,
   209  		patterns: `string: {string}`,
   210  		allowed:  "", // all fields
   211  	}, {
   212  		name: "shared on one level",
   213  		run: func(x *adt.FieldTester) {
   214  			shared := ref("shared")
   215  			x.Run(
   216  				x.Pat(v(`string`), v(`1`)),
   217  				x.Pat(v(`>"a"`), shared),
   218  				x.Pat(v(`>"a"`), shared),
   219  				x.Field("m", "foo"),
   220  				x.Pat(v(`string`), v(`2`)),
   221  				x.Pat(v(`>"a"`), shared),
   222  				x.Pat(v(`>"b"`), shared),
   223  			)
   224  		},
   225  		arcs: `m: {"foo" 1 shared 2}`,
   226  		patterns: `
   227  			string: {1 2}
   228  			>"a": {shared}
   229  			>"b": {shared}`,
   230  	}, {
   231  		// The same conjunct in different groups could result in the different
   232  		// closedness rules. Hence they should not be shared.
   233  		name: "do not share between groups",
   234  		run: func(x *adt.FieldTester) {
   235  			notShared := ref("notShared")
   236  			x.Run(
   237  				x.Def(x.Field("m", notShared)),
   238  
   239  				// TODO(perf): since the nodes in Def have strictly more
   240  				// restrictive closedness requirements, this node could be
   241  				// eliminated from arcs.
   242  				x.Field("m", notShared),
   243  
   244  				// This could be shared with the first entry, but since there
   245  				// is no mechanism to identify equality for tis case, it is
   246  				// not shared.
   247  				x.Def(x.Field("m", notShared)),
   248  
   249  				// Under some conditions the same holds for embeddings.
   250  				x.Embed(x.Field("m", notShared)),
   251  			)
   252  		},
   253  		arcs: `m: {
   254  				[d]{notShared}
   255  				notShared
   256  				[d]{notShared}
   257  				[e]{notShared}
   258  			}`,
   259  	}, {
   260  		name: "conjunction of patterns",
   261  		run: func(x *adt.FieldTester) {
   262  			x.Run(
   263  				x.Def(x.Pat(v(`>"a"`), v("1"))),
   264  				x.Def(x.Pat(v(`<"m"`), v("2"))),
   265  			)
   266  		},
   267  		patterns: `
   268  			>"a": {1}
   269  			<"m": {2}`,
   270  		// allowed reflects explicitly matched fields, even if node is not closed.
   271  		allowed: `&(>"a", <"m")`,
   272  	}, {
   273  		name: "pattern in definition in embedding",
   274  		run: func(x *adt.FieldTester) {
   275  			x.Run(
   276  				x.Embed(x.Def(x.Pat(v(`>"a"`), v("1")))),
   277  			)
   278  		},
   279  		patterns: `>"a": {1}`,
   280  		allowed:  `>"a"`,
   281  	}, {
   282  		name: "avoid duplicate pattern entries",
   283  		run: func(x *adt.FieldTester) {
   284  			x.Run(
   285  				x.Field("b", "bar"),
   286  				x.Field("b", "baz"),
   287  				x.Pat(v(`string`), v("2")),
   288  				x.Pat(v(`string`), v("3")),
   289  				x.Field("c", "bar"),
   290  				x.Field("c", "baz"),
   291  			)
   292  		},
   293  		arcs: `
   294  			b: {"bar" "baz" 2 3}
   295  			c: {"bar" 2 3 "baz"}`,
   296  
   297  		patterns: `string: {2 3}`,
   298  	}, {
   299  		name: "conjunction in embedding",
   300  		run: func(x *adt.FieldTester) {
   301  			x.Run(
   302  				x.Field("b", "foo"),
   303  				x.Embed(
   304  					x.Def(x.Pat(v(`>"a"`), v("1"))),
   305  					x.Def(x.Pat(v(`<"z"`), v("2"))),
   306  				),
   307  				x.Field("c", "bar"),
   308  				x.Pat(v(`<"q"`), v("3")),
   309  			)
   310  		},
   311  		arcs: `
   312  			b: {
   313  				"foo"
   314  				[e]{
   315  					[d]{1}
   316  					[d]{2}
   317  				}
   318  				3
   319  			}
   320  			c: {
   321  				"bar"
   322  				[ed]{
   323  					[d]{1}
   324  					[d]{2}
   325  				}
   326  				3
   327  			}`, //
   328  		patterns: `
   329  			>"a": {1}
   330  			<"z": {2}
   331  			<"q": {3}`,
   332  		allowed: `|(<"q", &(>"a", <"z"))`,
   333  	}, {
   334  		// The point of this test is to see if the "allow" expression nests
   335  		// properly.
   336  		name: "conjunctions in embedding 1",
   337  		run: func(x *adt.FieldTester) {
   338  			x.Run(
   339  				x.Def(
   340  					x.Embed(
   341  						x.Def(x.Pat(v(`=~"^[a-s]*$"`), v("3"))),
   342  						x.Def(x.Pat(v(`=~"^xxx$"`), v("4"))),
   343  					),
   344  					x.Pat(v(`=~"^b*$"`), v("4")),
   345  				),
   346  				x.Field("b", v("4")),
   347  			)
   348  		},
   349  		arcs: `b: {
   350  				4
   351  				[d]{
   352  					[ed]{
   353  						[d]{3}
   354  					}
   355  					4
   356  				}
   357  			}`,
   358  		err: ``,
   359  		patterns: `
   360  			=~"^[a-s]*$": {3}
   361  			=~"^xxx$": {4}
   362  			=~"^b*$": {4}`, allowed: `|(=~"^b*$", &(=~"^[a-s]*$", =~"^xxx$"))`,
   363  	}, {
   364  		name: "conjunctions in embedding 2",
   365  		run: func(x *adt.FieldTester) {
   366  			x.Run(
   367  				x.Embed(
   368  					x.Field("b", v("4")),
   369  					x.Embed(
   370  						x.Def(x.Pat(v(`>"b"`), v("3"))),
   371  						x.Def(x.Pat(v(`<"h"`), v("4"))),
   372  					),
   373  				),
   374  			)
   375  		},
   376  		arcs: `b: {
   377  				[e]{
   378  					4
   379  					[ed]{
   380  						[d]{4}
   381  					}
   382  				}
   383  			}`,
   384  
   385  		err: ``,
   386  		patterns: `
   387  			>"b": {3}
   388  			<"h": {4}`,
   389  	}, {
   390  		name: "conjunctions in embedding 3",
   391  		run: func(x *adt.FieldTester) {
   392  			x.Run(
   393  				x.Embed(
   394  					x.Def(
   395  						x.Field("b", "foo"),
   396  						x.Embed(
   397  							x.Def(x.Pat(v(`>"a"`), v("1"))),
   398  							x.Def(x.Pat(v(`<"g"`), v("2"))),
   399  							x.Pat(v(`<"z"`), v("3")),
   400  						),
   401  						x.Embed(
   402  							x.Def(x.Pat(v(`>"b"`), v("3"))),
   403  							x.Def(x.Pat(v(`<"h"`), v("4"))),
   404  						),
   405  						x.Field("c", "bar"),
   406  						x.Pat(v(`<"q"`), v("5")),
   407  					),
   408  					x.Def(
   409  						x.Embed(
   410  							x.Def(x.Pat(v(`>"m"`), v("6"))),
   411  							x.Def(x.Pat(v(`<"y"`), v("7"))),
   412  							x.Pat(v(`<"z"`), v("8")),
   413  						),
   414  						x.Embed(
   415  							x.Def(x.Pat(v(`>"n"`), v("9"))),
   416  							x.Def(x.Pat(v(`<"z"`), v("10"))),
   417  						),
   418  						x.Field("c", "bar"),
   419  						x.Pat(v(`<"q"`), v("11")),
   420  					),
   421  				),
   422  				x.Pat(v(`<"h"`), v("12")),
   423  			)
   424  		},
   425  		arcs: `
   426  			b: {
   427  				[e]{
   428  					[d]{
   429  						"foo"
   430  						[e]{
   431  							[d]{1}
   432  							[d]{2}
   433  							3
   434  						}
   435  						[ed]{
   436  							[d]{4}
   437  						}
   438  						5
   439  					}
   440  					[d]{
   441  						[ed]{
   442  							[d]{7}
   443  							8
   444  						}
   445  						[ed]{
   446  							[d]{10}
   447  						}
   448  						11
   449  					}
   450  				}
   451  				12
   452  			}
   453  			c: {
   454  				[e]{
   455  					[d]{
   456  						"bar"
   457  						[ed]{
   458  							[d]{1}
   459  							[d]{2}
   460  							3
   461  						}
   462  						[ed]{
   463  							[d]{3}
   464  							[d]{4}
   465  						}
   466  						5
   467  					}
   468  					[d]{
   469  						[ed]{
   470  							[d]{7}
   471  							8
   472  						}
   473  						[ed]{
   474  							[d]{10}
   475  						}
   476  						"bar"
   477  						11
   478  					}
   479  				}
   480  				12
   481  			}`,
   482  		patterns: `
   483  			>"a": {1}
   484  			<"g": {2}
   485  			<"z": {3 8 10}
   486  			>"b": {3}
   487  			<"h": {4 12}
   488  			<"q": {5 11}
   489  			>"m": {6}
   490  			<"y": {7}
   491  			>"n": {9}`,
   492  		allowed: `|(<"h", &(|(<"q", &(>"b", <"h"), &(>"a", <"g")), |(<"q", &(>"n", <"z"), &(>"m", <"y"))))`,
   493  	}, {
   494  		name: "dedup equal",
   495  		run: func(x *adt.FieldTester) {
   496  			shared := v("1")
   497  			x.Run(
   498  				x.FieldDedup("a", shared),
   499  				x.FieldDedup("a", shared),
   500  			)
   501  		},
   502  		patterns: "",
   503  		allowed:  "",
   504  		arcs:     `a: {1}`,
   505  	}, {
   506  		// Effectively {a: 1} & #D, where #D is {b: 1}
   507  		name: "disallowed before",
   508  		run: func(x *adt.FieldTester) {
   509  			x.Run(
   510  				x.Field("a", v("1")),
   511  				x.Def(x.Field("b", v("1"))),
   512  			)
   513  		},
   514  		arcs: `
   515  			a: {1}
   516  			b: {
   517  				[d]{1}
   518  			}`,
   519  		err: `a: field not allowed`,
   520  	}, {
   521  		// Effectively #D & {a: 1}, where #D is {b: 1}
   522  		name: "disallowed after",
   523  		run: func(x *adt.FieldTester) {
   524  			x.Run(
   525  				x.Def(x.Field("b", v("1"))),
   526  				x.Field("a", v("1")),
   527  			)
   528  		},
   529  		arcs: `
   530  			b: {
   531  				[d]{1}
   532  			}
   533  			a: {1}`,
   534  		err: `a: field not allowed`,
   535  	}, {
   536  		// a: {#A}
   537  		// a: c: 1
   538  		// #A: b: 1
   539  		name: "def embed",
   540  		run: func(x *adt.FieldTester) {
   541  			x.Run(
   542  				x.Group(x.EmbedDef(x.Field("b", "foo"))),
   543  				x.Field("c", "bar"),
   544  			)
   545  		},
   546  		arcs: `
   547  			b: {
   548  				[]{
   549  					[e]{
   550  						[d]{"foo"}
   551  					}
   552  				}
   553  			}
   554  			c: {"bar"}`,
   555  		err: `c: field not allowed`,
   556  	}, {
   557  		// a: {#A}
   558  		// a: c: 1
   559  		// #A: b: 1
   560  		name: "def embed",
   561  		run: func(x *adt.FieldTester) {
   562  			x.Run(
   563  				x.Group(x.EmbedDef(x.Field("b", "foo")),
   564  					x.Field("c", "foo"),
   565  				),
   566  				x.Field("d", "bar"),
   567  			)
   568  		},
   569  		arcs: `
   570  			b: {
   571  				[]{
   572  					[e]{
   573  						[d]{"foo"}
   574  					}
   575  				}
   576  			}
   577  			c: {
   578  				[d]{"foo"}
   579  			}
   580  			d: {"bar"}`,
   581  		err: `d: field not allowed`,
   582  	}, {
   583  		// This test is for debugging and can be changed.
   584  		name: "X",
   585  		run: func(x *adt.FieldTester) {
   586  			x.Run(
   587  				x.Field("b", "foo"),
   588  				x.Embed(
   589  					x.Def(x.Pat(v(`>"b"`), v("3"))),
   590  					x.Def(x.Pat(v(`<"h"`), v("4"))),
   591  				),
   592  			)
   593  		},
   594  		// TODO: could probably be b: {"foo" 4}. See TODO(constraintNode).
   595  		arcs: `b: {
   596  				"foo"
   597  				[ed]{
   598  					[d]{4}
   599  				}
   600  			}`,
   601  		err: ``,
   602  		patterns: `
   603  			>"b": {3}
   604  			<"h": {4}`,
   605  		allowed: `&(>"b", <"h")`,
   606  	}}
   607  
   608  	cuetest.Run(t, cases, func(t *cuetest.T, tc *testCase) {
   609  		// Uncomment to debug isolated test X.
   610  		// adt.Debug = true
   611  		// adt.Verbosity = 2
   612  		// t.Select("X")
   613  
   614  		adt.DebugDeps = true
   615  		showGraph := false
   616  		x := adt.NewFieldTester(r)
   617  		tc.run(x)
   618  
   619  		ctx := x.OpContext
   620  
   621  		switch graph, hasError := adt.CreateMermaidGraph(ctx, x.Root, true); {
   622  		case !hasError:
   623  		case showGraph:
   624  			path := filepath.Join(".debug", "TestCloseContext", tc.name)
   625  			adt.OpenNodeGraph(tc.name, path, "in", "out", graph)
   626  			fallthrough
   627  		default:
   628  			t.Errorf("imbalanced counters")
   629  		}
   630  
   631  		t.Equal(writeArcs(x, x.Root), tc.arcs)
   632  		t.Equal(x.Error(), tc.err)
   633  
   634  		var patterns, allowed string
   635  		if pcs := x.Root.PatternConstraints; pcs != nil {
   636  			patterns = writePatterns(x, pcs.Pairs)
   637  			if pcs.Allowed != nil {
   638  				allowed = debug.NodeString(r, pcs.Allowed, nil)
   639  				// TODO: output is nicer, but need either parenthesis or
   640  				// removed spaces around more tightly bound expressions.
   641  				// allowed = pExpr(x, pcs.Allowed)
   642  			}
   643  		}
   644  
   645  		t.Equal(patterns, tc.patterns)
   646  		t.Equal(allowed, tc.allowed)
   647  	})
   648  }
   649  
   650  const (
   651  	initialIndent = 3
   652  	indentString  = "\t"
   653  )
   654  
   655  func writeArcs(x adt.Runtime, v *adt.Vertex) string {
   656  	b := &strings.Builder{}
   657  	for _, a := range v.Arcs {
   658  		if len(v.Arcs) > 1 {
   659  			fmt.Fprint(b, "\n", strings.Repeat(indentString, initialIndent))
   660  		}
   661  		fmt.Fprintf(b, "%s: ", a.Label.RawString(x))
   662  
   663  		// TODO(perf): optimize this so that a single-element conjunct does
   664  		// not need a group.
   665  		if len(a.Conjuncts) != 1 {
   666  			panic("unexpected conjunct length")
   667  		}
   668  		g := a.Conjuncts[0].Elem().(*adt.ConjunctGroup)
   669  		vertexString(x, b, *g, initialIndent)
   670  	}
   671  	return b.String()
   672  }
   673  
   674  func writePatterns(x adt.Runtime, pairs []adt.PatternConstraint) string {
   675  	b := &strings.Builder{}
   676  	for _, pc := range pairs {
   677  		if len(pairs) > 1 {
   678  			fmt.Fprint(b, "\n", strings.Repeat(indentString, initialIndent))
   679  		}
   680  		b.WriteString(pExpr(x, pc.Pattern))
   681  		b.WriteString(": ")
   682  		vertexString(x, b, pc.Constraint.Conjuncts, initialIndent)
   683  	}
   684  	return b.String()
   685  }
   686  
   687  func hasVertex(a []adt.Conjunct) bool {
   688  	for _, c := range a {
   689  		switch c.Elem().(type) {
   690  		case *adt.ConjunctGroup:
   691  			return true
   692  		case *adt.Vertex:
   693  			return true
   694  		}
   695  	}
   696  	return false
   697  }
   698  
   699  func vertexString(x adt.Runtime, b *strings.Builder, a []adt.Conjunct, indent int) {
   700  	hasVertex := hasVertex(a)
   701  
   702  	b.WriteString("{")
   703  	for i, c := range a {
   704  		if g, ok := c.Elem().(*adt.ConjunctGroup); ok {
   705  			if hasVertex {
   706  				doIndent(b, indent+1)
   707  			}
   708  			b.WriteString("[")
   709  			if c.CloseInfo.FromEmbed {
   710  				b.WriteString("e")
   711  			}
   712  			if c.CloseInfo.FromDef {
   713  				b.WriteString("d")
   714  			}
   715  			b.WriteString("]")
   716  			vertexString(x, b, *g, indent+1)
   717  		} else {
   718  			if hasVertex {
   719  				doIndent(b, indent+1)
   720  			} else if i > 0 {
   721  				b.WriteString(" ")
   722  			}
   723  			b.WriteString(pExpr(x, c.Expr()))
   724  		}
   725  	}
   726  	if hasVertex {
   727  		doIndent(b, indent)
   728  	}
   729  	b.WriteString("}")
   730  }
   731  
   732  func doIndent(b *strings.Builder, indent int) {
   733  	fmt.Fprint(b, "\n", strings.Repeat(indentString, indent))
   734  }
   735  
   736  func pExpr(x adt.Runtime, e adt.Expr) string {
   737  	a, _ := export.All.Expr(x, "test", e)
   738  	b, _ := format.Node(a)
   739  	return string(b)
   740  }
   741  

View as plain text