main.mx raw

   1  package main
   2  
   3  import (
   4  	"os"
   5  
   6  	"git.smesh.lol/moxie/pkg/mxregexp"
   7  )
   8  
   9  // Reads a case file of "pattern<TAB>text" lines and prints one "i ok" line
  10  // per case, so the same file fed to a Go regexp reference can be diffed
  11  // against this output. The text column is escape-decoded (\n, \t, \r, \\)
  12  // because a real newline cannot be carried in a line-oriented file; the
  13  // pattern column is passed to the matcher verbatim.
  14  func main() {
  15  	if len(os.Args) < 2 {
  16  		println("usage: regexp_case <cases-file>")
  17  		return
  18  	}
  19  	data, rerr := os.ReadFile(os.Args[1])
  20  	if rerr != nil {
  21  		println("cannot read " | os.Args[1])
  22  		return
  23  	}
  24  	line := int32(0)
  25  	start := int32(0)
  26  	i := int32(0)
  27  	for i <= int32(len(data)) {
  28  		if i < int32(len(data)) && data[i] != '\n' {
  29  			i++
  30  			continue
  31  		}
  32  		if i > start {
  33  			tab := start
  34  			for tab < i && data[tab] != '\t' {
  35  				tab++
  36  			}
  37  			if tab >= i {
  38  				println("case " | itoa(line) | ": missing tab")
  39  				return
  40  			}
  41  			// The error text is not compared: both sides must agree only on
  42  			// whether the pattern is rejected at all.
  43  			m, merr := mxregexp.Match(data[start:tab], unescape(data[tab+1:i]))
  44  			if merr != "" {
  45  				println(itoa(line) | " error")
  46  			} else if m {
  47  				println(itoa(line) | " true")
  48  			} else {
  49  				println(itoa(line) | " false")
  50  			}
  51  		}
  52  		line++
  53  		i++
  54  		start = i
  55  	}
  56  }
  57  
  58  // grow is mxutil.Ensure inlined: the test program must not import mxutil,
  59  // whose file helpers are //export externs supplied by the compiler binary.
  60  func grow(b []byte, n int32) (r0 []byte) {
  61  	if cap(b)-len(b) >= n {
  62  		return b
  63  	}
  64  	nc := cap(b) * 2
  65  	if nc < 16 {
  66  		nc = 16
  67  	}
  68  	for nc-len(b) < n {
  69  		nc = nc * 2
  70  	}
  71  	nb := []byte{:0:nc}
  72  	copy(nb[:len(b)], b)
  73  	return nb[:len(b)]
  74  }
  75  
  76  func unescape(s string) (r string) {
  77  	needs := false
  78  	for i := int32(0); i < int32(len(s)); i++ {
  79  		if s[i] == '\\' {
  80  			needs = true
  81  			break
  82  		}
  83  	}
  84  	if !needs {
  85  		return s
  86  	}
  87  	buf := []byte{:0:int32(len(s))}
  88  	j := int32(0)
  89  	for j < int32(len(s)) {
  90  		if s[j] == '\\' && j+1 < int32(len(s)) {
  91  			c := s[j+1]
  92  			if c == 'n' {
  93  				buf = grow(buf, 1)
  94  				push(buf, '\n')
  95  				j += 2
  96  				continue
  97  			}
  98  			if c == 't' {
  99  				buf = grow(buf, 1)
 100  				push(buf, '\t')
 101  				j += 2
 102  				continue
 103  			}
 104  			if c == 'r' {
 105  				buf = grow(buf, 1)
 106  				push(buf, '\r')
 107  				j += 2
 108  				continue
 109  			}
 110  			if c == '\\' {
 111  				buf = grow(buf, 1)
 112  				push(buf, '\\')
 113  				j += 2
 114  				continue
 115  			}
 116  		}
 117  		buf = grow(buf, 1)
 118  		push(buf, s[j])
 119  		j++
 120  	}
 121  	return string(buf)
 122  }
 123  
 124  func itoa(n int32) (s string) {
 125  	if n == 0 {
 126  		return "0"
 127  	}
 128  	buf := []byte{:0:12}
 129  	for n > 0 {
 130  		buf = grow(buf, 1)
 131  		push(buf, byte('0'+n%10))
 132  		n /= 10
 133  	}
 134  	for i, j := 0, len(buf)-1; i < j; i, j = i+1, j-1 {
 135  		buf[i], buf[j] = buf[j], buf[i]
 136  	}
 137  	return string(buf)
 138  }
 139