xframe/vendor/github.com/zeromicro/ddl-parser/parser/parser.go

175 lines
4.2 KiB
Go

/*
* MIT License
*
* Copyright (c) 2021 zeromicro
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*/
package parser
import (
"fmt"
"io/ioutil"
"path/filepath"
"github.com/antlr/antlr4/runtime/Go/antlr"
"github.com/zeromicro/ddl-parser/console"
"github.com/zeromicro/ddl-parser/gen"
)
var (
empty []*Table
)
// Parser is the syntax entry to parse sql as AST, you can use NewParser to create
// an instance with options, WithDebugMode option can parse sql with debug, WithLogger
// option can print logs while parsing.
type Parser struct {
antlr.DefaultErrorListener
debug bool
logger console.Console
prefix string
}
// Option is the alias of function.
type Option func(p *Parser)
// Acceptor is the alias of function
type Acceptor func(p *gen.MySqlParser, visitor *visitor) interface{}
// NewParser creates an instance of Parser.
func NewParser(options ...Option) *Parser {
p := &Parser{}
for _, opt := range options {
opt(p)
}
if p.logger == nil {
p.logger = console.NewColorConsole()
}
return p
}
// SyntaxError overrides SyntaxError from antlr.DefaultErrorListener, which could catch error from this function, and panic,
// the parser would catch the panic by testMysqlSyntax and returns.
func (p *Parser) SyntaxError(_ antlr.Recognizer, _ interface{}, line, column int, msg string, _ antlr.RecognitionException) {
str := fmt.Sprintf(`%s line %d:%d %s`, p.prefix, line, column, msg)
if p.debug {
p.logger.Error(str)
}
panic(str)
}
// WithDebugMode is a Parser option to set debug mode.
func WithDebugMode(debug bool) Option {
return func(p *Parser) {
p.debug = debug
}
}
// WithConsole is a Parser option to set console.
func WithConsole(logger console.Console) Option {
return func(p *Parser) {
p.logger = logger
}
}
func (p *Parser) From(filename string) (ret []*Table, err error) {
if !filepath.IsAbs(filename) {
return nil, fmt.Errorf("%s is not a valid path", filename)
}
defer func() {
p := recover()
if p != nil {
switch e := p.(type) {
case error:
err = e
default:
err = fmt.Errorf("%+v", p)
}
}
}()
bytes, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
prefix := filepath.Base(filename)
p.prefix = prefix
inputStream := antlr.NewInputStream(string(bytes))
caseChangingStream := newCaseChangingStream(inputStream, true)
lexer := gen.NewMySqlLexer(caseChangingStream)
lexer.RemoveErrorListeners()
tokens := antlr.NewCommonTokenStream(lexer, antlr.LexerDefaultTokenChannel)
mysqlParser := gen.NewMySqlParser(tokens)
mysqlParser.RemoveErrorListeners()
mysqlParser.AddErrorListener(p)
visitor := &visitor{
prefix: prefix,
debug: p.debug,
logger: p.logger,
}
v := mysqlParser.Root().Accept(visitor)
if v == nil {
return empty, nil
}
createTables, ok := v.([]*CreateTable)
if !ok {
return empty, nil
}
for _, e := range createTables {
ret = append(ret, e.Convert())
}
return
}
// testMysqlSyntax tests the mysql syntax with unit test.
func (p *Parser) testMysqlSyntax(prefix string, acceptor Acceptor, sql string) (v interface{}, err error) {
defer func() {
p := recover()
if p != nil {
switch e := p.(type) {
case error:
err = e
default:
err = fmt.Errorf("%+v", p)
}
}
}()
p.prefix = prefix
inputStream := antlr.NewInputStream(sql)
caseChangingStream := newCaseChangingStream(inputStream, true)
lexer := gen.NewMySqlLexer(caseChangingStream)
lexer.RemoveErrorListeners()
tokens := antlr.NewCommonTokenStream(lexer, antlr.LexerDefaultTokenChannel)
mysqlParser := gen.NewMySqlParser(tokens)
mysqlParser.RemoveErrorListeners()
mysqlParser.AddErrorListener(p)
visitor := &visitor{
prefix: prefix,
debug: p.debug,
logger: p.logger,
}
v = acceptor(mysqlParser, visitor)
return
}