feat(hosts): Add hosts parser

This commit is contained in:
Myzel394 2024-08-25 11:06:28 +02:00
parent a2b5f1ea93
commit 4fe2f46b1b
No known key found for this signature in database
GPG Key ID: DEC4AAB876F73185
18 changed files with 3476 additions and 3 deletions

14
common/errors.go Normal file
View File

@ -0,0 +1,14 @@
package common
type LSPError struct {
Range LocationRange
Err error
}
type SyntaxError struct {
Message string
}
func (s SyntaxError) Error() string {
return s.Message
}

29
common/location.go Normal file
View File

@ -0,0 +1,29 @@
package common
type Location struct {
Line uint32
Character uint32
}
type LocationRange struct {
Start Location
End Location
}
func (l *LocationRange) ChangeBothLines(newLine uint32) {
l.Start.Line = newLine
l.End.Line = newLine
}
func CreateSingleCharRange(line uint32, character uint32) LocationRange {
return LocationRange{
Start: Location{
Line: line,
Character: character,
},
End: Location{
Line: line,
Character: character,
},
}
}

1
go.mod
View File

@ -8,6 +8,7 @@ require (
)
require (
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/gorilla/websocket v1.5.3 // 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/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
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,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

@ -0,0 +1,82 @@
package tree
import (
"config-lsp/common"
"config-lsp/handlers/hosts/parser"
"regexp"
"strings"
"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 := strings.Split(input, "\n")
for rawLineNumber, line := range lines {
lineNumber := uint32(rawLineNumber + 1)
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,143 @@
package tree
import (
"config-lsp/utils"
"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[1].IPAddress == nil {
t.Errorf("Expected IP address to be present, but got nil")
}
if !(parser.Tree.Entries[1].IPAddress.Value == "1.2.3.4") {
t.Errorf("Expected IP address to be 1.2.3.4, but got %v", parser.Tree.Entries[1].IPAddress.Value)
}
if !(parser.Tree.Entries[1].Hostname.Value == "hello.com") {
t.Errorf("Expected hostname to be hello.com, but got %v", parser.Tree.Entries[1].Hostname.Value)
}
if !(parser.Tree.Entries[1].Aliases == nil) {
t.Errorf("Expected no aliases, but got %v", parser.Tree.Entries[1].Aliases)
}
if !(parser.Tree.Entries[1].Location.Start.Line == 1) {
t.Errorf("Expected line to be 1, but got %v", parser.Tree.Entries[1].Location.Start.Line)
}
if !(parser.Tree.Entries[1].Location.Start.Character == 0) {
t.Errorf("Expected start to be 0, but got %v", parser.Tree.Entries[1].Location.Start)
}
if !(parser.Tree.Entries[1].Location.End.Character == 16) {
t.Errorf("Expected end to be 16, but got %v", parser.Tree.Entries[1].Location.End.Character)
}
if !(parser.Tree.Entries[1].IPAddress.Location.Start.Line == 1) {
t.Errorf("Expected IP address line to be 1, but got %v", parser.Tree.Entries[1].IPAddress.Location.Start.Line)
}
if !(parser.Tree.Entries[1].IPAddress.Location.Start.Character == 0) {
t.Errorf("Expected IP address start to be 0, but got %v", parser.Tree.Entries[1].IPAddress.Location.Start.Character)
}
if !(parser.Tree.Entries[1].IPAddress.Location.End.Character == 6) {
t.Errorf("Expected IP address end to be 6, but got %v", parser.Tree.Entries[1].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[3].IPAddress == nil {
t.Errorf("Expected IP address to be present, but got nil")
}
if !(parser.Tree.Entries[3].IPAddress.Value == "1.2.3.4") {
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, 2)) {
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[1].IPAddress.Value == "1.2.3.4") {
t.Errorf("Expected IP address to be nil, but got %v", parser.Tree.Entries[1].IPAddress)
}
if !(parser.Tree.Entries[1].Hostname == nil) {
t.Errorf("Expected hostname to be nil, but got %v", parser.Tree.Entries[1].Hostname)
}
if !(parser.Tree.Entries[1].Aliases == nil) {
t.Errorf("Expected aliases to be nil, but got %v", parser.Tree.Entries[1].Aliases)
}
}

View File

@ -0,0 +1,73 @@
package tree
import (
"config-lsp/common"
"fmt"
"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,
},
}
}
type HostsParser struct {
Tree HostsTree
CommentLines map[uint32]struct{}
}
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
}
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 string
}
type HostsHostname struct {
Location common.LocationRange
Value string
}

View File

@ -0,0 +1,129 @@
package tree
import (
"config-lsp/common"
"config-lsp/handlers/hosts/parser"
"github.com/antlr4-go/antlr/v4"
)
type hostsListenerContext struct {
line uint32
}
type hostsParserListener struct {
*parser.BaseHostsListener
Parser *HostsParser
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)
entry := s.Parser.Tree.Entries[location.Start.Line]
entry.IPAddress = &HostsIPAddress{
Location: location,
Value: ctx.GetText(),
}
}
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,
},
}
}
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

@ -1,9 +1,7 @@
package utils
import "strings"
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 {