// Copyright 2014 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package expfmt

import (
	
	
	
	
	
	
	
	
	

	dto 
	

	
)

// A stateFn is a function that represents a state in a state machine. By
// executing it, the state is progressed to the next state. The stateFn returns
// another stateFn, which represents the new state. The end state is represented
// by nil.
type stateFn func() stateFn

// ParseError signals errors while parsing the simple and flat text-based
// exchange format.
type ParseError struct {
	Line int
	Msg  string
}

// Error implements the error interface.
func ( ParseError) () string {
	return fmt.Sprintf("text format parsing error in line %d: %s", .Line, .Msg)
}

// TextParser is used to parse the simple and flat text-based exchange format. Its
// zero value is ready to use.
type TextParser struct {
	metricFamiliesByName map[string]*dto.MetricFamily
	buf                  *bufio.Reader // Where the parsed input is read through.
	err                  error         // Most recent error.
	lineCount            int           // Tracks the line count for error messages.
	currentByte          byte          // The most recent byte read.
	currentToken         bytes.Buffer  // Re-used each time a token has to be gathered from multiple bytes.
	currentMF            *dto.MetricFamily
	currentMetric        *dto.Metric
	currentLabelPair     *dto.LabelPair
	currentLabelPairs    []*dto.LabelPair // Temporarily stores label pairs while parsing a metric line.

	// The remaining member variables are only used for summaries/histograms.
	currentLabels map[string]string // All labels including '__name__' but excluding 'quantile'/'le'
	// Summary specific.
	summaries       map[uint64]*dto.Metric // Key is created with LabelsToSignature.
	currentQuantile float64
	// Histogram specific.
	histograms    map[uint64]*dto.Metric // Key is created with LabelsToSignature.
	currentBucket float64
	// These tell us if the currently processed line ends on '_count' or
	// '_sum' respectively and belong to a summary/histogram, representing the sample
	// count and sum of that summary/histogram.
	currentIsSummaryCount, currentIsSummarySum     bool
	currentIsHistogramCount, currentIsHistogramSum bool
	// These indicate if the metric name from the current line being parsed is inside
	// braces and if that metric name was found respectively.
	currentMetricIsInsideBraces, currentMetricInsideBracesIsPresent bool
}

// TextToMetricFamilies reads 'in' as the simple and flat text-based exchange
// format and creates MetricFamily proto messages. It returns the MetricFamily
// proto messages in a map where the metric names are the keys, along with any
// error encountered.
//
// If the input contains duplicate metrics (i.e. lines with the same metric name
// and exactly the same label set), the resulting MetricFamily will contain
// duplicate Metric proto messages. Similar is true for duplicate label
// names. Checks for duplicates have to be performed separately, if required.
// Also note that neither the metrics within each MetricFamily are sorted nor
// the label pairs within each Metric. Sorting is not required for the most
// frequent use of this method, which is sample ingestion in the Prometheus
// server. However, for presentation purposes, you might want to sort the
// metrics, and in some cases, you must sort the labels, e.g. for consumption by
// the metric family injection hook of the Prometheus registry.
//
// Summaries and histograms are rather special beasts. You would probably not
// use them in the simple text format anyway. This method can deal with
// summaries and histograms if they are presented in exactly the way the
// text.Create function creates them.
//
// This method must not be called concurrently. If you want to parse different
// input concurrently, instantiate a separate Parser for each goroutine.
func ( *TextParser) ( io.Reader) (map[string]*dto.MetricFamily, error) {
	.reset()
	for  := .startOfLine;  != nil;  = () {
		// Magic happens here...
	}
	// Get rid of empty metric families.
	for ,  := range .metricFamiliesByName {
		if len(.GetMetric()) == 0 {
			delete(.metricFamiliesByName, )
		}
	}
	// If p.err is io.EOF now, we have run into a premature end of the input
	// stream. Turn this error into something nicer and more
	// meaningful. (io.EOF is often used as a signal for the legitimate end
	// of an input stream.)
	if .err != nil && errors.Is(.err, io.EOF) {
		.parseError("unexpected end of input stream")
	}
	return .metricFamiliesByName, .err
}

