Merge pull request #13 from Myzel394/add-hosts

Add hosts
This commit is contained in:
Myzel394 2024-08-26 21:19:11 +02:00 committed by GitHub
commit 6c3e0d9d23
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 4655 additions and 37 deletions

25
common/errors.go Normal file
View File

@ -0,0 +1,25 @@
package common
import (
protocol "github.com/tliron/glsp/protocol_3_16"
)
type LSPError struct {
Range LocationRange
Err error
}
func (l LSPError) ToDiagnostic() protocol.Diagnostic {
return protocol.Diagnostic{
Range: l.Range.ToLSPRange(),
Message: l.Err.Error(),
}
}
type SyntaxError struct {
Message string
}
func (s SyntaxError) Error() string {
return s.Message
}

65
common/location.go Normal file
View File

@ -0,0 +1,65 @@
package common
import protocol "github.com/tliron/glsp/protocol_3_16"
type Location struct {
Line uint32
Character uint32
}
type LocationRange struct {
Start Location
End Location
}
func (l LocationRange) ToLSPRange() protocol.Range {
return protocol.Range{
Start: protocol.Position{
Line: l.Start.Line,
Character: l.Start.Character,
},
End: protocol.Position{
Line: l.End.Line,
Character: l.End.Character,
},
}
}
func (l *LocationRange) ChangeBothLines(newLine uint32) {
l.Start.Line = newLine
l.End.Line = newLine
}
func (l LocationRange) ContainsCursor(line uint32, character uint32) bool {
return line == l.Start.Line && character >= l.Start.Character && character <= l.End.Character
}
func (l LocationRange) ContainsCursorByCharacter(character uint32) bool {
return character >= l.Start.Character && character <= l.End.Character
}
func CreateFullLineRange(line uint32) LocationRange {
return LocationRange{
Start: Location{
Line: line,
Character: 0,
},
End: Location{
Line: line,
Character: 999999,
},
}
}
func CreateSingleCharRange(line uint32, character uint32) LocationRange {
return LocationRange{
Start: Location{
Line: line,
Character: character,
},
End: Location{
Line: line,
Character: character,
},
}
}

Binary file not shown.

View File

@ -3,6 +3,7 @@ package docvalues
import ( import (
"config-lsp/utils" "config-lsp/utils"
"os" "os"
"regexp"
"strings" "strings"
) )
@ -173,3 +174,9 @@ func SingleEnumValue(value string) EnumValue {
}, },
} }
} }
func DomainValue() Value {
return RegexValue{
Regex: *regexp.MustCompile(`^.+?\..+$`),
}
}

1
go.mod
View File

