104 lines
2.3 KiB
Go
104 lines
2.3 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
// Package compiler implements the Hime-san .sando compiler.
|
|
package compiler
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// Severity describes the impact of a diagnostic.
|
|
type Severity string
|
|
|
|
const (
|
|
SeverityError Severity = "error"
|
|
SeverityWarning Severity = "warning"
|
|
)
|
|
|
|
// Diagnostic is a stable, machine-readable compiler message. Line and Column
|
|
// are one-based. A diagnostic without a source position uses line and column 1.
|
|
type Diagnostic struct {
|
|
Path string `json:"path"`
|
|
Line int `json:"line"`
|
|
Column int `json:"column"`
|
|
Code string `json:"code"`
|
|
Severity Severity `json:"severity"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func (d Diagnostic) Error() string {
|
|
line, column := d.Line, d.Column
|
|
if line < 1 {
|
|
line = 1
|
|
}
|
|
if column < 1 {
|
|
column = 1
|
|
}
|
|
return fmt.Sprintf("%s:%d:%d: %s: %s", d.Path, line, column, d.Code, d.Message)
|
|
}
|
|
|
|
// DiagnosticsError reports one or more error diagnostics.
|
|
type DiagnosticsError struct {
|
|
Diagnostics []Diagnostic
|
|
}
|
|
|
|
func (e *DiagnosticsError) Error() string {
|
|
if e == nil || len(e.Diagnostics) == 0 {
|
|
return "himesan: compilation failed"
|
|
}
|
|
if len(e.Diagnostics) == 1 {
|
|
return e.Diagnostics[0].Error()
|
|
}
|
|
return fmt.Sprintf("%s (and %d more diagnostics)", e.Diagnostics[0].Error(), len(e.Diagnostics)-1)
|
|
}
|
|
|
|
func errorFromDiagnostics(ds []Diagnostic) error {
|
|
if !hasErrors(ds) {
|
|
return nil
|
|
}
|
|
copyOfDiagnostics := append([]Diagnostic(nil), ds...)
|
|
sortDiagnostics(copyOfDiagnostics)
|
|
return &DiagnosticsError{Diagnostics: copyOfDiagnostics}
|
|
}
|
|
|
|
func hasErrors(ds []Diagnostic) bool {
|
|
for _, d := range ds {
|
|
if d.Severity == SeverityError || d.Severity == "" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func sortDiagnostics(ds []Diagnostic) {
|
|
sort.SliceStable(ds, func(i, j int) bool {
|
|
a, b := ds[i], ds[j]
|
|
if a.Path != b.Path {
|
|
return a.Path < b.Path
|
|
}
|
|
if a.Line != b.Line {
|
|
return a.Line < b.Line
|
|
}
|
|
if a.Column != b.Column {
|
|
return a.Column < b.Column
|
|
}
|
|
if a.Code != b.Code {
|
|
return a.Code < b.Code
|
|
}
|
|
return a.Message < b.Message
|
|
})
|
|
}
|
|
|
|
func diagnostic(path string, pos sourcePosition, code, message string) Diagnostic {
|
|
return Diagnostic{
|
|
Path: path,
|
|
Line: pos.Line,
|
|
Column: pos.Column,
|
|
Code: code,
|
|
Severity: SeverityError,
|
|
Message: strings.TrimSpace(message),
|
|
}
|
|
}
|