diff --git a/handlers/sshd_config/Config.g4 b/handlers/sshd_config/Config.g4 new file mode 100644 index 0000000..65c951a --- /dev/null +++ b/handlers/sshd_config/Config.g4 @@ -0,0 +1,42 @@ +grammar Config; + +lineStatement + : (entry | (WHITESPACE? leadingComment) | WHITESPACE?) EOF + ; + +entry + : WHITESPACE? key? WHITESPACE value? WHITESPACE? leadingComment? + ; + +key + : STRING + ; + +value + : STRING + ; + +leadingComment + : HASH WHITESPACE? (STRING WHITESPACE?)+ + ; + + +HASH + : '#' + ; + +MATCH + : ('M' | 'm') ('A' | 'a') ('T' | 't') ('C' | 'c') ('H' | 'h') + ; + +WHITESPACE + : [ \t]+ + ; + +STRING + : ~(' ' | '\t' | '\r' | '\n' | '#')+ + ; + +NEWLINE + : '\r'? '\n' + ; diff --git a/handlers/sshd_config/ast/error-listener.go b/handlers/sshd_config/ast/error-listener.go new file mode 100644 index 0000000..74d4387 --- /dev/null +++ b/handlers/sshd_config/ast/error-listener.go @@ -0,0 +1,45 @@ +package ast + +import ( + "config-lsp/common" + + "github.com/antlr4-go/antlr/v4" +) + +type errorListenerContext struct { + line uint32 +} + +type errorListener struct { + *antlr.DefaultErrorListener + Errors []common.LSPError + context errorListenerContext +} + +func (d *errorListener) SyntaxError( + recognizer antlr.Recognizer, + offendingSymbol interface{}, + _ int, + character int, + message string, + error antlr.RecognitionException, +) { + line := d.context.line + d.Errors = append(d.Errors, common.LSPError{ + Range: common.CreateSingleCharRange(uint32(line), uint32(character)), + Err: common.SyntaxError{ + Message: message, + }, + }) +} + +func createErrorListener( + line uint32, +) errorListener { + return errorListener{ + Errors: make([]common.LSPError, 0), + context: errorListenerContext{ + line: line, + }, + } +} diff --git a/handlers/sshd_config/ast/listener.go b/handlers/sshd_config/ast/listener.go new file mode 100644 index 0000000..c574f96 --- /dev/null +++ b/handlers/sshd_config/ast/listener.go @@ -0,0 +1,117 @@ +package ast + +import ( + "config-lsp/common" + "config-lsp/handlers/sshd_config/ast/parser" + "strings" +) + +func createListener( + config *SSHConfig, + context *sshListenerContext, +) sshParserListener { + return sshParserListener{ + Config: config, + Errors: make([]common.LSPError, 0), + sshContext: context, + } +} + +type sshListenerContext struct { + line uint32 + currentOption *SSHOption + currentMatchBlock *SSHMatchBlock + isKeyAMatchBlock bool +} + +func createSSHListenerContext() *sshListenerContext { + context := new(sshListenerContext) + context.isKeyAMatchBlock = false + + return context +} + +type sshParserListener struct { + *parser.BaseConfigListener + Config *SSHConfig + Errors []common.LSPError + sshContext *sshListenerContext +} + +func (s *sshParserListener) EnterEntry(ctx *parser.EntryContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location.ChangeBothLines(s.sshContext.line) + + option := &SSHOption{ + LocationRange: location, + Value: ctx.GetText(), + } + + if s.sshContext.currentMatchBlock == nil { + s.Config.Options.Put( + location.Start.Line, + option, + ) + + s.sshContext.currentOption = option + } else { + s.sshContext.currentMatchBlock.Options.Put( + location.Start.Line, + option, + ) + + s.sshContext.currentOption = option + } +} + +func (s *sshParserListener) EnterKey(ctx *parser.KeyContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location.ChangeBothLines(s.sshContext.line) + + text := ctx.GetText() + + if strings.ToLower(text) == "match" { + s.sshContext.isKeyAMatchBlock = true + } + + s.sshContext.currentOption.Key = &SSHKey{ + LocationRange: location, + Value: ctx.GetText(), + } +} + +func (s *sshParserListener) EnterValue(ctx *parser.ValueContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location.ChangeBothLines(s.sshContext.line) + + s.sshContext.currentOption.OptionValue = &SSHValue{ + LocationRange: location, + Value: ctx.GetText(), + } +} + +func (s *sshParserListener) ExitValue(ctx *parser.ValueContext) { + if s.sshContext.isKeyAMatchBlock { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location.ChangeBothLines(s.sshContext.line) + + rawEntry, _ := s.Config.Options.Get(location.Start.Line) + entry := rawEntry.(*SSHOption) + + // Overwrite the current match block + matchBlock := &SSHMatchBlock{ + LocationRange: location, + MatchEntry: entry, + } + s.Config.Options.Put( + location.Start.Line, + matchBlock, + ) + + s.sshContext.currentMatchBlock = matchBlock + s.sshContext.isKeyAMatchBlock = false + } + + s.sshContext.currentOption = nil +} + diff --git a/handlers/sshd_config/ast/parser.go b/handlers/sshd_config/ast/parser.go new file mode 100644 index 0000000..9143211 --- /dev/null +++ b/handlers/sshd_config/ast/parser.go @@ -0,0 +1,86 @@ +package ast + +import ( + "config-lsp/common" + "config-lsp/handlers/sshd_config/ast/parser" + "config-lsp/utils" + "regexp" + + "github.com/antlr4-go/antlr/v4" + "github.com/emirpasic/gods/maps/treemap" + + gods "github.com/emirpasic/gods/utils" +) + +func NewSSHConfig() *SSHConfig { + config := &SSHConfig{} + config.Clear() + + return config +} + +func (c *SSHConfig) Clear() { + c.Options = treemap.NewWith(gods.UInt32Comparator) + c.CommentLines = make(map[uint32]struct{}) +} + +var commentPattern = regexp.MustCompile(`^\s*#.*$`) +var emptyPattern = regexp.MustCompile(`^\s*$`) + +func (c *SSHConfig) Parse(input string) []common.LSPError { + errors := make([]common.LSPError, 0) + lines := utils.SplitIntoLines(input) + context := createSSHListenerContext() + + for rawLineNumber, line := range lines { + context.line = uint32(rawLineNumber) + + if commentPattern.MatchString(line) { + c.CommentLines[context.line] = struct{}{} + continue + } + + if emptyPattern.MatchString(line) { + continue + } + + errors = append( + errors, + c.parseStatement(context, line)..., + ) + } + + return errors +} + +func (c *SSHConfig) parseStatement( + context *sshListenerContext, + input string, +) []common.LSPError { + stream := antlr.NewInputStream(input) + + lexerErrorListener := createErrorListener(context.line) + lexer := parser.NewConfigLexer(&stream) + lexer.RemoveErrorListeners() + lexer.AddErrorListener(&lexerErrorListener) + + tokenStream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel) + + parserErrorListener := createErrorListener(context.line) + antlrParser := parser.NewConfigParser(tokenStream) + antlrParser.RemoveErrorListeners() + antlrParser.AddErrorListener(&parserErrorListener) + + listener := createListener(c, context) + antlr.ParseTreeWalkerDefault.Walk( + &listener, + antlrParser.LineStatement(), + ) + + errors := lexerErrorListener.Errors + errors = append(errors, parserErrorListener.Errors...) + errors = append(errors, listener.Errors...) + + return errors +} + diff --git a/handlers/sshd_config/ast/parser/Config.interp b/handlers/sshd_config/ast/parser/Config.interp new file mode 100644 index 0000000..74f62e9 --- /dev/null +++ b/handlers/sshd_config/ast/parser/Config.interp @@ -0,0 +1,26 @@ +token literal names: +null +'#' +null +null +null +null + +token symbolic names: +null +HASH +MATCH +WHITESPACE +STRING +NEWLINE + +rule names: +lineStatement +entry +key +value +leadingComment + + +atn: +[4, 1, 5, 55, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 1, 0, 1, 0, 3, 0, 13, 8, 0, 1, 0, 1, 0, 3, 0, 17, 8, 0, 3, 0, 19, 8, 0, 1, 0, 1, 0, 1, 1, 3, 1, 24, 8, 1, 1, 1, 3, 1, 27, 8, 1, 1, 1, 1, 1, 3, 1, 31, 8, 1, 1, 1, 3, 1, 34, 8, 1, 1, 1, 3, 1, 37, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 3, 4, 45, 8, 4, 1, 4, 1, 4, 3, 4, 49, 8, 4, 4, 4, 51, 8, 4, 11, 4, 12, 4, 52, 1, 4, 0, 0, 5, 0, 2, 4, 6, 8, 0, 0, 61, 0, 18, 1, 0, 0, 0, 2, 23, 1, 0, 0, 0, 4, 38, 1, 0, 0, 0, 6, 40, 1, 0, 0, 0, 8, 42, 1, 0, 0, 0, 10, 19, 3, 2, 1, 0, 11, 13, 5, 3, 0, 0, 12, 11, 1, 0, 0, 0, 12, 13, 1, 0, 0, 0, 13, 14, 1, 0, 0, 0, 14, 19, 3, 8, 4, 0, 15, 17, 5, 3, 0, 0, 16, 15, 1, 0, 0, 0, 16, 17, 1, 0, 0, 0, 17, 19, 1, 0, 0, 0, 18, 10, 1, 0, 0, 0, 18, 12, 1, 0, 0, 0, 18, 16, 1, 0, 0, 0, 19, 20, 1, 0, 0, 0, 20, 21, 5, 0, 0, 1, 21, 1, 1, 0, 0, 0, 22, 24, 5, 3, 0, 0, 23, 22, 1, 0, 0, 0, 23, 24, 1, 0, 0, 0, 24, 26, 1, 0, 0, 0, 25, 27, 3, 4, 2, 0, 26, 25, 1, 0, 0, 0, 26, 27, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 30, 5, 3, 0, 0, 29, 31, 3, 6, 3, 0, 30, 29, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 33, 1, 0, 0, 0, 32, 34, 5, 3, 0, 0, 33, 32, 1, 0, 0, 0, 33, 34, 1, 0, 0, 0, 34, 36, 1, 0, 0, 0, 35, 37, 3, 8, 4, 0, 36, 35, 1, 0, 0, 0, 36, 37, 1, 0, 0, 0, 37, 3, 1, 0, 0, 0, 38, 39, 5, 4, 0, 0, 39, 5, 1, 0, 0, 0, 40, 41, 5, 4, 0, 0, 41, 7, 1, 0, 0, 0, 42, 44, 5, 1, 0, 0, 43, 45, 5, 3, 0, 0, 44, 43, 1, 0, 0, 0, 44, 45, 1, 0, 0, 0, 45, 50, 1, 0, 0, 0, 46, 48, 5, 4, 0, 0, 47, 49, 5, 3, 0, 0, 48, 47, 1, 0, 0, 0, 48, 49, 1, 0, 0, 0, 49, 51, 1, 0, 0, 0, 50, 46, 1, 0, 0, 0, 51, 52, 1, 0, 0, 0, 52, 50, 1, 0, 0, 0, 52, 53, 1, 0, 0, 0, 53, 9, 1, 0, 0, 0, 11, 12, 16, 18, 23, 26, 30, 33, 36, 44, 48, 52] \ No newline at end of file diff --git a/handlers/sshd_config/ast/parser/Config.tokens b/handlers/sshd_config/ast/parser/Config.tokens new file mode 100644 index 0000000..30ff5e0 --- /dev/null +++ b/handlers/sshd_config/ast/parser/Config.tokens @@ -0,0 +1,6 @@ +HASH=1 +MATCH=2 +WHITESPACE=3 +STRING=4 +NEWLINE=5 +'#'=1 diff --git a/handlers/sshd_config/ast/parser/ConfigLexer.interp b/handlers/sshd_config/ast/parser/ConfigLexer.interp new file mode 100644 index 0000000..4ac663c --- /dev/null +++ b/handlers/sshd_config/ast/parser/ConfigLexer.interp @@ -0,0 +1,32 @@ +token literal names: +null +'#' +null +null +null +null + +token symbolic names: +null +HASH +MATCH +WHITESPACE +STRING +NEWLINE + +rule names: +HASH +MATCH +WHITESPACE +STRING +NEWLINE + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[4, 0, 5, 34, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 2, 21, 8, 2, 11, 2, 12, 2, 22, 1, 3, 4, 3, 26, 8, 3, 11, 3, 12, 3, 27, 1, 4, 3, 4, 31, 8, 4, 1, 4, 1, 4, 0, 0, 5, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 1, 0, 7, 2, 0, 77, 77, 109, 109, 2, 0, 65, 65, 97, 97, 2, 0, 84, 84, 116, 116, 2, 0, 67, 67, 99, 99, 2, 0, 72, 72, 104, 104, 2, 0, 9, 9, 32, 32, 4, 0, 9, 10, 13, 13, 32, 32, 35, 35, 36, 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, 1, 11, 1, 0, 0, 0, 3, 13, 1, 0, 0, 0, 5, 20, 1, 0, 0, 0, 7, 25, 1, 0, 0, 0, 9, 30, 1, 0, 0, 0, 11, 12, 5, 35, 0, 0, 12, 2, 1, 0, 0, 0, 13, 14, 7, 0, 0, 0, 14, 15, 7, 1, 0, 0, 15, 16, 7, 2, 0, 0, 16, 17, 7, 3, 0, 0, 17, 18, 7, 4, 0, 0, 18, 4, 1, 0, 0, 0, 19, 21, 7, 5, 0, 0, 20, 19, 1, 0, 0, 0, 21, 22, 1, 0, 0, 0, 22, 20, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 6, 1, 0, 0, 0, 24, 26, 8, 6, 0, 0, 25, 24, 1, 0, 0, 0, 26, 27, 1, 0, 0, 0, 27, 25, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 8, 1, 0, 0, 0, 29, 31, 5, 13, 0, 0, 30, 29, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 32, 1, 0, 0, 0, 32, 33, 5, 10, 0, 0, 33, 10, 1, 0, 0, 0, 4, 0, 22, 27, 30, 0] \ No newline at end of file diff --git a/handlers/sshd_config/ast/parser/ConfigLexer.tokens b/handlers/sshd_config/ast/parser/ConfigLexer.tokens new file mode 100644 index 0000000..30ff5e0 --- /dev/null +++ b/handlers/sshd_config/ast/parser/ConfigLexer.tokens @@ -0,0 +1,6 @@ +HASH=1 +MATCH=2 +WHITESPACE=3 +STRING=4 +NEWLINE=5 +'#'=1 diff --git a/handlers/sshd_config/ast/parser/config_base_listener.go b/handlers/sshd_config/ast/parser/config_base_listener.go new file mode 100644 index 0000000..a161a6a --- /dev/null +++ b/handlers/sshd_config/ast/parser/config_base_listener.go @@ -0,0 +1,52 @@ +// Code generated from Config.g4 by ANTLR 4.13.0. DO NOT EDIT. + +package parser // Config + +import "github.com/antlr4-go/antlr/v4" + +// BaseConfigListener is a complete listener for a parse tree produced by ConfigParser. +type BaseConfigListener struct{} + +var _ ConfigListener = &BaseConfigListener{} + +// VisitTerminal is called when a terminal node is visited. +func (s *BaseConfigListener) VisitTerminal(node antlr.TerminalNode) {} + +// VisitErrorNode is called when an error node is visited. +func (s *BaseConfigListener) VisitErrorNode(node antlr.ErrorNode) {} + +// EnterEveryRule is called when any rule is entered. +func (s *BaseConfigListener) EnterEveryRule(ctx antlr.ParserRuleContext) {} + +// ExitEveryRule is called when any rule is exited. +func (s *BaseConfigListener) ExitEveryRule(ctx antlr.ParserRuleContext) {} + +// EnterLineStatement is called when production lineStatement is entered. +func (s *BaseConfigListener) EnterLineStatement(ctx *LineStatementContext) {} + +// ExitLineStatement is called when production lineStatement is exited. +func (s *BaseConfigListener) ExitLineStatement(ctx *LineStatementContext) {} + +// EnterEntry is called when production entry is entered. +func (s *BaseConfigListener) EnterEntry(ctx *EntryContext) {} + +// ExitEntry is called when production entry is exited. +func (s *BaseConfigListener) ExitEntry(ctx *EntryContext) {} + +// EnterKey is called when production key is entered. +func (s *BaseConfigListener) EnterKey(ctx *KeyContext) {} + +// ExitKey is called when production key is exited. +func (s *BaseConfigListener) ExitKey(ctx *KeyContext) {} + +// EnterValue is called when production value is entered. +func (s *BaseConfigListener) EnterValue(ctx *ValueContext) {} + +// ExitValue is called when production value is exited. +func (s *BaseConfigListener) ExitValue(ctx *ValueContext) {} + +// EnterLeadingComment is called when production leadingComment is entered. +func (s *BaseConfigListener) EnterLeadingComment(ctx *LeadingCommentContext) {} + +// ExitLeadingComment is called when production leadingComment is exited. +func (s *BaseConfigListener) ExitLeadingComment(ctx *LeadingCommentContext) {} diff --git a/handlers/sshd_config/ast/parser/config_lexer.go b/handlers/sshd_config/ast/parser/config_lexer.go new file mode 100644 index 0000000..cf0a348 --- /dev/null +++ b/handlers/sshd_config/ast/parser/config_lexer.go @@ -0,0 +1,118 @@ +// Code generated from Config.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 ConfigLexer struct { + *antlr.BaseLexer + channelNames []string + modeNames []string + // TODO: EOF string +} + +var ConfigLexerLexerStaticData 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 configlexerLexerInit() { + staticData := &ConfigLexerLexerStaticData + staticData.ChannelNames = []string{ + "DEFAULT_TOKEN_CHANNEL", "HIDDEN", + } + staticData.ModeNames = []string{ + "DEFAULT_MODE", + } + staticData.LiteralNames = []string{ + "", "'#'", + } + staticData.SymbolicNames = []string{ + "", "HASH", "MATCH", "WHITESPACE", "STRING", "NEWLINE", + } + staticData.RuleNames = []string{ + "HASH", "MATCH", "WHITESPACE", "STRING", "NEWLINE", + } + staticData.PredictionContextCache = antlr.NewPredictionContextCache() + staticData.serializedATN = []int32{ + 4, 0, 5, 34, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, + 4, 7, 4, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 2, 21, + 8, 2, 11, 2, 12, 2, 22, 1, 3, 4, 3, 26, 8, 3, 11, 3, 12, 3, 27, 1, 4, 3, + 4, 31, 8, 4, 1, 4, 1, 4, 0, 0, 5, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 1, 0, 7, + 2, 0, 77, 77, 109, 109, 2, 0, 65, 65, 97, 97, 2, 0, 84, 84, 116, 116, 2, + 0, 67, 67, 99, 99, 2, 0, 72, 72, 104, 104, 2, 0, 9, 9, 32, 32, 4, 0, 9, + 10, 13, 13, 32, 32, 35, 35, 36, 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, 1, 11, 1, 0, 0, 0, 3, + 13, 1, 0, 0, 0, 5, 20, 1, 0, 0, 0, 7, 25, 1, 0, 0, 0, 9, 30, 1, 0, 0, 0, + 11, 12, 5, 35, 0, 0, 12, 2, 1, 0, 0, 0, 13, 14, 7, 0, 0, 0, 14, 15, 7, + 1, 0, 0, 15, 16, 7, 2, 0, 0, 16, 17, 7, 3, 0, 0, 17, 18, 7, 4, 0, 0, 18, + 4, 1, 0, 0, 0, 19, 21, 7, 5, 0, 0, 20, 19, 1, 0, 0, 0, 21, 22, 1, 0, 0, + 0, 22, 20, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 6, 1, 0, 0, 0, 24, 26, 8, + 6, 0, 0, 25, 24, 1, 0, 0, 0, 26, 27, 1, 0, 0, 0, 27, 25, 1, 0, 0, 0, 27, + 28, 1, 0, 0, 0, 28, 8, 1, 0, 0, 0, 29, 31, 5, 13, 0, 0, 30, 29, 1, 0, 0, + 0, 30, 31, 1, 0, 0, 0, 31, 32, 1, 0, 0, 0, 32, 33, 5, 10, 0, 0, 33, 10, + 1, 0, 0, 0, 4, 0, 22, 27, 30, 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) + } +} + +// ConfigLexerInit initializes any static state used to implement ConfigLexer. By default the +// static state used to implement the lexer is lazily initialized during the first call to +// NewConfigLexer(). You can call this function if you wish to initialize the static state ahead +// of time. +func ConfigLexerInit() { + staticData := &ConfigLexerLexerStaticData + staticData.once.Do(configlexerLexerInit) +} + +// NewConfigLexer produces a new lexer instance for the optional input antlr.CharStream. +func NewConfigLexer(input antlr.CharStream) *ConfigLexer { + ConfigLexerInit() + l := new(ConfigLexer) + l.BaseLexer = antlr.NewBaseLexer(input) + staticData := &ConfigLexerLexerStaticData + 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 = "Config.g4" + // TODO: l.EOF = antlr.TokenEOF + + return l +} + +// ConfigLexer tokens. +const ( + ConfigLexerHASH = 1 + ConfigLexerMATCH = 2 + ConfigLexerWHITESPACE = 3 + ConfigLexerSTRING = 4 + ConfigLexerNEWLINE = 5 +) diff --git a/handlers/sshd_config/ast/parser/config_listener.go b/handlers/sshd_config/ast/parser/config_listener.go new file mode 100644 index 0000000..00c5695 --- /dev/null +++ b/handlers/sshd_config/ast/parser/config_listener.go @@ -0,0 +1,40 @@ +// Code generated from Config.g4 by ANTLR 4.13.0. DO NOT EDIT. + +package parser // Config + +import "github.com/antlr4-go/antlr/v4" + +// ConfigListener is a complete listener for a parse tree produced by ConfigParser. +type ConfigListener 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) + + // EnterKey is called when entering the key production. + EnterKey(c *KeyContext) + + // EnterValue is called when entering the value production. + EnterValue(c *ValueContext) + + // 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) + + // ExitKey is called when exiting the key production. + ExitKey(c *KeyContext) + + // ExitValue is called when exiting the value production. + ExitValue(c *ValueContext) + + // ExitLeadingComment is called when exiting the leadingComment production. + ExitLeadingComment(c *LeadingCommentContext) +} diff --git a/handlers/sshd_config/ast/parser/config_parser.go b/handlers/sshd_config/ast/parser/config_parser.go new file mode 100644 index 0000000..a52548d --- /dev/null +++ b/handlers/sshd_config/ast/parser/config_parser.go @@ -0,0 +1,919 @@ +// Code generated from Config.g4 by ANTLR 4.13.0. DO NOT EDIT. + +package parser // Config + +import ( + "fmt" + "strconv" + "sync" + + "github.com/antlr4-go/antlr/v4" +) + +// Suppress unused import errors +var _ = fmt.Printf +var _ = strconv.Itoa +var _ = sync.Once{} + +type ConfigParser struct { + *antlr.BaseParser +} + +var ConfigParserStaticData struct { + once sync.Once + serializedATN []int32 + LiteralNames []string + SymbolicNames []string + RuleNames []string + PredictionContextCache *antlr.PredictionContextCache + atn *antlr.ATN + decisionToDFA []*antlr.DFA +} + +func configParserInit() { + staticData := &ConfigParserStaticData + staticData.LiteralNames = []string{ + "", "'#'", + } + staticData.SymbolicNames = []string{ + "", "HASH", "MATCH", "WHITESPACE", "STRING", "NEWLINE", + } + staticData.RuleNames = []string{ + "lineStatement", "entry", "key", "value", "leadingComment", + } + staticData.PredictionContextCache = antlr.NewPredictionContextCache() + staticData.serializedATN = []int32{ + 4, 1, 5, 55, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, + 1, 0, 1, 0, 3, 0, 13, 8, 0, 1, 0, 1, 0, 3, 0, 17, 8, 0, 3, 0, 19, 8, 0, + 1, 0, 1, 0, 1, 1, 3, 1, 24, 8, 1, 1, 1, 3, 1, 27, 8, 1, 1, 1, 1, 1, 3, + 1, 31, 8, 1, 1, 1, 3, 1, 34, 8, 1, 1, 1, 3, 1, 37, 8, 1, 1, 2, 1, 2, 1, + 3, 1, 3, 1, 4, 1, 4, 3, 4, 45, 8, 4, 1, 4, 1, 4, 3, 4, 49, 8, 4, 4, 4, + 51, 8, 4, 11, 4, 12, 4, 52, 1, 4, 0, 0, 5, 0, 2, 4, 6, 8, 0, 0, 61, 0, + 18, 1, 0, 0, 0, 2, 23, 1, 0, 0, 0, 4, 38, 1, 0, 0, 0, 6, 40, 1, 0, 0, 0, + 8, 42, 1, 0, 0, 0, 10, 19, 3, 2, 1, 0, 11, 13, 5, 3, 0, 0, 12, 11, 1, 0, + 0, 0, 12, 13, 1, 0, 0, 0, 13, 14, 1, 0, 0, 0, 14, 19, 3, 8, 4, 0, 15, 17, + 5, 3, 0, 0, 16, 15, 1, 0, 0, 0, 16, 17, 1, 0, 0, 0, 17, 19, 1, 0, 0, 0, + 18, 10, 1, 0, 0, 0, 18, 12, 1, 0, 0, 0, 18, 16, 1, 0, 0, 0, 19, 20, 1, + 0, 0, 0, 20, 21, 5, 0, 0, 1, 21, 1, 1, 0, 0, 0, 22, 24, 5, 3, 0, 0, 23, + 22, 1, 0, 0, 0, 23, 24, 1, 0, 0, 0, 24, 26, 1, 0, 0, 0, 25, 27, 3, 4, 2, + 0, 26, 25, 1, 0, 0, 0, 26, 27, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 30, + 5, 3, 0, 0, 29, 31, 3, 6, 3, 0, 30, 29, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, + 31, 33, 1, 0, 0, 0, 32, 34, 5, 3, 0, 0, 33, 32, 1, 0, 0, 0, 33, 34, 1, + 0, 0, 0, 34, 36, 1, 0, 0, 0, 35, 37, 3, 8, 4, 0, 36, 35, 1, 0, 0, 0, 36, + 37, 1, 0, 0, 0, 37, 3, 1, 0, 0, 0, 38, 39, 5, 4, 0, 0, 39, 5, 1, 0, 0, + 0, 40, 41, 5, 4, 0, 0, 41, 7, 1, 0, 0, 0, 42, 44, 5, 1, 0, 0, 43, 45, 5, + 3, 0, 0, 44, 43, 1, 0, 0, 0, 44, 45, 1, 0, 0, 0, 45, 50, 1, 0, 0, 0, 46, + 48, 5, 4, 0, 0, 47, 49, 5, 3, 0, 0, 48, 47, 1, 0, 0, 0, 48, 49, 1, 0, 0, + 0, 49, 51, 1, 0, 0, 0, 50, 46, 1, 0, 0, 0, 51, 52, 1, 0, 0, 0, 52, 50, + 1, 0, 0, 0, 52, 53, 1, 0, 0, 0, 53, 9, 1, 0, 0, 0, 11, 12, 16, 18, 23, + 26, 30, 33, 36, 44, 48, 52, + } + 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) + } +} + +// ConfigParserInit initializes any static state used to implement ConfigParser. By default the +// static state used to implement the parser is lazily initialized during the first call to +// NewConfigParser(). You can call this function if you wish to initialize the static state ahead +// of time. +func ConfigParserInit() { + staticData := &ConfigParserStaticData + staticData.once.Do(configParserInit) +} + +// NewConfigParser produces a new parser instance for the optional input antlr.TokenStream. +func NewConfigParser(input antlr.TokenStream) *ConfigParser { + ConfigParserInit() + this := new(ConfigParser) + this.BaseParser = antlr.NewBaseParser(input) + staticData := &ConfigParserStaticData + this.Interpreter = antlr.NewParserATNSimulator(this, staticData.atn, staticData.decisionToDFA, staticData.PredictionContextCache) + this.RuleNames = staticData.RuleNames + this.LiteralNames = staticData.LiteralNames + this.SymbolicNames = staticData.SymbolicNames + this.GrammarFileName = "Config.g4" + + return this +} + +// ConfigParser tokens. +const ( + ConfigParserEOF = antlr.TokenEOF + ConfigParserHASH = 1 + ConfigParserMATCH = 2 + ConfigParserWHITESPACE = 3 + ConfigParserSTRING = 4 + ConfigParserNEWLINE = 5 +) + +// ConfigParser rules. +const ( + ConfigParserRULE_lineStatement = 0 + ConfigParserRULE_entry = 1 + ConfigParserRULE_key = 2 + ConfigParserRULE_value = 3 + ConfigParserRULE_leadingComment = 4 +) + +// ILineStatementContext is an interface to support dynamic dispatch. +type ILineStatementContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + EOF() antlr.TerminalNode + Entry() IEntryContext + LeadingComment() ILeadingCommentContext + WHITESPACE() antlr.TerminalNode + + // IsLineStatementContext differentiates from other interfaces. + IsLineStatementContext() +} + +type LineStatementContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyLineStatementContext() *LineStatementContext { + var p = new(LineStatementContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ConfigParserRULE_lineStatement + return p +} + +func InitEmptyLineStatementContext(p *LineStatementContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ConfigParserRULE_lineStatement +} + +func (*LineStatementContext) IsLineStatementContext() {} + +func NewLineStatementContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *LineStatementContext { + var p = new(LineStatementContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ConfigParserRULE_lineStatement + + return p +} + +func (s *LineStatementContext) GetParser() antlr.Parser { return s.parser } + +func (s *LineStatementContext) EOF() antlr.TerminalNode { + return s.GetToken(ConfigParserEOF, 0) +} + +func (s *LineStatementContext) Entry() IEntryContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEntryContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IEntryContext) +} + +func (s *LineStatementContext) LeadingComment() ILeadingCommentContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ILeadingCommentContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ILeadingCommentContext) +} + +func (s *LineStatementContext) WHITESPACE() antlr.TerminalNode { + return s.GetToken(ConfigParserWHITESPACE, 0) +} + +func (s *LineStatementContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *LineStatementContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *LineStatementContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ConfigListener); ok { + listenerT.EnterLineStatement(s) + } +} + +func (s *LineStatementContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ConfigListener); ok { + listenerT.ExitLineStatement(s) + } +} + +func (p *ConfigParser) LineStatement() (localctx ILineStatementContext) { + localctx = NewLineStatementContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 0, ConfigParserRULE_lineStatement) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(18) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 2, p.GetParserRuleContext()) { + case 1: + { + p.SetState(10) + p.Entry() + } + + case 2: + p.SetState(12) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ConfigParserWHITESPACE { + { + p.SetState(11) + p.Match(ConfigParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + { + p.SetState(14) + p.LeadingComment() + } + + case 3: + p.SetState(16) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ConfigParserWHITESPACE { + { + p.SetState(15) + p.Match(ConfigParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + { + p.SetState(20) + p.Match(ConfigParserEOF) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IEntryContext is an interface to support dynamic dispatch. +type IEntryContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllWHITESPACE() []antlr.TerminalNode + WHITESPACE(i int) antlr.TerminalNode + Key() IKeyContext + Value() IValueContext + LeadingComment() ILeadingCommentContext + + // IsEntryContext differentiates from other interfaces. + IsEntryContext() +} + +type EntryContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyEntryContext() *EntryContext { + var p = new(EntryContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ConfigParserRULE_entry + return p +} + +func InitEmptyEntryContext(p *EntryContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ConfigParserRULE_entry +} + +func (*EntryContext) IsEntryContext() {} + +func NewEntryContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *EntryContext { + var p = new(EntryContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ConfigParserRULE_entry + + return p +} + +func (s *EntryContext) GetParser() antlr.Parser { return s.parser } + +func (s *EntryContext) AllWHITESPACE() []antlr.TerminalNode { + return s.GetTokens(ConfigParserWHITESPACE) +} + +func (s *EntryContext) WHITESPACE(i int) antlr.TerminalNode { + return s.GetToken(ConfigParserWHITESPACE, i) +} + +func (s *EntryContext) Key() IKeyContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IKeyContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IKeyContext) +} + +func (s *EntryContext) Value() IValueContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IValueContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IValueContext) +} + +func (s *EntryContext) LeadingComment() ILeadingCommentContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ILeadingCommentContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ILeadingCommentContext) +} + +func (s *EntryContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *EntryContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *EntryContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ConfigListener); ok { + listenerT.EnterEntry(s) + } +} + +func (s *EntryContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ConfigListener); ok { + listenerT.ExitEntry(s) + } +} + +func (p *ConfigParser) Entry() (localctx IEntryContext) { + localctx = NewEntryContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 2, ConfigParserRULE_entry) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(23) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 3, p.GetParserRuleContext()) == 1 { + { + p.SetState(22) + p.Match(ConfigParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(26) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ConfigParserSTRING { + { + p.SetState(25) + p.Key() + } + + } + { + p.SetState(28) + p.Match(ConfigParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(30) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ConfigParserSTRING { + { + p.SetState(29) + p.Value() + } + + } + p.SetState(33) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ConfigParserWHITESPACE { + { + p.SetState(32) + p.Match(ConfigParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + p.SetState(36) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ConfigParserHASH { + { + p.SetState(35) + p.LeadingComment() + } + + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IKeyContext is an interface to support dynamic dispatch. +type IKeyContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + STRING() antlr.TerminalNode + + // IsKeyContext differentiates from other interfaces. + IsKeyContext() +} + +type KeyContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyKeyContext() *KeyContext { + var p = new(KeyContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ConfigParserRULE_key + return p +} + +func InitEmptyKeyContext(p *KeyContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ConfigParserRULE_key +} + +func (*KeyContext) IsKeyContext() {} + +func NewKeyContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *KeyContext { + var p = new(KeyContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ConfigParserRULE_key + + return p +} + +func (s *KeyContext) GetParser() antlr.Parser { return s.parser } + +func (s *KeyContext) STRING() antlr.TerminalNode { + return s.GetToken(ConfigParserSTRING, 0) +} + +func (s *KeyContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *KeyContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *KeyContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ConfigListener); ok { + listenerT.EnterKey(s) + } +} + +func (s *KeyContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ConfigListener); ok { + listenerT.ExitKey(s) + } +} + +func (p *ConfigParser) Key() (localctx IKeyContext) { + localctx = NewKeyContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 4, ConfigParserRULE_key) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(38) + p.Match(ConfigParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IValueContext is an interface to support dynamic dispatch. +type IValueContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + STRING() antlr.TerminalNode + + // IsValueContext differentiates from other interfaces. + IsValueContext() +} + +type ValueContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyValueContext() *ValueContext { + var p = new(ValueContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ConfigParserRULE_value + return p +} + +func InitEmptyValueContext(p *ValueContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ConfigParserRULE_value +} + +func (*ValueContext) IsValueContext() {} + +func NewValueContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ValueContext { + var p = new(ValueContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ConfigParserRULE_value + + return p +} + +func (s *ValueContext) GetParser() antlr.Parser { return s.parser } + +func (s *ValueContext) STRING() antlr.TerminalNode { + return s.GetToken(ConfigParserSTRING, 0) +} + +func (s *ValueContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ValueContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ValueContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ConfigListener); ok { + listenerT.EnterValue(s) + } +} + +func (s *ValueContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ConfigListener); ok { + listenerT.ExitValue(s) + } +} + +func (p *ConfigParser) Value() (localctx IValueContext) { + localctx = NewValueContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 6, ConfigParserRULE_value) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(40) + p.Match(ConfigParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// ILeadingCommentContext is an interface to support dynamic dispatch. +type ILeadingCommentContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + HASH() antlr.TerminalNode + AllWHITESPACE() []antlr.TerminalNode + WHITESPACE(i int) antlr.TerminalNode + AllSTRING() []antlr.TerminalNode + STRING(i int) antlr.TerminalNode + + // IsLeadingCommentContext differentiates from other interfaces. + IsLeadingCommentContext() +} + +type LeadingCommentContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyLeadingCommentContext() *LeadingCommentContext { + var p = new(LeadingCommentContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ConfigParserRULE_leadingComment + return p +} + +func InitEmptyLeadingCommentContext(p *LeadingCommentContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ConfigParserRULE_leadingComment +} + +func (*LeadingCommentContext) IsLeadingCommentContext() {} + +func NewLeadingCommentContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *LeadingCommentContext { + var p = new(LeadingCommentContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ConfigParserRULE_leadingComment + + return p +} + +func (s *LeadingCommentContext) GetParser() antlr.Parser { return s.parser } + +func (s *LeadingCommentContext) HASH() antlr.TerminalNode { + return s.GetToken(ConfigParserHASH, 0) +} + +func (s *LeadingCommentContext) AllWHITESPACE() []antlr.TerminalNode { + return s.GetTokens(ConfigParserWHITESPACE) +} + +func (s *LeadingCommentContext) WHITESPACE(i int) antlr.TerminalNode { + return s.GetToken(ConfigParserWHITESPACE, i) +} + +func (s *LeadingCommentContext) AllSTRING() []antlr.TerminalNode { + return s.GetTokens(ConfigParserSTRING) +} + +func (s *LeadingCommentContext) STRING(i int) antlr.TerminalNode { + return s.GetToken(ConfigParserSTRING, i) +} + +func (s *LeadingCommentContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *LeadingCommentContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *LeadingCommentContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ConfigListener); ok { + listenerT.EnterLeadingComment(s) + } +} + +func (s *LeadingCommentContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ConfigListener); ok { + listenerT.ExitLeadingComment(s) + } +} + +func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { + localctx = NewLeadingCommentContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 8, ConfigParserRULE_leadingComment) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(42) + p.Match(ConfigParserHASH) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(44) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ConfigParserWHITESPACE { + { + p.SetState(43) + p.Match(ConfigParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + p.SetState(50) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for ok := true; ok; ok = _la == ConfigParserSTRING { + { + p.SetState(46) + p.Match(ConfigParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(48) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ConfigParserWHITESPACE { + { + p.SetState(47) + p.Match(ConfigParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + + p.SetState(52) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} diff --git a/handlers/sshd_config/ast/sshd_config.go b/handlers/sshd_config/ast/sshd_config.go new file mode 100644 index 0000000..6364bb9 --- /dev/null +++ b/handlers/sshd_config/ast/sshd_config.go @@ -0,0 +1,59 @@ +package ast + +import ( + "config-lsp/common" + + "github.com/emirpasic/gods/maps/treemap" +) + +type SSHKey struct { + common.LocationRange + Value string +} + +type SSHValue struct { + common.LocationRange + Value string +} + +type SSHEntryType uint + +const ( + SSHEntryTypeOption SSHEntryType = iota + SSHEntryTypeMatchBlock +) + +type SSHEntry interface { + GetType() SSHEntryType +} + +type SSHOption struct { + common.LocationRange + Value string + + Key *SSHKey + OptionValue *SSHValue +} + +func (o SSHOption) GetType() SSHEntryType { + return SSHEntryTypeOption +} + +type SSHMatchBlock struct { + common.LocationRange + MatchEntry *SSHOption + + // [uint32]*SSHOption -> line number -> *SSHOption + Options *treemap.Map +} + +func (m SSHMatchBlock) GetType() SSHEntryType { + return SSHEntryTypeMatchBlock +} + +type SSHConfig struct { + // [uint32]SSHOption -> line number -> *SSHEntry + Options *treemap.Map + // [uint32]{} -> line number -> {} + CommentLines map[uint32]struct{} +}