Files
upstream-mirror 0f4df23290
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
2026-09-16 02:17:34 +05:00

163 lines
4.2 KiB
Go

package dolphin
import (
"errors"
"fmt"
"io"
"regexp"
"strconv"
"strings"
"github.com/sqlc-dev/marino/parser"
"github.com/sqlc-dev/sqlc/internal/source"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
"github.com/sqlc-dev/sqlc/internal/sql/sqlerr"
)
func NewParser() *Parser {
return &Parser{pingcap: parser.New()}
}
// NewFormatParser returns the parser sqlc fmt uses. It differs from the
// compiler's parser in one way: identifiers keep the case the author wrote.
// The compiler lowercases them so catalog lookups are case-insensitive, but
// a formatter that prints `Event` as `event` renames a table on the many
// servers where table names are case-sensitive.
func NewFormatParser() *Parser {
return &Parser{pingcap: parser.New(), preserveCase: true}
}
type Parser struct {
pingcap *parser.Parser
preserveCase bool
}
var lineColumn = regexp.MustCompile(`^line (\d+) column (\d+) (.*)`)
func normalizeErr(err error) error {
if err == nil {
return err
}
parts := strings.Split(err.Error(), "\n")
msg := strings.TrimSpace(parts[0] + "\"")
out := lineColumn.FindStringSubmatch(msg)
if len(out) == 4 {
line, lineErr := strconv.Atoi(out[1])
col, colErr := strconv.Atoi(out[2])
if lineErr != nil || colErr != nil {
return errors.New(msg)
}
return &sqlerr.Error{
Message: "syntax error",
Err: errors.New(out[3]),
Line: line,
Column: col,
}
}
return errors.New(msg)
}
func (p *Parser) Parse(r io.Reader) ([]ast.Statement, error) {
f, err := p.ParseFile(r)
if err != nil {
return nil, err
}
// The compiler skips statements sqlc has no node for; the formatter
// must see them, so the filter lives here, not in ParseFile.
var stmts []ast.Statement
for _, stmt := range f.Stmts {
if _, ok := stmt.Raw.Stmt.(*ast.TODO); ok {
continue
}
stmts = append(stmts, stmt)
}
return stmts, nil
}
// ParseFile parses like Parse and also carries the file's comments, which
// marino's lexer records as it scans.
func (p *Parser) ParseFile(r io.Reader) (*ast.File, error) {
blob, err := io.ReadAll(r)
if err != nil {
return nil, err
}
src := string(blob)
stmtNodes, _, err := p.pingcap.Parse(src, "", "")
if err != nil {
return nil, normalizeErr(err)
}
var stmts []ast.Statement
// A statement's text spans from the end of the previous statement
// through its terminator, so it carries the comments written above it
// (that's where the "-- name:" annotation lives). Each text is a
// contiguous slice of src laid down after the one before it, so
// searching from the previous statement's end pins every text to its
// own occurrence even when two statements read the same.
searchFrom := 0
for i := range stmtNodes {
converter := &cc{preserveCase: p.preserveCase}
// A statement sqlc has no node for converts to a TODO and stays in
// the list: the formatter needs its extent to keep it as written,
// and Parse filters it out for the compiler.
out := converter.convert(stmtNodes[i])
text := stmtNodes[i].Text()
idx := strings.Index(src[searchFrom:], text)
if idx < 0 {
return nil, fmt.Errorf("could not locate statement %d in source", i)
}
loc := searchFrom + idx
searchFrom = loc + len(text)
stmtLen := len(text)
if stmtLen > 0 && text[stmtLen-1] == ';' {
stmtLen -= 1 // Subtract one to remove semicolon
}
stmts = append(stmts, ast.Statement{
Raw: &ast.RawStmt{
Stmt: out,
StmtLocation: loc,
StmtLen: stmtLen,
},
})
}
var comments []ast.Comment
for _, c := range p.pingcap.Comments() {
comments = append(comments, ast.Comment{
Text: strings.TrimRight(src[c.Begin:c.End], " \t\r\n"),
Start: c.Begin,
End: c.End,
OwnLine: ownLine(src, c.Begin),
})
}
return &ast.File{Stmts: stmts, Comments: comments}, nil
}
// ownLine reports that only blank space sits between the preceding line
// break and pos.
func ownLine(src string, pos int) bool {
for j := pos - 1; j >= 0; j-- {
switch src[j] {
case '\n':
return true
case ' ', '\t', '\r':
continue
default:
return false
}
}
return true
}
// https://dev.mysql.com/doc/refman/8.0/en/comments.html
func (p *Parser) CommentSyntax() source.CommentSyntax {
return source.CommentSyntax{
Dash: true,
SlashStar: true,
Hash: true,
}
}