func ( *TextParser) ( io.Reader) {
	.metricFamiliesByName = map[string]*dto.MetricFamily{}
	if .buf == nil {
		.buf = bufio.NewReader()
	} else {
		.buf.Reset()
	}
	.err = nil
	.lineCount = 0
	if .summaries == nil || len(.summaries) > 0 {
		.summaries = map[uint64]*dto.Metric{}
	}
	if .histograms == nil || len(.histograms) > 0 {
		.histograms = map[uint64]*dto.Metric{}
	}
	.currentQuantile = math.NaN()
	.currentBucket = math.NaN()
	.currentMF = nil
}

// startOfLine represents the state where the next byte read from p.buf is the
// start of a line (or whitespace leading up to it).
func ( *TextParser) () stateFn {
	.lineCount++
	.currentMetricIsInsideBraces = false
	.currentMetricInsideBracesIsPresent = false
	if .skipBlankTab(); .err != nil {
		// This is the only place that we expect to see io.EOF,
		// which is not an error but the signal that we are done.
		// Any other error that happens to align with the start of
		// a line is still an error.
		if errors.Is(.err, io.EOF) {
			.err = nil
		}
		return nil
	}
	switch .currentByte {
	case '#':
		return .startComment
	case '\n':
		return . // Empty line, start the next one.
	case '{':
		.currentMetricIsInsideBraces = true
		return .readingLabels
	}
	return .readingMetricName
}

// startComment represents the state where the next byte read from p.buf is the
// start of a comment (or whitespace leading up to it).
func ( *TextParser) () stateFn {
	if .skipBlankTab(); .err != nil {
		return nil // Unexpected end of input.
	}
	if .currentByte == '\n' {
		return .startOfLine
	}
	if .readTokenUntilWhitespace(); .err != nil {
		return nil // Unexpected end of input.
	}
	// If we have hit the end of line already, there is nothing left
	// to do. This is not considered a syntax error.
	if .currentByte == '\n' {
		return .startOfLine
	}
	 := .currentToken.String()
	if  != "HELP" &&  != "TYPE" {
		// Generic comment, ignore by fast forwarding to end of line.
		for .currentByte != '\n' {
			if .currentByte, .err = .buf.ReadByte(); .err != nil {
				return nil // Unexpected end of input.
			}
		}
		return .startOfLine
	}
	// There is something. Next has to be a metric name.
	if .skipBlankTab(); .err != nil {
		return nil // Unexpected end of input.
	}
	if .readTokenAsMetricName(); .err != nil {
		return nil // Unexpected end of input.
	}
	if .currentByte == '\n' {
		// At the end of the line already.
		// Again, this is not considered a syntax error.
		return .startOfLine
	}
	if !isBlankOrTab(.currentByte) {
		.parseError("invalid metric name in comment")
		return nil
	}
	.setOrCreateCurrentMF()
	if .skipBlankTab(); .err != nil {
		return nil // Unexpected end of input.
	}
	if .currentByte == '\n' {
		// At the end of the line already.
		// Again, this is not considered a syntax error.
		return .startOfLine
	}
	switch  {
	case "HELP":
		return .readingHelp
	case "TYPE":
		return .readingType
	}
	panic(fmt.Sprintf("code error: unexpected keyword %q", ))
}

// readingMetricName represents the state where the last byte read (now in
// p.currentByte) is the first byte of a metric name.
func ( *TextParser) () stateFn {
	if .readTokenAsMetricName(); .err != nil {
		return nil
	}
	if .currentToken.Len() == 0 {
		.parseError("invalid metric name")
		return nil
	}
	.setOrCreateCurrentMF()
	// Now is the time to fix the type if it hasn't happened yet.
	if .currentMF.Type == nil {
		.currentMF.Type = dto.MetricType_UNTYPED.Enum()
	}
	.currentMetric = &dto.Metric{}
	// Do not append the newly created currentMetric to
	// currentMF.Metric right now. First wait if this is a summary,
	// and the metric exists already, which we can only know after
	// having read all the labels.
	if .skipBlankTabIfCurrentBlankTab(); .err != nil {
		return nil // Unexpected end of input.
	}
	return .readingLabels
}

