LCOV - code coverage report
Current view: top level - pebble/metamorphic - parser.go (source / functions) Hit Total Coverage
Test: 2025-01-05 08:17Z fca2fd50 - tests only.lcov Lines: 466 518 90.0 %
Date: 2025-01-05 08:17:46 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2019 The LevelDB-Go and Pebble Authors. All rights reserved. Use
       2             : // of this source code is governed by a BSD-style license that can be found in
       3             : // the LICENSE file.
       4             : 
       5             : package metamorphic
       6             : 
       7             : import (
       8             :         "bytes"
       9             :         "fmt"
      10             :         "go/scanner"
      11             :         "go/token"
      12             :         "reflect"
      13             :         "slices"
      14             :         "strconv"
      15             : 
      16             :         "github.com/cockroachdb/errors"
      17             :         "github.com/cockroachdb/pebble"
      18             :         "github.com/cockroachdb/pebble/sstable/block"
      19             : )
      20             : 
      21             : type methodInfo struct {
      22             :         constructor func() op
      23             :         validTags   uint32
      24             : }
      25             : 
      26           1 : func makeMethod(i interface{}, tags ...objTag) *methodInfo {
      27           1 :         var validTags uint32
      28           1 :         for _, tag := range tags {
      29           1 :                 validTags |= 1 << tag
      30           1 :         }
      31             : 
      32           1 :         t := reflect.TypeOf(i)
      33           1 :         return &methodInfo{
      34           1 :                 constructor: func() op {
      35           1 :                         return reflect.New(t).Interface().(op)
      36           1 :                 },
      37             :                 validTags: validTags,
      38             :         }
      39             : }
      40             : 
      41             : // args returns the receiverID, targetID and arguments for the op. The
      42             : // receiverID is the ID of the object the op will be applied to. The targetID
      43             : // is the ID of the object for assignment. If the method does not return a new
      44             : // object, then targetID will be nil.
      45             : //
      46             : // The argument list returns pointers to operation fields that map to arguments
      47             : // for the operation. The last argument can be a pointer to a slice,
      48             : // corresponding to a variable number of arguments.
      49           1 : func opArgs(op op) (receiverID *objID, targetID *objID, args []interface{}) {
      50           1 :         switch t := op.(type) {
      51           1 :         case *applyOp:
      52           1 :                 return &t.writerID, nil, []interface{}{&t.batchID}
      53           1 :         case *checkpointOp:
      54           1 :                 return &t.dbID, nil, []interface{}{&t.spans}
      55           1 :         case *closeOp:
      56           1 :                 return &t.objID, nil, nil
      57           1 :         case *compactOp:
      58           1 :                 return &t.dbID, nil, []interface{}{&t.start, &t.end, &t.parallelize}
      59           1 :         case *batchCommitOp:
      60           1 :                 return &t.batchID, nil, nil
      61           1 :         case *dbRatchetFormatMajorVersionOp:
      62           1 :                 return &t.dbID, nil, []interface{}{&t.vers}
      63           1 :         case *dbRestartOp:
      64           1 :                 return &t.dbID, nil, nil
      65           1 :         case *deleteOp:
      66           1 :                 return &t.writerID, nil, []interface{}{&t.key}
      67           1 :         case *deleteRangeOp:
      68           1 :                 return &t.writerID, nil, []interface{}{&t.start, &t.end}
      69           1 :         case *downloadOp:
      70           1 :                 return &t.dbID, nil, []interface{}{&t.spans}
      71           1 :         case *iterFirstOp:
      72           1 :                 return &t.iterID, nil, nil
      73           1 :         case *flushOp:
      74           1 :                 return &t.db, nil, nil
      75           1 :         case *getOp:
      76           1 :                 return &t.readerID, nil, []interface{}{&t.key}
      77           1 :         case *ingestOp:
      78           1 :                 return &t.dbID, nil, []interface{}{&t.batchIDs}
      79           1 :         case *ingestAndExciseOp:
      80           1 :                 return &t.dbID, nil, []interface{}{&t.batchID, &t.exciseStart, &t.exciseEnd}
      81           1 :         case *ingestExternalFilesOp:
      82           1 :                 return &t.dbID, nil, []interface{}{&t.objs}
      83           1 :         case *initOp:
      84           1 :                 return nil, nil, []interface{}{&t.dbSlots, &t.batchSlots, &t.iterSlots, &t.snapshotSlots, &t.externalObjSlots}
      85           1 :         case *iterLastOp:
      86           1 :                 return &t.iterID, nil, nil
      87           1 :         case *logDataOp:
      88           1 :                 return &t.writerID, nil, []interface{}{&t.data}
      89           1 :         case *mergeOp:
      90           1 :                 return &t.writerID, nil, []interface{}{&t.key, &t.value}
      91           1 :         case *newBatchOp:
      92           1 :                 return &t.dbID, &t.batchID, nil
      93           1 :         case *newIndexedBatchOp:
      94           1 :                 return &t.dbID, &t.batchID, nil
      95           1 :         case *newIterOp:
      96           1 :                 return &t.readerID, &t.iterID, []interface{}{&t.lower, &t.upper, &t.keyTypes, &t.filterMin, &t.filterMax, &t.useL6Filters, &t.maskSuffix}
      97           1 :         case *newIterUsingCloneOp:
      98           1 :                 return &t.existingIterID, &t.iterID, []interface{}{&t.refreshBatch, &t.lower, &t.upper, &t.keyTypes, &t.filterMin, &t.filterMax, &t.useL6Filters, &t.maskSuffix}
      99           1 :         case *newSnapshotOp:
     100           1 :                 return &t.dbID, &t.snapID, []interface{}{&t.bounds}
     101           1 :         case *newExternalObjOp:
     102           1 :                 return &t.batchID, &t.externalObjID, nil
     103           1 :         case *iterNextOp:
     104           1 :                 return &t.iterID, nil, []interface{}{&t.limit}
     105           1 :         case *iterNextPrefixOp:
     106           1 :                 return &t.iterID, nil, nil
     107           1 :         case *iterCanSingleDelOp:
     108           1 :                 return &t.iterID, nil, []interface{}{}
     109           1 :         case *iterPrevOp:
     110           1 :                 return &t.iterID, nil, []interface{}{&t.limit}
     111           1 :         case *iterSeekLTOp:
     112           1 :                 return &t.iterID, nil, []interface{}{&t.key, &t.limit}
     113           1 :         case *iterSeekGEOp:
     114           1 :                 return &t.iterID, nil, []interface{}{&t.key, &t.limit}
     115           1 :         case *iterSeekPrefixGEOp:
     116           1 :                 return &t.iterID, nil, []interface{}{&t.key}
     117           1 :         case *setOp:
     118           1 :                 return &t.writerID, nil, []interface{}{&t.key, &t.value}
     119           1 :         case *iterSetBoundsOp:
     120           1 :                 return &t.iterID, nil, []interface{}{&t.lower, &t.upper}
     121           1 :         case *iterSetOptionsOp:
     122           1 :                 return &t.iterID, nil, []interface{}{&t.lower, &t.upper, &t.keyTypes, &t.filterMin, &t.filterMax, &t.useL6Filters, &t.maskSuffix}
     123           1 :         case *singleDeleteOp:
     124           1 :                 return &t.writerID, nil, []interface{}{&t.key, &t.maybeReplaceDelete}
     125           1 :         case *rangeKeyDeleteOp:
     126           1 :                 return &t.writerID, nil, []interface{}{&t.start, &t.end}
     127           1 :         case *rangeKeySetOp:
     128           1 :                 return &t.writerID, nil, []interface{}{&t.start, &t.end, &t.suffix, &t.value}
     129           1 :         case *rangeKeyUnsetOp:
     130           1 :                 return &t.writerID, nil, []interface{}{&t.start, &t.end, &t.suffix}
     131           1 :         case *replicateOp:
     132           1 :                 return &t.source, nil, []interface{}{&t.dest, &t.start, &t.end}
     133             :         }
     134           0 :         panic(fmt.Sprintf("unsupported op type: %T", op))
     135             : }
     136             : 
     137             : var methods = map[string]*methodInfo{
     138             :         "Apply":                     makeMethod(applyOp{}, dbTag, batchTag),
     139             :         "Checkpoint":                makeMethod(checkpointOp{}, dbTag),
     140             :         "Clone":                     makeMethod(newIterUsingCloneOp{}, iterTag),
     141             :         "Close":                     makeMethod(closeOp{}, dbTag, batchTag, iterTag, snapTag),
     142             :         "Commit":                    makeMethod(batchCommitOp{}, batchTag),
     143             :         "Compact":                   makeMethod(compactOp{}, dbTag),
     144             :         "Delete":                    makeMethod(deleteOp{}, dbTag, batchTag),
     145             :         "DeleteRange":               makeMethod(deleteRangeOp{}, dbTag, batchTag),
     146             :         "Download":                  makeMethod(downloadOp{}, dbTag),
     147             :         "First":                     makeMethod(iterFirstOp{}, iterTag),
     148             :         "Flush":                     makeMethod(flushOp{}, dbTag),
     149             :         "Get":                       makeMethod(getOp{}, dbTag, batchTag, snapTag),
     150             :         "Ingest":                    makeMethod(ingestOp{}, dbTag),
     151             :         "IngestAndExcise":           makeMethod(ingestAndExciseOp{}, dbTag),
     152             :         "IngestExternalFiles":       makeMethod(ingestExternalFilesOp{}, dbTag),
     153             :         "Init":                      makeMethod(initOp{}, dbTag),
     154             :         "Last":                      makeMethod(iterLastOp{}, iterTag),
     155             :         "LogData":                   makeMethod(logDataOp{}, dbTag, batchTag),
     156             :         "Merge":                     makeMethod(mergeOp{}, dbTag, batchTag),
     157             :         "NewBatch":                  makeMethod(newBatchOp{}, dbTag),
     158             :         "NewIndexedBatch":           makeMethod(newIndexedBatchOp{}, dbTag),
     159             :         "NewIter":                   makeMethod(newIterOp{}, dbTag, batchTag, snapTag),
     160             :         "NewSnapshot":               makeMethod(newSnapshotOp{}, dbTag),
     161             :         "NewExternalObj":            makeMethod(newExternalObjOp{}, batchTag),
     162             :         "Next":                      makeMethod(iterNextOp{}, iterTag),
     163             :         "NextPrefix":                makeMethod(iterNextPrefixOp{}, iterTag),
     164             :         "InternalNext":              makeMethod(iterCanSingleDelOp{}, iterTag),
     165             :         "Prev":                      makeMethod(iterPrevOp{}, iterTag),
     166             :         "RangeKeyDelete":            makeMethod(rangeKeyDeleteOp{}, dbTag, batchTag),
     167             :         "RangeKeySet":               makeMethod(rangeKeySetOp{}, dbTag, batchTag),
     168             :         "RangeKeyUnset":             makeMethod(rangeKeyUnsetOp{}, dbTag, batchTag),
     169             :         "RatchetFormatMajorVersion": makeMethod(dbRatchetFormatMajorVersionOp{}, dbTag),
     170             :         "Replicate":                 makeMethod(replicateOp{}, dbTag),
     171             :         "Restart":                   makeMethod(dbRestartOp{}, dbTag),
     172             :         "SeekGE":                    makeMethod(iterSeekGEOp{}, iterTag),
     173             :         "SeekLT":                    makeMethod(iterSeekLTOp{}, iterTag),
     174             :         "SeekPrefixGE":              makeMethod(iterSeekPrefixGEOp{}, iterTag),
     175             :         "Set":                       makeMethod(setOp{}, dbTag, batchTag),
     176             :         "SetBounds":                 makeMethod(iterSetBoundsOp{}, iterTag),
     177             :         "SetOptions":                makeMethod(iterSetOptionsOp{}, iterTag),
     178             :         "SingleDelete":              makeMethod(singleDeleteOp{}, dbTag, batchTag),
     179             : }
     180             : 
     181             : type parser struct {
     182             :         opts parserOpts
     183             :         fset *token.FileSet
     184             :         s    scanner.Scanner
     185             :         objs map[objID]bool
     186             : }
     187             : 
     188             : type parserOpts struct {
     189             :         allowUndefinedObjs          bool
     190             :         parseFormattedUserKey       func(string) (UserKey, error)
     191             :         parseFormattedUserKeySuffix func(string) (UserKeySuffix, error)
     192             : }
     193             : 
     194           1 : func parse(src []byte, opts parserOpts) (_ []op, err error) {
     195           1 :         // Various bits of magic incantation to set up a scanner for Go compatible
     196           1 :         // syntax. We arranged for the textual format of ops (e.g. op.String()) to
     197           1 :         // look like Go which allows us to use the Go scanner for parsing.
     198           1 :         p := &parser{
     199           1 :                 opts: opts,
     200           1 :                 fset: token.NewFileSet(),
     201           1 :                 objs: map[objID]bool{makeObjID(dbTag, 1): true, makeObjID(dbTag, 2): true},
     202           1 :         }
     203           1 :         file := p.fset.AddFile("", -1, len(src))
     204           1 :         p.s.Init(file, src, nil /* no error handler */, 0)
     205           1 :         return p.parse()
     206           1 : }
     207             : 
     208           1 : func (p *parser) parse() (_ []op, err error) {
     209           1 :         defer func() {
     210           1 :                 if r := recover(); r != nil {
     211           1 :                         var ok bool
     212           1 :                         if err, ok = r.(error); ok {
     213           1 :                                 return
     214           1 :                         }
     215           0 :                         err = errors.Errorf("%v", r)
     216             :                 }
     217             :         }()
     218             : 
     219           1 :         var ops []op
     220           1 :         for {
     221           1 :                 op := p.parseOp()
     222           1 :                 if op == nil {
     223           1 :                         computeDerivedFields(ops)
     224           1 :                         return ops, nil
     225           1 :                 }
     226           1 :                 ops = append(ops, op)
     227             :         }
     228             : }
     229             : 
     230           1 : func (p *parser) parseOp() op {
     231           1 :         destPos, destTok, destLit := p.s.Scan()
     232           1 :         if destTok == token.EOF {
     233           1 :                 return nil
     234           1 :         }
     235           1 :         if destTok != token.IDENT {
     236           1 :                 panic(p.errorf(destPos, "unexpected token: %s %q", destTok, destLit))
     237             :         }
     238           1 :         if destLit == "Init" {
     239           1 :                 // <op>(<args>)
     240           1 :                 return p.makeOp(destLit, makeObjID(dbTag, 1), 0, destPos)
     241           1 :         }
     242             : 
     243           1 :         destID := p.parseObjID(destPos, destLit)
     244           1 : 
     245           1 :         pos, tok, lit := p.s.Scan()
     246           1 :         switch tok {
     247           1 :         case token.PERIOD:
     248           1 :                 // <obj>.<op>(<args>)
     249           1 :                 if !p.objs[destID] {
     250           1 :                         if p.opts.allowUndefinedObjs {
     251           1 :                                 p.objs[destID] = true
     252           1 :                         } else {
     253           0 :                                 panic(p.errorf(destPos, "unknown object: %s", destID))
     254             :                         }
     255             :                 }
     256           1 :                 _, methodLit := p.scanToken(token.IDENT)
     257           1 :                 return p.makeOp(methodLit, destID, 0, destPos)
     258             : 
     259           1 :         case token.ASSIGN:
     260           1 :                 // <obj> = <obj>.<op>(<args>)
     261           1 :                 srcPos, srcLit := p.scanToken(token.IDENT)
     262           1 :                 srcID := p.parseObjID(srcPos, srcLit)
     263           1 :                 if !p.objs[srcID] {
     264           0 :                         if p.opts.allowUndefinedObjs {
     265           0 :                                 p.objs[srcID] = true
     266           0 :                         } else {
     267           0 :                                 panic(p.errorf(srcPos, "unknown object %q", srcLit))
     268             :                         }
     269             :                 }
     270           1 :                 p.scanToken(token.PERIOD)
     271           1 :                 _, methodLit := p.scanToken(token.IDENT)
     272           1 :                 p.objs[destID] = true
     273           1 :                 return p.makeOp(methodLit, srcID, destID, srcPos)
     274             :         }
     275           0 :         panic(p.errorf(pos, "unexpected token: %q", p.tokenf(tok, lit)))
     276             : }
     277             : 
     278           1 : func (p *parser) parseObjID(pos token.Pos, str string) objID {
     279           1 :         id, err := parseObjID(str)
     280           1 :         if err != nil {
     281           1 :                 panic(p.errorf(pos, "%s", err))
     282             :         }
     283           1 :         return id
     284             : }
     285             : 
     286           1 : func (p *parser) parseUserKey(pos token.Pos, lit string) UserKey {
     287           1 :         s, err := strconv.Unquote(lit)
     288           1 :         if err != nil {
     289           0 :                 panic(p.errorf(pos, "%s", err))
     290             :         }
     291           1 :         if len(s) == 0 {
     292           1 :                 return nil
     293           1 :         }
     294           1 :         if p.opts.parseFormattedUserKey == nil {
     295           1 :                 return []byte(s)
     296           1 :         }
     297           0 :         key, err := p.opts.parseFormattedUserKey(s)
     298           0 :         if err != nil {
     299           0 :                 panic(p.errorf(pos, "%s", err))
     300             :         }
     301           0 :         return key
     302             : }
     303             : 
     304           1 : func (p *parser) parseUserKeySuffix(pos token.Pos, lit string) UserKeySuffix {
     305           1 :         s, err := strconv.Unquote(lit)
     306           1 :         if err != nil {
     307           0 :                 panic(p.errorf(pos, "%s", err))
     308             :         }
     309           1 :         if len(s) == 0 {
     310           1 :                 return nil
     311           1 :         }
     312           1 :         if p.opts.parseFormattedUserKeySuffix == nil {
     313           1 :                 return []byte(s)
     314           1 :         }
     315           0 :         suffix, err := p.opts.parseFormattedUserKeySuffix(s)
     316           0 :         if err != nil {
     317           0 :                 panic(p.errorf(pos, "%s", err))
     318             :         }
     319           0 :         return suffix
     320             : }
     321             : 
     322           1 : func unquoteBytes(lit string) []byte {
     323           1 :         s, err := strconv.Unquote(lit)
     324           1 :         if err != nil {
     325           0 :                 panic(err)
     326             :         }
     327           1 :         if len(s) == 0 {
     328           1 :                 return nil
     329           1 :         }
     330           1 :         return []byte(s)
     331             : }
     332             : 
     333           1 : func (p *parser) parseArgs(op op, methodName string, args []interface{}) {
     334           1 :         pos, list := p.parseList()
     335           1 :         p.scanToken(token.SEMICOLON)
     336           1 : 
     337           1 :         // The last argument can have variable length.
     338           1 :         var varArg interface{}
     339           1 :         if len(args) > 0 {
     340           1 :                 switch args[len(args)-1].(type) {
     341           1 :                 case *[]objID, *[]pebble.KeyRange, *[]pebble.CheckpointSpan, *[]pebble.DownloadSpan, *[]externalObjWithBounds:
     342           1 :                         varArg = args[len(args)-1]
     343           1 :                         args = args[:len(args)-1]
     344             :                 }
     345             :         }
     346             : 
     347           1 :         if len(list) < len(args) {
     348           1 :                 panic(p.errorf(pos, "%s: not enough arguments", methodName))
     349             :         }
     350           1 :         if len(list) > len(args) && varArg == nil {
     351           0 :                 panic(p.errorf(pos, "%s: too many arguments", methodName))
     352             :         }
     353             : 
     354           1 :         for i := range args {
     355           1 :                 elem := list[i]
     356           1 :                 switch t := args[i].(type) {
     357           1 :                 case *uint32:
     358           1 :                         elem.expectToken(p, token.INT)
     359           1 :                         val, err := strconv.ParseUint(elem.lit, 10, 32)
     360           1 :                         if err != nil {
     361           0 :                                 panic(p.errorf(elem.pos, "error parsing %q: %s", elem.lit, err))
     362             :                         }
     363           1 :                         *t = uint32(val)
     364             : 
     365           0 :                 case *uint64:
     366           0 :                         elem.expectToken(p, token.INT)
     367           0 :                         val, err := strconv.ParseUint(elem.lit, 10, 64)
     368           0 :                         if err != nil {
     369           0 :                                 panic(p.errorf(elem.pos, "error parsing %q: %s", elem.lit, err))
     370             :                         }
     371           0 :                         *t = val
     372             : 
     373           1 :                 case *UserKey:
     374           1 :                         elem.expectToken(p, token.STRING)
     375           1 :                         *t = p.parseUserKey(elem.pos, elem.lit)
     376             : 
     377           1 :                 case *UserKeySuffix:
     378           1 :                         elem.expectToken(p, token.STRING)
     379           1 :                         *t = p.parseUserKeySuffix(elem.pos, elem.lit)
     380             : 
     381           1 :                 case *[]byte:
     382           1 :                         elem.expectToken(p, token.STRING)
     383           1 :                         *t = unquoteBytes(elem.lit)
     384             : 
     385           1 :                 case *bool:
     386           1 :                         elem.expectToken(p, token.IDENT)
     387           1 :                         b, err := strconv.ParseBool(elem.lit)
     388           1 :                         if err != nil {
     389           0 :                                 panic(p.errorf(elem.pos, "error parsing %q: %s", elem.lit, err))
     390             :                         }
     391           1 :                         *t = b
     392             : 
     393           1 :                 case *objID:
     394           1 :                         elem.expectToken(p, token.IDENT)
     395           1 :                         *t = p.parseObjID(elem.pos, elem.lit)
     396             : 
     397           1 :                 case *pebble.FormatMajorVersion:
     398           1 :                         elem.expectToken(p, token.INT)
     399           1 :                         val, err := strconv.ParseUint(elem.lit, 10, 64)
     400           1 :                         if err != nil {
     401           0 :                                 panic(p.errorf(elem.pos, "error parsing %q: %s", elem.lit, err))
     402             :                         }
     403           1 :                         *t = pebble.FormatMajorVersion(val)
     404             : 
     405           0 :                 default:
     406           0 :                         panic(p.errorf(pos, "%s: unsupported arg[%d] type: %T", methodName, i, args[i]))
     407             :                 }
     408             :         }
     409             : 
     410           1 :         if varArg != nil {
     411           1 :                 list = list[len(args):]
     412           1 :                 switch t := varArg.(type) {
     413           1 :                 case *[]objID:
     414           1 :                         *t = p.parseObjIDs(list)
     415           1 :                 case *[]pebble.KeyRange:
     416           1 :                         *t = p.parseKeyRanges(list)
     417           1 :                 case *[]pebble.CheckpointSpan:
     418           1 :                         *t = p.parseCheckpointSpans(list)
     419           1 :                 case *[]pebble.DownloadSpan:
     420           1 :                         *t = p.parseDownloadSpans(list)
     421           1 :                 case *[]externalObjWithBounds:
     422           1 :                         *t = p.parseExternalObjsWithBounds(list)
     423           0 :                 default:
     424           0 :                         // We already checked for these types when we set varArgs.
     425           0 :                         panic("unreachable")
     426             :                 }
     427             :         }
     428             : }
     429             : 
     430             : type listElem struct {
     431             :         pos token.Pos
     432             :         tok token.Token
     433             :         lit string
     434             : }
     435             : 
     436           1 : func (e listElem) expectToken(p *parser, expTok token.Token) {
     437           1 :         if e.tok != expTok {
     438           0 :                 panic(p.errorf(e.pos, "unexpected token: %q", p.tokenf(e.tok, e.lit)))
     439             :         }
     440             : }
     441             : 
     442             : // pop checks that the first element of the list matches the expected token,
     443             : // removes it from the list and returns the pos and literal.
     444           1 : func (p *parser) pop(list *[]listElem, expTok token.Token) (token.Pos, string) {
     445           1 :         (*list)[0].expectToken(p, expTok)
     446           1 :         pos := (*list)[0].pos
     447           1 :         lit := (*list)[0].lit
     448           1 :         (*list) = (*list)[1:]
     449           1 :         return pos, lit
     450           1 : }
     451             : 
     452             : // popLit checks that the first element of the list matches the expected token,
     453             : // removes it from the list and returns the pos and literal.
     454           1 : func (p *parser) popLit(list *[]listElem, expTok token.Token) string {
     455           1 :         (*list)[0].expectToken(p, expTok)
     456           1 :         lit := (*list)[0].lit
     457           1 :         (*list) = (*list)[1:]
     458           1 :         return lit
     459           1 : }
     460             : 
     461             : // parseKeyRange parses an arbitrary number of comma separated STRING/IDENT/INT
     462             : // tokens surrounded by parens.
     463           1 : func (p *parser) parseList() (token.Pos, []listElem) {
     464           1 :         p.scanToken(token.LPAREN)
     465           1 :         var list []listElem
     466           1 :         for {
     467           1 :                 pos, tok, lit := p.s.Scan()
     468           1 :                 if len(list) == 0 && tok == token.RPAREN {
     469           1 :                         return pos, nil
     470           1 :                 }
     471             : 
     472           1 :                 switch tok {
     473           1 :                 case token.STRING, token.IDENT, token.INT:
     474           1 :                         list = append(list, listElem{
     475           1 :                                 pos: pos,
     476           1 :                                 tok: tok,
     477           1 :                                 lit: lit,
     478           1 :                         })
     479           1 :                         pos, tok, lit = p.s.Scan()
     480           1 :                         switch tok {
     481           1 :                         case token.COMMA:
     482           1 :                                 continue
     483           1 :                         case token.RPAREN:
     484           1 :                                 return pos, list
     485             :                         }
     486             :                 }
     487           0 :                 panic(p.errorf(pos, "unexpected token: %q", p.tokenf(tok, lit)))
     488             :         }
     489             : }
     490             : 
     491           1 : func (p *parser) parseObjIDs(list []listElem) []objID {
     492           1 :         res := make([]objID, len(list))
     493           1 :         for i, elem := range list {
     494           1 :                 res[i] = p.parseObjID(elem.pos, elem.lit)
     495           1 :         }
     496           1 :         return res
     497             : }
     498             : 
     499           1 : func (p *parser) parseKeys(list []listElem) []UserKey {
     500           1 :         res := make([]UserKey, len(list))
     501           1 :         for i := range res {
     502           1 :                 res[i] = p.parseUserKey(p.pop(&list, token.STRING))
     503           1 :         }
     504           1 :         return res
     505             : }
     506             : 
     507           1 : func (p *parser) parseKeyRanges(list []listElem) []pebble.KeyRange {
     508           1 :         keys := p.parseKeys(list)
     509           1 :         if len(keys)%2 != 0 {
     510           0 :                 panic(p.errorf(list[0].pos, "expected even number of keys"))
     511             :         }
     512           1 :         res := make([]pebble.KeyRange, len(keys)/2)
     513           1 :         for i := range res {
     514           1 :                 res[i].Start = keys[2*i]
     515           1 :                 res[i].End = keys[2*i+1]
     516           1 :         }
     517           1 :         return res
     518             : }
     519             : 
     520           1 : func (p *parser) parseCheckpointSpans(list []listElem) []pebble.CheckpointSpan {
     521           1 :         keys := p.parseKeys(list)
     522           1 :         if len(keys)%2 == 1 {
     523           0 :                 panic(p.errorf(list[0].pos, "expected even number of keys"))
     524             :         }
     525           1 :         if len(keys) == 0 {
     526           1 :                 // Necessary for round-trip tests which differentiate between nil and empty slice.
     527           1 :                 return nil
     528           1 :         }
     529           1 :         res := make([]pebble.CheckpointSpan, len(keys)/2)
     530           1 :         for i := range res {
     531           1 :                 res[i] = pebble.CheckpointSpan{
     532           1 :                         Start: keys[i*2],
     533           1 :                         End:   keys[i*2+1],
     534           1 :                 }
     535           1 :         }
     536           1 :         return res
     537             : }
     538             : 
     539           1 : func (p *parser) parseDownloadSpans(list []listElem) []pebble.DownloadSpan {
     540           1 :         if len(list)%3 != 0 {
     541           0 :                 panic(p.errorf(list[0].pos, "expected 3k args"))
     542             :         }
     543           1 :         res := make([]pebble.DownloadSpan, len(list)/3)
     544           1 :         for i := range res {
     545           1 :                 res[i] = pebble.DownloadSpan{
     546           1 :                         StartKey:               p.parseUserKey(p.pop(&list, token.STRING)),
     547           1 :                         EndKey:                 p.parseUserKey(p.pop(&list, token.STRING)),
     548           1 :                         ViaBackingFileDownload: p.popLit(&list, token.IDENT) == "true",
     549           1 :                 }
     550           1 :         }
     551           1 :         return res
     552             : }
     553             : 
     554           1 : func (p *parser) parseExternalObjsWithBounds(list []listElem) []externalObjWithBounds {
     555           1 :         const numArgs = 5
     556           1 :         if len(list)%5 != 0 {
     557           0 :                 panic(p.errorf(list[0].pos, "expected number of arguments to be multiple of %d", numArgs))
     558             :         }
     559           1 :         objs := make([]externalObjWithBounds, len(list)/numArgs)
     560           1 :         for i := range objs {
     561           1 :                 objs[i] = externalObjWithBounds{
     562           1 :                         externalObjID: p.parseObjID(p.pop(&list, token.IDENT)),
     563           1 :                         bounds: pebble.KeyRange{
     564           1 :                                 Start: p.parseUserKey(p.pop(&list, token.STRING)),
     565           1 :                                 End:   p.parseUserKey(p.pop(&list, token.STRING)),
     566           1 :                         },
     567           1 :                 }
     568           1 :                 if syntheticSuffix := p.parseUserKeySuffix(p.pop(&list, token.STRING)); len(syntheticSuffix) > 0 {
     569           1 :                         objs[i].syntheticSuffix = block.SyntheticSuffix(syntheticSuffix)
     570           1 :                 }
     571             : 
     572           1 :                 syntheticPrefix := p.parseUserKey(p.pop(&list, token.STRING))
     573           1 :                 if len(syntheticPrefix) > 0 {
     574           1 :                         if !bytes.HasPrefix(objs[i].bounds.Start, syntheticPrefix) {
     575           0 :                                 panic(fmt.Sprintf("invalid synthetic prefix %q %q", objs[i].bounds.Start, syntheticPrefix))
     576             :                         }
     577           1 :                         if !bytes.HasPrefix(objs[i].bounds.End, syntheticPrefix) {
     578           0 :                                 panic(fmt.Sprintf("invalid synthetic prefix %q %q", objs[i].bounds.End, syntheticPrefix))
     579             :                         }
     580           1 :                         objs[i].syntheticPrefix = block.SyntheticPrefix(syntheticPrefix)
     581             :                 }
     582             :         }
     583           1 :         return objs
     584             : }
     585             : 
     586           1 : func (p *parser) scanToken(expected token.Token) (pos token.Pos, lit string) {
     587           1 :         pos, tok, lit := p.s.Scan()
     588           1 :         if tok != expected {
     589           0 :                 panic(p.errorf(pos, "unexpected token: %q", p.tokenf(tok, lit)))
     590             :         }
     591           1 :         return pos, lit
     592             : }
     593             : 
     594           1 : func (p *parser) makeOp(methodName string, receiverID, targetID objID, pos token.Pos) op {
     595           1 :         info := methods[methodName]
     596           1 :         if info == nil {
     597           1 :                 panic(p.errorf(pos, "unknown op %s.%s", receiverID, methodName))
     598             :         }
     599           1 :         if info.validTags&(1<<receiverID.tag()) == 0 {
     600           1 :                 panic(p.errorf(pos, "%s.%s: %s is not a method on %s",
     601           1 :                         receiverID, methodName, methodName, receiverID))
     602             :         }
     603             : 
     604           1 :         op := info.constructor()
     605           1 :         receiver, target, args := opArgs(op)
     606           1 : 
     607           1 :         // The form of an operation is:
     608           1 :         //   [target =] receiver.method(args)
     609           1 :         //
     610           1 :         // The receiver is the object the operation will be called on, which can be
     611           1 :         // any valid ID. Certain operations such as Ingest are only valid on the DB
     612           1 :         // object. That is indicated by opArgs returning a nil receiver.
     613           1 :         if receiver != nil {
     614           1 :                 *receiver = receiverID
     615           1 :         } else if receiverID.tag() != dbTag {
     616           0 :                 panic(p.errorf(pos, "unknown op %s.%s", receiverID, methodName))
     617             :         }
     618             : 
     619             :         // The target is the object that will be assigned the result of an object
     620             :         // creation operation such as newBatchOp or newIterOp.
     621           1 :         if target != nil {
     622           1 :                 // It is invalid to not have a targetID for a method which generates a new
     623           1 :                 // object.
     624           1 :                 if targetID == 0 {
     625           1 :                         panic(p.errorf(pos, "assignment expected for %s.%s", receiverID, methodName))
     626             :                 }
     627             :                 // It is invalid to try to assign to the DB object.
     628           1 :                 if targetID.tag() == dbTag {
     629           0 :                         panic(p.errorf(pos, "cannot use %s as target of assignment", targetID))
     630             :                 }
     631           1 :                 *target = targetID
     632           1 :         } else if targetID != 0 {
     633           1 :                 panic(p.errorf(pos, "cannot use %s.%s in assignment", receiverID, methodName))
     634             :         }
     635             : 
     636           1 :         p.parseArgs(op, methodName, args)
     637           1 :         return op
     638             : }
     639             : 
     640           0 : func (p *parser) tokenf(tok token.Token, lit string) string {
     641           0 :         if tok.IsLiteral() {
     642           0 :                 return lit
     643           0 :         }
     644           0 :         return tok.String()
     645             : }
     646             : 
     647           1 : func (p *parser) errorf(pos token.Pos, format string, args ...interface{}) error {
     648           1 :         return errors.New("metamorphic test internal error: " + p.fset.Position(pos).String() + ": " + fmt.Sprintf(format, args...))
     649           1 : }
     650             : 
     651             : // computeDerivedFields makes one pass through the provided operations, filling
     652             : // any derived fields. This pass must happen before execution because concurrent
     653             : // execution depends on these fields.
     654           1 : func computeDerivedFields(ops []op) {
     655           1 :         iterToReader := make(map[objID]objID)
     656           1 :         objToDB := make(map[objID]objID)
     657           1 :         for i := range ops {
     658           1 :                 switch v := ops[i].(type) {
     659           1 :                 case *newSnapshotOp:
     660           1 :                         objToDB[v.snapID] = v.dbID
     661           1 :                 case *newIterOp:
     662           1 :                         iterToReader[v.iterID] = v.readerID
     663           1 :                         dbReaderID := v.readerID
     664           1 :                         if dbReaderID.tag() != dbTag {
     665           1 :                                 dbReaderID = objToDB[dbReaderID]
     666           1 :                         }
     667           1 :                         objToDB[v.iterID] = dbReaderID
     668           1 :                         v.derivedDBID = dbReaderID
     669           1 :                 case *newIterUsingCloneOp:
     670           1 :                         v.derivedReaderID = iterToReader[v.existingIterID]
     671           1 :                         iterToReader[v.iterID] = v.derivedReaderID
     672           1 :                         objToDB[v.iterID] = objToDB[v.existingIterID]
     673           1 :                 case *iterSetOptionsOp:
     674           1 :                         v.derivedReaderID = iterToReader[v.iterID]
     675           1 :                 case *iterFirstOp:
     676           1 :                         v.derivedReaderID = iterToReader[v.iterID]
     677           1 :                 case *iterLastOp:
     678           1 :                         v.derivedReaderID = iterToReader[v.iterID]
     679           1 :                 case *iterSeekGEOp:
     680           1 :                         v.derivedReaderID = iterToReader[v.iterID]
     681           1 :                 case *iterSeekPrefixGEOp:
     682           1 :                         v.derivedReaderID = iterToReader[v.iterID]
     683           1 :                 case *iterSeekLTOp:
     684           1 :                         v.derivedReaderID = iterToReader[v.iterID]
     685           1 :                 case *iterNextOp:
     686           1 :                         v.derivedReaderID = iterToReader[v.iterID]
     687           1 :                 case *iterNextPrefixOp:
     688           1 :                         v.derivedReaderID = iterToReader[v.iterID]
     689           1 :                 case *iterCanSingleDelOp:
     690           1 :                         v.derivedReaderID = iterToReader[v.iterID]
     691           1 :                 case *iterPrevOp:
     692           1 :                         v.derivedReaderID = iterToReader[v.iterID]
     693           1 :                 case *newBatchOp:
     694           1 :                         objToDB[v.batchID] = v.dbID
     695           1 :                 case *newIndexedBatchOp:
     696           1 :                         objToDB[v.batchID] = v.dbID
     697           1 :                 case *applyOp:
     698           1 :                         if derivedDBID, ok := objToDB[v.batchID]; ok && v.writerID.tag() != dbTag {
     699           0 :                                 objToDB[v.writerID] = derivedDBID
     700           0 :                         }
     701           1 :                 case *getOp:
     702           1 :                         if derivedDBID, ok := objToDB[v.readerID]; ok {
     703           1 :                                 v.derivedDBID = derivedDBID
     704           1 :                         }
     705           1 :                 case *batchCommitOp:
     706           1 :                         v.dbID = objToDB[v.batchID]
     707           1 :                 case *closeOp:
     708           1 :                         if v.objID.tag() == dbTag {
     709           1 :                                 // Find all objects that use this db.
     710           1 :                                 v.affectedObjects = nil
     711           1 :                                 for obj, db := range objToDB {
     712           1 :                                         if db == v.objID {
     713           1 :                                                 v.affectedObjects = append(v.affectedObjects, obj)
     714           1 :                                         }
     715             :                                 }
     716             :                                 // Sort so the output is deterministic.
     717           1 :                                 slices.Sort(v.affectedObjects)
     718           1 :                         } else if dbID, ok := objToDB[v.objID]; ok {
     719           1 :                                 v.affectedObjects = objIDSlice{dbID}
     720           1 :                         }
     721           1 :                 case *dbRestartOp:
     722           1 :                         // Find all objects that use this db.
     723           1 :                         v.affectedObjects = nil
     724           1 :                         for obj, db := range objToDB {
     725           1 :                                 if db == v.dbID {
     726           1 :                                         v.affectedObjects = append(v.affectedObjects, obj)
     727           1 :                                 }
     728             :                         }
     729             :                         // Sort so the output is deterministic.
     730           1 :                         slices.Sort(v.affectedObjects)
     731           1 :                 case *ingestOp:
     732           1 :                         v.derivedDBIDs = make([]objID, len(v.batchIDs))
     733           1 :                         for i := range v.batchIDs {
     734           1 :                                 v.derivedDBIDs[i] = objToDB[v.batchIDs[i]]
     735           1 :                         }
     736           1 :                 case *ingestAndExciseOp:
     737           1 :                         v.derivedDBID = objToDB[v.batchID]
     738           1 :                 case *deleteOp:
     739           1 :                         derivedDBID := v.writerID
     740           1 :                         if v.writerID.tag() != dbTag {
     741           1 :                                 derivedDBID = objToDB[v.writerID]
     742           1 :                         }
     743           1 :                         v.derivedDBID = derivedDBID
     744             :                 }
     745             :         }
     746             : }

Generated by: LCOV version 1.14