@ -8,6 +8,7 @@ require (
) )
require ( require (
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect github.com/gorilla/websocket v1.5.3 // indirect
github.com/iancoleman/strcase v0.3.0 // indirect github.com/iancoleman/strcase v0.3.0 // indirect

2
go.sum
View File

@ -1,3 +1,5 @@
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=

126
handlers/hosts/Hosts.g4 Normal file
View File

@ -0,0 +1,126 @@
grammar Hosts;
lineStatement
: SEPARATOR? entry SEPARATOR? leadingComment? EOF
;
entry
: ipAddress SEPARATOR hostname (SEPARATOR aliases)?
;
aliases
: (alias SEPARATOR?)+
;
alias
: DOMAIN
;
hostname
: domain
;
domain
: DOMAIN
;
ipAddress
: (ipv4Address | ipv6Address)
;
ipv4Address
: singleIPv4Address
// Allow optional range to tell user ranges are not allowed
ipRange?
;
singleIPv4Address
: ipv4Digit DOT ipv4Digit DOT ipv4Digit DOT ipv4Digit
;
// This is not correct but fits for now
ipv6Address
: singleIPv6Address
// Allow optional range to tell user ranges are not allowed
ipRange?
;
singleIPv6Address
: (ipv6Octet COLON)+ ipv6Octet
;
ipv4Digit
: DIGITS
;
ipv6Octet
: OCTETS
;
ipRange
: SLASH ipRangeBits
;
ipRangeBits
: DIGITS
;
comment
: COMMENTLINE
;
leadingComment
: COMMENTLINE
;
COMMENTLINE
: HASHTAG ~[\r\n]+
;
SLASH
: '/'
;
DOT
: '.'
;
COLON
: ':'
;
HASHTAG
: '#'
;
SEPARATOR
: [ \t]+
;
NEWLINE
: [\r]? [\n]
;
DIGITS
: DIGIT+
;
fragment DIGIT
: [0-9]
;
OCTETS
: OCTET+
;
fragment OCTET
: [0-9a-fA-F]
;
DOMAIN
: ((STRING)+ (DOT [a-zA-Z]+)*)
;
fragment STRING
: ~(' ' | '\t' | '\n' | '\r' | '#' | '.')
;

View File

@ -0,0 +1,13 @@
package fields
import docvalues "config-lsp/doc-values"
var IPAddressField = docvalues.IPAddressValue{
AllowIPv4: true,
AllowIPv6: true,
}
var HostnameField = docvalues.DocumentationValue{
Documentation: `Host names may contain only alphanumeric characters, minus signs ("-"), and periods ("."). They must begin with an alphabetic character and end with an alphanumeric character. Optional aliases provide for name changes, alternate spellings, shorter hostnames, or generic hostnames (for example, localhost).`,
Value: docvalues.DomainValue(),
}

View File

@ -0,0 +1,42 @@
package analyzer
import (
"config-lsp/common"
"config-lsp/utils"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func Analyze(parser *HostsParser) []protocol.Diagnostic {
errors := analyzeEntriesSetCorrectly(*parser)
if len(errors) > 0 {
return utils.Map(
errors,
func(err common.LSPError) protocol.Diagnostic {
return err.ToDiagnostic()
},
)
}
errors = analyzeEntriesAreValid(*parser)
if len(errors) > 0 {
return utils.Map(
errors,
func(err common.LSPError) protocol.Diagnostic {
return err.ToDiagnostic()
},
)
}
errors = append(errors, analyzeDoubleIPs(parser)...)
errors = append(errors, analyzeDoubleHostNames(parser)...)
return utils.Map(
errors,
func(err common.LSPError) protocol.Diagnostic {
return err.ToDiagnostic()
},
)
}

View File

@ -0,0 +1,39 @@
package analyzer
import (
"config-lsp/common"
"net"
)
func ipToString(ip net.IPAddr) string {
return ip.IP.String()
}
func analyzeDoubleIPs(p *HostsParser) []common.LSPError {
errors := make([]common.LSPError, 0)
ips := make(map[string]uint32)
p.DoubleIPs = make(map[uint32]DuplicateIPDeclaration)
for lineNumber, entry := range p.Tree.Entries {
if entry.IPAddress != nil {
key := ipToString(entry.IPAddress.Value)
if foundLine, found := ips[key]; found {
err := DuplicateIPDeclaration{
AlreadyFoundAt: foundLine,
}
p.DoubleIPs[lineNumber] = err
errors = append(errors, common.LSPError{
Range: entry.IPAddress.Location,
Err: err,
})
} else {
ips[key] = lineNumber
}
}
}
return errors
}

View File

@ -0,0 +1,61 @@
package analyzer
import (
"config-lsp/utils"
"testing"
)
func TestWorksWithNonDoubleIPs(
t *testing.T,
) {
input := utils.Dedent(`
1.2.3.4 hello.com
5.5.5.5 world.com
1.2.3.5 foo.com
1.2.3.6 bar.com
`)
parser := CreateNewHostsParser()
errors := parser.Parse(input)
if len(errors) != 0 {
t.Fatalf("PARER FAILED! Expected no errors, but got %v", errors)
}
errors = analyzeDoubleIPs(&parser)
if len(errors) != 0 {
t.Errorf("Expected no errors, but got %v", errors)
}
}
func TestWorksWithDoubleIPs(
t *testing.T,
) {
input := utils.Dedent(`
1.2.3.4 hello.com
5.5.5.5 world.com
1.2.3.4 foo.com
`)
parser := CreateNewHostsParser()
errors := parser.Parse(input)
if len(errors) != 0 {
t.Fatalf("PARER FAILED! Expected no errors, but got %v", errors)
}
errors = analyzeDoubleIPs(&parser)
if !(len(errors) == 1) {
t.Errorf("Expected 1 error, but got %v", len(errors))
}
if !(errors[0].Range.Start.Line == 2) {
t.Errorf("Expected error on line 2, but got %v", errors[0].Range.Start.Line)
}
if !(errors[0].Err.(DuplicateIPDeclaration).AlreadyFoundAt == 0) {
t.Errorf("Expected error on line 0, but got %v", errors[0].Err.(DuplicateIPDeclaration).AlreadyFoundAt)
}
}

View File

@ -0,0 +1,20 @@
package analyzer
import "fmt"
type DuplicateHostEntry struct {
AlreadyFoundAt uint32
Hostname string
}
func (d DuplicateHostEntry) Error() string {
return fmt.Sprintf("'%s' already defined on line %d", d.Hostname, d.AlreadyFoundAt+1)
}
type DuplicateIPDeclaration struct {
AlreadyFoundAt uint32
}
func (d DuplicateIPDeclaration) Error() string {
return fmt.Sprintf("This IP address is already defined on line %d", d.AlreadyFoundAt+1)
}

View File

@ -0,0 +1,82 @@
package analyzer
import (
"config-lsp/common"
"config-lsp/handlers/hosts/parser"
"config-lsp/utils"
"regexp"
"github.com/antlr4-go/antlr/v4"
)
func (p *HostsParser) Clear() {
p.Tree = HostsTree{
Entries: make(map[uint32]*HostsEntry),
}
p.CommentLines = make(map[uint32]struct{})
}
var commentPattern = regexp.MustCompile(`^\s*#.*$`)
var emptyPattern = regexp.MustCompile(`^\s*$`)
func (p *HostsParser) parseStatement(
line uint32,
input string,
) []common.LSPError {
stream := antlr.NewInputStream(input)
errorListener := createErrorListener(line)
lexer := parser.NewHostsLexer(stream)
lexer.RemoveErrorListeners()
lexer.AddErrorListener(&errorListener)
errors := errorListener.Errors
errorListener = createErrorListener(line)
tokenStream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)
antlrParser := parser.NewHostsParser(tokenStream)
antlrParser.RemoveErrorListeners()
antlrParser.AddErrorListener(&errorListener)
listener := createHostsFileListener(p, line)
antlr.ParseTreeWalkerDefault.Walk(
&listener,
antlrParser.LineStatement(),
)
errors = append(errors, errorListener.Errors...)
return errors
}
func (p *HostsParser) Parse(input string) []common.LSPError {
errors := make([]common.LSPError, 0)
lines := utils.SplitIntoLines(input)
for rawLineNumber, line := range lines {
lineNumber := uint32(rawLineNumber)
if commentPattern.MatchString(line) {
p.CommentLines[lineNumber] = struct{}{}
continue
}
if emptyPattern.MatchString(line) {
continue
}
errors = append(
errors,
p.parseStatement(lineNumber, line)...,
)
}
return errors
}
func CreateNewHostsParser() HostsParser {
p := HostsParser{}
p.Clear()
return p
}

View File

@ -0,0 +1,144 @@
package analyzer
import (
"config-lsp/utils"
"net"
"testing"
)
func TestValidSimpleExampleWorks(
t *testing.T,
) {
input := utils.Dedent(`
1.2.3.4 hello.com
`)
parser := CreateNewHostsParser()
errors := parser.Parse(input)
if len(errors) != 0 {
t.Errorf("Expected no errors, but got %v", errors)
}
if !(len(parser.Tree.Entries) == 1) {
t.Errorf("Expected 1 entry, but got %v", len(parser.Tree.Entries))
}
if parser.Tree.Entries[0].IPAddress == nil {
t.Errorf("Expected IP address to be present, but got nil")
}
if !(parser.Tree.Entries[0].IPAddress.Value.String() == net.ParseIP("1.2.3.4").String()) {
t.Errorf("Expected IP address to be 1.2.3.4, but got %v", parser.Tree.Entries[0].IPAddress.Value)
}
if !(parser.Tree.Entries[0].Hostname.Value == "hello.com") {
t.Errorf("Expected hostname to be hello.com, but got %v", parser.Tree.Entries[0].Hostname.Value)
}
if !(parser.Tree.Entries[0].Aliases == nil) {
t.Errorf("Expected no aliases, but got %v", parser.Tree.Entries[0].Aliases)
}
if !(parser.Tree.Entries[0].Location.Start.Line == 0) {
t.Errorf("Expected line to be 1, but got %v", parser.Tree.Entries[0].Location.Start.Line)
}
if !(parser.Tree.Entries[0].Location.Start.Character == 0) {
t.Errorf("Expected start to be 0, but got %v", parser.Tree.Entries[0].Location.Start)
}
if !(parser.Tree.Entries[0].Location.End.Character == 17) {
t.Errorf("Expected end to be 17, but got %v", parser.Tree.Entries[0].Location.End.Character)
}
if !(parser.Tree.Entries[0].IPAddress.Location.Start.Line == 0) {
t.Errorf("Expected IP address line to be 1, but got %v", parser.Tree.Entries[0].IPAddress.Location.Start.Line)
}
if !(parser.Tree.Entries[0].IPAddress.Location.Start.Character == 0) {
t.Errorf("Expected IP address start to be 0, but got %v", parser.Tree.Entries[0].IPAddress.Location.Start.Character)
}
if !(parser.Tree.Entries[0].IPAddress.Location.End.Character == 7) {
t.Errorf("Expected IP address end to be 7, but got %v", parser.Tree.Entries[0].IPAddress.Location.End.Character)
}
if !(len(parser.CommentLines) == 0) {
t.Errorf("Expected no comment lines, but got %v", len(parser.CommentLines))
}
}
func TestValidComplexExampleWorks(
t *testing.T,
) {
input := utils.Dedent(`
# This is a comment
1.2.3.4 hello.com test.com # This is another comment
5.5.5.5 test.com
1.2.3.4 example.com check.com
`)
parser := CreateNewHostsParser()
errors := parser.Parse(input)
if len(errors) != 0 {
t.Errorf("Expected no errors, but got %v", errors)
}
if !(len(parser.Tree.Entries) == 3) {
t.Errorf("Expected 3 entries, but got %v", len(parser.Tree.Entries))
}
if parser.Tree.Entries[2].IPAddress == nil {
t.Errorf("Expected IP address to be present, but got nil")
}
if !(parser.Tree.Entries[2].IPAddress.Value.String() == net.ParseIP("1.2.3.4").String()) {
t.Errorf("Expected IP address to be 1.2.3.4, but got %v", parser.Tree.Entries[2].IPAddress.Value)
}
if !(len(parser.CommentLines) == 1) {
t.Errorf("Expected 1 comment line, but got %v", len(parser.CommentLines))
}
if !(utils.KeyExists(parser.CommentLines, 1)) {
t.Errorf("Expected comment line 2 to exist, but it does not")
}
}
func TestInvalidExampleWorks(
t *testing.T,
) {
input := utils.Dedent(`
1.2.3.4
`)
parser := CreateNewHostsParser()
errors := parser.Parse(input)
if len(errors) == 0 {
t.Errorf("Expected errors, but got none")
}
if !(len(parser.Tree.Entries) == 1) {
t.Errorf("Expected 1 entries, but got %v", len(parser.Tree.Entries))
}
if !(len(parser.CommentLines) == 0) {
t.Errorf("Expected no comment lines, but got %v", len(parser.CommentLines))
}
if !(parser.Tree.Entries[0].IPAddress.Value.String() == net.ParseIP("1.2.3.4").String()) {
t.Errorf("Expected IP address to be nil, but got %v", parser.Tree.Entries[0].IPAddress)
}
if !(parser.Tree.Entries[0].Hostname == nil) {
t.Errorf("Expected hostname to be nil, but got %v", parser.Tree.Entries[0].Hostname)
}
if !(parser.Tree.Entries[0].Aliases == nil) {
t.Errorf("Expected aliases to be nil, but got %v", parser.Tree.Entries[0].Aliases)
}
}

View File

@ -0,0 +1,77 @@
package analyzer
import (
"config-lsp/common"
"fmt"
"net"
"github.com/antlr4-go/antlr/v4"
)
func characterRangeFromCtx(
ctx antlr.BaseParserRuleContext,
) common.LocationRange {
line := uint32(ctx.GetStart().GetLine())
start := uint32(ctx.GetStart().GetStart())
end := uint32(ctx.GetStop().GetStop())
return common.LocationRange{
Start: common.Location{
Line: line,
Character: start,
},
End: common.Location{
Line: line,
Character: end + 1,
},
}
}
type HostsParser struct {
Tree HostsTree
CommentLines map[uint32]struct{}
Resolver *Resolver
// [line]error
DoubleIPs map[uint32]DuplicateIPDeclaration
}
type HostsTree struct {
// [line]entry
Entries map[uint32]*HostsEntry
}
type HostsEntry struct {
Location common.LocationRange
IPAddress *HostsIPAddress
Hostname *HostsHostname
Aliases []*HostsHostname
}
func (p HostsEntry) String() string {
str := fmt.Sprintf("HostsEntry(%v)", p.Location)
if p.IPAddress != nil {
str += " " + p.IPAddress.Value.String()
}
if p.Hostname != nil {
str += " " + p.Hostname.Value
}
if p.Aliases != nil {
str += " " + fmt.Sprintf("%v", p.Aliases)
}
return str
}
type HostsIPAddress struct {
Location common.LocationRange
Value net.IPAddr
}
type HostsHostname struct {
Location common.LocationRange
Value string
}

View File

@ -0,0 +1,152 @@
package analyzer
import (
"config-lsp/common"
docvalues "config-lsp/doc-values"
"config-lsp/handlers/hosts/parser"
"net"
"github.com/antlr4-go/antlr/v4"
)
type hostsListenerContext struct {
line uint32
}
type hostsParserListener struct {
*parser.BaseHostsListener
Parser *HostsParser
Errors []common.LSPError
hostsContext hostsListenerContext
}
func (s *hostsParserListener) EnterComment(ctx *parser.CommentContext) {
line := uint32(s.hostsContext.line)
s.Parser.CommentLines[line] = struct{}{}
}
func (s *hostsParserListener) EnterEntry(ctx *parser.EntryContext) {
location := characterRangeFromCtx(ctx.BaseParserRuleContext)
location.ChangeBothLines(s.hostsContext.line)
s.Parser.Tree.Entries[location.Start.Line] = &HostsEntry{
Location: location,
}
}
func (s *hostsParserListener) EnterIpAddress(ctx *parser.IpAddressContext) {
location := characterRangeFromCtx(ctx.BaseParserRuleContext)
location.ChangeBothLines(s.hostsContext.line)
ip := net.ParseIP(ctx.GetText())
if ip == nil {
s.Errors = append(s.Errors, common.LSPError{
Range: location,
Err: docvalues.InvalidIPAddress{},
})
return
}
ipAddr, err := net.ResolveIPAddr("ip", ip.String())
if err != nil {
s.Errors = append(s.Errors, common.LSPError{
Range: location,
Err: docvalues.InvalidIPAddress{},
})
}
entry := s.Parser.Tree.Entries[location.Start.Line]
entry.IPAddress = &HostsIPAddress{
Location: location,
Value: *ipAddr,
}
}
func (s *hostsParserListener) EnterHostname(ctx *parser.HostnameContext) {
location := characterRangeFromCtx(ctx.BaseParserRuleContext)
location.ChangeBothLines(s.hostsContext.line)
entry := s.Parser.Tree.Entries[location.Start.Line]
entry.Hostname = &HostsHostname{
Location: location,
Value: ctx.GetText(),
}
s.Parser.Tree.Entries[location.Start.Line] = entry
}
func (s *hostsParserListener) EnterAliases(ctx *parser.AliasesContext) {
location := characterRangeFromCtx(ctx.BaseParserRuleContext)
location.ChangeBothLines(s.hostsContext.line)
entry := s.Parser.Tree.Entries[location.Start.Line]
aliases := make([]*HostsHostname, 0)
entry.Aliases = aliases
}
func (s *hostsParserListener) EnterAlias(ctx *parser.AliasContext) {
location := characterRangeFromCtx(ctx.BaseParserRuleContext)
location.ChangeBothLines(s.hostsContext.line)
entry := s.Parser.Tree.Entries[location.Start.Line]
alias := HostsHostname{
Location: location,
Value: ctx.GetText(),
}
entry.Aliases = append(entry.Aliases, &alias)
}
func createHostsFileListener(
parser *HostsParser,
line uint32,
) hostsParserListener {
return hostsParserListener{
Parser: parser,
hostsContext: hostsListenerContext{
line: line,
},
Errors: make([]common.LSPError, 0),
}
}
type errorListener struct {
*antlr.DefaultErrorListener
Errors []common.LSPError
hostsContext hostsListenerContext
}
func createErrorListener(
line uint32,
) errorListener {
return errorListener{
Errors: make([]common.LSPError, 0),
hostsContext: hostsListenerContext{
line: line,
},
}
}
func (d *errorListener) SyntaxError(
recognizer antlr.Recognizer,
offendingSymbol interface{},
_ int,
character int,
message string,
error antlr.RecognitionException,
) {
line := d.hostsContext.line
d.Errors = append(d.Errors, common.LSPError{
Range: common.CreateSingleCharRange(uint32(line), uint32(character)),
Err: common.SyntaxError{
Message: message,
},
})
}

View File

@ -0,0 +1,108 @@
package analyzer
import (
"config-lsp/common"
"config-lsp/utils"
"net"
)
type ResolverEntry struct {
IPv4Address net.IP
IPv6Address net.IP
Line uint32
}
func (e ResolverEntry) GetInfo() string {
if e.IPv4Address != nil {
return e.IPv4Address.String()
}
return e.IPv6Address.String()
}
type Resolver struct {
Entries map[string]ResolverEntry
}
func createEntry(
line uint32,
ip net.IP,
) ResolverEntry {
entry := ResolverEntry{
Line: line,
}
if ipv4 := ip.To4(); ipv4 != nil {
entry.IPv4Address = ipv4
} else if ipv6 := ip.To16(); ipv6 != nil {
entry.IPv6Address = ipv6
}
return entry
}
type hostnameEntry struct {
Location common.LocationRange
HostName string
}
func createResolverFromParser(p HostsParser) (Resolver, []common.LSPError) {
errors := make([]common.LSPError, 0)
resolver := Resolver{
Entries: make(map[string]ResolverEntry),
}
for lineNumber, entry := range p.Tree.Entries {
if entry.IPAddress != nil && entry.Hostname != nil {
hostNames := append(
[]hostnameEntry{
{
Location: entry.Hostname.Location,
HostName: entry.Hostname.Value,
},
},
utils.Map(
entry.Aliases,
func(alias *HostsHostname) hostnameEntry {
return hostnameEntry{
Location: alias.Location,
HostName: alias.Value,
}
},
)...,
)
for _, hostName := range hostNames {
entry := createEntry(
lineNumber,
entry.IPAddress.Value.IP,
)
if resolv, found := resolver.Entries[hostName.HostName]; found {
errors = append(
errors,
common.LSPError{
Range: hostName.Location,
Err: DuplicateHostEntry{
AlreadyFoundAt: resolv.Line,
Hostname: hostName.HostName,
},
},
)
} else {
resolver.Entries[hostName.HostName] = entry
}
}
}
}
return resolver, errors
}
func analyzeDoubleHostNames(p *HostsParser) []common.LSPError {
resolver, errors := createResolverFromParser(*p)
p.Resolver = &resolver
return errors
}

View File

@ -0,0 +1,116 @@
package analyzer
import (
"config-lsp/utils"
"testing"
)
func TestResolverEntriesWorksWithNonOverlapping(
t *testing.T,
) {
input := utils.Dedent(`
1.2.3.4 hello.com
5.5.5.5 world.com
`)
parser := CreateNewHostsParser()
errors := parser.Parse(input)
if len(errors) != 0 {
t.Fatalf("PARER FAILED! Expected no errors, but got %v", errors)
}
resolver, errors := createResolverFromParser(parser)
if len(errors) != 0 {
t.Errorf("Expected no errors, but got %v", errors)
}
if len(resolver.Entries) != 2 {
t.Errorf("Expected 2 entries, but got %v", len(resolver.Entries))
}
if !(resolver.Entries["hello.com"].IPv4Address.String() == "1.2.3.4") {
t.Errorf("Expected hello.com to be 1.2.3.4, but got %v", resolver.Entries["hello.com"].IPv4Address)
}
if !(resolver.Entries["world.com"].IPv4Address.String() == "5.5.5.5") {
t.Errorf("Expected world.com to be 5.5.5.5, but got %v", resolver.Entries["world.com"].IPv4Address)
}
if !(resolver.Entries["hello.com"].IPv6Address == nil) {
t.Errorf("Expected hello.com to have no IPv6 address, but got %v", resolver.Entries["hello.com"].IPv6Address)
}
if !(resolver.Entries["world.com"].IPv6Address == nil) {
t.Errorf("Expected world.com to have no IPv6 address, but got %v", resolver.Entries["world.com"].IPv6Address)
}
}
func TestResolverEntriesWithSimpleOverlapping(
t *testing.T,
) {
input := utils.Dedent(`
1.2.3.4 hello.com
5.5.5.5 hello.com
`)
parser := CreateNewHostsParser()
errors := parser.Parse(input)
if len(errors) != 0 {
t.Fatalf("PARER FAILED! Expected no errors, but got %v", errors)
}
resolver, errors := createResolverFromParser(parser)
if !(len(errors) == 1) {
t.Errorf("Expected 1 error, but got %v", len(errors))
}
if len(resolver.Entries) != 1 {
t.Errorf("Expected 1 entry, but got %v", len(resolver.Entries))
}
if !(resolver.Entries["hello.com"].IPv4Address.String() == "1.2.3.4") {
t.Errorf("Expected hello.com to be 1.2.3.4, but got %v", resolver.Entries["hello.com"].IPv4Address)
}
}
func TestResolverEntriesWithComplexOverlapping(
t *testing.T,
) {
input := utils.Dedent(`
1.2.3.4 hello.com test.com
5.5.5.5 check.com test.com
`)
parser := CreateNewHostsParser()
errors := parser.Parse(input)
if len(errors) != 0 {
t.Fatalf("PARER FAILED! Expected no errors, but got %v", errors)
}
resolver, errors := createResolverFromParser(parser)
if !(len(errors) == 1) {
t.Errorf("Expected 1 error, but got %v", len(errors))
}
if len(resolver.Entries) != 3 {
t.Errorf("Expected 3 entries, but got %v", len(resolver.Entries))
}
if !(resolver.Entries["hello.com"].IPv4Address.String() == "1.2.3.4") {
t.Errorf("Expected hello.com to be 1.2.3.4, but got %v", resolver.Entries["hello.com"].IPv4Address)
}
if !(resolver.Entries["check.com"].IPv4Address.String() == "5.5.5.5") {
t.Errorf("Expected check.com to be 5.5.5.5, but got %v", resolver.Entries["check.com"].IPv4Address)
}
if !(resolver.Entries["test.com"].IPv4Address.String() == "1.2.3.4") {
t.Errorf("Expected test.com to have no IPv4 address, but got %v", resolver.Entries["test.com"].IPv4Address)
}
}

View File

@ -0,0 +1,86 @@
package analyzer
import (
"config-lsp/common"
docvalues "config-lsp/doc-values"
"config-lsp/handlers/hosts/fields"
"config-lsp/utils"
"errors"
)
func analyzeEntriesSetCorrectly(
parser HostsParser,
) []common.LSPError {
err := make([]common.LSPError, 0)
for lineNumber, entry := range parser.Tree.Entries {
if entry.IPAddress == nil {
err = append(err, common.LSPError{
Range: common.CreateFullLineRange(lineNumber),
Err: errors.New("IP Address is required"),
})
continue
}
if entry.Hostname == nil {
err = append(err, common.LSPError{
Range: common.CreateFullLineRange(lineNumber),
Err: errors.New("Hostname is required"),
})
continue
}
}
return err
}
func analyzeEntriesAreValid(
parser HostsParser,
) []common.LSPError {
err := make([]common.LSPError, 0)
for _, entry := range parser.Tree.Entries {
err = append(
err,
utils.Map(
fields.IPAddressField.CheckIsValid(entry.IPAddress.Value.String()),
func(val *docvalues.InvalidValue) common.LSPError {
return common.LSPError{
Range: entry.IPAddress.Location,
Err: val.Err,
}
},
)...,
)
err = append(
err,
utils.Map(
fields.HostnameField.CheckIsValid(entry.Hostname.Value),
func(val *docvalues.InvalidValue) common.LSPError {
return common.LSPError{
Range: entry.Hostname.Location,
Err: val.Err,
}
},
)...,
)
for _, alias := range entry.Aliases {
err = append(
err,
utils.Map(
fields.HostnameField.CheckIsValid(alias.Value),
func(val *docvalues.InvalidValue) common.LSPError {
return common.LSPError{
Range: alias.Location,
Err: val.Err,
}
},
)...,
)
}
}
return err
}

View File

@ -0,0 +1,105 @@
package handlers
import (
"config-lsp/handlers/hosts/handlers/analyzer"
"config-lsp/utils"
"fmt"
"strings"
protocol "github.com/tliron/glsp/protocol_3_16"
)
type CodeActionName string
const (
CodeActionInlineAliases CodeActionName = "inlineAliases"
)
type CodeAction interface {
RunCommand(analyzer.HostsParser) (*protocol.ApplyWorkspaceEditParams, error)
}
type CodeActionArgs interface{}
type CodeActionInlineAliasesArgs struct {
URI protocol.DocumentUri
FromLine uint32
ToLine uint32
}
func CodeActionInlineAliasesArgsFromArguments(arguments map[string]any) CodeActionInlineAliasesArgs {
return CodeActionInlineAliasesArgs{
URI: arguments["URI"].(protocol.DocumentUri),
FromLine: uint32(arguments["FromLine"].(float64)),
ToLine: uint32(arguments["ToLine"].(float64)),
}
}
func (args CodeActionInlineAliasesArgs) RunCommand(hostsParser analyzer.HostsParser) (*protocol.ApplyWorkspaceEditParams, error) {
fromEntry := hostsParser.Tree.Entries[args.FromLine]
toEntry := hostsParser.Tree.Entries[args.ToLine]
if fromEntry == nil || toEntry == nil {
// Weird
return nil, nil
}
var insertCharacter uint32
if toEntry.Aliases != nil {
insertCharacter = toEntry.Aliases[len(toEntry.Aliases)-1].Location.End.Character
} else {
insertCharacter = toEntry.Hostname.Location.End.Character
}
hostnames := append(
[]string{
fromEntry.Hostname.Value,
},
utils.Map(
fromEntry.Aliases,
func(alias *analyzer.HostsHostname) string {
return alias.Value
},
)...,
)
label := fmt.Sprintf("Inline aliases from %d to %d", args.FromLine, args.ToLine)
return &protocol.ApplyWorkspaceEditParams{
Label: &label,
Edit: protocol.WorkspaceEdit{
Changes: map[protocol.DocumentUri][]protocol.TextEdit{
args.URI: {
// Delete old line
{
Range: protocol.Range{
Start: protocol.Position{
Line: args.FromLine,
Character: 0,
},
End: protocol.Position{
Line: args.FromLine + 1,
Character: 0,
},
},
NewText: "",
},
// Insert aliases
{
Range: protocol.Range{
Start: protocol.Position{
Line: args.ToLine,
Character: insertCharacter,
},
End: protocol.Position{
Line: args.ToLine,
Character: insertCharacter,
},
},
NewText: " " + strings.Join(hostnames, " "),
},
},
},
},
}, nil
}

View File

@ -0,0 +1,37 @@
package handlers
import (
"config-lsp/handlers/hosts/handlers/analyzer"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func GetInlineAliasesCodeAction(
p analyzer.HostsParser,
params *protocol.CodeActionParams,
) []protocol.CodeAction {
line := params.Range.Start.Line
if duplicateInfo, found := p.DoubleIPs[line]; found {
commandID := "hosts." + CodeActionInlineAliases
command := protocol.Command{
Title: "Inline Aliases",
Command: string(commandID),
Arguments: []any{
CodeActionInlineAliasesArgs{
URI: params.TextDocument.URI,
FromLine: line,
ToLine: duplicateInfo.AlreadyFoundAt,
},
},
}
return []protocol.CodeAction{
{
Title: "Inline Aliases",
Command: &command,
},
}
}
return []protocol.CodeAction{}
}

View File

@ -0,0 +1,50 @@
package handlers
import (
"config-lsp/handlers/hosts/handlers/analyzer"
"fmt"
)
type HoverTarget string
const (
HoverTargetIPAddress HoverTarget = "ip_address"
HoverTargetHostname HoverTarget = "hostname"
HoverTargetAlias HoverTarget = "alias"
)
func GetHoverTargetInEntry(
e analyzer.HostsEntry,
cursor uint32,
) *HoverTarget {
if e.IPAddress != nil && e.IPAddress.Location.ContainsCursorByCharacter(cursor) {
target := HoverTargetIPAddress
return &target
}
if e.Hostname != nil && e.Hostname.Location.ContainsCursorByCharacter(cursor) {
target := HoverTargetHostname
return &target
}
for _, alias := range e.Aliases {
if alias.Location.ContainsCursorByCharacter(cursor) {
target := HoverTargetAlias
return &target
}
}
return nil
}
func GetHoverInfoForHostname(
parser analyzer.HostsParser,
hostname analyzer.HostsHostname,
cursor uint32,
) []string {
ipAddress := parser.Resolver.Entries[hostname.Value]
return []string{
fmt.Sprintf("**%s** maps to _%s_", hostname.Value, ipAddress.GetInfo()),
}
}

View File

@ -0,0 +1,9 @@
package lsp
import (
"config-lsp/handlers/hosts/handlers/analyzer"
protocol "github.com/tliron/glsp/protocol_3_16"
)
var documentParserMap = map[protocol.DocumentUri]*analyzer.HostsParser{}

View File

@ -0,0 +1,22 @@
package lsp
import (
"config-lsp/handlers/hosts/handlers"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
parser := documentParserMap[params.TextDocument.URI]
actions := make([]protocol.CodeAction, 0, 1)
actions = append(actions, handlers.GetInlineAliasesCodeAction(*parser, params)...)
if len(actions) > 0 {
return actions, nil
}
return nil, nil
}

View File

@ -0,0 +1,12 @@
package lsp
import (
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) {
// p := documentParserMap[params.TextDocument.URI]
return nil, nil
}

View File

@ -0,0 +1,41 @@
package lsp
import (
"config-lsp/common"
"config-lsp/handlers/hosts/handlers/analyzer"
"config-lsp/utils"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TextDocumentDidChange(
context *glsp.Context,
params *protocol.DidChangeTextDocumentParams,
) error {
content := params.ContentChanges[0].(protocol.TextDocumentContentChangeEventWhole).Text
common.ClearDiagnostics(context, params.TextDocument.URI)
parser := documentParserMap[params.TextDocument.URI]
parser.Clear()
diagnostics := make([]protocol.Diagnostic, 0)
errors := parser.Parse(content)
if len(errors) > 0 {
diagnostics = append(diagnostics, utils.Map(
errors,
func(err common.LSPError) protocol.Diagnostic {
return err.ToDiagnostic()
},
)...)
}
diagnostics = append(diagnostics, analyzer.Analyze(parser)...)
if len(diagnostics) > 0 {
common.SendDiagnostics(context, params.TextDocument.URI, diagnostics)
}
return nil
}

View File

@ -0,0 +1,12 @@
package lsp
import (
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TextDocumentDidClose(context *glsp.Context, params *protocol.DidCloseTextDocumentParams) error {
delete(documentParserMap, params.TextDocument.URI)
return nil
}

View File

@ -0,0 +1,40 @@
package lsp
import (
"config-lsp/common"
"config-lsp/handlers/hosts/handlers/analyzer"
"config-lsp/utils"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TextDocumentDidOpen(
context *glsp.Context,
params *protocol.DidOpenTextDocumentParams,
) error {
common.ClearDiagnostics(context, params.TextDocument.URI)
parser := analyzer.CreateNewHostsParser()
documentParserMap[params.TextDocument.URI] = &parser
errors := parser.Parse(params.TextDocument.Text)
diagnostics := utils.Map(
errors,
func(err common.LSPError) protocol.Diagnostic {
return err.ToDiagnostic()
},
)
diagnostics = append(
diagnostics,
analyzer.Analyze(&parser)...,
)
if len(diagnostics) > 0 {
common.SendDiagnostics(context, params.TextDocument.URI, diagnostics)
}
return nil
}

View File

@ -0,0 +1,89 @@
package lsp
import (
"config-lsp/handlers/hosts/fields"
"config-lsp/handlers/hosts/handlers"
"config-lsp/handlers/hosts/handlers/analyzer"
"strings"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TextDocumentHover(
context *glsp.Context,
params *protocol.HoverParams,
) (*protocol.Hover, error) {
parser := documentParserMap[params.TextDocument.URI]
line := params.Position.Line
character := params.Position.Character
if _, found := parser.CommentLines[line]; found {
// Comment
return nil, nil
}
entry, found := parser.Tree.Entries[line]
if !found {
// Empty line
return nil, nil
}
target := handlers.GetHoverTargetInEntry(*entry, character)
var hostname *analyzer.HostsHostname
switch *target {
case handlers.HoverTargetIPAddress:
relativeCursor := character - entry.IPAddress.Location.Start.Character
hover := fields.IPAddressField.FetchHoverInfo(entry.IPAddress.Value.String(), relativeCursor)
return &protocol.Hover{
Contents: hover,
}, nil
case handlers.HoverTargetHostname:
hostname = entry.Hostname
case handlers.HoverTargetAlias:
for _, alias := range entry.Aliases {
if alias.Location.Start.Character <= character && character <= alias.Location.End.Character {
hostname = alias
break
}
}
}
if hostname != nil {
contents := []string{
"## Hostname",
}
contents = append(
contents,
fields.HostnameField.GetTypeDescription()...,
)
contents = append(
contents,
[]string{
"",
}...,
)
contents = append(
contents,
fields.HostnameField.Documentation,
)
contents = append(
contents,
handlers.GetHoverInfoForHostname(*parser, *hostname, character)...,
)
return &protocol.Hover{
Contents: &protocol.MarkupContent{
Kind: protocol.MarkupKindMarkdown,
Value: strings.Join(contents, "\n"),
},
}, nil
}
return nil, nil
}

View File

@ -0,0 +1,24 @@
package lsp
import (
"config-lsp/handlers/hosts/handlers"
"strings"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func WorkspaceExecuteCommand(context *glsp.Context, params *protocol.ExecuteCommandParams) (*protocol.ApplyWorkspaceEditParams, error) {
_, command, _ := strings.Cut(params.Command, ".")
switch command {
case string(handlers.CodeActionInlineAliases):
args := handlers.CodeActionInlineAliasesArgsFromArguments(params.Arguments[0].(map[string]any))
parser := documentParserMap[args.URI]
return args.RunCommand(*parser)
}
return nil, nil
}

View File

@ -0,0 +1,48 @@
token literal names:
null
null
'/'
'.'
':'
'#'
null
null
null
null
null
token symbolic names:
null
COMMENTLINE
SLASH
DOT
COLON
HASHTAG
SEPARATOR
NEWLINE
DIGITS
OCTETS
DOMAIN
rule names:
lineStatement
entry
aliases
alias
hostname
domain
ipAddress
ipv4Address
singleIPv4Address
ipv6Address
singleIPv6Address
ipv4Digit
ipv6Octet
ipRange
ipRangeBits
comment
leadingComment
atn:
[4, 1, 10, 110, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 1, 0, 3, 0, 36, 8, 0, 1, 0, 1, 0, 3, 0, 40, 8, 0, 1, 0, 3, 0, 43, 8, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 52, 8, 1, 1, 2, 1, 2, 3, 2, 56, 8, 2, 4, 2, 58, 8, 2, 11, 2, 12, 2, 59, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 3, 6, 70, 8, 6, 1, 7, 1, 7, 3, 7, 74, 8, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 3, 9, 86, 8, 9, 1, 10, 1, 10, 1, 10, 4, 10, 91, 8, 10, 11, 10, 12, 10, 92, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 0, 0, 17, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 0, 0, 102, 0, 35, 1, 0, 0, 0, 2, 46, 1, 0, 0, 0, 4, 57, 1, 0, 0, 0, 6, 61, 1, 0, 0, 0, 8, 63, 1, 0, 0, 0, 10, 65, 1, 0, 0, 0, 12, 69, 1, 0, 0, 0, 14, 71, 1, 0, 0, 0, 16, 75, 1, 0, 0, 0, 18, 83, 1, 0, 0, 0, 20, 90, 1, 0, 0, 0, 22, 96, 1, 0, 0, 0, 24, 98, 1, 0, 0, 0, 26, 100, 1, 0, 0, 0, 28, 103, 1, 0, 0, 0, 30, 105, 1, 0, 0, 0, 32, 107, 1, 0, 0, 0, 34, 36, 5, 6, 0, 0, 35, 34, 1, 0, 0, 0, 35, 36, 1, 0, 0, 0, 36, 37, 1, 0, 0, 0, 37, 39, 3, 2, 1, 0, 38, 40, 5, 6, 0, 0, 39, 38, 1, 0, 0, 0, 39, 40, 1, 0, 0, 0, 40, 42, 1, 0, 0, 0, 41, 43, 3, 32, 16, 0, 42, 41, 1, 0, 0, 0, 42, 43, 1, 0, 0, 0, 43, 44, 1, 0, 0, 0, 44, 45, 5, 0, 0, 1, 45, 1, 1, 0, 0, 0, 46, 47, 3, 12, 6, 0, 47, 48, 5, 6, 0, 0, 48, 51, 3, 8, 4, 0, 49, 50, 5, 6, 0, 0, 50, 52, 3, 4, 2, 0, 51, 49, 1, 0, 0, 0, 51, 52, 1, 0, 0, 0, 52, 3, 1, 0, 0, 0, 53, 55, 3, 6, 3, 0, 54, 56, 5, 6, 0, 0, 55, 54, 1, 0, 0, 0, 55, 56, 1, 0, 0, 0, 56, 58, 1, 0, 0, 0, 57, 53, 1, 0, 0, 0, 58, 59, 1, 0, 0, 0, 59, 57, 1, 0, 0, 0, 59, 60, 1, 0, 0, 0, 60, 5, 1, 0, 0, 0, 61, 62, 5, 10, 0, 0, 62, 7, 1, 0, 0, 0, 63, 64, 3, 10, 5, 0, 64, 9, 1, 0, 0, 0, 65, 66, 5, 10, 0, 0, 66, 11, 1, 0, 0, 0, 67, 70, 3, 14, 7, 0, 68, 70, 3, 18, 9, 0, 69, 67, 1, 0, 0, 0, 69, 68, 1, 0, 0, 0, 70, 13, 1, 0, 0, 0, 71, 73, 3, 16, 8, 0, 72, 74, 3, 26, 13, 0, 73, 72, 1, 0, 0, 0, 73, 74, 1, 0, 0, 0, 74, 15, 1, 0, 0, 0, 75, 76, 3, 22, 11, 0, 76, 77, 5, 3, 0, 0, 77, 78, 3, 22, 11, 0, 78, 79, 5, 3, 0, 0, 79, 80, 3, 22, 11, 0, 80, 81, 5, 3, 0, 0, 81, 82, 3, 22, 11, 0, 82, 17, 1, 0, 0, 0, 83, 85, 3, 20, 10, 0, 84, 86, 3, 26, 13, 0, 85, 84, 1, 0, 0, 0, 85, 86, 1, 0, 0, 0, 86, 19, 1, 0, 0, 0, 87, 88, 3, 24, 12, 0, 88, 89, 5, 4, 0, 0, 89, 91, 1, 0, 0, 0, 90, 87, 1, 0, 0, 0, 91, 92, 1, 0, 0, 0, 92, 90, 1, 0, 0, 0, 92, 93, 1, 0, 0, 0, 93, 94, 1, 0, 0, 0, 94, 95, 3, 24, 12, 0, 95, 21, 1, 0, 0, 0, 96, 97, 5, 8, 0, 0, 97, 23, 1, 0, 0, 0, 98, 99, 5, 9, 0, 0, 99, 25, 1, 0, 0, 0, 100, 101, 5, 2, 0, 0, 101, 102, 3, 28, 14, 0, 102, 27, 1, 0, 0, 0, 103, 104, 5, 8, 0, 0, 104, 29, 1, 0, 0, 0, 105, 106, 5, 1, 0, 0, 106, 31, 1, 0, 0, 0, 107, 108, 5, 1, 0, 0, 108, 33, 1, 0, 0, 0, 10, 35, 39, 42, 51, 55, 59, 69, 73, 85, 92]

View File

@ -0,0 +1,14 @@
COMMENTLINE=1
SLASH=2
DOT=3
COLON=4
HASHTAG=5
SEPARATOR=6
NEWLINE=7
DIGITS=8
OCTETS=9
DOMAIN=10
'/'=2
'.'=3
':'=4
'#'=5

View File

@ -0,0 +1,50 @@
token literal names:
null
null
'/'
'.'
':'
'#'
null
null
null
null
null
token symbolic names:
null
COMMENTLINE
SLASH
DOT
COLON
HASHTAG
SEPARATOR
NEWLINE
DIGITS
OCTETS
DOMAIN
rule names:
COMMENTLINE
SLASH
DOT
COLON
HASHTAG
SEPARATOR
NEWLINE
DIGITS
DIGIT
OCTETS
OCTET
DOMAIN
STRING
channel names:
DEFAULT_TOKEN_CHANNEL
HIDDEN
mode names:
DEFAULT_MODE
atn:
[4, 0, 10, 83, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 1, 0, 1, 0, 4, 0, 30, 8, 0, 11, 0, 12, 0, 31, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 4, 5, 43, 8, 5, 11, 5, 12, 5, 44, 1, 6, 3, 6, 48, 8, 6, 1, 6, 1, 6, 1, 7, 4, 7, 53, 8, 7, 11, 7, 12, 7, 54, 1, 8, 1, 8, 1, 9, 4, 9, 60, 8, 9, 11, 9, 12, 9, 61, 1, 10, 1, 10, 1, 11, 4, 11, 67, 8, 11, 11, 11, 12, 11, 68, 1, 11, 1, 11, 4, 11, 73, 8, 11, 11, 11, 12, 11, 74, 5, 11, 77, 8, 11, 10, 11, 12, 11, 80, 9, 11, 1, 12, 1, 12, 0, 0, 13, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 0, 19, 9, 21, 0, 23, 10, 25, 0, 1, 0, 8, 2, 0, 10, 10, 13, 13, 2, 0, 9, 9, 32, 32, 1, 0, 13, 13, 1, 0, 10, 10, 1, 0, 48, 57, 3, 0, 48, 57, 65, 70, 97, 102, 2, 0, 65, 90, 97, 122, 5, 0, 9, 10, 13, 13, 32, 32, 35, 35, 46, 46, 87, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 1, 27, 1, 0, 0, 0, 3, 33, 1, 0, 0, 0, 5, 35, 1, 0, 0, 0, 7, 37, 1, 0, 0, 0, 9, 39, 1, 0, 0, 0, 11, 42, 1, 0, 0, 0, 13, 47, 1, 0, 0, 0, 15, 52, 1, 0, 0, 0, 17, 56, 1, 0, 0, 0, 19, 59, 1, 0, 0, 0, 21, 63, 1, 0, 0, 0, 23, 66, 1, 0, 0, 0, 25, 81, 1, 0, 0, 0, 27, 29, 3, 9, 4, 0, 28, 30, 8, 0, 0, 0, 29, 28, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 29, 1, 0, 0, 0, 31, 32, 1, 0, 0, 0, 32, 2, 1, 0, 0, 0, 33, 34, 5, 47, 0, 0, 34, 4, 1, 0, 0, 0, 35, 36, 5, 46, 0, 0, 36, 6, 1, 0, 0, 0, 37, 38, 5, 58, 0, 0, 38, 8, 1, 0, 0, 0, 39, 40, 5, 35, 0, 0, 40, 10, 1, 0, 0, 0, 41, 43, 7, 1, 0, 0, 42, 41, 1, 0, 0, 0, 43, 44, 1, 0, 0, 0, 44, 42, 1, 0, 0, 0, 44, 45, 1, 0, 0, 0, 45, 12, 1, 0, 0, 0, 46, 48, 7, 2, 0, 0, 47, 46, 1, 0, 0, 0, 47, 48, 1, 0, 0, 0, 48, 49, 1, 0, 0, 0, 49, 50, 7, 3, 0, 0, 50, 14, 1, 0, 0, 0, 51, 53, 3, 17, 8, 0, 52, 51, 1, 0, 0, 0, 53, 54, 1, 0, 0, 0, 54, 52, 1, 0, 0, 0, 54, 55, 1, 0, 0, 0, 55, 16, 1, 0, 0, 0, 56, 57, 7, 4, 0, 0, 57, 18, 1, 0, 0, 0, 58, 60, 3, 21, 10, 0, 59, 58, 1, 0, 0, 0, 60, 61, 1, 0, 0, 0, 61, 59, 1, 0, 0, 0, 61, 62, 1, 0, 0, 0, 62, 20, 1, 0, 0, 0, 63, 64, 7, 5, 0, 0, 64, 22, 1, 0, 0, 0, 65, 67, 3, 25, 12, 0, 66, 65, 1, 0, 0, 0, 67, 68, 1, 0, 0, 0, 68, 66, 1, 0, 0, 0, 68, 69, 1, 0, 0, 0, 69, 78, 1, 0, 0, 0, 70, 72, 3, 5, 2, 0, 71, 73, 7, 6, 0, 0, 72, 71, 1, 0, 0, 0, 73, 74, 1, 0, 0, 0, 74, 72, 1, 0, 0, 0, 74, 75, 1, 0, 0, 0, 75, 77, 1, 0, 0, 0, 76, 70, 1, 0, 0, 0, 77, 80, 1, 0, 0, 0, 78, 76, 1, 0, 0, 0, 78, 79, 1, 0, 0, 0, 79, 24, 1, 0, 0, 0, 80, 78, 1, 0, 0, 0, 81, 82, 8, 7, 0, 0, 82, 26, 1, 0, 0, 0, 9, 0, 31, 44, 47, 54, 61, 68, 74, 78, 0]

View File

@ -0,0 +1,14 @@
COMMENTLINE=1
SLASH=2
DOT=3
COLON=4
HASHTAG=5
SEPARATOR=6
NEWLINE=7
DIGITS=8
OCTETS=9
DOMAIN=10
'/'=2
'.'=3
':'=4
'#'=5

View File

@ -0,0 +1,124 @@
// Code generated from Hosts.g4 by ANTLR 4.13.0. DO NOT EDIT.
package parser // Hosts
import "github.com/antlr4-go/antlr/v4"
// BaseHostsListener is a complete listener for a parse tree produced by HostsParser.
type BaseHostsListener struct{}
var _ HostsListener = &BaseHostsListener{}
// VisitTerminal is called when a terminal node is visited.
func (s *BaseHostsListener) VisitTerminal(node antlr.TerminalNode) {}
// VisitErrorNode is called when an error node is visited.
func (s *BaseHostsListener) VisitErrorNode(node antlr.ErrorNode) {}
// EnterEveryRule is called when any rule is entered.
func (s *BaseHostsListener) EnterEveryRule(ctx antlr.ParserRuleContext) {}
// ExitEveryRule is called when any rule is exited.
func (s *BaseHostsListener) ExitEveryRule(ctx antlr.ParserRuleContext) {}
// EnterLineStatement is called when production lineStatement is entered.
func (s *BaseHostsListener) EnterLineStatement(ctx *LineStatementContext) {}
// ExitLineStatement is called when production lineStatement is exited.
func (s *BaseHostsListener) ExitLineStatement(ctx *LineStatementContext) {}
// EnterEntry is called when production entry is entered.
func (s *BaseHostsListener) EnterEntry(ctx *EntryContext) {}
// ExitEntry is called when production entry is exited.
func (s *BaseHostsListener) ExitEntry(ctx *EntryContext) {}
// EnterAliases is called when production aliases is entered.
func (s *BaseHostsListener) EnterAliases(ctx *AliasesContext) {}
// ExitAliases is called when production aliases is exited.
func (s *BaseHostsListener) ExitAliases(ctx *AliasesContext) {}
// EnterAlias is called when production alias is entered.
func (s *BaseHostsListener) EnterAlias(ctx *AliasContext) {}
// ExitAlias is called when production alias is exited.
func (s *BaseHostsListener) ExitAlias(ctx *AliasContext) {}
// EnterHostname is called when production hostname is entered.
func (s *BaseHostsListener) EnterHostname(ctx *HostnameContext) {}
// ExitHostname is called when production hostname is exited.
func (s *BaseHostsListener) ExitHostname(ctx *HostnameContext) {}
// EnterDomain is called when production domain is entered.
func (s *BaseHostsListener) EnterDomain(ctx *DomainContext) {}
// ExitDomain is called when production domain is exited.
func (s *BaseHostsListener) ExitDomain(ctx *DomainContext) {}
// EnterIpAddress is called when production ipAddress is entered.
func (s *BaseHostsListener) EnterIpAddress(ctx *IpAddressContext) {}
// ExitIpAddress is called when production ipAddress is exited.
func (s *BaseHostsListener) ExitIpAddress(ctx *IpAddressContext) {}
// EnterIpv4Address is called when production ipv4Address is entered.
func (s *BaseHostsListener) EnterIpv4Address(ctx *Ipv4AddressContext) {}
// ExitIpv4Address is called when production ipv4Address is exited.
func (s *BaseHostsListener) ExitIpv4Address(ctx *Ipv4AddressContext) {}
// EnterSingleIPv4Address is called when production singleIPv4Address is entered.
func (s *BaseHostsListener) EnterSingleIPv4Address(ctx *SingleIPv4AddressContext) {}
// ExitSingleIPv4Address is called when production singleIPv4Address is exited.
func (s *BaseHostsListener) ExitSingleIPv4Address(ctx *SingleIPv4AddressContext) {}
// EnterIpv6Address is called when production ipv6Address is entered.
func (s *BaseHostsListener) EnterIpv6Address(ctx *Ipv6AddressContext) {}
// ExitIpv6Address is called when production ipv6Address is exited.
func (s *BaseHostsListener) ExitIpv6Address(ctx *Ipv6AddressContext) {}
// EnterSingleIPv6Address is called when production singleIPv6Address is entered.
func (s *BaseHostsListener) EnterSingleIPv6Address(ctx *SingleIPv6AddressContext) {}
// ExitSingleIPv6Address is called when production singleIPv6Address is exited.
func (s *BaseHostsListener) ExitSingleIPv6Address(ctx *SingleIPv6AddressContext) {}
// EnterIpv4Digit is called when production ipv4Digit is entered.
func (s *BaseHostsListener) EnterIpv4Digit(ctx *Ipv4DigitContext) {}
// ExitIpv4Digit is called when production ipv4Digit is exited.
func (s *BaseHostsListener) ExitIpv4Digit(ctx *Ipv4DigitContext) {}
// EnterIpv6Octet is called when production ipv6Octet is entered.
func (s *BaseHostsListener) EnterIpv6Octet(ctx *Ipv6OctetContext) {}
// ExitIpv6Octet is called when production ipv6Octet is exited.
func (s *BaseHostsListener) ExitIpv6Octet(ctx *Ipv6OctetContext) {}
// EnterIpRange is called when production ipRange is entered.
func (s *BaseHostsListener) EnterIpRange(ctx *IpRangeContext) {}
// ExitIpRange is called when production ipRange is exited.
func (s *BaseHostsListener) ExitIpRange(ctx *IpRangeContext) {}
// EnterIpRangeBits is called when production ipRangeBits is entered.
func (s *BaseHostsListener) EnterIpRangeBits(ctx *IpRangeBitsContext) {}
// ExitIpRangeBits is called when production ipRangeBits is exited.
func (s *BaseHostsListener) ExitIpRangeBits(ctx *IpRangeBitsContext) {}
// EnterComment is called when production comment is entered.
func (s *BaseHostsListener) EnterComment(ctx *CommentContext) {}
// ExitComment is called when production comment is exited.
func (s *BaseHostsListener) ExitComment(ctx *CommentContext) {}
// EnterLeadingComment is called when production leadingComment is entered.
func (s *BaseHostsListener) EnterLeadingComment(ctx *LeadingCommentContext) {}
// ExitLeadingComment is called when production leadingComment is exited.
func (s *BaseHostsListener) ExitLeadingComment(ctx *LeadingCommentContext) {}

View File

@ -0,0 +1,145 @@
// Code generated from Hosts.g4 by ANTLR 4.13.0. DO NOT EDIT.
package parser
import (
"fmt"
"github.com/antlr4-go/antlr/v4"
"sync"
"unicode"
)
// Suppress unused import error
var _ = fmt.Printf
var _ = sync.Once{}
var _ = unicode.IsLetter
type HostsLexer struct {
*antlr.BaseLexer
channelNames []string
modeNames []string
// TODO: EOF string
}
var HostsLexerLexerStaticData struct {
once sync.Once
serializedATN []int32
ChannelNames []string
ModeNames []string
LiteralNames []string
SymbolicNames []string
RuleNames []string
PredictionContextCache *antlr.PredictionContextCache
atn *antlr.ATN
decisionToDFA []*antlr.DFA
}
func hostslexerLexerInit() {
staticData := &HostsLexerLexerStaticData
staticData.ChannelNames = []string{
"DEFAULT_TOKEN_CHANNEL", "HIDDEN",
}
staticData.ModeNames = []string{
"DEFAULT_MODE",
}
staticData.LiteralNames = []string{
"", "", "'/'", "'.'", "':'", "'#'",
}
staticData.SymbolicNames = []string{
"", "COMMENTLINE", "SLASH", "DOT", "COLON", "HASHTAG", "SEPARATOR",
"NEWLINE", "DIGITS", "OCTETS", "DOMAIN",
}
staticData.RuleNames = []string{
"COMMENTLINE", "SLASH", "DOT", "COLON", "HASHTAG", "SEPARATOR", "NEWLINE",
"DIGITS", "DIGIT", "OCTETS", "OCTET", "DOMAIN", "STRING",
}
staticData.PredictionContextCache = antlr.NewPredictionContextCache()
staticData.serializedATN = []int32{
4, 0, 10, 83, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2,
4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2,
10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 1, 0, 1, 0, 4, 0, 30, 8, 0, 11,
0, 12, 0, 31, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 4,
5, 43, 8, 5, 11, 5, 12, 5, 44, 1, 6, 3, 6, 48, 8, 6, 1, 6, 1, 6, 1, 7,
4, 7, 53, 8, 7, 11, 7, 12, 7, 54, 1, 8, 1, 8, 1, 9, 4, 9, 60, 8, 9, 11,
9, 12, 9, 61, 1, 10, 1, 10, 1, 11, 4, 11, 67, 8, 11, 11, 11, 12, 11, 68,
1, 11, 1, 11, 4, 11, 73, 8, 11, 11, 11, 12, 11, 74, 5, 11, 77, 8, 11, 10,
11, 12, 11, 80, 9, 11, 1, 12, 1, 12, 0, 0, 13, 1, 1, 3, 2, 5, 3, 7, 4,
9, 5, 11, 6, 13, 7, 15, 8, 17, 0, 19, 9, 21, 0, 23, 10, 25, 0, 1, 0, 8,
2, 0, 10, 10, 13, 13, 2, 0, 9, 9, 32, 32, 1, 0, 13, 13, 1, 0, 10, 10, 1,
0, 48, 57, 3, 0, 48, 57, 65, 70, 97, 102, 2, 0, 65, 90, 97, 122, 5, 0,
9, 10, 13, 13, 32, 32, 35, 35, 46, 46, 87, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0,
0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0,
0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 23, 1,
0, 0, 0, 1, 27, 1, 0, 0, 0, 3, 33, 1, 0, 0, 0, 5, 35, 1, 0, 0, 0, 7, 37,
1, 0, 0, 0, 9, 39, 1, 0, 0, 0, 11, 42, 1, 0, 0, 0, 13, 47, 1, 0, 0, 0,
15, 52, 1, 0, 0, 0, 17, 56, 1, 0, 0, 0, 19, 59, 1, 0, 0, 0, 21, 63, 1,
0, 0, 0, 23, 66, 1, 0, 0, 0, 25, 81, 1, 0, 0, 0, 27, 29, 3, 9, 4, 0, 28,
30, 8, 0, 0, 0, 29, 28, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 29, 1, 0, 0,
0, 31, 32, 1, 0, 0, 0, 32, 2, 1, 0, 0, 0, 33, 34, 5, 47, 0, 0, 34, 4, 1,
0, 0, 0, 35, 36, 5, 46, 0, 0, 36, 6, 1, 0, 0, 0, 37, 38, 5, 58, 0, 0, 38,
8, 1, 0, 0, 0, 39, 40, 5, 35, 0, 0, 40, 10, 1, 0, 0, 0, 41, 43, 7, 1, 0,
0, 42, 41, 1, 0, 0, 0, 43, 44, 1, 0, 0, 0, 44, 42, 1, 0, 0, 0, 44, 45,
1, 0, 0, 0, 45, 12, 1, 0, 0, 0, 46, 48, 7, 2, 0, 0, 47, 46, 1, 0, 0, 0,
47, 48, 1, 0, 0, 0, 48, 49, 1, 0, 0, 0, 49, 50, 7, 3, 0, 0, 50, 14, 1,
0, 0, 0, 51, 53, 3, 17, 8, 0, 52, 51, 1, 0, 0, 0, 53, 54, 1, 0, 0, 0, 54,
52, 1, 0, 0, 0, 54, 55, 1, 0, 0, 0, 55, 16, 1, 0, 0, 0, 56, 57, 7, 4, 0,
0, 57, 18, 1, 0, 0, 0, 58, 60, 3, 21, 10, 0, 59, 58, 1, 0, 0, 0, 60, 61,
1, 0, 0, 0, 61, 59, 1, 0, 0, 0, 61, 62, 1, 0, 0, 0, 62, 20, 1, 0, 0, 0,
63, 64, 7, 5, 0, 0, 64, 22, 1, 0, 0, 0, 65, 67, 3, 25, 12, 0, 66, 65, 1,
0, 0, 0, 67, 68, 1, 0, 0, 0, 68, 66, 1, 0, 0, 0, 68, 69, 1, 0, 0, 0, 69,
78, 1, 0, 0, 0, 70, 72, 3, 5, 2, 0, 71, 73, 7, 6, 0, 0, 72, 71, 1, 0, 0,
0, 73, 74, 1, 0, 0, 0, 74, 72, 1, 0, 0, 0, 74, 75, 1, 0, 0, 0, 75, 77,
1, 0, 0, 0, 76, 70, 1, 0, 0, 0, 77, 80, 1, 0, 0, 0, 78, 76, 1, 0, 0, 0,
78, 79, 1, 0, 0, 0, 79, 24, 1, 0, 0, 0, 80, 78, 1, 0, 0, 0, 81, 82, 8,
7, 0, 0, 82, 26, 1, 0, 0, 0, 9, 0, 31, 44, 47, 54, 61, 68, 74, 78, 0,
}
deserializer := antlr.NewATNDeserializer(nil)
staticData.atn = deserializer.Deserialize(staticData.serializedATN)
atn := staticData.atn
staticData.decisionToDFA = make([]*antlr.DFA, len(atn.DecisionToState))
decisionToDFA := staticData.decisionToDFA
for index, state := range atn.DecisionToState {
decisionToDFA[index] = antlr.NewDFA(state, index)
}
}
// HostsLexerInit initializes any static state used to implement HostsLexer. By default the
// static state used to implement the lexer is lazily initialized during the first call to
// NewHostsLexer(). You can call this function if you wish to initialize the static state ahead
// of time.
func HostsLexerInit() {
staticData := &HostsLexerLexerStaticData
staticData.once.Do(hostslexerLexerInit)
}
// NewHostsLexer produces a new lexer instance for the optional input antlr.CharStream.
func NewHostsLexer(input antlr.CharStream) *HostsLexer {
HostsLexerInit()
l := new(HostsLexer)
l.BaseLexer = antlr.NewBaseLexer(input)
staticData := &HostsLexerLexerStaticData
l.Interpreter = antlr.NewLexerATNSimulator(l, staticData.atn, staticData.decisionToDFA, staticData.PredictionContextCache)
l.channelNames = staticData.ChannelNames
l.modeNames = staticData.ModeNames
l.RuleNames = staticData.RuleNames
l.LiteralNames = staticData.LiteralNames
l.SymbolicNames = staticData.SymbolicNames
l.GrammarFileName = "Hosts.g4"
// TODO: l.EOF = antlr.TokenEOF
return l
}
// HostsLexer tokens.
const (
HostsLexerCOMMENTLINE = 1
HostsLexerSLASH = 2
HostsLexerDOT = 3
HostsLexerCOLON = 4
HostsLexerHASHTAG = 5
HostsLexerSEPARATOR = 6
HostsLexerNEWLINE = 7
HostsLexerDIGITS = 8
HostsLexerOCTETS = 9
HostsLexerDOMAIN = 10
)

View File

@ -0,0 +1,112 @@
// Code generated from Hosts.g4 by ANTLR 4.13.0. DO NOT EDIT.
package parser // Hosts
import "github.com/antlr4-go/antlr/v4"
// HostsListener is a complete listener for a parse tree produced by HostsParser.
type HostsListener interface {
antlr.ParseTreeListener
// EnterLineStatement is called when entering the lineStatement production.
EnterLineStatement(c *LineStatementContext)
// EnterEntry is called when entering the entry production.
EnterEntry(c *EntryContext)
// EnterAliases is called when entering the aliases production.
EnterAliases(c *AliasesContext)
// EnterAlias is called when entering the alias production.
EnterAlias(c *AliasContext)
// EnterHostname is called when entering the hostname production.
EnterHostname(c *HostnameContext)
// EnterDomain is called when entering the domain production.
EnterDomain(c *DomainContext)
// EnterIpAddress is called when entering the ipAddress production.
EnterIpAddress(c *IpAddressContext)
// EnterIpv4Address is called when entering the ipv4Address production.
EnterIpv4Address(c *Ipv4AddressContext)
// EnterSingleIPv4Address is called when entering the singleIPv4Address production.
EnterSingleIPv4Address(c *SingleIPv4AddressContext)
// EnterIpv6Address is called when entering the ipv6Address production.
EnterIpv6Address(c *Ipv6AddressContext)
// EnterSingleIPv6Address is called when entering the singleIPv6Address production.
EnterSingleIPv6Address(c *SingleIPv6AddressContext)
// EnterIpv4Digit is called when entering the ipv4Digit production.
EnterIpv4Digit(c *Ipv4DigitContext)
// EnterIpv6Octet is called when entering the ipv6Octet production.
EnterIpv6Octet(c *Ipv6OctetContext)
// EnterIpRange is called when entering the ipRange production.
EnterIpRange(c *IpRangeContext)
// EnterIpRangeBits is called when entering the ipRangeBits production.
EnterIpRangeBits(c *IpRangeBitsContext)
// EnterComment is called when entering the comment production.
EnterComment(c *CommentContext)
// EnterLeadingComment is called when entering the leadingComment production.
EnterLeadingComment(c *LeadingCommentContext)
// ExitLineStatement is called when exiting the lineStatement production.
ExitLineStatement(c *LineStatementContext)
// ExitEntry is called when exiting the entry production.
ExitEntry(c *EntryContext)
// ExitAliases is called when exiting the aliases production.
ExitAliases(c *AliasesContext)
// ExitAlias is called when exiting the alias production.
ExitAlias(c *AliasContext)
// ExitHostname is called when exiting the hostname production.
ExitHostname(c *HostnameContext)
// ExitDomain is called when exiting the domain production.
ExitDomain(c *DomainContext)
// ExitIpAddress is called when exiting the ipAddress production.
ExitIpAddress(c *IpAddressContext)
// ExitIpv4Address is called when exiting the ipv4Address production.
ExitIpv4Address(c *Ipv4AddressContext)
// ExitSingleIPv4Address is called when exiting the singleIPv4Address production.
ExitSingleIPv4Address(c *SingleIPv4AddressContext)
// ExitIpv6Address is called when exiting the ipv6Address production.
ExitIpv6Address(c *Ipv6AddressContext)
// ExitSingleIPv6Address is called when exiting the singleIPv6Address production.
ExitSingleIPv6Address(c *SingleIPv6AddressContext)
// ExitIpv4Digit is called when exiting the ipv4Digit production.
ExitIpv4Digit(c *Ipv4DigitContext)
// ExitIpv6Octet is called when exiting the ipv6Octet production.
ExitIpv6Octet(c *Ipv6OctetContext)
// ExitIpRange is called when exiting the ipRange production.
ExitIpRange(c *IpRangeContext)
// ExitIpRangeBits is called when exiting the ipRangeBits production.
ExitIpRangeBits(c *IpRangeBitsContext)
// ExitComment is called when exiting the comment production.
ExitComment(c *CommentContext)
// ExitLeadingComment is called when exiting the leadingComment production.
ExitLeadingComment(c *LeadingCommentContext)
}

File diff suppressed because it is too large Load Diff

View File

@ -16,12 +16,14 @@ const (
LanguageSSHDConfig SupportedLanguage = "sshd_config" LanguageSSHDConfig SupportedLanguage = "sshd_config"
LanguageFstab SupportedLanguage = "fstab" LanguageFstab SupportedLanguage = "fstab"
LanguageWireguard SupportedLanguage = "languagewireguard" LanguageWireguard SupportedLanguage = "languagewireguard"
LanguageHosts SupportedLanguage = "hosts"
) )
var AllSupportedLanguages = []string{ var AllSupportedLanguages = []string{
string(LanguageSSHDConfig), string(LanguageSSHDConfig),
string(LanguageFstab), string(LanguageFstab),
string(LanguageWireguard), string(LanguageWireguard),
string(LanguageHosts),
} }
type FatalFileNotReadableError struct { type FatalFileNotReadableError struct {
@ -59,11 +61,19 @@ var valueToLanguageMap = map[string]SupportedLanguage{
"wireguard": LanguageWireguard, "wireguard": LanguageWireguard,
"wg": LanguageWireguard, "wg": LanguageWireguard,
"languagewireguard": LanguageWireguard, "languagewireguard": LanguageWireguard,
"host": LanguageHosts,
"hosts": LanguageHosts,
"etc/hosts": LanguageHosts,
} }
var typeOverwriteRegex = regexp.MustCompile(`^#\?\s*lsp\.language\s*=\s*(\w+)\s*$`) var typeOverwriteRegex = regexp.MustCompile(`#\?\s*lsp\.language\s*=\s*(\w+)\s*`)
var wireguardPattern = regexp.MustCompile(`/wg\d+\.conf$`) var wireguardPattern = regexp.MustCompile(`/wg\d+\.conf$`)
var undetectableError = common.ParseError{
Line: 0,
Err: LanguageUndetectableError{},
}
func DetectLanguage( func DetectLanguage(
content string, content string,
advertisedLanguage string, advertisedLanguage string,
@ -99,14 +109,13 @@ func DetectLanguage(
return LanguageSSHDConfig, nil return LanguageSSHDConfig, nil
case "file:///etc/fstab": case "file:///etc/fstab":
return LanguageFstab, nil return LanguageFstab, nil
case "file:///etc/hosts":
return LanguageHosts, nil
} }
if strings.HasPrefix(uri, "file:///etc/wireguard/") || wireguardPattern.MatchString(uri) { if strings.HasPrefix(uri, "file:///etc/wireguard/") || wireguardPattern.MatchString(uri) {
return LanguageWireguard, nil return LanguageWireguard, nil
} }
return "", common.ParseError{ return "", undetectableError
Line: 0,
Err: LanguageUndetectableError{},
}
} }

View File

@ -20,8 +20,14 @@ func (h *RootHandler) AddDocument(uri protocol.DocumentUri, language SupportedLa
h.languageMap[uri] = language h.languageMap[uri] = language
} }
func (h *RootHandler) GetLanguageForDocument(uri protocol.DocumentUri) SupportedLanguage { func (h *RootHandler) GetLanguageForDocument(uri protocol.DocumentUri) *SupportedLanguage {
return h.languageMap[uri] language, found := h.languageMap[uri]
if !found {
return nil
}
return &language
} }
func (h *RootHandler) RemoveDocument(uri protocol.DocumentUri) { func (h *RootHandler) RemoveDocument(uri protocol.DocumentUri) {

View File

@ -1,7 +1,9 @@
package roothandler package roothandler
import ( import (
hosts "config-lsp/handlers/hosts/lsp"
wireguard "config-lsp/handlers/wireguard/lsp" wireguard "config-lsp/handlers/wireguard/lsp"
"github.com/tliron/glsp" "github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16" protocol "github.com/tliron/glsp/protocol_3_16"
) )
@ -9,14 +11,26 @@ import (
func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionParams) (any, error) { func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionParams) (any, error) {
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI) language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
switch language { if language == nil {
showParseError(
context,
params.TextDocument.URI,
undetectableError,
)
return nil, undetectableError.Err
}
switch *language {
case LanguageFstab: case LanguageFstab:
return nil, nil fallthrough
case LanguageHosts:
return hosts.TextDocumentCodeAction(context, params)
case LanguageSSHDConfig: case LanguageSSHDConfig:
return nil, nil return nil, nil
case LanguageWireguard: case LanguageWireguard:
return wireguard.TextDocumentCodeAction(context, params) return wireguard.TextDocumentCodeAction(context, params)
} }
panic("root-handler/TextDocumentCompletion: unexpected language" + language) panic("root-handler/TextDocumentCompletion: unexpected language" + *language)
} }

View File

@ -2,6 +2,7 @@ package roothandler
import ( import (
"config-lsp/handlers/fstab" "config-lsp/handlers/fstab"
hosts "config-lsp/handlers/hosts/lsp"
wireguard "config-lsp/handlers/wireguard/lsp" wireguard "config-lsp/handlers/wireguard/lsp"
"github.com/tliron/glsp" "github.com/tliron/glsp"
@ -11,14 +12,26 @@ import (
func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) { func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) {
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI) language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
switch language { if language == nil {
showParseError(
context,
params.TextDocument.URI,
undetectableError,
)
return nil, undetectableError.Err
}
switch *language {
case LanguageFstab: case LanguageFstab:
return fstab.TextDocumentCompletion(context, params) return fstab.TextDocumentCompletion(context, params)
case LanguageSSHDConfig: case LanguageSSHDConfig:
return nil, nil return nil, nil
case LanguageWireguard: case LanguageWireguard:
return wireguard.TextDocumentCompletion(context, params) return wireguard.TextDocumentCompletion(context, params)
case LanguageHosts:
return hosts.TextDocumentCompletion(context, params)
} }
panic("root-handler/TextDocumentCompletion: unexpected language" + language) panic("root-handler/TextDocumentCompletion: unexpected language" + *language)
} }

View File

@ -2,6 +2,7 @@ package roothandler
import ( import (
"config-lsp/handlers/fstab" "config-lsp/handlers/fstab"
hosts "config-lsp/handlers/hosts/lsp"
wireguard "config-lsp/handlers/wireguard/lsp" wireguard "config-lsp/handlers/wireguard/lsp"
"github.com/tliron/glsp" "github.com/tliron/glsp"
@ -11,14 +12,52 @@ import (
func TextDocumentDidChange(context *glsp.Context, params *protocol.DidChangeTextDocumentParams) error { func TextDocumentDidChange(context *glsp.Context, params *protocol.DidChangeTextDocumentParams) error {
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI) language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
switch language { if language == nil {
content := params.ContentChanges[0].(protocol.TextDocumentContentChangeEventWhole).Text
newLanguage, err := initFile(
context,
content,
params.TextDocument.URI,
"",
)
if err != nil {
return err
}
language = newLanguage
params := &protocol.DidOpenTextDocumentParams{
TextDocument: protocol.TextDocumentItem{
URI: params.TextDocument.URI,
Text: content,
Version: params.TextDocument.Version,
LanguageID: string(*language),
},
}
switch *language {
case LanguageFstab:
return fstab.TextDocumentDidOpen(context, params)
case LanguageSSHDConfig:
break
case LanguageWireguard:
return wireguard.TextDocumentDidOpen(context, params)
case LanguageHosts:
return hosts.TextDocumentDidOpen(context, params)
}
}
switch *language {
case LanguageFstab: case LanguageFstab:
return fstab.TextDocumentDidChange(context, params) return fstab.TextDocumentDidChange(context, params)
case LanguageSSHDConfig: case LanguageSSHDConfig:
return nil return nil
case LanguageWireguard: case LanguageWireguard:
return wireguard.TextDocumentDidChange(context, params) return wireguard.TextDocumentDidChange(context, params)
case LanguageHosts:
return hosts.TextDocumentDidChange(context, params)
} }
panic("root-handler/TextDocumentDidChange: unexpected language" + language) panic("root-handler/TextDocumentDidChange: unexpected language" + *language)
} }

View File

@ -1,6 +1,7 @@
package roothandler package roothandler
import ( import (
hosts "config-lsp/handlers/hosts/lsp"
wireguard "config-lsp/handlers/wireguard/lsp" wireguard "config-lsp/handlers/wireguard/lsp"
"github.com/tliron/glsp" "github.com/tliron/glsp"
@ -10,14 +11,26 @@ import (
func TextDocumentDidClose(context *glsp.Context, params *protocol.DidCloseTextDocumentParams) error { func TextDocumentDidClose(context *glsp.Context, params *protocol.DidCloseTextDocumentParams) error {
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI) language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
if language == nil {
showParseError(
context,
params.TextDocument.URI,
undetectableError,
)
return undetectableError.Err
}
delete(openedFiles, params.TextDocument.URI) delete(openedFiles, params.TextDocument.URI)
rootHandler.RemoveDocument(params.TextDocument.URI) rootHandler.RemoveDocument(params.TextDocument.URI)
switch language { switch *language {
case LanguageFstab: case LanguageFstab:
case LanguageSSHDConfig: case LanguageSSHDConfig:
case LanguageWireguard: case LanguageWireguard:
return wireguard.TextDocumentDidClose(context, params) return wireguard.TextDocumentDidClose(context, params)
case LanguageHosts:
return hosts.TextDocumentDidClose(context, params)
default: default:
} }

View File

@ -3,6 +3,7 @@ package roothandler
import ( import (
"config-lsp/common" "config-lsp/common"
fstab "config-lsp/handlers/fstab" fstab "config-lsp/handlers/fstab"
hosts "config-lsp/handlers/hosts/lsp"
wireguard "config-lsp/handlers/wireguard/lsp" wireguard "config-lsp/handlers/wireguard/lsp"
"fmt" "fmt"
@ -15,31 +16,26 @@ func TextDocumentDidOpen(context *glsp.Context, params *protocol.DidOpenTextDocu
// Find the file type // Find the file type
content := params.TextDocument.Text content := params.TextDocument.Text
language, err := DetectLanguage(content, params.TextDocument.LanguageID, params.TextDocument.URI) language, err := initFile(
context,
content,
params.TextDocument.URI,
params.TextDocument.LanguageID,
)
if err != nil { if err != nil {
parseError := err.(common.ParseError) return err
showParseError(
context,
params.TextDocument.URI,
parseError,
)
return parseError.Err
} }
openedFiles[params.TextDocument.URI] = struct{}{} switch *language {
// Everything okay, now we can handle the file
rootHandler.AddDocument(params.TextDocument.URI, language)
switch language {
case LanguageFstab: case LanguageFstab:
return fstab.TextDocumentDidOpen(context, params) return fstab.TextDocumentDidOpen(context, params)
case LanguageSSHDConfig: case LanguageSSHDConfig:
break break
case LanguageWireguard: case LanguageWireguard:
return wireguard.TextDocumentDidOpen(context, params) return wireguard.TextDocumentDidOpen(context, params)
case LanguageHosts:
return hosts.TextDocumentDidOpen(context, params)
} }
panic(fmt.Sprintf("unexpected roothandler.SupportedLanguage: %#v", language)) panic(fmt.Sprintf("unexpected roothandler.SupportedLanguage: %#v", language))
@ -73,3 +69,30 @@ func showParseError(
}, },
) )
} }
func initFile(
context *glsp.Context,
content string,
uri protocol.DocumentUri,
advertisedLanguage string,
) (*SupportedLanguage, error) {
language, err := DetectLanguage(content, advertisedLanguage, uri)
if err != nil {
parseError := err.(common.ParseError)
showParseError(
context,
uri,
parseError,
)
return nil, parseError.Err
}
openedFiles[uri] = struct{}{}
// Everything okay, now we can handle the file
rootHandler.AddDocument(uri, language)
return &language, nil
}

View File

@ -2,6 +2,7 @@ package roothandler
import ( import (
"config-lsp/handlers/fstab" "config-lsp/handlers/fstab"
hosts "config-lsp/handlers/hosts/lsp"
wireguard "config-lsp/handlers/wireguard/lsp" wireguard "config-lsp/handlers/wireguard/lsp"
"github.com/tliron/glsp" "github.com/tliron/glsp"
@ -11,14 +12,26 @@ import (
func TextDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) { func TextDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) {
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI) language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
switch language { if language == nil {
case LanguageFstab: showParseError(
return fstab.TextDocumentHover(context, params) context,
params.TextDocument.URI,
undetectableError,
)
return nil, undetectableError.Err
}
switch *language {
case LanguageHosts:
return hosts.TextDocumentHover(context, params)
case LanguageSSHDConfig: case LanguageSSHDConfig:
return nil, nil return nil, nil
case LanguageFstab:
return fstab.TextDocumentHover(context, params)
case LanguageWireguard: case LanguageWireguard:
return wireguard.TextDocumentHover(context, params) return wireguard.TextDocumentHover(context, params)
} }
panic("root-handler/TextDocumentHover: unexpected language" + language) panic("root-handler/TextDocumentHover: unexpected language" + *language)
} }

View File

@ -1,6 +1,7 @@
package roothandler package roothandler
import ( import (
hosts "config-lsp/handlers/hosts/lsp"
wireguard "config-lsp/handlers/wireguard/lsp" wireguard "config-lsp/handlers/wireguard/lsp"
"strings" "strings"
@ -17,6 +18,8 @@ func WorkspaceExecuteCommand(context *glsp.Context, params *protocol.ExecuteComm
switch commandSection { switch commandSection {
case "wireguard": case "wireguard":
edit, err = wireguard.WorkspaceExecuteCommand(context, params) edit, err = wireguard.WorkspaceExecuteCommand(context, params)
case "hosts":
edit, err = hosts.WorkspaceExecuteCommand(context, params)
} }
if err != nil { if err != nil {

View File

@ -15,3 +15,7 @@ func GetTrimIndex(s string) []int {
return indexes[2:4] return indexes[2:4]
} }
func SplitIntoLines(s string) []string {
return regexp.MustCompile("\r?\n").Split(s, -1)
}

View File

@ -1,9 +1,7 @@
package utils package utils
import "strings"
func Dedent(s string) string { func Dedent(s string) string {
return strings.TrimLeft(s, "\n") return s[len("\n"):]
} }
func KeyExists[T comparable, V any](keys map[T]V, key T) bool { func KeyExists[T comparable, V any](keys map[T]V, key T) bool {