// readingLabels represents the state where the last byte read (now in
// p.currentByte) is either the first byte of the label set (i.e. a '{'), or the
// first byte of the value (otherwise).
func ( *TextParser) () stateFn {
	// Summaries/histograms are special. We have to reset the
	// currentLabels map, currentQuantile and currentBucket before starting to
	// read labels.
	if .currentMF.GetType() == dto.MetricType_SUMMARY || .currentMF.GetType() == dto.MetricType_HISTOGRAM {
		.currentLabels = map[string]string{}
		.currentLabels[string(model.MetricNameLabel)] = .currentMF.GetName()
		.currentQuantile = math.NaN()
		.currentBucket = math.NaN()
	}
	if .currentByte != '{' {
		return .readingValue
	}
	return .startLabelName
}

// startLabelName represents the state where the next byte read from p.buf is
// the start of a label name (or whitespace leading up to it).
func ( *TextParser) () stateFn {
	if .skipBlankTab(); .err != nil {
		return nil // Unexpected end of input.
	}
	if .currentByte == '}' {
		.currentMetric.Label = append(.currentMetric.Label, .currentLabelPairs...)
		.currentLabelPairs = nil
		if .skipBlankTab(); .err != nil {
			return nil // Unexpected end of input.
		}
		return .readingValue
	}
	if .readTokenAsLabelName(); .err != nil {
		return nil // Unexpected end of input.
	}
	if .currentToken.Len() == 0 {
		.parseError(fmt.Sprintf("invalid label name for metric %q", .currentMF.GetName()))
		return nil
	}
	if .skipBlankTabIfCurrentBlankTab(); .err != nil {
		return nil // Unexpected end of input.
	}
	if .currentByte != '=' {
		if .currentMetricIsInsideBraces {
			if .currentMetricInsideBracesIsPresent {
				.parseError(fmt.Sprintf("multiple metric names for metric %q", .currentMF.GetName()))
				return nil
			}
			switch .currentByte {
			case ',':
				.setOrCreateCurrentMF()
				if .currentMF.Type == nil {
					.currentMF.Type = dto.MetricType_UNTYPED.Enum()
				}
				.currentMetric = &dto.Metric{}
				.currentMetricInsideBracesIsPresent = true
				return .
			case '}':
				.setOrCreateCurrentMF()
				if .currentMF.Type == nil {
					.currentMF.Type = dto.MetricType_UNTYPED.Enum()
				}
				.currentMetric = &dto.Metric{}
				.currentMetric.Label = append(.currentMetric.Label, .currentLabelPairs...)
				.currentLabelPairs = nil
				if .skipBlankTab(); .err != nil {
					return nil // Unexpected end of input.
				}
				return .readingValue
			default:
				.parseError(fmt.Sprintf("unexpected end of metric name %q", .currentByte))
				return nil
			}
		}
		.parseError(fmt.Sprintf("expected '=' after label name, found %q", .currentByte))
		.currentLabelPairs = nil
		return nil
	}
	.currentLabelPair = &dto.LabelPair{Name: proto.String(.currentToken.String())}
	if .currentLabelPair.GetName() == string(model.MetricNameLabel) {
		.parseError(fmt.Sprintf("label name %q is reserved", model.MetricNameLabel))
		return nil
	}
	// Special summary/histogram treatment. Don't add 'quantile' and 'le'
	// labels to 'real' labels.
	if (.currentMF.GetType() != dto.MetricType_SUMMARY || .currentLabelPair.GetName() != model.QuantileLabel) &&
		(.currentMF.GetType() != dto.MetricType_HISTOGRAM || .currentLabelPair.GetName() != model.BucketLabel) {
		.currentLabelPairs = append(.currentLabelPairs, .currentLabelPair)
	}
	// Check for duplicate label names.
	 := make(map[string]struct{})
	for ,  := range .currentLabelPairs {
		 := .GetName()
		if ,  := []; ! {
			[] = struct{}{}
		} else {
			.parseError(fmt.Sprintf("duplicate label names for metric %q", .currentMF.GetName()))
			.currentLabelPairs = nil
			return nil
		}
	}
	return .startLabelValue
}

