github.com/couchbaselabs/nex@v0.0.0-20230419191105-421cb5932838/test/nex_test.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"strings"
    11  	"testing"
    12  )
    13  
    14  var nexBin string
    15  
    16  func init() {
    17  	var err error
    18  	try := []string{
    19  		filepath.Join(os.Getenv("GOPATH"), "bin", "nex"),
    20  		filepath.Join("..", "nex"),
    21  		"nex", // look in all of PATH
    22  	}
    23  	for _, path := range try {
    24  		if path, err = exec.LookPath(path); err != nil {
    25  			continue
    26  		}
    27  		if path, err = filepath.Abs(path); err != nil {
    28  			panic(fmt.Sprintf("cannot get absolute path to nex binary: %s", err))
    29  		}
    30  		nexBin = path
    31  		return
    32  	}
    33  	panic("cannot find nex binary")
    34  }
    35  
    36  func dieErr(t *testing.T, err error, s string) {
    37  	if err != nil {
    38  		t.Fatalf("%s: %s", s, err)
    39  	}
    40  }
    41  
    42  // Test the reverse-Polish notation calculator rp.{nex,y}.
    43  func TestNexPlusYacc(t *testing.T) {
    44  	tmpdir, err := ioutil.TempDir("", "nex")
    45  	dieErr(t, err, "TempDir")
    46  	defer func() {
    47  		dieErr(t, os.RemoveAll(tmpdir), "RemoveAll")
    48  	}()
    49  	run := func(s string) {
    50  		v := strings.Split(s, " ")
    51  		err := exec.Command(v[0], v[1:]...).Run()
    52  		dieErr(t, err, s)
    53  	}
    54  	dieErr(t, copyToDir(tmpdir, "rp.nex"), "copy rp.nex")
    55  	dieErr(t, copyToDir(tmpdir, "rp.y"), "copy rp.y")
    56  	wd, err := os.Getwd()
    57  	dieErr(t, err, "Getwd")
    58  	dieErr(t, os.Chdir(tmpdir), "Chdir")
    59  	defer func() {
    60  		dieErr(t, os.Chdir(wd), "Chdir")
    61  	}()
    62  	run(nexBin + " rp.nex")
    63  	run("go tool yacc rp.y")
    64  	run("go build y.go rp.nn.go")
    65  	cmd := exec.Command("./y")
    66  	cmd.Stdin = strings.NewReader(
    67  		`1 2 3 4 + * -
    68  9 8 * 7 * 3 2 * 1 * / n
    69  `)
    70  	want := "-13\n-84\n"
    71  	got, err := cmd.CombinedOutput()
    72  	dieErr(t, err, "CombinedOutput")
    73  	if want != string(got) {
    74  		t.Fatalf("want %q, got %q", want, string(got))
    75  	}
    76  }
    77  
    78  func TestNexPrograms(t *testing.T) {
    79  	tmpdir, err := ioutil.TempDir("", "nex")
    80  	dieErr(t, err, "TempDir")
    81  	defer func() {
    82  		dieErr(t, os.RemoveAll(tmpdir), "RemoveAll")
    83  	}()
    84  
    85  	for _, x := range []struct {
    86  		prog, in, out string
    87  	}{
    88  		{"lc.nex", "no newline", "0 10\n"},
    89  		{"lc.nex", "one two three\nfour five six\n", "2 28\n"},
    90  
    91  		{"toy.nex", "if\t\n6 * 9 ==   42  then {one-line comment } else 1.23. end",
    92  			`A keyword: if
    93  An integer: 6
    94  An operator: *
    95  An integer: 9
    96  Unrecognized character: =
    97  Unrecognized character: =
    98  An integer: 42
    99  A keyword: then
   100  An identifier: else
   101  A float: 1.23
   102  Unrecognized character: .
   103  A keyword: end
   104  `},
   105  
   106  		{"wc.nex", "no newline", "0 0 0\n"},
   107  		{"wc.nex", "\n", "1 0 1\n"},
   108  		{"wc.nex", "1\na b\nA B C\n", "3 6 12\n"},
   109  		{"wc.nex", "one two three\nfour five six\n", "2 6 28\n"},
   110  
   111  		{"rob.nex",
   112  			`1 robot
   113  2 robo
   114  3 rob
   115  4 rob robot
   116  5 robot rob
   117  6 roboot
   118  `, "2 robo\n3 rob\n6 roboot\n"},
   119  
   120  		{"peter.nex",
   121  			`    #######
   122     #########
   123    ####  #####
   124   ####    ####   #
   125   ####      #####
   126  ####        ###
   127  ########   #####
   128  #### #########
   129  #### #  # ####
   130  ## #  ###   ##
   131  ###    #  ###
   132  ###    ##
   133   ##   #
   134    #   ####
   135    # #
   136  ##   #   ##
   137  `,
   138  			`rect 5 6 1 2
   139  rect 6 7 1 2
   140  rect 7 8 1 2
   141  rect 8 9 1 2
   142  rect 9 10 1 2
   143  rect 10 11 1 2
   144  rect 11 12 1 2
   145  rect 4 5 2 3
   146  rect 5 6 2 3
   147  rect 6 7 2 3
   148  rect 7 8 2 3
   149  rect 8 9 2 3
   150  rect 9 10 2 3
   151  rect 10 11 2 3
   152  rect 11 12 2 3
   153  rect 12 13 2 3
   154  rect 3 4 3 4
   155  rect 4 5 3 4
   156  rect 5 6 3 4
   157  rect 6 7 3 4
   158  rect 9 10 3 4
   159  rect 10 11 3 4
   160  rect 11 12 3 4
   161  rect 12 13 3 4
   162  rect 13 14 3 4
   163  rect 2 3 4 5
   164  rect 3 4 4 5
   165  rect 4 5 4 5
   166  rect 5 6 4 5
   167  rect 10 11 4 5
   168  rect 11 12 4 5
   169  rect 12 13 4 5
   170  rect 13 14 4 5
   171  rect 17 18 4 5
   172  rect 2 3 5 6
   173  rect 3 4 5 6
   174  rect 4 5 5 6
   175  rect 5 6 5 6
   176  rect 12 13 5 6
   177  rect 13 14 5 6
   178  rect 14 15 5 6
   179  rect 15 16 5 6
   180  rect 16 17 5 6
   181  rect 1 2 6 7
   182  rect 2 3 6 7
   183  rect 3 4 6 7
   184  rect 4 5 6 7
   185  rect 13 14 6 7
   186  rect 14 15 6 7
   187  rect 15 16 6 7
   188  rect 1 2 7 8
   189  rect 2 3 7 8
   190  rect 3 4 7 8
   191  rect 4 5 7 8
   192  rect 5 6 7 8
   193  rect 6 7 7 8
   194  rect 7 8 7 8
   195  rect 8 9 7 8
   196  rect 12 13 7 8
   197  rect 13 14 7 8
   198  rect 14 15 7 8
   199  rect 15 16 7 8
   200  rect 16 17 7 8
   201  rect 1 2 8 9
   202  rect 2 3 8 9
   203  rect 3 4 8 9
   204  rect 4 5 8 9
   205  rect 6 7 8 9
   206  rect 7 8 8 9
   207  rect 8 9 8 9
   208  rect 9 10 8 9
   209  rect 10 11 8 9
   210  rect 11 12 8 9
   211  rect 12 13 8 9
   212  rect 13 14 8 9
   213  rect 14 15 8 9
   214  rect 1 2 9 10
   215  rect 2 3 9 10
   216  rect 3 4 9 10
   217  rect 4 5 9 10
   218  rect 6 7 9 10
   219  rect 9 10 9 10
   220  rect 11 12 9 10
   221  rect 12 13 9 10
   222  rect 13 14 9 10
   223  rect 14 15 9 10
   224  rect 1 2 10 11
   225  rect 2 3 10 11
   226  rect 4 5 10 11
   227  rect 7 8 10 11
   228  rect 8 9 10 11
   229  rect 9 10 10 11
   230  rect 13 14 10 11
   231  rect 14 15 10 11
   232  rect 1 2 11 12
   233  rect 2 3 11 12
   234  rect 3 4 11 12
   235  rect 8 9 11 12
   236  rect 11 12 11 12
   237  rect 12 13 11 12
   238  rect 13 14 11 12
   239  rect 1 2 12 13
   240  rect 2 3 12 13
   241  rect 3 4 12 13
   242  rect 8 9 12 13
   243  rect 9 10 12 13
   244  rect 2 3 13 14
   245  rect 3 4 13 14
   246  rect 7 8 13 14
   247  rect 3 4 14 15
   248  rect 7 8 14 15
   249  rect 8 9 14 15
   250  rect 9 10 14 15
   251  rect 10 11 14 15
   252  rect 3 4 15 16
   253  rect 5 6 15 16
   254  rect 1 2 16 17
   255  rect 2 3 16 17
   256  rect 6 7 16 17
   257  rect 10 11 16 17
   258  rect 11 12 16 17
   259  `},
   260  		{"peter2.nex", "###\n#\n####\n", "rect 1 4 1 2\nrect 1 2 2 3\nrect 1 5 3 4\n"},
   261  		{"u.nex", "١ + ٢ + ... + ١٨ = 一百五十三", "1 + 2 + ... + 18 = 153"},
   262  	} {
   263  		cmd := exec.Command(nexBin, "-r", "-s", x.prog)
   264  		cmd.Stdin = strings.NewReader(x.in)
   265  		got, err := cmd.CombinedOutput()
   266  		dieErr(t, err, x.prog+" "+string(got))
   267  		if string(got) != x.out {
   268  			t.Fatalf("program: %s\nwant %q, got %q", x.prog, x.out, string(got))
   269  		}
   270  	}
   271  }
   272  
   273  // To save time, we combine several test cases into a single nex program.
   274  func TestGiantProgram(t *testing.T) {
   275  	tmpdir, err := ioutil.TempDir("", "nex")
   276  	dieErr(t, err, "TempDir")
   277  	wd, err := os.Getwd()
   278  	dieErr(t, err, "Getwd")
   279  	dieErr(t, os.Chdir(tmpdir), "Chdir")
   280  	defer func() {
   281  		dieErr(t, os.RemoveAll(tmpdir), "RemoveAll")
   282  	}()
   283  	defer func() {
   284  		dieErr(t, os.Chdir(wd), "Chdir")
   285  	}()
   286  	s := "package main\n"
   287  	body := ""
   288  	for i, x := range []struct {
   289  		prog, in, out string
   290  	}{
   291  		// Test parentheses and $.
   292  		{`
   293  /[a-z]*/ <  { *lval += "[" }
   294    /a(($*|$$)($($)$$$))$($$$)*/ { *lval += "0" }
   295    /(e$|f$)/ { *lval += "1" }
   296    /(qux)*/  { *lval += "2" }
   297    /$/       { *lval += "." }
   298  >           { *lval += "]" }
   299  `, "a b c d e f g aaab aaaa eeeg fffe quxqux quxq quxe",
   300  			"[0][.][.][.][1][1][.][.][0][.][1][2][2][21]"},
   301  		// Exercise ^ and rule precedence.
   302  		{`
   303  /[a-z]*/ <  { *lval += "[" }
   304    /((^*|^^)(^(^)^^^))^(^^^)*bar/ { *lval += "0" }
   305    /(^foo)*/ { *lval += "1" }
   306    /^fooo$/  { *lval += "2" }
   307    /^f(oo)*/ { *lval += "3" }
   308    /^foo*/   { *lval += "4" }
   309    /^/       { *lval += "." }
   310  >           { *lval += "]" }
   311  `, "foo bar foooo fooo fooooo fooof baz foofoo",
   312  			"[1][0][3][2][4][4][.][1]"},
   313  		// Anchored empty matches.
   314  		{`
   315  /^/ { *lval += "BEGIN" }
   316  /$/ { *lval += "END" }
   317  `, "", "BEGIN"},
   318  
   319  		{`
   320  /$/ { *lval += "END" }
   321  /^/ { *lval += "BEGIN" }
   322  `, "", "END"},
   323  
   324  		{`
   325  /^$/ { *lval += "BOTH" }
   326  /^/ { *lval += "BEGIN" }
   327  /$/ { *lval += "END" }
   328  `, "", "BOTH"},
   329  		// Built-in Line and Column counters.
   330  		// Ugly hack to import fmt.
   331  		{`"fmt"
   332  /\*/    { *lval += yySymType(fmt.Sprintf("[%d,%d]", yylex.Line(), yylex.Column())) }
   333  `,
   334  			`..*.
   335  **
   336  ...
   337  ...*.*
   338  *
   339  `, "[0,2][1,0][1,1][3,3][3,5][4,0]"},
   340  		// Patterns like awk's BEGIN and END.
   341  		{`
   342  <          { *lval += "[" }
   343    /[0-9]*/ { *lval += "N" }
   344    /;/      { *lval += ";" }
   345    /./      { *lval += "." }
   346  >          { *lval += "]\n" }
   347  `, "abc 123 xyz;a1b2c3;42", "[....N....;.N.N.N;N]\n"},
   348  		// A partial match regex has no effect on an immediately following match.
   349  		{`
   350  /abcd/ { *lval += "ABCD" }
   351  /\n/   { *lval += "\n" }
   352  `, "abcd\nbabcd\naabcd\nabcabcd\n", "ABCD\nABCD\nABCD\nABCD\n"},
   353  
   354  		// Nested regex test. The simplistic parser means we must use commented
   355  		// braces to balance out quoted braces.
   356  		// Sprinkle in a couple of return statements to check Lex() saves stack
   357  		// state correctly between calls.
   358  		{`
   359  /a[bcd]*e/ < { *lval += "[" }
   360    /a/        { *lval += "A" }
   361    /bcd/ <    { *lval += "(" }
   362    /c/        { *lval += "X"; return 1 }
   363    >          { *lval += ")" }
   364    /e/        { *lval += "E" }
   365    /ccc/ <    {
   366      *lval += "{"
   367      // }  [balance out the quoted left brace]
   368    }
   369    /./        { *lval += "?" }
   370    >          {
   371      // {  [balance out the quoted right brace]
   372      *lval += "}"
   373      return 2
   374    }
   375  >            { *lval += "]" }
   376  /\n/ { *lval += "\n" }
   377  /./ { *lval += "." }
   378  `, "abcdeabcabcdabcdddcccbbbcde", "[A(X)E].......[A(X){???}(X)E]"},
   379  
   380  		// Exercise hyphens in character classes.
   381  		{`
   382  /[a-z-]*/ < { *lval += "[" }
   383    /[^-a-df-m]/ { *lval += "0" }
   384    /./       { *lval += "1" }
   385  >           { *lval += "]" }
   386  /\n/ { *lval += "\n" }
   387  /./ { *lval += "." }
   388  `, "-azb-ycx@d--w-e-", "[11011010].[1110101]"},
   389  
   390  		// Overlapping character classes.
   391  		{`
   392  /[a-e]+[d-h]+/ { *lval += "0" }
   393  /[m-n]+[k-p]+[^k-r]+[o-p]+/ { *lval += "1" }
   394  /./ { *(*string)(lval) += yylex.Text() }
   395  `, "abcdefghijmnopabcoq", "0ij1q"},
   396  	} {
   397  		id := fmt.Sprintf("%v", i)
   398  		s += `import "./nex_test` + id + "\"\n"
   399  		dieErr(t, os.Mkdir("nex_test"+id, 0777), "Mkdir")
   400  		// Ugly hack to import packages.
   401  		prog := x.prog
   402  		importLine := ""
   403  		if prog[0] != '\n' {
   404  			v := strings.SplitN(prog, "\n", 2)
   405  			prog = v[1]
   406  			importLine = "import " + v[0]
   407  		}
   408  		dieErr(t, ioutil.WriteFile(id+".nex", []byte(prog+`//
   409  package nex_test`+id+`
   410  
   411  `+importLine+`
   412  
   413  type yySymType string
   414  
   415  func Go() {
   416    x := NewLexer(bufio.NewReader(strings.NewReader(`+"`"+x.in+"`"+`)))
   417    lval := new(yySymType)
   418    for x.Lex(lval) != 0 { }
   419    s := string(*lval)
   420    if s != `+"`"+x.out+"`"+`{
   421      panic(`+"`"+x.prog+": want "+x.out+", got ` + s"+`)
   422    }
   423  }
   424  `), 0777), "WriteFile")
   425  		_, cerr := exec.Command(nexBin, "-o", filepath.Join("nex_test"+id, "tmp.go"), id+".nex").CombinedOutput()
   426  		dieErr(t, cerr, "nex: "+s)
   427  		body += "nex_test" + id + ".Go()\n"
   428  	}
   429  	s += "func main() {\n" + body + "}\n"
   430  	err = ioutil.WriteFile("tmp.go", []byte(s), 0777)
   431  	dieErr(t, err, "WriteFile")
   432  	output, err := exec.Command("go", "run", "tmp.go").CombinedOutput()
   433  	dieErr(t, err, string(output))
   434  }
   435  
   436  func copy(dst, src string) error {
   437  	s, err := os.Open(src)
   438  	if err != nil {
   439  		return err
   440  	}
   441  	defer s.Close()
   442  	d, err := os.Create(dst)
   443  	if err != nil {
   444  		return err
   445  	}
   446  	if _, err := io.Copy(d, s); err != nil {
   447  		d.Close()
   448  		return err
   449  	}
   450  	return d.Close()
   451  }
   452  
   453  func copyToDir(dst, src string) error {
   454  	return copy(filepath.Join(dst, filepath.Base(src)), src)
   455  }