Golang代码审查:基于正则表达式的"tokenizer"库实现分析

Golang代码审查:基于正则表达式的"tokenizer"库实现分析 大家好,

这是我用GO编写的第一个库。这是一个基于正则表达式的"分词器"库:

GitHub

gomillas/parser

一个基于正则表达式解析字符串的GO库 - gomillas/parser

这是一个概念验证。以下代码用于计算数学表达式:

github.com

package parser_test

import (
	"errors"
	"fmt"
	"math"
	"strconv"

	"github.com/gomillas/parser"
)

func Example() {
	const src = "125 + 2 * (sqrt 9 - 1) - 3"

	m := parser.New(src)
	if result, err := mathExp(m, "="); err == nil {
		fmt.Println(result)
	} else {
		panic(
			fmt.Errorf("%s (line: %d, col: %d)", err.Error(), m.Line(), m.Column()),

我希望能够得到一些评论,以及代码是否可以在某些方面进行改进。 谢谢。


更多关于Golang代码审查:基于正则表达式的"tokenizer"库实现分析的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang代码审查:基于正则表达式的"tokenizer"库实现分析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


以下是针对您基于正则表达式的分词器库的代码审查分析。我将重点讨论设计模式、正则表达式使用、错误处理和性能优化等方面,并提供具体示例说明。

1. 正则表达式设计与效率

您的分词器核心依赖于正则表达式匹配,建议优化模式定义以提高性能:

// 当前实现可能存在的问题:重复编译正则表达式
// 建议预编译正则表达式模式
var (
    numberRegex = regexp.MustCompile(`^\d+(\.\d+)?`)
    operatorRegex = regexp.MustCompile(`^[+\-*/]`)
    parenRegex = regexp.MustCompile(`^[()]`)
    funcRegex = regexp.MustCompile(`^sqrt`)
)

func (m *Matcher) matchToken() (Token, error) {
    // 按优先级匹配,避免回溯
    if match := numberRegex.FindStringSubmatch(m.input[m.pos:]); match != nil {
        return Token{Type: Number, Value: match[0]}, nil
    }
    if match := funcRegex.FindStringSubmatch(m.input[m.pos:]); match != nil {
        return Token{Type: Function, Value: match[0]}, nil
    }
    // ... 其他匹配
}

2. 词法分析器状态管理

当前实现可能缺少明确的有限状态机,建议采用更结构化的状态转换:

type Lexer struct {
    input   string
    pos     int
    tokens  []Token
    state   lexState
}

type lexState int

const (
    stateDefault lexState = iota
    stateNumber
    stateIdentifier
    stateOperator
)

func (l *Lexer) tokenize() error {
    for l.pos < len(l.input) {
        switch l.state {
        case stateDefault:
            if unicode.IsDigit(rune(l.input[l.pos])) {
                l.state = stateNumber
                continue
            }
            // 其他状态转换
        case stateNumber:
            // 处理数字状态
            if !unicode.IsDigit(rune(l.input[l.pos])) {
                l.emitToken(TokenNumber)
                l.state = stateDefault
                continue
            }
        }
        l.pos++
    }
    return nil
}

3. 错误处理与位置跟踪

改进错误报告机制,提供更精确的定位信息:

type ParseError struct {
    Message string
    Line    int
    Column  int
    Offset  int
}

func (m *Matcher) expectToken(expectedType TokenType) (Token, error) {
    token, err := m.nextToken()
    if err != nil {
        return Token{}, err
    }
    if token.Type != expectedType {
        return Token{}, &ParseError{
            Message: fmt.Sprintf("expected %s, got %s", expectedType, token.Type),
            Line:    m.line,
            Column:  m.column,
            Offset:  m.pos,
        }
    }
    return token, nil
}

4. 内存分配优化

减少在分词过程中不必要的字符串分配:

func (l *Lexer) readNumber() string {
    start := l.pos
    for l.pos < len(l.input) && unicode.IsDigit(rune(l.input[l.pos])) {
        l.pos++
    }
    // 直接返回切片,避免字符串复制
    return l.input[start:l.pos]
}

// 使用字节切片而非字符串进行匹配
func matchPattern(input []byte, pattern *regexp.Regexp) bool {
    return pattern.Match(input)
}

5. 递归下降解析器实现

针对数学表达式的解析,建议采用明确的递归下降方法:

func (p *Parser) parseExpression() (float64, error) {
    left, err := p.parseTerm()
    if err != nil {
        return 0, err
    }
    
    for p.current().Type == Add || p.current().Type == Subtract {
        op := p.current()
        p.advance()
        right, err := p.parseTerm()
        if err != nil {
            return 0, err
        }
        
        if op.Type == Add {
            left += right
        } else {
            left -= right
        }
    }
    return left, nil
}

func (p *Parser) parseTerm() (float64, error) {
    left, err := p.parseFactor()
    if err != nil {
        return 0, err
    }
    
    for p.current().Type == Multiply || p.current().Type == Divide {
        op := p.current()
        p.advance()
        right, err := p.parseFactor()
        if err != nil {
            return 0, err
        }
        
        if op.Type == Multiply {
            left *= right
        } else {
            if right == 0 {
                return 0, fmt.Errorf("division by zero")
            }
            left /= right
        }
    }
    return left, nil
}

6. 测试覆盖与基准测试

添加全面的测试用例和性能基准:

func TestMathExpressions(t *testing.T) {
    tests := []struct {
        input    string
        expected float64
        hasError bool
    }{
        {"2 + 3", 5, false},
        {"(1 + 2) * 3", 9, false},
        {"sqrt 16", 4, false},
        {"1 / 0", 0, true},
    }
    
    for _, test := range tests {
        m := parser.New(test.input)
        result, err := mathExp(m, "=")
        if test.hasError && err == nil {
            t.Errorf("expected error for input %s", test.input)
        }
        if !test.hasError && result != test.expected {
            t.Errorf("for input %s, expected %f, got %f", test.input, test.expected, result)
        }
    }
}

func BenchmarkTokenizer(b *testing.B) {
    input := "125 + 2 * (sqrt 9 - 1) - 3"
    for i := 0; i < b.N; i++ {
        m := parser.New(input)
        _, _ = mathExp(m, "=")
    }
}

这些改进建议主要关注代码结构、性能优化和错误处理,将使您的分词器库更加健壮和高效。正则表达式分词器的关键在于平衡灵活性与性能,通过预编译模式和状态机设计可以显著提升处理速度。

回到顶部