// startLabelValue represents the state where the next byte read from p.buf is
// the start of a (quoted) label value (or whitespace leading up to it).
func ( *TextParser) () stateFn {
	if .skipBlankTab(); .err != nil {
		return nil // Unexpected end of input.
	}
	if .currentByte != '"' {
		.parseError(fmt.Sprintf("expected '\"' at start of label value, found %q", .currentByte))
		return nil
	}
	if .readTokenAsLabelValue(); .err != nil {
		return nil
	}
	if !model.LabelValue(.currentToken.String()).IsValid() {
		.parseError(fmt.Sprintf("invalid label value %q", .currentToken.String()))
		return nil
	}
	.currentLabelPair.Value = proto.String(.currentToken.String())
	// Special treatment of summaries:
	// - Quantile labels are special, will result in dto.Quantile later.
	// - Other labels have to be added to currentLabels for signature calculation.
	if .currentMF.GetType() == dto.MetricType_SUMMARY {
		if .currentLabelPair.GetName() == model.QuantileLabel {
			if .currentQuantile, .err = parseFloat(.currentLabelPair.GetValue()); .err != nil {
				// Create a more helpful error message.
				.parseError(fmt.Sprintf("expected float as value for 'quantile' label, got %q", .currentLabelPair.GetValue()))
				.currentLabelPairs = nil
				return nil
			}
		} else {
			.currentLabels[.currentLabelPair.GetName()] = .currentLabelPair.GetValue()
		}
	}
	// Similar special treatment of histograms.
	if .currentMF.GetType() == dto.MetricType_HISTOGRAM {
		if .currentLabelPair.GetName() == model.BucketLabel {
			if .currentBucket, .err = parseFloat(.currentLabelPair.GetValue()); .err != nil {
				// Create a more helpful error message.
				.parseError(fmt.Sprintf("expected float as value for 'le' label, got %q", .currentLabelPair.GetValue()))
				return nil
			}
		} else {
			.currentLabels[.currentLabelPair.GetName()] = .currentLabelPair.GetValue()
		}
	}
	if .skipBlankTab(); .err != nil {
		return nil // Unexpected end of input.
	}
	switch .currentByte {
	case ',':
		return .startLabelName

	case '}':
		if .currentMF == nil {
			.parseError("invalid metric name")
			return nil
		}
		.currentMetric.Label = append(.currentMetric.Label, .currentLabelPairs...)
		.currentLabelPairs = nil
		if .skipBlankTab(); .err != nil {
			return nil // Unexpected end of input.
		}
		return .readingValue
	default:
		.parseError(fmt.Sprintf("unexpected end of label value %q", .currentLabelPair.GetValue()))
		.currentLabelPairs = nil
		return nil
	}
}

// readingValue represents the state where the last byte read (now in
// p.currentByte) is the first byte of the sample value (i.e. a float).
func ( *TextParser) () stateFn {
	// When we are here, we have read all the labels, so for the
	// special case of a summary/histogram, we can finally find out
	// if the metric already exists.
	if .currentMF.GetType() == dto.MetricType_SUMMARY {
		 := model.LabelsToSignature(.currentLabels)
		if  := .summaries[];  != nil {
			.currentMetric = 
		} else {
			.summaries[] = .currentMetric
			.currentMF.Metric = append(.currentMF.Metric, .currentMetric)
		}
	} else if .currentMF.GetType() == dto.MetricType_HISTOGRAM {
		 := model.LabelsToSignature(.currentLabels)
		if  := .histograms[];  != nil {
			.currentMetric = 
		} else {
			.histograms[] = .currentMetric
			.currentMF.Metric = append(.currentMF.Metric, .currentMetric)
		}
	} else {
		.currentMF.Metric = append(.currentMF.Metric, .currentMetric)
	}
	if .readTokenUntilWhitespace(); .err != nil {
		return nil // Unexpected end of input.
	}
	,  := parseFloat(.currentToken.String())
	if  != nil {
		// Create a more helpful error message.
		.parseError(fmt.Sprintf("expected float as value, got %q", .currentToken.String()))
		return nil
	}
	switch .currentMF.GetType() {
	case dto.MetricType_COUNTER:
		.currentMetric.Counter = &dto.Counter{Value: proto.Float64()}
	case dto.MetricType_GAUGE:
		.currentMetric.Gauge = &dto.Gauge{Value: proto.Float64()}
	case dto.MetricType_UNTYPED:
		.currentMetric.Untyped = &dto.Untyped{Value: proto.Float64()}
	case dto.MetricType_SUMMARY:
		// *sigh*
		if .currentMetric.Summary == nil {
			.currentMetric.Summary = &dto.Summary{}
		}
		switch {
		case .currentIsSummaryCount:
			.currentMetric.Summary.SampleCount = proto.Uint64(uint64())
		case .currentIsSummarySum:
			.currentMetric.Summary.SampleSum = proto.Float64()
		case !math.IsNaN(.currentQuantile):
			.currentMetric.Summary.Quantile = append(
				.currentMetric.Summary.Quantile,
				&dto.Quantile{
					Quantile: proto.Float64(.currentQuantile),
					Value:    proto.Float64(),
				},
			)
		}
	case dto.MetricType_HISTOGRAM:
		// *sigh*
		if .currentMetric.Histogram == nil {
			.currentMetric.Histogram = &dto.Histogram{}
		}
		switch {
		case .currentIsHistogramCount:
			.currentMetric.Histogram.SampleCount = proto.Uint64(uint64())
		case .currentIsHistogramSum:
			.currentMetric.Histogram.SampleSum = proto.Float64()
		case !math.IsNaN(.currentBucket):
			.currentMetric.Histogram.Bucket = append(
				.currentMetric.Histogram.Bucket,
				&dto.Bucket{
					UpperBound:      proto.Float64(.currentBucket),
					CumulativeCount: proto.Uint64(uint64()),
				},
			)
		}
	default:
		.err = fmt.Errorf("unexpected type for metric name %q", .currentMF.GetName())
	}
	if .currentByte == '\n' {
		return .startOfLine
	}
	return .startTimestamp
}

