Source

units / token.go

package units

type tokenError string

func (err tokenError) Error() string {
	return string(err)
}

type tokenizer struct {
	src string
	i   int
}

func (t *tokenizer) scanDigits() {
	for ; t.i < len(t.src); t.i++ {
		if c := t.src[t.i]; '0' > c || c > '9' {
			break
		}
	}
	return
}

func (t *tokenizer) scanNumber() (token string, err error, done bool) {
	start := t.i
	t.scanDigits()
	if t.i >= len(t.src) {
		goto exit
	}

	if t.src[t.i] == '.' {
		t.i++
		t.scanDigits()
		if t.i >= len(t.src) {
			goto exit
		}
	}

	if c := t.src[t.i]; c == 'e' || c == 'E' {
		t.i++
		if t.i >= len(t.src) {
			goto exit
		}

		if c = t.src[t.i]; c == '-' || c == '+' {
			t.i++
		}
		t.scanDigits()
	}

exit:
	return t.src[start:t.i], nil, false
}

func unitByte(b byte) bool {
	if '0' <= b && b <= '9' {
		return false
	}
	switch b {
	case '(', ')', '+', '-', '.', ' ':
		return false
	}
	return true
}

func (t *tokenizer) scanUnit() (token string, err error, done bool) {
	start := t.i
	for ; t.i < len(t.src); t.i++ {
		c := t.src[t.i]
		if !unitByte(c) {
			return t.src[start:t.i], nil, false
		}
		if c == '/' || c == '*' {
			if t.i+1 == len(t.src) || !unitByte(t.src[t.i+1]) {
				break
			}
		}
	}
	return t.src[start:t.i], nil, false
}

// Return the next token, or error, or no tokens left.
func (t *tokenizer) next() (token string, err error, done bool) {
	start := t.i
	for ; t.i < len(t.src); t.i += 1 {
		switch t.src[t.i] {
		case ' ':
			if start == t.i {
				start += 1
			} else {
				token = t.src[start:t.i]
				return
			}
		case '(', ')', '*', '/', '+', '-':
			if start == t.i {
				t.i += 1
			}
			token = t.src[start:t.i]
			return
		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.':
			if start == t.i {
				return t.scanNumber()
			}
			token = t.src[start:t.i]
			return
		default:
			return t.scanUnit()
		}
	}
	if start != t.i {
		token = t.src[start:t.i]
		return
	}
	return "", nil, true
}

func tokenize(src string) (tokens []string, err error) {
	tokenizer := &tokenizer{src: src}
	token, err, done := tokenizer.next()
	for err == nil && !done {
		tokens = append(tokens, token)
		token, err, done = tokenizer.next()
	}
	if err != nil {
		tokens = nil
	}
	return
}