code.gitea.io/gitea@v1.19.3/modules/gitgraph/parser.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package gitgraph
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  )
    10  
    11  // Parser represents a git graph parser. It is stateful containing the previous
    12  // glyphs, detected flows and color assignments.
    13  type Parser struct {
    14  	glyphs           []byte
    15  	oldGlyphs        []byte
    16  	flows            []int64
    17  	oldFlows         []int64
    18  	maxFlow          int64
    19  	colors           []int
    20  	oldColors        []int
    21  	availableColors  []int
    22  	nextAvailable    int
    23  	firstInUse       int
    24  	firstAvailable   int
    25  	maxAllowedColors int
    26  }
    27  
    28  // Reset resets the internal parser state.
    29  func (parser *Parser) Reset() {
    30  	parser.glyphs = parser.glyphs[0:0]
    31  	parser.oldGlyphs = parser.oldGlyphs[0:0]
    32  	parser.flows = parser.flows[0:0]
    33  	parser.oldFlows = parser.oldFlows[0:0]
    34  	parser.maxFlow = 0
    35  	parser.colors = parser.colors[0:0]
    36  	parser.oldColors = parser.oldColors[0:0]
    37  	parser.availableColors = parser.availableColors[0:0]
    38  	parser.availableColors = append(parser.availableColors, 1, 2)
    39  	parser.nextAvailable = 0
    40  	parser.firstInUse = -1
    41  	parser.firstAvailable = 0
    42  	parser.maxAllowedColors = 0
    43  }
    44  
    45  // AddLineToGraph adds the line as a row to the graph
    46  func (parser *Parser) AddLineToGraph(graph *Graph, row int, line []byte) error {
    47  	idx := bytes.Index(line, []byte("DATA:"))
    48  	if idx < 0 {
    49  		parser.ParseGlyphs(line)
    50  	} else {
    51  		parser.ParseGlyphs(line[:idx])
    52  	}
    53  
    54  	var err error
    55  	commitDone := false
    56  
    57  	for column, glyph := range parser.glyphs {
    58  		if glyph == ' ' {
    59  			continue
    60  		}
    61  
    62  		flowID := parser.flows[column]
    63  
    64  		graph.AddGlyph(row, column, flowID, parser.colors[column], glyph)
    65  
    66  		if glyph == '*' {
    67  			if commitDone {
    68  				if err != nil {
    69  					err = fmt.Errorf("double commit on line %d: %s. %w", row, string(line), err)
    70  				} else {
    71  					err = fmt.Errorf("double commit on line %d: %s", row, string(line))
    72  				}
    73  			}
    74  			commitDone = true
    75  			if idx < 0 {
    76  				if err != nil {
    77  					err = fmt.Errorf("missing data section on line %d with commit: %s. %w", row, string(line), err)
    78  				} else {
    79  					err = fmt.Errorf("missing data section on line %d with commit: %s", row, string(line))
    80  				}
    81  				continue
    82  			}
    83  			err2 := graph.AddCommit(row, column, flowID, line[idx+5:])
    84  			if err != nil && err2 != nil {
    85  				err = fmt.Errorf("%v %w", err2, err)
    86  				continue
    87  			} else if err2 != nil {
    88  				err = err2
    89  				continue
    90  			}
    91  		}
    92  	}
    93  	if !commitDone {
    94  		graph.Commits = append(graph.Commits, RelationCommit)
    95  	}
    96  	return err
    97  }
    98  
    99  func (parser *Parser) releaseUnusedColors() {
   100  	if parser.firstInUse > -1 {
   101  		// Here we step through the old colors, searching for them in the
   102  		// "in-use" section of availableColors (that is, the colors between
   103  		// firstInUse and firstAvailable)
   104  		// Ensure that the benchmarks are not worsened with proposed changes
   105  		stepstaken := 0
   106  		position := parser.firstInUse
   107  		for _, color := range parser.oldColors {
   108  			if color == 0 {
   109  				continue
   110  			}
   111  			found := false
   112  			i := position
   113  			for j := stepstaken; i != parser.firstAvailable && j < len(parser.availableColors); j++ {
   114  				colorToCheck := parser.availableColors[i]
   115  				if colorToCheck == color {
   116  					found = true
   117  					break
   118  				}
   119  				i = (i + 1) % len(parser.availableColors)
   120  			}
   121  			if !found {
   122  				// Duplicate color
   123  				continue
   124  			}
   125  			// Swap them around
   126  			parser.availableColors[position], parser.availableColors[i] = parser.availableColors[i], parser.availableColors[position]
   127  			stepstaken++
   128  			position = (parser.firstInUse + stepstaken) % len(parser.availableColors)
   129  			if position == parser.firstAvailable || stepstaken == len(parser.availableColors) {
   130  				break
   131  			}
   132  		}
   133  		if stepstaken == len(parser.availableColors) {
   134  			parser.firstAvailable = -1
   135  		} else {
   136  			parser.firstAvailable = position
   137  			if parser.nextAvailable == -1 {
   138  				parser.nextAvailable = parser.firstAvailable
   139  			}
   140  		}
   141  	}
   142  }
   143  
   144  // ParseGlyphs parses the provided glyphs and sets the internal state
   145  func (parser *Parser) ParseGlyphs(glyphs []byte) {
   146  	// Clean state for parsing this row
   147  	parser.glyphs, parser.oldGlyphs = parser.oldGlyphs, parser.glyphs
   148  	parser.glyphs = parser.glyphs[0:0]
   149  	parser.flows, parser.oldFlows = parser.oldFlows, parser.flows
   150  	parser.flows = parser.flows[0:0]
   151  	parser.colors, parser.oldColors = parser.oldColors, parser.colors
   152  
   153  	// Ensure we have enough flows and colors
   154  	parser.colors = parser.colors[0:0]
   155  	for range glyphs {
   156  		parser.flows = append(parser.flows, 0)
   157  		parser.colors = append(parser.colors, 0)
   158  	}
   159  
   160  	// Copy the provided glyphs in to state.glyphs for safekeeping
   161  	parser.glyphs = append(parser.glyphs, glyphs...)
   162  
   163  	// release unused colors
   164  	parser.releaseUnusedColors()
   165  
   166  	for i := len(glyphs) - 1; i >= 0; i-- {
   167  		glyph := glyphs[i]
   168  		switch glyph {
   169  		case '|':
   170  			fallthrough
   171  		case '*':
   172  			parser.setUpFlow(i)
   173  		case '/':
   174  			parser.setOutFlow(i)
   175  		case '\\':
   176  			parser.setInFlow(i)
   177  		case '_':
   178  			parser.setRightFlow(i)
   179  		case '.':
   180  			fallthrough
   181  		case '-':
   182  			parser.setLeftFlow(i)
   183  		case ' ':
   184  			// no-op
   185  		default:
   186  			parser.newFlow(i)
   187  		}
   188  	}
   189  }
   190  
   191  func (parser *Parser) takePreviousFlow(i, j int) {
   192  	if j < len(parser.oldFlows) && parser.oldFlows[j] > 0 {
   193  		parser.flows[i] = parser.oldFlows[j]
   194  		parser.oldFlows[j] = 0
   195  		parser.colors[i] = parser.oldColors[j]
   196  		parser.oldColors[j] = 0
   197  	} else {
   198  		parser.newFlow(i)
   199  	}
   200  }
   201  
   202  func (parser *Parser) takeCurrentFlow(i, j int) {
   203  	if j < len(parser.flows) && parser.flows[j] > 0 {
   204  		parser.flows[i] = parser.flows[j]
   205  		parser.colors[i] = parser.colors[j]
   206  	} else {
   207  		parser.newFlow(i)
   208  	}
   209  }
   210  
   211  func (parser *Parser) newFlow(i int) {
   212  	parser.maxFlow++
   213  	parser.flows[i] = parser.maxFlow
   214  
   215  	// Now give this flow a color
   216  	if parser.nextAvailable == -1 {
   217  		next := len(parser.availableColors)
   218  		if parser.maxAllowedColors < 1 || next < parser.maxAllowedColors {
   219  			parser.nextAvailable = next
   220  			parser.firstAvailable = next
   221  			parser.availableColors = append(parser.availableColors, next+1)
   222  		}
   223  	}
   224  	parser.colors[i] = parser.availableColors[parser.nextAvailable]
   225  	if parser.firstInUse == -1 {
   226  		parser.firstInUse = parser.nextAvailable
   227  	}
   228  	parser.availableColors[parser.firstAvailable], parser.availableColors[parser.nextAvailable] = parser.availableColors[parser.nextAvailable], parser.availableColors[parser.firstAvailable]
   229  
   230  	parser.nextAvailable = (parser.nextAvailable + 1) % len(parser.availableColors)
   231  	parser.firstAvailable = (parser.firstAvailable + 1) % len(parser.availableColors)
   232  
   233  	if parser.nextAvailable == parser.firstInUse {
   234  		parser.nextAvailable = parser.firstAvailable
   235  	}
   236  	if parser.nextAvailable == parser.firstInUse {
   237  		parser.nextAvailable = -1
   238  		parser.firstAvailable = -1
   239  	}
   240  }
   241  
   242  // setUpFlow handles '|' or '*'
   243  func (parser *Parser) setUpFlow(i int) {
   244  	// In preference order:
   245  	//
   246  	// Previous Row: '\? ' ' |' '  /'
   247  	// Current Row:  ' | ' ' |' ' | '
   248  	if i > 0 && i-1 < len(parser.oldGlyphs) && parser.oldGlyphs[i-1] == '\\' {
   249  		parser.takePreviousFlow(i, i-1)
   250  	} else if i < len(parser.oldGlyphs) && (parser.oldGlyphs[i] == '|' || parser.oldGlyphs[i] == '*') {
   251  		parser.takePreviousFlow(i, i)
   252  	} else if i+1 < len(parser.oldGlyphs) && parser.oldGlyphs[i+1] == '/' {
   253  		parser.takePreviousFlow(i, i+1)
   254  	} else {
   255  		parser.newFlow(i)
   256  	}
   257  }
   258  
   259  // setOutFlow handles '/'
   260  func (parser *Parser) setOutFlow(i int) {
   261  	// In preference order:
   262  	//
   263  	// Previous Row: ' |/' ' |_' ' |' ' /' ' _' '\'
   264  	// Current Row:  '/| ' '/| ' '/ ' '/ ' '/ ' '/'
   265  	if i+2 < len(parser.oldGlyphs) &&
   266  		(parser.oldGlyphs[i+1] == '|' || parser.oldGlyphs[i+1] == '*') &&
   267  		(parser.oldGlyphs[i+2] == '/' || parser.oldGlyphs[i+2] == '_') &&
   268  		i+1 < len(parser.glyphs) &&
   269  		(parser.glyphs[i+1] == '|' || parser.glyphs[i+1] == '*') {
   270  		parser.takePreviousFlow(i, i+2)
   271  	} else if i+1 < len(parser.oldGlyphs) &&
   272  		(parser.oldGlyphs[i+1] == '|' || parser.oldGlyphs[i+1] == '*' ||
   273  			parser.oldGlyphs[i+1] == '/' || parser.oldGlyphs[i+1] == '_') {
   274  		parser.takePreviousFlow(i, i+1)
   275  		if parser.oldGlyphs[i+1] == '/' {
   276  			parser.glyphs[i] = '|'
   277  		}
   278  	} else if i < len(parser.oldGlyphs) && parser.oldGlyphs[i] == '\\' {
   279  		parser.takePreviousFlow(i, i)
   280  	} else {
   281  		parser.newFlow(i)
   282  	}
   283  }
   284  
   285  // setInFlow handles '\'
   286  func (parser *Parser) setInFlow(i int) {
   287  	// In preference order:
   288  	//
   289  	// Previous Row: '| ' '-. ' '| ' '\ ' '/' '---'
   290  	// Current Row:  '|\' '  \' ' \' ' \' '\' ' \ '
   291  	if i > 0 && i-1 < len(parser.oldGlyphs) &&
   292  		(parser.oldGlyphs[i-1] == '|' || parser.oldGlyphs[i-1] == '*') &&
   293  		(parser.glyphs[i-1] == '|' || parser.glyphs[i-1] == '*') {
   294  		parser.newFlow(i)
   295  	} else if i > 0 && i-1 < len(parser.oldGlyphs) &&
   296  		(parser.oldGlyphs[i-1] == '|' || parser.oldGlyphs[i-1] == '*' ||
   297  			parser.oldGlyphs[i-1] == '.' || parser.oldGlyphs[i-1] == '\\') {
   298  		parser.takePreviousFlow(i, i-1)
   299  		if parser.oldGlyphs[i-1] == '\\' {
   300  			parser.glyphs[i] = '|'
   301  		}
   302  	} else if i < len(parser.oldGlyphs) && parser.oldGlyphs[i] == '/' {
   303  		parser.takePreviousFlow(i, i)
   304  	} else {
   305  		parser.newFlow(i)
   306  	}
   307  }
   308  
   309  // setRightFlow handles '_'
   310  func (parser *Parser) setRightFlow(i int) {
   311  	// In preference order:
   312  	//
   313  	// Current Row:  '__' '_/' '_|_' '_|/'
   314  	if i+1 < len(parser.glyphs) &&
   315  		(parser.glyphs[i+1] == '_' || parser.glyphs[i+1] == '/') {
   316  		parser.takeCurrentFlow(i, i+1)
   317  	} else if i+2 < len(parser.glyphs) &&
   318  		(parser.glyphs[i+1] == '|' || parser.glyphs[i+1] == '*') &&
   319  		(parser.glyphs[i+2] == '_' || parser.glyphs[i+2] == '/') {
   320  		parser.takeCurrentFlow(i, i+2)
   321  	} else {
   322  		parser.newFlow(i)
   323  	}
   324  }
   325  
   326  // setLeftFlow handles '----.'
   327  func (parser *Parser) setLeftFlow(i int) {
   328  	if parser.glyphs[i] == '.' {
   329  		parser.newFlow(i)
   330  	} else if i+1 < len(parser.glyphs) &&
   331  		(parser.glyphs[i+1] == '-' || parser.glyphs[i+1] == '.') {
   332  		parser.takeCurrentFlow(i, i+1)
   333  	} else {
   334  		parser.newFlow(i)
   335  	}
   336  }