// startTimestamp represents the state where the next byte read from p.buf is
// the start of the timestamp (or whitespace leading up to it).
func ( *TextParser) () stateFn {
	if .skipBlankTab(); .err != nil {
		return nil // Unexpected end of input.
	}
	if .readTokenUntilWhitespace(); .err != nil {
		return nil // Unexpected end of input.
	}
	,  := strconv.ParseInt(.currentToken.String(), 10, 64)
	if  != nil {
		// Create a more helpful error message.
		.parseError(fmt.Sprintf("expected integer as timestamp, got %q", .currentToken.String()))
		return nil
	}
	.currentMetric.TimestampMs = proto.Int64()
	if .readTokenUntilNewline(false); .err != nil {
		return nil // Unexpected end of input.
	}
	if .currentToken.Len() > 0 {
		.parseError(fmt.Sprintf("spurious string after timestamp: %q", .currentToken.String()))
		return nil
	}
	return .startOfLine
}

// readingHelp represents the state where the last byte read (now in
// p.currentByte) is the first byte of the docstring after 'HELP'.
func ( *TextParser) () stateFn {
	if .currentMF.Help != nil {
		.parseError(fmt.Sprintf("second HELP line for metric name %q", .currentMF.GetName()))
		return nil
	}
	// Rest of line is the docstring.
	if .readTokenUntilNewline(true); .err != nil {
		return nil // Unexpected end of input.
	}
	.currentMF.Help = proto.String(.currentToken.String())
	return .startOfLine
}

// readingType represents the state where the last byte read (now in
// p.currentByte) is the first byte of the type hint after 'HELP'.
func ( *TextParser) () stateFn {
	if .currentMF.Type != nil {
		.parseError(fmt.Sprintf("second TYPE line for metric name %q, or TYPE reported after samples", .currentMF.GetName()))
		return nil
	}
	// Rest of line is the type.
	if .readTokenUntilNewline(false); .err != nil {
		return nil // Unexpected end of input.
	}
	,  := dto.MetricType_value[strings.ToUpper(.currentToken.String())]
	if ! {
		.parseError(fmt.Sprintf("unknown metric type %q", .currentToken.String()))
		return nil
	}
	.currentMF.Type = dto.MetricType().Enum()
	return .startOfLine
}

// parseError sets p.err to a ParseError at the current line with the given
// message.
func ( *TextParser) ( string) {
	.err = ParseError{
		Line: .lineCount,
		Msg:  ,
	}
}

