9fans.net/go@v0.0.7/cmd/sam/cmd.go (about)

     1  // #include "sam.h"
     2  // #include "parse.h"
     3  
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"strings"
    10  	"unicode/utf8"
    11  )
    12  
    13  var linex = "\n"
    14  var wordx = " \t\n"
    15  
    16  var cmdtab []Cmdtab = nil
    17  
    18  func init() { cmdtab = cmdtab1 } // break init loop
    19  
    20  var cmdtab1 = []Cmdtab{
    21  	/*	cmdc	text	regexp	addr	defcmd	defaddr	count	token	 fn	*/
    22  	{'\n', false, false, false, 0, aDot, 0, "", nl_cmd},
    23  	{'a', true, false, false, 0, aDot, 0, "", a_cmd},
    24  	{'b', false, false, false, 0, aNo, 0, linex, b_cmd},
    25  	{'B', false, false, false, 0, aNo, 0, linex, b_cmd},
    26  	{'c', true, false, false, 0, aDot, 0, "", c_cmd},
    27  	{'d', false, false, false, 0, aDot, 0, "", d_cmd},
    28  	{'D', false, false, false, 0, aNo, 0, linex, D_cmd},
    29  	{'e', false, false, false, 0, aNo, 0, wordx, e_cmd},
    30  	{'f', false, false, false, 0, aNo, 0, wordx, f_cmd},
    31  	{'g', false, true, false, 'p', aDot, 0, "", g_cmd},
    32  	{'i', true, false, false, 0, aDot, 0, "", i_cmd},
    33  	{'k', false, false, false, 0, aDot, 0, "", k_cmd},
    34  	{'m', false, false, true, 0, aDot, 0, "", m_cmd},
    35  	{'n', false, false, false, 0, aNo, 0, "", n_cmd},
    36  	{'p', false, false, false, 0, aDot, 0, "", p_cmd},
    37  	{'q', false, false, false, 0, aNo, 0, "", q_cmd},
    38  	{'r', false, false, false, 0, aDot, 0, wordx, e_cmd},
    39  	{'s', false, true, false, 0, aDot, 1, "", s_cmd},
    40  	{'t', false, false, true, 0, aDot, 0, "", m_cmd},
    41  	{'u', false, false, false, 0, aNo, 2, "", u_cmd},
    42  	{'v', false, true, false, 'p', aDot, 0, "", g_cmd},
    43  	{'w', false, false, false, 0, aAll, 0, wordx, w_cmd},
    44  	{'x', false, true, false, 'p', aDot, 0, "", x_cmd},
    45  	{'y', false, true, false, 'p', aDot, 0, "", x_cmd},
    46  	{'X', false, true, false, 'f', aNo, 0, "", X_cmd},
    47  	{'Y', false, true, false, 'f', aNo, 0, "", X_cmd},
    48  	{'!', false, false, false, 0, aNo, 0, linex, plan9_cmd},
    49  	{'>', false, false, false, 0, aDot, 0, linex, plan9_cmd},
    50  	{'<', false, false, false, 0, aDot, 0, linex, plan9_cmd},
    51  	{'|', false, false, false, 0, aDot, 0, linex, plan9_cmd},
    52  	{'=', false, false, false, 0, aDot, 0, linex, eq_cmd},
    53  	{'c' | 0x100, false, false, false, 0, aNo, 0, wordx, cd_cmd},
    54  }
    55  
    56  var line = make([]rune, 0, BLOCKSIZE)
    57  var linep int // index in line
    58  
    59  var termline [BLOCKSIZE]rune
    60  var terminp int  // write index in termline
    61  var termoutp int // read index in termline
    62  
    63  var cmdlist []*Cmd
    64  var addrlist []*Addr
    65  var relist []*String
    66  var stringlist []*String
    67  
    68  var eof bool
    69  
    70  func resetcmd() {
    71  	linep = 0
    72  	line = line[:0]
    73  	termoutp = 0
    74  	terminp = 0
    75  	freecmd()
    76  }
    77  
    78  func inputc() rune {
    79  Again:
    80  	nbuf := 0
    81  	var r rune
    82  	if downloaded {
    83  		for termoutp == terminp {
    84  			cmdupdate()
    85  			if patset {
    86  				tellpat()
    87  			}
    88  			for termlocked > 0 {
    89  				outT0(Hunlock)
    90  				termlocked--
    91  			}
    92  			if !rcv() {
    93  				return -1
    94  			}
    95  		}
    96  		r = termline[termoutp]
    97  		termoutp++
    98  		if termoutp == terminp {
    99  			termoutp = 0
   100  			terminp = 0
   101  		}
   102  	} else {
   103  		var buf [utf8.UTFMax]byte
   104  		for {
   105  			n, err := os.Stdin.Read(buf[nbuf : nbuf+1])
   106  			if err != nil || n <= 0 {
   107  				return -1
   108  			}
   109  			nbuf += n
   110  			if utf8.FullRune(buf[:nbuf]) {
   111  				break
   112  			}
   113  		}
   114  		r, _ = utf8.DecodeRune(buf[:])
   115  	}
   116  	if r == 0 {
   117  		warn(Wnulls)
   118  		goto Again
   119  	}
   120  	return r
   121  }
   122  
   123  var Dflag bool
   124  
   125  func debug(format string, args ...interface{}) {
   126  	if !Dflag {
   127  		return
   128  	}
   129  	s := fmt.Sprintf(format, args...)
   130  	if !strings.HasSuffix(s, "\n") {
   131  		s += "\n"
   132  	}
   133  	os.Stderr.WriteString(s)
   134  }
   135  
   136  func inputline() int {
   137  	/*
   138  	 * Could set linep = line and i = 0 here and just
   139  	 * error(Etoolong) below, but this way we keep
   140  	 * old input buffer history around for a while.
   141  	 * This is useful only for debugging.
   142  	 */
   143  	i := linep
   144  	for {
   145  		c := inputc()
   146  		if c <= 0 {
   147  			return -1
   148  		}
   149  		if i >= cap(line) {
   150  			if linep == 0 {
   151  				error_(Etoolong)
   152  			}
   153  			i = copy(line[0:], line[linep:])
   154  			linep = 0
   155  		}
   156  		line = append(line, c)
   157  		if c == '\n' {
   158  			break
   159  		}
   160  	}
   161  	debug("input: %q\n", string(line[linep:]))
   162  	return 1
   163  }
   164  
   165  func getch() rune {
   166  	if eof {
   167  		return -1
   168  	}
   169  	if linep == len(line) && inputline() < 0 {
   170  		eof = true
   171  		return -1
   172  	}
   173  	r := line[linep]
   174  	debug("getch %d %q\n", linep, r)
   175  	linep++
   176  	return r
   177  }
   178  
   179  func nextc() rune {
   180  	if linep >= len(line) {
   181  		return -1
   182  	}
   183  	return line[linep]
   184  }
   185  
   186  func ungetch() {
   187  	linep--
   188  	if linep < 0 {
   189  		panic_("ungetch")
   190  	}
   191  	// debug("ungetch %d %q\n", linep, line[linep])
   192  }
   193  
   194  func getnum(signok int) Posn {
   195  	n := 0
   196  	sign := 1
   197  	if signok > 1 && nextc() == '-' {
   198  		sign = -1
   199  		getch()
   200  	}
   201  	c := nextc()
   202  	if c < '0' || '9' < c { /* no number defaults to 1 */
   203  		return sign
   204  	}
   205  	for {
   206  		c = getch()
   207  		if !('0' <= c) || !(c <= '9') {
   208  			break
   209  		}
   210  		n = n*10 + int(c-'0')
   211  	}
   212  	ungetch()
   213  	return sign * n
   214  }
   215  
   216  func skipbl() rune {
   217  	var c rune
   218  	for {
   219  		c = getch()
   220  		if !(c == ' ') && !(c == '\t') {
   221  			break
   222  		}
   223  	}
   224  	if c >= 0 {
   225  		ungetch()
   226  	}
   227  	return c
   228  }
   229  
   230  func termcommand() {
   231  	for p := cmdpt; p < cmd.b.nc; p++ {
   232  		if terminp >= len(termline) {
   233  			cmdpt = cmd.b.nc
   234  			error_(Etoolong)
   235  		}
   236  		termline[terminp] = filereadc(cmd, p)
   237  		terminp++
   238  	}
   239  	cmdpt = cmd.b.nc
   240  }
   241  
   242  func cmdloop() {
   243  	for {
   244  		if !downloaded && curfile != nil && curfile.unread {
   245  			load(curfile)
   246  		}
   247  		cmdp := parsecmd(0)
   248  		if cmdp == nil {
   249  			if downloaded {
   250  				rescue()
   251  				os.Exit(1) // "eof"
   252  			}
   253  			break
   254  		}
   255  		ocurfile := curfile
   256  		loaded := curfile != nil && !curfile.unread
   257  		if !cmdexec(curfile, cmdp) {
   258  			break
   259  		}
   260  		freecmd()
   261  		cmdupdate()
   262  		update()
   263  		if downloaded && curfile != nil && (ocurfile != curfile || (!loaded && !curfile.unread)) {
   264  			outTs(Hcurrent, curfile.tag)
   265  		}
   266  		/* don't allow type ahead on files that aren't bound */
   267  		if downloaded && curfile != nil && curfile.rasp == nil {
   268  			terminp = termoutp
   269  		}
   270  	}
   271  }
   272  
   273  func newcmd() *Cmd {
   274  	p := new(Cmd)
   275  	cmdlist = append(cmdlist, p)
   276  	return p
   277  }
   278  
   279  func newaddr() *Addr {
   280  	p := new(Addr)
   281  	addrlist = append(addrlist, p)
   282  	return p
   283  }
   284  
   285  func newre() *String {
   286  	p := new(String)
   287  	relist = append(relist, p)
   288  	return p
   289  }
   290  
   291  func newstring() *String {
   292  	p := new(String)
   293  	stringlist = append(stringlist, p)
   294  	return p
   295  }
   296  
   297  func freecmd() {
   298  	for _, c := range cmdlist {
   299  		// free(c)
   300  		_ = c
   301  	}
   302  	cmdlist = cmdlist[:0]
   303  	for _, a := range addrlist {
   304  		// free(a)
   305  		_ = a
   306  	}
   307  	addrlist = addrlist[:0]
   308  	for _, s := range relist {
   309  		Strclose(s)
   310  		// free(s)
   311  	}
   312  	relist = relist[:0]
   313  	for _, s := range stringlist {
   314  		Strclose(s)
   315  		// free(s)
   316  	}
   317  	stringlist = stringlist[:0]
   318  }
   319  
   320  func lookup(c rune) int {
   321  	for i := range cmdtab {
   322  		if cmdtab[i].cmdc == c {
   323  			return i
   324  		}
   325  	}
   326  	return -1
   327  }
   328  
   329  func okdelim(c rune) {
   330  	if c == '\\' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') {
   331  		error_c(Edelim, c)
   332  	}
   333  }
   334  
   335  func atnl() {
   336  	skipbl()
   337  	if c := getch(); c != '\n' {
   338  		error_(Enewline)
   339  	}
   340  }
   341  
   342  func getrhs(s *String, delim rune, cmd int) {
   343  	for {
   344  		c := getch()
   345  		if !(c > 0 && c != delim) || !(c != '\n') {
   346  			break
   347  		}
   348  		if c == '\\' {
   349  			c = getch()
   350  			if c <= 0 {
   351  				error_(Ebadrhs)
   352  			}
   353  			if c == '\n' {
   354  				ungetch()
   355  				c = '\\'
   356  			} else if c == 'n' {
   357  				c = '\n'
   358  			} else if c != delim && (cmd == 's' || c != '\\') { /* s does its own */
   359  				Straddc(s, '\\')
   360  			}
   361  		}
   362  		Straddc(s, c)
   363  	}
   364  	ungetch() /* let client read whether delimeter, '\n' or whatever */
   365  }
   366  
   367  func collecttoken(end string) *String {
   368  	s := newstring()
   369  	var c rune
   370  	for {
   371  		c = nextc()
   372  		if !(c == ' ') && !(c == '\t') {
   373  			break
   374  		}
   375  		Straddc(s, getch()) /* blanks significant for getname() */
   376  	}
   377  	for {
   378  		c = getch()
   379  		if c <= 0 || strings.ContainsRune(end, c) {
   380  			break
   381  		}
   382  		Straddc(s, c)
   383  	}
   384  	if c != '\n' {
   385  		atnl()
   386  	}
   387  	return s
   388  }
   389  
   390  func collecttext() *String {
   391  	s := newstring()
   392  	if skipbl() == '\n' {
   393  		getch()
   394  		i := 0
   395  		for {
   396  			begline := i
   397  			var c rune
   398  			for {
   399  				c = getch()
   400  				if !(c > 0) || !(c != '\n') {
   401  					break
   402  				}
   403  				i++
   404  				Straddc(s, c)
   405  			}
   406  			i++
   407  			Straddc(s, '\n')
   408  			if c < 0 {
   409  				goto Return
   410  			}
   411  			if !(s.s[begline] != '.') && !(s.s[begline+1] != '\n') {
   412  				break
   413  			}
   414  		}
   415  		Strdelete(s, len(s.s)-2, len(s.s))
   416  	} else {
   417  		delim := getch()
   418  		okdelim(delim)
   419  		getrhs(s, delim, 'a')
   420  		if nextc() == delim {
   421  			getch()
   422  		}
   423  		atnl()
   424  	}
   425  Return:
   426  	return s
   427  }
   428  
   429  func parsecmd(nest int) *Cmd {
   430  	var cmd Cmd
   431  	cmd.ccmd = nil
   432  	cmd.next = cmd.ccmd
   433  	cmd.re = nil
   434  	cmd.num = 0
   435  	cmd.flag = false
   436  	cmd.addr = compoundaddr()
   437  	if skipbl() == -1 {
   438  		return nil
   439  	}
   440  	c := getch()
   441  	if c == -1 {
   442  		return nil
   443  	}
   444  	cmd.cmdc = c
   445  	if cmd.cmdc == 'c' && nextc() == 'd' { /* sleazy two-character case */
   446  		getch() /* the 'd' */
   447  		cmd.cmdc = 'c' | 0x100
   448  	}
   449  	i := lookup(cmd.cmdc)
   450  	var cp *Cmd
   451  	if i >= 0 {
   452  		if cmd.cmdc == '\n' {
   453  			goto Return /* let nl_cmd work it all out */
   454  		}
   455  		ct := &cmdtab[i]
   456  		if ct.defaddr == aNo && cmd.addr != nil {
   457  			error_(Enoaddr)
   458  		}
   459  		if ct.count != 0 {
   460  			cmd.num = getnum(int(ct.count))
   461  		}
   462  		if ct.regexp {
   463  			/* x without pattern -> .*\n, indicated by cmd.re==0 */
   464  			/* X without pattern is all files */
   465  			if (ct.cmdc != 'x' && ct.cmdc != 'X') || func() bool { c = nextc(); return c != ' ' && c != '\t' && c != '\n' }() {
   466  				skipbl()
   467  				c = getch()
   468  				if c == '\n' || c < 0 {
   469  					error_(Enopattern)
   470  				}
   471  				okdelim(c)
   472  				cmd.re = getregexp(c)
   473  				if ct.cmdc == 's' {
   474  					cmd.ctext = newstring()
   475  					getrhs(cmd.ctext, c, 's')
   476  					if nextc() == c {
   477  						getch()
   478  						if nextc() == 'g' {
   479  							getch()
   480  							cmd.flag = true
   481  						}
   482  					}
   483  
   484  				}
   485  			}
   486  		}
   487  		if ct.addr {
   488  			cmd.caddr = simpleaddr()
   489  			if cmd.caddr == nil {
   490  				error_(Eaddress)
   491  			}
   492  		}
   493  		if ct.defcmd != 0 {
   494  			if skipbl() == '\n' {
   495  				getch()
   496  				cmd.ccmd = newcmd()
   497  				cmd.ccmd.cmdc = ct.defcmd
   498  			} else {
   499  				cmd.ccmd = parsecmd(nest)
   500  				if cmd.ccmd == nil {
   501  					panic_("defcmd")
   502  				}
   503  			}
   504  		} else if ct.text {
   505  			cmd.ctext = collecttext()
   506  		} else if ct.token != "" {
   507  			cmd.ctext = collecttoken(ct.token)
   508  		} else {
   509  			atnl()
   510  		}
   511  	} else {
   512  		var ncp *Cmd
   513  		switch cmd.cmdc {
   514  		case '{':
   515  			cp = nil
   516  			for {
   517  				if skipbl() == '\n' {
   518  					getch()
   519  				}
   520  				ncp = parsecmd(nest + 1)
   521  				if cp != nil {
   522  					cp.next = ncp
   523  				} else {
   524  					cmd.ccmd = ncp
   525  				}
   526  				cp = ncp
   527  				if cp == nil {
   528  					break
   529  				}
   530  			}
   531  		case '}':
   532  			atnl()
   533  			if nest == 0 {
   534  				error_(Enolbrace)
   535  			}
   536  			return nil
   537  		default:
   538  			error_c(Eunk, cmd.cmdc)
   539  		}
   540  	}
   541  Return:
   542  	cp = newcmd()
   543  	*cp = cmd
   544  	return cp /* BUGGERED */
   545  }
   546  
   547  func getregexp(delim rune) *String {
   548  	r := newre()
   549  	var c rune
   550  	for Strzero(&genstr); ; Straddc(&genstr, c) {
   551  		c = getch()
   552  		if c == '\\' {
   553  			if nextc() == delim {
   554  				c = getch()
   555  			} else if nextc() == '\\' {
   556  				Straddc(&genstr, c)
   557  				c = getch()
   558  			}
   559  		} else if c == delim || c == '\n' {
   560  			break
   561  		}
   562  	}
   563  	if c != delim && c != 0 {
   564  		ungetch()
   565  	}
   566  	if len(genstr.s) > 0 {
   567  		patset = true
   568  		Strduplstr(&lastpat, &genstr)
   569  	}
   570  	if len(lastpat.s) <= 0 {
   571  		error_(Epattern)
   572  	}
   573  	Strduplstr(r, &lastpat)
   574  	return r
   575  }
   576  
   577  func simpleaddr() *Addr {
   578  	var addr Addr
   579  	addr.next = nil
   580  	addr.left = nil
   581  	addr.num = 0
   582  	switch skipbl() {
   583  	case '#':
   584  		addr.type_ = getch()
   585  		addr.num = getnum(1)
   586  	case '0',
   587  		'1',
   588  		'2',
   589  		'3',
   590  		'4',
   591  		'5',
   592  		'6',
   593  		'7',
   594  		'8',
   595  		'9':
   596  		addr.num = getnum(1)
   597  		addr.type_ = 'l'
   598  	case '/',
   599  		'?',
   600  		'"':
   601  		addr.type_ = getch()
   602  		addr.are = getregexp(addr.type_)
   603  	case '.',
   604  		'$',
   605  		'+',
   606  		'-',
   607  		'\'':
   608  		addr.type_ = getch()
   609  	default:
   610  		return nil
   611  	}
   612  	addr.next = simpleaddr()
   613  	if addr.next != nil {
   614  		var nap *Addr
   615  		switch addr.next.type_ {
   616  		case '.',
   617  			'$',
   618  			'\'':
   619  			if addr.type_ == '"' {
   620  				break
   621  			}
   622  			fallthrough
   623  		/* fall through */
   624  		case '"':
   625  			error_(Eaddress)
   626  		case 'l',
   627  			'#':
   628  			if addr.type_ == '"' {
   629  				break
   630  			}
   631  			fallthrough
   632  		/* fall through */
   633  		case '/',
   634  			'?':
   635  			if addr.type_ != '+' && addr.type_ != '-' {
   636  				/* insert the missing '+' */
   637  				nap = newaddr()
   638  				nap.type_ = '+'
   639  				nap.next = addr.next
   640  				addr.next = nap
   641  			}
   642  		case '+',
   643  			'-':
   644  			break
   645  		default:
   646  			panic_("simpleaddr")
   647  		}
   648  	}
   649  	ap := newaddr()
   650  	*ap = addr
   651  	return ap
   652  }
   653  
   654  func compoundaddr() *Addr {
   655  	var addr Addr
   656  	addr.left = simpleaddr()
   657  	addr.type_ = skipbl()
   658  	if addr.type_ != ',' && addr.type_ != ';' {
   659  		return addr.left
   660  	}
   661  	getch()
   662  	addr.next = compoundaddr()
   663  	next := addr.next
   664  	if next != nil && (next.type_ == ',' || next.type_ == ';') && next.left == nil {
   665  		error_(Eaddress)
   666  	}
   667  	ap := newaddr()
   668  	*ap = addr
   669  	return ap
   670  }