// skipBlankTab reads (and discards) bytes from p.buf until it encounters a byte
// that is neither ' ' nor '\t'. That byte is left in p.currentByte.
func ( *TextParser) () {
	for {
		if .currentByte, .err = .buf.ReadByte(); .err != nil || !isBlankOrTab(.currentByte) {
			return
		}
	}
}

// skipBlankTabIfCurrentBlankTab works exactly as skipBlankTab but doesn't do
// anything if p.currentByte is neither ' ' nor '\t'.
func ( *TextParser) () {
	if isBlankOrTab(.currentByte) {
		.skipBlankTab()
	}
}

// readTokenUntilWhitespace copies bytes from p.buf into p.currentToken.  The
// first byte considered is the byte already read (now in p.currentByte).  The
// first whitespace byte encountered is still copied into p.currentByte, but not
// into p.currentToken.
func ( *TextParser) () {
	.currentToken.Reset()
	for .err == nil && !isBlankOrTab(.currentByte) && .currentByte != '\n' {
		.currentToken.WriteByte(.currentByte)
		.currentByte, .err = .buf.ReadByte()
	}
}

// readTokenUntilNewline copies bytes from p.buf into p.currentToken.  The first
// byte considered is the byte already read (now in p.currentByte).  The first
// newline byte encountered is still copied into p.currentByte, but not into
// p.currentToken. If recognizeEscapeSequence is true, two escape sequences are
// recognized: '\\' translates into '\', and '\n' into a line-feed character.
// All other escape sequences are invalid and cause an error.
func ( *TextParser) ( bool) {
	.currentToken.Reset()
	 := false
	for .err == nil {
		if  &&  {
			switch .currentByte {
			case '\\':
				.currentToken.WriteByte(.currentByte)
			case 'n':
				.currentToken.WriteByte('\n')
			case '"':
				.currentToken.WriteByte('"')
			default:
				.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", .currentByte))
				return
			}
			 = false
		} else {
			switch .currentByte {
			case '\n':
				return
			case '\\':
				 = true
			default:
				.currentToken.WriteByte(.currentByte)
			}
		}
		.currentByte, .err = .buf.ReadByte()
	}
}

// readTokenAsMetricName copies a metric name from p.buf into p.currentToken.
// The first byte considered is the byte already read (now in p.currentByte).
// The first byte not part of a metric name is still copied into p.currentByte,
// but not into p.currentToken.
func ( *TextParser) () {
	.currentToken.Reset()
	// A UTF-8 metric name must be quoted and may have escaped characters.
	 := false
	 := false
	if !isValidMetricNameStart(.currentByte) {
		return
	}
	for .err == nil {
		if  {
			switch .currentByte {
			case '\\':
				.currentToken.WriteByte(.currentByte)
			case 'n':
				.currentToken.WriteByte('\n')
			case '"':
				.currentToken.WriteByte('"')
			default:
				.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", .currentByte))
				return
			}
			 = false
		} else {
			switch .currentByte {
			case '"':
				 = !
				if ! {
					.currentByte, .err = .buf.ReadByte()
					return
				}
			case '\n':
				.parseError(fmt.Sprintf("metric name %q contains unescaped new-line", .currentToken.String()))
				return
			case '\\':
				 = true
			default:
				.currentToken.WriteByte(.currentByte)
			}
		}
		.currentByte, .err = .buf.ReadByte()
		if !isValidMetricNameContinuation(.currentByte, ) || (! && .currentByte == ' ') {
			return
		}
	}
}

// readTokenAsLabelName copies a label name from p.buf into p.currentToken.
// The first byte considered is the byte already read (now in p.currentByte).
// The first byte not part of a label name is still copied into p.currentByte,
// but not into p.currentToken.
func ( *TextParser) () {
	.currentToken.Reset()
	// A UTF-8 label name must be quoted and may have escaped characters.
	 := false
	 := false
	if !isValidLabelNameStart(.currentByte) {
		return
	}
	for .err == nil {
		if  {
			switch .currentByte {
			case '\\':
				.currentToken.WriteByte(.currentByte)
			case 'n':
				.currentToken.WriteByte('\n')
			case '"':
				.currentToken.WriteByte('"')
			default:
				.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", .currentByte))
				return
			}
			 = false
		} else {
			switch .currentByte {
			case '"':
				 = !
				if ! {
					.currentByte, .err = .buf.ReadByte()
					return
				}
			case '\n':
				.parseError(fmt.Sprintf("label name %q contains unescaped new-line", .currentToken.String()))
				return
			case '\\':
				 = true
			default:
				.currentToken.WriteByte(.currentByte)
			}
		}
		.currentByte, .err = .buf.ReadByte()
		if !isValidLabelNameContinuation(.currentByte, ) || (! && .currentByte == '=') {
			return
		}
	}
}

// readTokenAsLabelValue copies a label value from p.buf into p.currentToken.
// In contrast to the other 'readTokenAs...' functions, which start with the
// last read byte in p.currentByte, this method ignores p.currentByte and starts
// with reading a new byte from p.buf. The first byte not part of a label value
// is still copied into p.currentByte, but not into p.currentToken.
func ( *TextParser) () {
	.currentToken.Reset()
	 := false
	for {
		if .currentByte, .err = .buf.ReadByte(); .err != nil {
			return
		}
		if  {
			switch .currentByte {
			case '"', '\\':
				.currentToken.WriteByte(.currentByte)
			case 'n':
				.currentToken.WriteByte('\n')
			default:
				.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", .currentByte))
				.currentLabelPairs = nil
				return
			}
			 = false
			continue
		}
		switch .currentByte {
		case '"':
			return
		case '\n':
			.parseError(fmt.Sprintf("label value %q contains unescaped new-line", .currentToken.String()))
			return
		case '\\':
			 = true
		default:
			.currentToken.WriteByte(.currentByte)
		}
	}
}

func ( *TextParser) () {
	.currentIsSummaryCount = false
	.currentIsSummarySum = false
	.currentIsHistogramCount = false
	.currentIsHistogramSum = false
	 := .currentToken.String()
	if .currentMF = .metricFamiliesByName[]; .currentMF != nil {
		return
	}
	// Try out if this is a _sum or _count for a summary/histogram.
	 := summaryMetricName()
	if .currentMF = .metricFamiliesByName[]; .currentMF != nil {
		if .currentMF.GetType() == dto.MetricType_SUMMARY {
			if isCount() {
				.currentIsSummaryCount = true
			}
			if isSum() {
				.currentIsSummarySum = true
			}
			return
		}
	}
	 := histogramMetricName()
	if .currentMF = .metricFamiliesByName[]; .currentMF != nil {
		if .currentMF.GetType() == dto.MetricType_HISTOGRAM {
			if isCount() {
				.currentIsHistogramCount = true
			}
			if isSum() {
				.currentIsHistogramSum = true
			}
			return
		}
	}
	.currentMF = &dto.MetricFamily{Name: proto.String()}
	.metricFamiliesByName[] = .currentMF
}

func isValidLabelNameStart( byte) bool {
	return ( >= 'a' &&  <= 'z') || ( >= 'A' &&  <= 'Z') ||  == '_' ||  == '"'
}

func isValidLabelNameContinuation( byte,  bool) bool {
	return isValidLabelNameStart() || ( >= '0' &&  <= '9') || ( && utf8.ValidString(string()))
}

func isValidMetricNameStart( byte) bool {
	return isValidLabelNameStart() ||  == ':'
}

func isValidMetricNameContinuation( byte,  bool) bool {
	return isValidLabelNameContinuation(, ) ||  == ':'
}

func isBlankOrTab( byte) bool {
	return  == ' ' ||  == '\t'
}

func isCount( string) bool {
	return len() > 6 && [len()-6:] == "_count"
}

func isSum( string) bool {
	return len() > 4 && [len()-4:] == "_sum"
}

func isBucket( string) bool {
	return len() > 7 && [len()-7:] == "_bucket"
}

func summaryMetricName( string) string {
	switch {
	case isCount():
		return [:len()-6]
	case isSum():
		return [:len()-4]
	default:
		return 
	}
}

func histogramMetricName( string) string {
	switch {
	case isCount():
		return [:len()-6]
	case isSum():
		return [:len()-4]
	case isBucket():
		return [:len()-7]
	default:
		return 
	}
}

func parseFloat( string) (float64, error) {
	if strings.ContainsAny(, "pP_") {
		return 0, errors.New("unsupported character in float")
	}
	return strconv.ParseFloat(, 64)
}