diff --git a/handlers/ssh_config/ast/error-listener.go b/handlers/ssh_config/ast/error-listener.go new file mode 100644 index 0000000..74d4387 --- /dev/null +++ b/handlers/ssh_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/ssh_config/ast/indexes.go b/handlers/ssh_config/ast/indexes.go new file mode 100644 index 0000000..109be56 --- /dev/null +++ b/handlers/ssh_config/ast/indexes.go @@ -0,0 +1,35 @@ +package ast + +import "config-lsp/common" + + +type ValidPath string + +func (v ValidPath) AsURI() string { + return "file://" + string(v) +} + +type SSHIndexIncludeValue struct { + common.LocationRange + Value string + + // Actual valid paths, these will be set by the analyzer + Paths []ValidPath +} + +type SSHIndexIncludeLine struct { + Values []*SSHIndexIncludeValue + Option *SSHOption + Block *SSHBlock +} + +type SSHIndexes struct { + Includes []*SSHIndexIncludeLine +} + +func NewSSHIndexes() *SSHIndexes { + return &SSHIndexes{ + Includes: make([]*SSHIndexIncludeLine, 0), + } +} + diff --git a/handlers/ssh_config/ast/listener.go b/handlers/ssh_config/ast/listener.go new file mode 100644 index 0000000..3adfe6e --- /dev/null +++ b/handlers/ssh_config/ast/listener.go @@ -0,0 +1,171 @@ +package ast + +import ( + "config-lsp/common" + commonparser "config-lsp/common/parser" + "config-lsp/handlers/ssh_config/ast/parser" + "config-lsp/handlers/ssh_config/match-parser" + "strings" + + "github.com/emirpasic/gods/maps/treemap" + gods "github.com/emirpasic/gods/utils" +) + +type sshListenerContext struct { + line uint32 + currentOption *SSHOption + currentBlock SSHBlock + currentKeyIsBlockOf *SSHBlockType + currentIndexes *SSHIndexes +} + +func createListenerContext() *sshListenerContext { + context := new(sshListenerContext) + context.currentIndexes = NewSSHIndexes() + + return context +} + +func createListener( + config *SSHConfig, + context *sshListenerContext, +) sshParserListener { + return sshParserListener{ + Config: config, + Errors: make([]common.LSPError, 0), + sshContext: 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) + + value := commonparser.ParseRawString(ctx.GetText(), commonparser.FullFeatures) + option := &SSHOption{ + LocationRange: location, + Value: value, + } + + s.sshContext.currentOption = option +} + +func (s *sshParserListener) EnterKey(ctx *parser.KeyContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location.ChangeBothLines(s.sshContext.line) + + text := ctx.GetText() + value := commonparser.ParseRawString(text, commonparser.FullFeatures) + key := strings.Trim( + value.Value, + " ", + ) + + switch strings.ToLower(text) { + case "match": + value := SSHBlockTypeMatch + s.sshContext.currentKeyIsBlockOf = &value + case "host": + value := SSHBlockTypeHost + s.sshContext.currentKeyIsBlockOf = &value + } + + s.sshContext.currentOption.Key = &SSHKey{ + LocationRange: location, + Value: value, + Key: key, + } +} + +func (s *sshParserListener) EnterSeparator(ctx *parser.SeparatorContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location.ChangeBothLines(s.sshContext.line) + + text := ctx.GetText() + value := commonparser.ParseRawString(text, commonparser.FullFeatures) + + s.sshContext.currentOption.Separator = &SSHSeparator{ + LocationRange: location, + Value: value, + } +} + +func (s *sshParserListener) EnterValue(ctx *parser.ValueContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location.ChangeBothLines(s.sshContext.line) + + value := commonparser.ParseRawString(ctx.GetText(), commonparser.FullFeatures) + s.sshContext.currentOption.OptionValue = &SSHValue{ + LocationRange: location, + Value: value, + } +} + +func (s *sshParserListener) ExitEntry(ctx *parser.EntryContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location.ChangeBothLines(s.sshContext.line) + + defer (func() { + s.sshContext.currentOption = nil + })() + + if s.sshContext.currentKeyIsBlockOf != nil { + switch *s.sshContext.currentKeyIsBlockOf { + case SSHBlockTypeMatch: + var match *matchparser.Match + + matchParser := matchparser.NewMatch() + errors := matchParser.Parse( + s.sshContext.currentOption.OptionValue.Value.Raw, + location.Start.Line, + s.sshContext.currentOption.OptionValue.Start.Character, + ) + + if len(errors) > 0 { + for _, err := range errors { + s.Errors = append(s.Errors, common.LSPError{ + Range: err.Range.ShiftHorizontal(s.sshContext.currentOption.Start.Character), + Err: err.Err, + }) + } + } else { + match = matchParser + } + + matchBlock := &SSHMatchBlock{ + LocationRange: location, + MatchOption: s.sshContext.currentOption, + MatchValue: match, + Options: treemap.NewWith(gods.UInt32Comparator), + } + + s.Config.Options.Put( + location.Start.Line, + matchBlock, + ) + + s.sshContext.currentKeyIsBlockOf = nil + s.sshContext.currentBlock = matchBlock + } + + return + } + + if s.sshContext.currentBlock == nil { + s.Config.Options.Put( + location.Start.Line, + s.sshContext.currentOption, + ) + } else { + block := s.sshContext.currentBlock + block.AddOption(s.sshContext.currentOption) + block.SetEnd(location.End) + } +} diff --git a/handlers/ssh_config/ast/parser.go b/handlers/ssh_config/ast/parser.go new file mode 100644 index 0000000..c6298d7 --- /dev/null +++ b/handlers/ssh_config/ast/parser.go @@ -0,0 +1,85 @@ +package ast + +import ( + "config-lsp/common" + "config-lsp/handlers/ssh_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 = 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 := createListenerContext() + + for rawLineNumber, line := range lines { + context.line = uint32(rawLineNumber) + + if emptyPattern.MatchString(line) { + continue + } + + if commentPattern.MatchString(line) { + c.CommentLines[context.line] = struct{}{} + 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/ssh_config/ast/parser/Config.interp b/handlers/ssh_config/ast/parser/Config.interp index 2832f42..e02ab96 100644 --- a/handlers/ssh_config/ast/parser/Config.interp +++ b/handlers/ssh_config/ast/parser/Config.interp @@ -4,6 +4,7 @@ null null null null +null token symbolic names: null @@ -11,6 +12,7 @@ HASH WHITESPACE STRING NEWLINE +QUOTED_STRING rule names: lineStatement @@ -19,7 +21,8 @@ separator key value leadingComment +string atn: -[4, 1, 4, 66, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 1, 0, 1, 0, 1, 0, 3, 0, 16, 8, 0, 3, 0, 18, 8, 0, 1, 0, 1, 0, 1, 1, 3, 1, 23, 8, 1, 1, 1, 3, 1, 26, 8, 1, 1, 1, 3, 1, 29, 8, 1, 1, 1, 3, 1, 32, 8, 1, 1, 1, 3, 1, 35, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 5, 4, 43, 8, 4, 10, 4, 12, 4, 46, 9, 4, 1, 4, 3, 4, 49, 8, 4, 1, 4, 3, 4, 52, 8, 4, 1, 5, 1, 5, 3, 5, 56, 8, 5, 1, 5, 1, 5, 3, 5, 60, 8, 5, 4, 5, 62, 8, 5, 11, 5, 12, 5, 63, 1, 5, 0, 0, 6, 0, 2, 4, 6, 8, 10, 0, 0, 73, 0, 17, 1, 0, 0, 0, 2, 22, 1, 0, 0, 0, 4, 36, 1, 0, 0, 0, 6, 38, 1, 0, 0, 0, 8, 44, 1, 0, 0, 0, 10, 53, 1, 0, 0, 0, 12, 18, 3, 2, 1, 0, 13, 18, 3, 10, 5, 0, 14, 16, 5, 2, 0, 0, 15, 14, 1, 0, 0, 0, 15, 16, 1, 0, 0, 0, 16, 18, 1, 0, 0, 0, 17, 12, 1, 0, 0, 0, 17, 13, 1, 0, 0, 0, 17, 15, 1, 0, 0, 0, 18, 19, 1, 0, 0, 0, 19, 20, 5, 0, 0, 1, 20, 1, 1, 0, 0, 0, 21, 23, 5, 2, 0, 0, 22, 21, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 25, 1, 0, 0, 0, 24, 26, 3, 6, 3, 0, 25, 24, 1, 0, 0, 0, 25, 26, 1, 0, 0, 0, 26, 28, 1, 0, 0, 0, 27, 29, 3, 4, 2, 0, 28, 27, 1, 0, 0, 0, 28, 29, 1, 0, 0, 0, 29, 31, 1, 0, 0, 0, 30, 32, 3, 8, 4, 0, 31, 30, 1, 0, 0, 0, 31, 32, 1, 0, 0, 0, 32, 34, 1, 0, 0, 0, 33, 35, 3, 10, 5, 0, 34, 33, 1, 0, 0, 0, 34, 35, 1, 0, 0, 0, 35, 3, 1, 0, 0, 0, 36, 37, 5, 2, 0, 0, 37, 5, 1, 0, 0, 0, 38, 39, 5, 3, 0, 0, 39, 7, 1, 0, 0, 0, 40, 41, 5, 3, 0, 0, 41, 43, 5, 2, 0, 0, 42, 40, 1, 0, 0, 0, 43, 46, 1, 0, 0, 0, 44, 42, 1, 0, 0, 0, 44, 45, 1, 0, 0, 0, 45, 48, 1, 0, 0, 0, 46, 44, 1, 0, 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, 52, 5, 2, 0, 0, 51, 50, 1, 0, 0, 0, 51, 52, 1, 0, 0, 0, 52, 9, 1, 0, 0, 0, 53, 55, 5, 1, 0, 0, 54, 56, 5, 2, 0, 0, 55, 54, 1, 0, 0, 0, 55, 56, 1, 0, 0, 0, 56, 61, 1, 0, 0, 0, 57, 59, 5, 3, 0, 0, 58, 60, 5, 2, 0, 0, 59, 58, 1, 0, 0, 0, 59, 60, 1, 0, 0, 0, 60, 62, 1, 0, 0, 0, 61, 57, 1, 0, 0, 0, 62, 63, 1, 0, 0, 0, 63, 61, 1, 0, 0, 0, 63, 64, 1, 0, 0, 0, 64, 11, 1, 0, 0, 0, 13, 15, 17, 22, 25, 28, 31, 34, 44, 48, 51, 55, 59, 63] \ No newline at end of file +[4, 1, 5, 71, 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, 1, 0, 1, 0, 1, 0, 3, 0, 18, 8, 0, 3, 0, 20, 8, 0, 1, 0, 1, 0, 1, 1, 3, 1, 25, 8, 1, 1, 1, 3, 1, 28, 8, 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, 1, 4, 5, 4, 46, 8, 4, 10, 4, 12, 4, 49, 9, 4, 1, 4, 3, 4, 52, 8, 4, 1, 4, 3, 4, 55, 8, 4, 1, 5, 1, 5, 3, 5, 59, 8, 5, 1, 5, 1, 5, 3, 5, 63, 8, 5, 4, 5, 65, 8, 5, 11, 5, 12, 5, 66, 1, 6, 1, 6, 1, 6, 0, 0, 7, 0, 2, 4, 6, 8, 10, 12, 0, 1, 2, 0, 3, 3, 5, 5, 77, 0, 19, 1, 0, 0, 0, 2, 24, 1, 0, 0, 0, 4, 38, 1, 0, 0, 0, 6, 40, 1, 0, 0, 0, 8, 47, 1, 0, 0, 0, 10, 56, 1, 0, 0, 0, 12, 68, 1, 0, 0, 0, 14, 20, 3, 2, 1, 0, 15, 20, 3, 10, 5, 0, 16, 18, 5, 2, 0, 0, 17, 16, 1, 0, 0, 0, 17, 18, 1, 0, 0, 0, 18, 20, 1, 0, 0, 0, 19, 14, 1, 0, 0, 0, 19, 15, 1, 0, 0, 0, 19, 17, 1, 0, 0, 0, 20, 21, 1, 0, 0, 0, 21, 22, 5, 0, 0, 1, 22, 1, 1, 0, 0, 0, 23, 25, 5, 2, 0, 0, 24, 23, 1, 0, 0, 0, 24, 25, 1, 0, 0, 0, 25, 27, 1, 0, 0, 0, 26, 28, 3, 6, 3, 0, 27, 26, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 30, 1, 0, 0, 0, 29, 31, 3, 4, 2, 0, 30, 29, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 33, 1, 0, 0, 0, 32, 34, 3, 8, 4, 0, 33, 32, 1, 0, 0, 0, 33, 34, 1, 0, 0, 0, 34, 36, 1, 0, 0, 0, 35, 37, 3, 10, 5, 0, 36, 35, 1, 0, 0, 0, 36, 37, 1, 0, 0, 0, 37, 3, 1, 0, 0, 0, 38, 39, 5, 2, 0, 0, 39, 5, 1, 0, 0, 0, 40, 41, 3, 12, 6, 0, 41, 7, 1, 0, 0, 0, 42, 43, 3, 12, 6, 0, 43, 44, 5, 2, 0, 0, 44, 46, 1, 0, 0, 0, 45, 42, 1, 0, 0, 0, 46, 49, 1, 0, 0, 0, 47, 45, 1, 0, 0, 0, 47, 48, 1, 0, 0, 0, 48, 51, 1, 0, 0, 0, 49, 47, 1, 0, 0, 0, 50, 52, 3, 12, 6, 0, 51, 50, 1, 0, 0, 0, 51, 52, 1, 0, 0, 0, 52, 54, 1, 0, 0, 0, 53, 55, 5, 2, 0, 0, 54, 53, 1, 0, 0, 0, 54, 55, 1, 0, 0, 0, 55, 9, 1, 0, 0, 0, 56, 58, 5, 1, 0, 0, 57, 59, 5, 2, 0, 0, 58, 57, 1, 0, 0, 0, 58, 59, 1, 0, 0, 0, 59, 64, 1, 0, 0, 0, 60, 62, 3, 12, 6, 0, 61, 63, 5, 2, 0, 0, 62, 61, 1, 0, 0, 0, 62, 63, 1, 0, 0, 0, 63, 65, 1, 0, 0, 0, 64, 60, 1, 0, 0, 0, 65, 66, 1, 0, 0, 0, 66, 64, 1, 0, 0, 0, 66, 67, 1, 0, 0, 0, 67, 11, 1, 0, 0, 0, 68, 69, 7, 0, 0, 0, 69, 13, 1, 0, 0, 0, 13, 17, 19, 24, 27, 30, 33, 36, 47, 51, 54, 58, 62, 66] \ No newline at end of file diff --git a/handlers/ssh_config/ast/parser/Config.tokens b/handlers/ssh_config/ast/parser/Config.tokens index aacc14c..fa8c415 100644 --- a/handlers/ssh_config/ast/parser/Config.tokens +++ b/handlers/ssh_config/ast/parser/Config.tokens @@ -2,4 +2,5 @@ HASH=1 WHITESPACE=2 STRING=3 NEWLINE=4 +QUOTED_STRING=5 '#'=1 diff --git a/handlers/ssh_config/ast/parser/ConfigLexer.interp b/handlers/ssh_config/ast/parser/ConfigLexer.interp index d61e14d..c093cfb 100644 --- a/handlers/ssh_config/ast/parser/ConfigLexer.interp +++ b/handlers/ssh_config/ast/parser/ConfigLexer.interp @@ -4,6 +4,7 @@ null null null null +null token symbolic names: null @@ -11,12 +12,14 @@ HASH WHITESPACE STRING NEWLINE +QUOTED_STRING rule names: HASH WHITESPACE STRING NEWLINE +QUOTED_STRING channel names: DEFAULT_TOKEN_CHANNEL @@ -26,4 +29,4 @@ mode names: DEFAULT_MODE atn: -[4, 0, 4, 26, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 1, 0, 1, 0, 1, 1, 4, 1, 13, 8, 1, 11, 1, 12, 1, 14, 1, 2, 4, 2, 18, 8, 2, 11, 2, 12, 2, 19, 1, 3, 3, 3, 23, 8, 3, 1, 3, 1, 3, 0, 0, 4, 1, 1, 3, 2, 5, 3, 7, 4, 1, 0, 2, 2, 0, 9, 9, 32, 32, 4, 0, 9, 10, 13, 13, 32, 32, 35, 35, 28, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 1, 9, 1, 0, 0, 0, 3, 12, 1, 0, 0, 0, 5, 17, 1, 0, 0, 0, 7, 22, 1, 0, 0, 0, 9, 10, 5, 35, 0, 0, 10, 2, 1, 0, 0, 0, 11, 13, 7, 0, 0, 0, 12, 11, 1, 0, 0, 0, 13, 14, 1, 0, 0, 0, 14, 12, 1, 0, 0, 0, 14, 15, 1, 0, 0, 0, 15, 4, 1, 0, 0, 0, 16, 18, 8, 1, 0, 0, 17, 16, 1, 0, 0, 0, 18, 19, 1, 0, 0, 0, 19, 17, 1, 0, 0, 0, 19, 20, 1, 0, 0, 0, 20, 6, 1, 0, 0, 0, 21, 23, 5, 13, 0, 0, 22, 21, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 24, 1, 0, 0, 0, 24, 25, 5, 10, 0, 0, 25, 8, 1, 0, 0, 0, 4, 0, 14, 19, 22, 0] \ No newline at end of file +[4, 0, 5, 46, 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, 4, 1, 15, 8, 1, 11, 1, 12, 1, 16, 1, 2, 4, 2, 20, 8, 2, 11, 2, 12, 2, 21, 1, 3, 3, 3, 25, 8, 3, 1, 3, 1, 3, 1, 4, 1, 4, 3, 4, 31, 8, 4, 1, 4, 1, 4, 1, 4, 5, 4, 36, 8, 4, 10, 4, 12, 4, 39, 9, 4, 1, 4, 3, 4, 42, 8, 4, 1, 4, 3, 4, 45, 8, 4, 0, 0, 5, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 1, 0, 2, 2, 0, 9, 9, 32, 32, 4, 0, 9, 10, 13, 13, 32, 32, 34, 35, 52, 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, 14, 1, 0, 0, 0, 5, 19, 1, 0, 0, 0, 7, 24, 1, 0, 0, 0, 9, 28, 1, 0, 0, 0, 11, 12, 5, 35, 0, 0, 12, 2, 1, 0, 0, 0, 13, 15, 7, 0, 0, 0, 14, 13, 1, 0, 0, 0, 15, 16, 1, 0, 0, 0, 16, 14, 1, 0, 0, 0, 16, 17, 1, 0, 0, 0, 17, 4, 1, 0, 0, 0, 18, 20, 8, 1, 0, 0, 19, 18, 1, 0, 0, 0, 20, 21, 1, 0, 0, 0, 21, 19, 1, 0, 0, 0, 21, 22, 1, 0, 0, 0, 22, 6, 1, 0, 0, 0, 23, 25, 5, 13, 0, 0, 24, 23, 1, 0, 0, 0, 24, 25, 1, 0, 0, 0, 25, 26, 1, 0, 0, 0, 26, 27, 5, 10, 0, 0, 27, 8, 1, 0, 0, 0, 28, 30, 5, 34, 0, 0, 29, 31, 3, 3, 1, 0, 30, 29, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 37, 1, 0, 0, 0, 32, 33, 3, 5, 2, 0, 33, 34, 3, 3, 1, 0, 34, 36, 1, 0, 0, 0, 35, 32, 1, 0, 0, 0, 36, 39, 1, 0, 0, 0, 37, 35, 1, 0, 0, 0, 37, 38, 1, 0, 0, 0, 38, 41, 1, 0, 0, 0, 39, 37, 1, 0, 0, 0, 40, 42, 3, 5, 2, 0, 41, 40, 1, 0, 0, 0, 41, 42, 1, 0, 0, 0, 42, 44, 1, 0, 0, 0, 43, 45, 5, 34, 0, 0, 44, 43, 1, 0, 0, 0, 44, 45, 1, 0, 0, 0, 45, 10, 1, 0, 0, 0, 8, 0, 16, 21, 24, 30, 37, 41, 44, 0] \ No newline at end of file diff --git a/handlers/ssh_config/ast/parser/ConfigLexer.tokens b/handlers/ssh_config/ast/parser/ConfigLexer.tokens index aacc14c..fa8c415 100644 --- a/handlers/ssh_config/ast/parser/ConfigLexer.tokens +++ b/handlers/ssh_config/ast/parser/ConfigLexer.tokens @@ -2,4 +2,5 @@ HASH=1 WHITESPACE=2 STRING=3 NEWLINE=4 +QUOTED_STRING=5 '#'=1 diff --git a/handlers/ssh_config/ast/parser/config_base_listener.go b/handlers/ssh_config/ast/parser/config_base_listener.go index ac8bdac..00e7840 100644 --- a/handlers/ssh_config/ast/parser/config_base_listener.go +++ b/handlers/ssh_config/ast/parser/config_base_listener.go @@ -56,3 +56,9 @@ func (s *BaseConfigListener) EnterLeadingComment(ctx *LeadingCommentContext) {} // ExitLeadingComment is called when production leadingComment is exited. func (s *BaseConfigListener) ExitLeadingComment(ctx *LeadingCommentContext) {} + +// EnterString is called when production string is entered. +func (s *BaseConfigListener) EnterString(ctx *StringContext) {} + +// ExitString is called when production string is exited. +func (s *BaseConfigListener) ExitString(ctx *StringContext) {} diff --git a/handlers/ssh_config/ast/parser/config_lexer.go b/handlers/ssh_config/ast/parser/config_lexer.go index ace491d..021f1fc 100644 --- a/handlers/ssh_config/ast/parser/config_lexer.go +++ b/handlers/ssh_config/ast/parser/config_lexer.go @@ -46,25 +46,34 @@ func configlexerLexerInit() { "", "'#'", } staticData.SymbolicNames = []string{ - "", "HASH", "WHITESPACE", "STRING", "NEWLINE", + "", "HASH", "WHITESPACE", "STRING", "NEWLINE", "QUOTED_STRING", } staticData.RuleNames = []string{ - "HASH", "WHITESPACE", "STRING", "NEWLINE", + "HASH", "WHITESPACE", "STRING", "NEWLINE", "QUOTED_STRING", } staticData.PredictionContextCache = antlr.NewPredictionContextCache() staticData.serializedATN = []int32{ - 4, 0, 4, 26, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 1, - 0, 1, 0, 1, 1, 4, 1, 13, 8, 1, 11, 1, 12, 1, 14, 1, 2, 4, 2, 18, 8, 2, - 11, 2, 12, 2, 19, 1, 3, 3, 3, 23, 8, 3, 1, 3, 1, 3, 0, 0, 4, 1, 1, 3, 2, - 5, 3, 7, 4, 1, 0, 2, 2, 0, 9, 9, 32, 32, 4, 0, 9, 10, 13, 13, 32, 32, 35, - 35, 28, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, - 0, 0, 0, 1, 9, 1, 0, 0, 0, 3, 12, 1, 0, 0, 0, 5, 17, 1, 0, 0, 0, 7, 22, - 1, 0, 0, 0, 9, 10, 5, 35, 0, 0, 10, 2, 1, 0, 0, 0, 11, 13, 7, 0, 0, 0, - 12, 11, 1, 0, 0, 0, 13, 14, 1, 0, 0, 0, 14, 12, 1, 0, 0, 0, 14, 15, 1, - 0, 0, 0, 15, 4, 1, 0, 0, 0, 16, 18, 8, 1, 0, 0, 17, 16, 1, 0, 0, 0, 18, - 19, 1, 0, 0, 0, 19, 17, 1, 0, 0, 0, 19, 20, 1, 0, 0, 0, 20, 6, 1, 0, 0, - 0, 21, 23, 5, 13, 0, 0, 22, 21, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 24, - 1, 0, 0, 0, 24, 25, 5, 10, 0, 0, 25, 8, 1, 0, 0, 0, 4, 0, 14, 19, 22, 0, + 4, 0, 5, 46, 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, 4, 1, 15, 8, 1, 11, 1, 12, 1, 16, 1, 2, 4, 2, + 20, 8, 2, 11, 2, 12, 2, 21, 1, 3, 3, 3, 25, 8, 3, 1, 3, 1, 3, 1, 4, 1, + 4, 3, 4, 31, 8, 4, 1, 4, 1, 4, 1, 4, 5, 4, 36, 8, 4, 10, 4, 12, 4, 39, + 9, 4, 1, 4, 3, 4, 42, 8, 4, 1, 4, 3, 4, 45, 8, 4, 0, 0, 5, 1, 1, 3, 2, + 5, 3, 7, 4, 9, 5, 1, 0, 2, 2, 0, 9, 9, 32, 32, 4, 0, 9, 10, 13, 13, 32, + 32, 34, 35, 52, 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, 14, 1, 0, 0, 0, + 5, 19, 1, 0, 0, 0, 7, 24, 1, 0, 0, 0, 9, 28, 1, 0, 0, 0, 11, 12, 5, 35, + 0, 0, 12, 2, 1, 0, 0, 0, 13, 15, 7, 0, 0, 0, 14, 13, 1, 0, 0, 0, 15, 16, + 1, 0, 0, 0, 16, 14, 1, 0, 0, 0, 16, 17, 1, 0, 0, 0, 17, 4, 1, 0, 0, 0, + 18, 20, 8, 1, 0, 0, 19, 18, 1, 0, 0, 0, 20, 21, 1, 0, 0, 0, 21, 19, 1, + 0, 0, 0, 21, 22, 1, 0, 0, 0, 22, 6, 1, 0, 0, 0, 23, 25, 5, 13, 0, 0, 24, + 23, 1, 0, 0, 0, 24, 25, 1, 0, 0, 0, 25, 26, 1, 0, 0, 0, 26, 27, 5, 10, + 0, 0, 27, 8, 1, 0, 0, 0, 28, 30, 5, 34, 0, 0, 29, 31, 3, 3, 1, 0, 30, 29, + 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 37, 1, 0, 0, 0, 32, 33, 3, 5, 2, 0, + 33, 34, 3, 3, 1, 0, 34, 36, 1, 0, 0, 0, 35, 32, 1, 0, 0, 0, 36, 39, 1, + 0, 0, 0, 37, 35, 1, 0, 0, 0, 37, 38, 1, 0, 0, 0, 38, 41, 1, 0, 0, 0, 39, + 37, 1, 0, 0, 0, 40, 42, 3, 5, 2, 0, 41, 40, 1, 0, 0, 0, 41, 42, 1, 0, 0, + 0, 42, 44, 1, 0, 0, 0, 43, 45, 5, 34, 0, 0, 44, 43, 1, 0, 0, 0, 44, 45, + 1, 0, 0, 0, 45, 10, 1, 0, 0, 0, 8, 0, 16, 21, 24, 30, 37, 41, 44, 0, } deserializer := antlr.NewATNDeserializer(nil) staticData.atn = deserializer.Deserialize(staticData.serializedATN) @@ -105,8 +114,9 @@ func NewConfigLexer(input antlr.CharStream) *ConfigLexer { // ConfigLexer tokens. const ( - ConfigLexerHASH = 1 - ConfigLexerWHITESPACE = 2 - ConfigLexerSTRING = 3 - ConfigLexerNEWLINE = 4 + ConfigLexerHASH = 1 + ConfigLexerWHITESPACE = 2 + ConfigLexerSTRING = 3 + ConfigLexerNEWLINE = 4 + ConfigLexerQUOTED_STRING = 5 ) diff --git a/handlers/ssh_config/ast/parser/config_listener.go b/handlers/ssh_config/ast/parser/config_listener.go index 0384e3c..1acf598 100644 --- a/handlers/ssh_config/ast/parser/config_listener.go +++ b/handlers/ssh_config/ast/parser/config_listener.go @@ -26,6 +26,9 @@ type ConfigListener interface { // EnterLeadingComment is called when entering the leadingComment production. EnterLeadingComment(c *LeadingCommentContext) + // EnterString is called when entering the string production. + EnterString(c *StringContext) + // ExitLineStatement is called when exiting the lineStatement production. ExitLineStatement(c *LineStatementContext) @@ -43,4 +46,7 @@ type ConfigListener interface { // ExitLeadingComment is called when exiting the leadingComment production. ExitLeadingComment(c *LeadingCommentContext) + + // ExitString is called when exiting the string production. + ExitString(c *StringContext) } diff --git a/handlers/ssh_config/ast/parser/config_parser.go b/handlers/ssh_config/ast/parser/config_parser.go index 018bbb8..aefb1e3 100644 --- a/handlers/ssh_config/ast/parser/config_parser.go +++ b/handlers/ssh_config/ast/parser/config_parser.go @@ -36,42 +36,44 @@ func configParserInit() { "", "'#'", } staticData.SymbolicNames = []string{ - "", "HASH", "WHITESPACE", "STRING", "NEWLINE", + "", "HASH", "WHITESPACE", "STRING", "NEWLINE", "QUOTED_STRING", } staticData.RuleNames = []string{ "lineStatement", "entry", "separator", "key", "value", "leadingComment", + "string", } staticData.PredictionContextCache = antlr.NewPredictionContextCache() staticData.serializedATN = []int32{ - 4, 1, 4, 66, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, - 2, 5, 7, 5, 1, 0, 1, 0, 1, 0, 3, 0, 16, 8, 0, 3, 0, 18, 8, 0, 1, 0, 1, - 0, 1, 1, 3, 1, 23, 8, 1, 1, 1, 3, 1, 26, 8, 1, 1, 1, 3, 1, 29, 8, 1, 1, - 1, 3, 1, 32, 8, 1, 1, 1, 3, 1, 35, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, - 1, 4, 5, 4, 43, 8, 4, 10, 4, 12, 4, 46, 9, 4, 1, 4, 3, 4, 49, 8, 4, 1, - 4, 3, 4, 52, 8, 4, 1, 5, 1, 5, 3, 5, 56, 8, 5, 1, 5, 1, 5, 3, 5, 60, 8, - 5, 4, 5, 62, 8, 5, 11, 5, 12, 5, 63, 1, 5, 0, 0, 6, 0, 2, 4, 6, 8, 10, - 0, 0, 73, 0, 17, 1, 0, 0, 0, 2, 22, 1, 0, 0, 0, 4, 36, 1, 0, 0, 0, 6, 38, - 1, 0, 0, 0, 8, 44, 1, 0, 0, 0, 10, 53, 1, 0, 0, 0, 12, 18, 3, 2, 1, 0, - 13, 18, 3, 10, 5, 0, 14, 16, 5, 2, 0, 0, 15, 14, 1, 0, 0, 0, 15, 16, 1, - 0, 0, 0, 16, 18, 1, 0, 0, 0, 17, 12, 1, 0, 0, 0, 17, 13, 1, 0, 0, 0, 17, - 15, 1, 0, 0, 0, 18, 19, 1, 0, 0, 0, 19, 20, 5, 0, 0, 1, 20, 1, 1, 0, 0, - 0, 21, 23, 5, 2, 0, 0, 22, 21, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 25, - 1, 0, 0, 0, 24, 26, 3, 6, 3, 0, 25, 24, 1, 0, 0, 0, 25, 26, 1, 0, 0, 0, - 26, 28, 1, 0, 0, 0, 27, 29, 3, 4, 2, 0, 28, 27, 1, 0, 0, 0, 28, 29, 1, - 0, 0, 0, 29, 31, 1, 0, 0, 0, 30, 32, 3, 8, 4, 0, 31, 30, 1, 0, 0, 0, 31, - 32, 1, 0, 0, 0, 32, 34, 1, 0, 0, 0, 33, 35, 3, 10, 5, 0, 34, 33, 1, 0, - 0, 0, 34, 35, 1, 0, 0, 0, 35, 3, 1, 0, 0, 0, 36, 37, 5, 2, 0, 0, 37, 5, - 1, 0, 0, 0, 38, 39, 5, 3, 0, 0, 39, 7, 1, 0, 0, 0, 40, 41, 5, 3, 0, 0, - 41, 43, 5, 2, 0, 0, 42, 40, 1, 0, 0, 0, 43, 46, 1, 0, 0, 0, 44, 42, 1, - 0, 0, 0, 44, 45, 1, 0, 0, 0, 45, 48, 1, 0, 0, 0, 46, 44, 1, 0, 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, 52, 5, 2, 0, 0, 51, 50, 1, 0, 0, 0, 51, 52, 1, 0, 0, 0, 52, 9, 1, - 0, 0, 0, 53, 55, 5, 1, 0, 0, 54, 56, 5, 2, 0, 0, 55, 54, 1, 0, 0, 0, 55, - 56, 1, 0, 0, 0, 56, 61, 1, 0, 0, 0, 57, 59, 5, 3, 0, 0, 58, 60, 5, 2, 0, - 0, 59, 58, 1, 0, 0, 0, 59, 60, 1, 0, 0, 0, 60, 62, 1, 0, 0, 0, 61, 57, - 1, 0, 0, 0, 62, 63, 1, 0, 0, 0, 63, 61, 1, 0, 0, 0, 63, 64, 1, 0, 0, 0, - 64, 11, 1, 0, 0, 0, 13, 15, 17, 22, 25, 28, 31, 34, 44, 48, 51, 55, 59, - 63, + 4, 1, 5, 71, 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, 1, 0, 1, 0, 1, 0, 3, 0, 18, 8, 0, 3, 0, 20, 8, + 0, 1, 0, 1, 0, 1, 1, 3, 1, 25, 8, 1, 1, 1, 3, 1, 28, 8, 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, 1, 4, 5, 4, 46, 8, 4, 10, 4, 12, 4, 49, 9, 4, 1, 4, 3, + 4, 52, 8, 4, 1, 4, 3, 4, 55, 8, 4, 1, 5, 1, 5, 3, 5, 59, 8, 5, 1, 5, 1, + 5, 3, 5, 63, 8, 5, 4, 5, 65, 8, 5, 11, 5, 12, 5, 66, 1, 6, 1, 6, 1, 6, + 0, 0, 7, 0, 2, 4, 6, 8, 10, 12, 0, 1, 2, 0, 3, 3, 5, 5, 77, 0, 19, 1, 0, + 0, 0, 2, 24, 1, 0, 0, 0, 4, 38, 1, 0, 0, 0, 6, 40, 1, 0, 0, 0, 8, 47, 1, + 0, 0, 0, 10, 56, 1, 0, 0, 0, 12, 68, 1, 0, 0, 0, 14, 20, 3, 2, 1, 0, 15, + 20, 3, 10, 5, 0, 16, 18, 5, 2, 0, 0, 17, 16, 1, 0, 0, 0, 17, 18, 1, 0, + 0, 0, 18, 20, 1, 0, 0, 0, 19, 14, 1, 0, 0, 0, 19, 15, 1, 0, 0, 0, 19, 17, + 1, 0, 0, 0, 20, 21, 1, 0, 0, 0, 21, 22, 5, 0, 0, 1, 22, 1, 1, 0, 0, 0, + 23, 25, 5, 2, 0, 0, 24, 23, 1, 0, 0, 0, 24, 25, 1, 0, 0, 0, 25, 27, 1, + 0, 0, 0, 26, 28, 3, 6, 3, 0, 27, 26, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, + 30, 1, 0, 0, 0, 29, 31, 3, 4, 2, 0, 30, 29, 1, 0, 0, 0, 30, 31, 1, 0, 0, + 0, 31, 33, 1, 0, 0, 0, 32, 34, 3, 8, 4, 0, 33, 32, 1, 0, 0, 0, 33, 34, + 1, 0, 0, 0, 34, 36, 1, 0, 0, 0, 35, 37, 3, 10, 5, 0, 36, 35, 1, 0, 0, 0, + 36, 37, 1, 0, 0, 0, 37, 3, 1, 0, 0, 0, 38, 39, 5, 2, 0, 0, 39, 5, 1, 0, + 0, 0, 40, 41, 3, 12, 6, 0, 41, 7, 1, 0, 0, 0, 42, 43, 3, 12, 6, 0, 43, + 44, 5, 2, 0, 0, 44, 46, 1, 0, 0, 0, 45, 42, 1, 0, 0, 0, 46, 49, 1, 0, 0, + 0, 47, 45, 1, 0, 0, 0, 47, 48, 1, 0, 0, 0, 48, 51, 1, 0, 0, 0, 49, 47, + 1, 0, 0, 0, 50, 52, 3, 12, 6, 0, 51, 50, 1, 0, 0, 0, 51, 52, 1, 0, 0, 0, + 52, 54, 1, 0, 0, 0, 53, 55, 5, 2, 0, 0, 54, 53, 1, 0, 0, 0, 54, 55, 1, + 0, 0, 0, 55, 9, 1, 0, 0, 0, 56, 58, 5, 1, 0, 0, 57, 59, 5, 2, 0, 0, 58, + 57, 1, 0, 0, 0, 58, 59, 1, 0, 0, 0, 59, 64, 1, 0, 0, 0, 60, 62, 3, 12, + 6, 0, 61, 63, 5, 2, 0, 0, 62, 61, 1, 0, 0, 0, 62, 63, 1, 0, 0, 0, 63, 65, + 1, 0, 0, 0, 64, 60, 1, 0, 0, 0, 65, 66, 1, 0, 0, 0, 66, 64, 1, 0, 0, 0, + 66, 67, 1, 0, 0, 0, 67, 11, 1, 0, 0, 0, 68, 69, 7, 0, 0, 0, 69, 13, 1, + 0, 0, 0, 13, 17, 19, 24, 27, 30, 33, 36, 47, 51, 54, 58, 62, 66, } deserializer := antlr.NewATNDeserializer(nil) staticData.atn = deserializer.Deserialize(staticData.serializedATN) @@ -109,11 +111,12 @@ func NewConfigParser(input antlr.TokenStream) *ConfigParser { // ConfigParser tokens. const ( - ConfigParserEOF = antlr.TokenEOF - ConfigParserHASH = 1 - ConfigParserWHITESPACE = 2 - ConfigParserSTRING = 3 - ConfigParserNEWLINE = 4 + ConfigParserEOF = antlr.TokenEOF + ConfigParserHASH = 1 + ConfigParserWHITESPACE = 2 + ConfigParserSTRING = 3 + ConfigParserNEWLINE = 4 + ConfigParserQUOTED_STRING = 5 ) // ConfigParser rules. @@ -124,6 +127,7 @@ const ( ConfigParserRULE_key = 3 ConfigParserRULE_value = 4 ConfigParserRULE_leadingComment = 5 + ConfigParserRULE_string = 6 ) // ILineStatementContext is an interface to support dynamic dispatch. @@ -241,7 +245,7 @@ func (p *ConfigParser) LineStatement() (localctx ILineStatementContext) { var _la int p.EnterOuterAlt(localctx, 1) - p.SetState(17) + p.SetState(19) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -250,18 +254,18 @@ func (p *ConfigParser) LineStatement() (localctx ILineStatementContext) { switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 1, p.GetParserRuleContext()) { case 1: { - p.SetState(12) + p.SetState(14) p.Entry() } case 2: { - p.SetState(13) + p.SetState(15) p.LeadingComment() } case 3: - p.SetState(15) + p.SetState(17) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -270,7 +274,7 @@ func (p *ConfigParser) LineStatement() (localctx ILineStatementContext) { if _la == ConfigParserWHITESPACE { { - p.SetState(14) + p.SetState(16) p.Match(ConfigParserWHITESPACE) if p.HasError() { // Recognition error - abort rule @@ -284,7 +288,7 @@ func (p *ConfigParser) LineStatement() (localctx ILineStatementContext) { goto errorExit } { - p.SetState(19) + p.SetState(21) p.Match(ConfigParserEOF) if p.HasError() { // Recognition error - abort rule @@ -449,12 +453,12 @@ func (p *ConfigParser) Entry() (localctx IEntryContext) { var _la int p.EnterOuterAlt(localctx, 1) - p.SetState(22) + p.SetState(24) p.GetErrorHandler().Sync(p) if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 2, p.GetParserRuleContext()) == 1 { { - p.SetState(21) + p.SetState(23) p.Match(ConfigParserWHITESPACE) if p.HasError() { // Recognition error - abort rule @@ -465,43 +469,43 @@ func (p *ConfigParser) Entry() (localctx IEntryContext) { } else if p.HasError() { // JIM goto errorExit } - p.SetState(25) + p.SetState(27) p.GetErrorHandler().Sync(p) if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 3, p.GetParserRuleContext()) == 1 { { - p.SetState(24) + p.SetState(26) p.Key() } } else if p.HasError() { // JIM goto errorExit } - p.SetState(28) + p.SetState(30) p.GetErrorHandler().Sync(p) if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 4, p.GetParserRuleContext()) == 1 { { - p.SetState(27) + p.SetState(29) p.Separator() } } else if p.HasError() { // JIM goto errorExit } - p.SetState(31) + p.SetState(33) p.GetErrorHandler().Sync(p) if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 5, p.GetParserRuleContext()) == 1 { { - p.SetState(30) + p.SetState(32) p.Value() } } else if p.HasError() { // JIM goto errorExit } - p.SetState(34) + p.SetState(36) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -510,7 +514,7 @@ func (p *ConfigParser) Entry() (localctx IEntryContext) { if _la == ConfigParserHASH { { - p.SetState(33) + p.SetState(35) p.LeadingComment() } @@ -604,7 +608,7 @@ func (p *ConfigParser) Separator() (localctx ISeparatorContext) { p.EnterRule(localctx, 4, ConfigParserRULE_separator) p.EnterOuterAlt(localctx, 1) { - p.SetState(36) + p.SetState(38) p.Match(ConfigParserWHITESPACE) if p.HasError() { // Recognition error - abort rule @@ -633,7 +637,7 @@ type IKeyContext interface { GetParser() antlr.Parser // Getter signatures - STRING() antlr.TerminalNode + String_() IStringContext // IsKeyContext differentiates from other interfaces. IsKeyContext() @@ -671,8 +675,20 @@ func NewKeyContext(parser antlr.Parser, parent antlr.ParserRuleContext, invoking func (s *KeyContext) GetParser() antlr.Parser { return s.parser } -func (s *KeyContext) STRING() antlr.TerminalNode { - return s.GetToken(ConfigParserSTRING, 0) +func (s *KeyContext) String_() IStringContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IStringContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IStringContext) } func (s *KeyContext) GetRuleContext() antlr.RuleContext { @@ -700,12 +716,8 @@ func (p *ConfigParser) Key() (localctx IKeyContext) { p.EnterRule(localctx, 6, ConfigParserRULE_key) p.EnterOuterAlt(localctx, 1) { - p.SetState(38) - p.Match(ConfigParserSTRING) - if p.HasError() { - // Recognition error - abort rule - goto errorExit - } + p.SetState(40) + p.String_() } errorExit: @@ -729,8 +741,8 @@ type IValueContext interface { GetParser() antlr.Parser // Getter signatures - AllSTRING() []antlr.TerminalNode - STRING(i int) antlr.TerminalNode + AllString_() []IStringContext + String_(i int) IStringContext AllWHITESPACE() []antlr.TerminalNode WHITESPACE(i int) antlr.TerminalNode @@ -770,12 +782,45 @@ func NewValueContext(parser antlr.Parser, parent antlr.ParserRuleContext, invoki func (s *ValueContext) GetParser() antlr.Parser { return s.parser } -func (s *ValueContext) AllSTRING() []antlr.TerminalNode { - return s.GetTokens(ConfigParserSTRING) +func (s *ValueContext) AllString_() []IStringContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IStringContext); ok { + len++ + } + } + + tst := make([]IStringContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IStringContext); ok { + tst[i] = t.(IStringContext) + i++ + } + } + + return tst } -func (s *ValueContext) STRING(i int) antlr.TerminalNode { - return s.GetToken(ConfigParserSTRING, i) +func (s *ValueContext) String_(i int) IStringContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IStringContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IStringContext) } func (s *ValueContext) AllWHITESPACE() []antlr.TerminalNode { @@ -814,7 +859,7 @@ func (p *ConfigParser) Value() (localctx IValueContext) { var _alt int p.EnterOuterAlt(localctx, 1) - p.SetState(44) + p.SetState(47) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -826,15 +871,11 @@ func (p *ConfigParser) Value() (localctx IValueContext) { for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { if _alt == 1 { { - p.SetState(40) - p.Match(ConfigParserSTRING) - if p.HasError() { - // Recognition error - abort rule - goto errorExit - } + p.SetState(42) + p.String_() } { - p.SetState(41) + p.SetState(43) p.Match(ConfigParserWHITESPACE) if p.HasError() { // Recognition error - abort rule @@ -843,7 +884,7 @@ func (p *ConfigParser) Value() (localctx IValueContext) { } } - p.SetState(46) + p.SetState(49) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -853,25 +894,21 @@ func (p *ConfigParser) Value() (localctx IValueContext) { goto errorExit } } - p.SetState(48) + p.SetState(51) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } _la = p.GetTokenStream().LA(1) - if _la == ConfigParserSTRING { + if _la == ConfigParserSTRING || _la == ConfigParserQUOTED_STRING { { - p.SetState(47) - p.Match(ConfigParserSTRING) - if p.HasError() { - // Recognition error - abort rule - goto errorExit - } + p.SetState(50) + p.String_() } } - p.SetState(51) + p.SetState(54) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -880,7 +917,7 @@ func (p *ConfigParser) Value() (localctx IValueContext) { if _la == ConfigParserWHITESPACE { { - p.SetState(50) + p.SetState(53) p.Match(ConfigParserWHITESPACE) if p.HasError() { // Recognition error - abort rule @@ -914,8 +951,8 @@ type ILeadingCommentContext interface { HASH() antlr.TerminalNode AllWHITESPACE() []antlr.TerminalNode WHITESPACE(i int) antlr.TerminalNode - AllSTRING() []antlr.TerminalNode - STRING(i int) antlr.TerminalNode + AllString_() []IStringContext + String_(i int) IStringContext // IsLeadingCommentContext differentiates from other interfaces. IsLeadingCommentContext() @@ -965,12 +1002,45 @@ 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) AllString_() []IStringContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IStringContext); ok { + len++ + } + } + + tst := make([]IStringContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IStringContext); ok { + tst[i] = t.(IStringContext) + i++ + } + } + + return tst } -func (s *LeadingCommentContext) STRING(i int) antlr.TerminalNode { - return s.GetToken(ConfigParserSTRING, i) +func (s *LeadingCommentContext) String_(i int) IStringContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IStringContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IStringContext) } func (s *LeadingCommentContext) GetRuleContext() antlr.RuleContext { @@ -1000,14 +1070,14 @@ func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { p.EnterOuterAlt(localctx, 1) { - p.SetState(53) + p.SetState(56) p.Match(ConfigParserHASH) if p.HasError() { // Recognition error - abort rule goto errorExit } } - p.SetState(55) + p.SetState(58) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -1016,7 +1086,7 @@ func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { if _la == ConfigParserWHITESPACE { { - p.SetState(54) + p.SetState(57) p.Match(ConfigParserWHITESPACE) if p.HasError() { // Recognition error - abort rule @@ -1025,23 +1095,19 @@ func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { } } - p.SetState(61) + p.SetState(64) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } _la = p.GetTokenStream().LA(1) - for ok := true; ok; ok = _la == ConfigParserSTRING { + for ok := true; ok; ok = _la == ConfigParserSTRING || _la == ConfigParserQUOTED_STRING { { - p.SetState(57) - p.Match(ConfigParserSTRING) - if p.HasError() { - // Recognition error - abort rule - goto errorExit - } + p.SetState(60) + p.String_() } - p.SetState(59) + p.SetState(62) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -1050,7 +1116,7 @@ func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { if _la == ConfigParserWHITESPACE { { - p.SetState(58) + p.SetState(61) p.Match(ConfigParserWHITESPACE) if p.HasError() { // Recognition error - abort rule @@ -1060,7 +1126,7 @@ func (p *ConfigParser) LeadingComment() (localctx ILeadingCommentContext) { } - p.SetState(63) + p.SetState(66) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -1080,3 +1146,109 @@ errorExit: return localctx goto errorExit // Trick to prevent compiler error if the label is not used } + +// IStringContext is an interface to support dynamic dispatch. +type IStringContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + QUOTED_STRING() antlr.TerminalNode + STRING() antlr.TerminalNode + + // IsStringContext differentiates from other interfaces. + IsStringContext() +} + +type StringContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyStringContext() *StringContext { + var p = new(StringContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ConfigParserRULE_string + return p +} + +func InitEmptyStringContext(p *StringContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ConfigParserRULE_string +} + +func (*StringContext) IsStringContext() {} + +func NewStringContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *StringContext { + var p = new(StringContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ConfigParserRULE_string + + return p +} + +func (s *StringContext) GetParser() antlr.Parser { return s.parser } + +func (s *StringContext) QUOTED_STRING() antlr.TerminalNode { + return s.GetToken(ConfigParserQUOTED_STRING, 0) +} + +func (s *StringContext) STRING() antlr.TerminalNode { + return s.GetToken(ConfigParserSTRING, 0) +} + +func (s *StringContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *StringContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *StringContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ConfigListener); ok { + listenerT.EnterString(s) + } +} + +func (s *StringContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ConfigListener); ok { + listenerT.ExitString(s) + } +} + +func (p *ConfigParser) String_() (localctx IStringContext) { + localctx = NewStringContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 12, ConfigParserRULE_string) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(68) + _la = p.GetTokenStream().LA(1) + + if !(_la == ConfigParserSTRING || _la == ConfigParserQUOTED_STRING) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + +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/ssh_config/ast/parser_test.go b/handlers/ssh_config/ast/parser_test.go new file mode 100644 index 0000000..dba0dc3 --- /dev/null +++ b/handlers/ssh_config/ast/parser_test.go @@ -0,0 +1,129 @@ +package ast + +import ( + "config-lsp/utils" + "testing" +) + +func TestSSHConfigParserExample( + t *testing.T, +) { + input := utils.Dedent(` +HostName 1.2.3.4 +User root +`) + p := NewSSHConfig() + errors := p.Parse(input) + + if len(errors) != 0 { + t.Fatalf("Expected no errors, got %v", errors) + } + + if !(p.Options.Size() == 2 && + len(utils.KeysOfMap(p.CommentLines)) == 0) { + t.Errorf("Expected 2 options and no comment lines, but got: %v, %v", p.Options, p.CommentLines) + } + + rawFirstEntry, _ := p.Options.Get(uint32(0)) + firstEntry := rawFirstEntry.(*SSHOption) + + if !(firstEntry.Value.Value == "HostName 1.2.3.4" && + firstEntry.LocationRange.Start.Line == 0 && + firstEntry.LocationRange.End.Line == 0 && + firstEntry.LocationRange.Start.Character == 0 && + firstEntry.LocationRange.End.Character == 16 && + firstEntry.Key.Value.Value == "HostName" && + firstEntry.Key.LocationRange.Start.Character == 0 && + firstEntry.Key.LocationRange.End.Character == 8 && + firstEntry.OptionValue.Value.Value == "1.2.3.4" && + firstEntry.OptionValue.LocationRange.Start.Character == 9 && + firstEntry.OptionValue.LocationRange.End.Character == 16) { + t.Errorf("Expected first entry to be HostName 1.2.3.4, but got: %v", firstEntry) + } + + rawSecondEntry, _ := p.Options.Get(uint32(1)) + secondEntry := rawSecondEntry.(*SSHOption) + + if !(secondEntry.Value.Value == "User root" && + secondEntry.LocationRange.Start.Line == 1 && + secondEntry.LocationRange.End.Line == 1 && + secondEntry.LocationRange.Start.Character == 0 && + secondEntry.LocationRange.End.Character == 9 && + secondEntry.Key.Value.Value == "User" && + secondEntry.Key.LocationRange.Start.Character == 0 && + secondEntry.Key.LocationRange.End.Character == 4 && + secondEntry.OptionValue.Value.Value == "root" && + secondEntry.OptionValue.LocationRange.Start.Character == 5 && + secondEntry.OptionValue.LocationRange.End.Character == 9) { + t.Errorf("Expected second entry to be User root, but got: %v", secondEntry) + } +} + +func TestMatchSimpleBlock( + t *testing.T, +) { + input := utils.Dedent(` +Hostname 1.2.3.4 + +Match originalhost "192.168.0.1" + User root +`) + p := NewSSHConfig() + errors := p.Parse(input) + + if len(errors) != 0 { + t.Fatalf("Expected no errors, got %v", errors) + } + + if !(p.Options.Size() == 2 && + len(utils.KeysOfMap(p.CommentLines)) == 0) { + t.Errorf("Expected 2 option and no comment lines, but got: %v entries, %v comment lines", p.Options.Size(), len(p.CommentLines)) + } + + rawFirstEntry, _ := p.Options.Get(uint32(0)) + firstEntry := rawFirstEntry.(*SSHOption) + + if !(firstEntry.Value.Value == "Hostname 1.2.3.4" && + firstEntry.LocationRange.Start.Line == 0 && + firstEntry.LocationRange.End.Line == 0 && + firstEntry.LocationRange.Start.Character == 0 && + firstEntry.LocationRange.End.Character == 16 && + firstEntry.Key.Value.Value == "Hostname" && + firstEntry.Key.LocationRange.Start.Character == 0 && + firstEntry.Key.LocationRange.End.Character == 8 && + firstEntry.OptionValue.Value.Value == "1.2.3.4" && + firstEntry.OptionValue.LocationRange.Start.Character == 9 && + firstEntry.OptionValue.LocationRange.End.Character == 16) { + t.Errorf("Expected first entry to be Hostname 1.2.3.4, but got: %v", firstEntry) + } + + rawSecondEntry, _ := p.Options.Get(uint32(2)) + secondEntry := rawSecondEntry.(*SSHMatchBlock) + + if !(secondEntry.Options.Size() == 1 && + secondEntry.LocationRange.Start.Line == 2 && + secondEntry.LocationRange.End.Line == 3 && + secondEntry.LocationRange.Start.Character == 0 && + secondEntry.LocationRange.End.Character == 10 && + secondEntry.MatchOption.OptionValue.Value.Raw == "originalhost \"192.168.0.1\"" && + secondEntry.MatchOption.OptionValue.LocationRange.Start.Character == 6 && + secondEntry.MatchOption.OptionValue.LocationRange.End.Character == 32) { + t.Errorf("Expected second entry to be Match originalhost \"192.168.0.1\", but got: %v; options amount: %d", secondEntry, secondEntry.Options.Size()) + } + + rawThirdEntry, _ := secondEntry.Options.Get(uint32(3)) + thirdEntry := rawThirdEntry.(*SSHOption) + if !(thirdEntry.Value.Raw == "\tUser root" && + thirdEntry.LocationRange.Start.Line == 3 && + thirdEntry.LocationRange.End.Line == 3 && + thirdEntry.LocationRange.Start.Character == 0 && + thirdEntry.LocationRange.End.Character == 10 && + thirdEntry.Key.Value.Value == "User" && + thirdEntry.Key.LocationRange.Start.Character == 1 && + thirdEntry.Key.LocationRange.End.Character == 5 && + thirdEntry.OptionValue.Value.Value == "root" && + thirdEntry.OptionValue.LocationRange.Start.Character == 6 && + thirdEntry.OptionValue.LocationRange.End.Character == 10) { + t.Errorf("Expected third entry to be User root, but got: %v", thirdEntry) + } +} diff --git a/handlers/ssh_config/ast/ssh_config.go b/handlers/ssh_config/ast/ssh_config.go index b3b5b6a..5757282 100644 --- a/handlers/ssh_config/ast/ssh_config.go +++ b/handlers/ssh_config/ast/ssh_config.go @@ -3,7 +3,7 @@ package ast import ( "config-lsp/common" commonparser "config-lsp/common/parser" - "config-lsp/common/parsers/openssh-match-parser" + "config-lsp/handlers/ssh_config/match-parser" "github.com/emirpasic/gods/maps/treemap" ) @@ -52,10 +52,7 @@ type SSHHostBlock struct { type SSHConfig struct { // [uint32]SSHOption -> line number -> *SSHEntry - RootOptions *treemap.Map - - MatchBlosks []*SSHMatchBlock - HostBlocks []*SSHHostBlock + Options *treemap.Map // [uint32]{} -> line number -> {} CommentLines map[uint32]struct{} diff --git a/handlers/ssh_config/ast/ssh_config_fields.go b/handlers/ssh_config/ast/ssh_config_fields.go index bd41296..30cf8d5 100644 --- a/handlers/ssh_config/ast/ssh_config_fields.go +++ b/handlers/ssh_config/ast/ssh_config_fields.go @@ -1 +1,77 @@ package ast + +import "config-lsp/common" + +type SSHBlockType uint8 + +const ( + SSHBlockTypeMatch SSHBlockType = iota + SSHBlockTypeHost +) + +type SSHBlock interface { + GetBlockType() SSHBlockType + AddOption(option *SSHOption) + SetEnd(common.Location) +} + +func (b *SSHMatchBlock) GetBlockType() SSHBlockType { + return SSHBlockTypeMatch +} + +func (b *SSHMatchBlock) AddOption(option *SSHOption) { + b.Options.Put(option.LocationRange.Start.Line, option) +} + +func (b *SSHMatchBlock) SetEnd(end common.Location) { + b.LocationRange.End = end +} + +func (b *SSHHostBlock) GetBlockType() SSHBlockType { + return SSHBlockTypeHost +} + +func (b *SSHHostBlock) AddOption(option *SSHOption) { + b.Others.Put(option.LocationRange.Start.Line, option) +} + +func (b *SSHHostBlock) SetEnd(end common.Location) { + b.LocationRange.End = end +} + +type SSHType uint8 + +const ( + SSHTypeOption SSHType = iota + SSHTypeMatch + SSHTypeHost +) + +type SSHEntry interface { + GetType() SSHType + GetOption() *SSHOption +} + +func (o *SSHOption) GetType() SSHType { + return SSHTypeOption +} + +func (o *SSHOption) GetOption() *SSHOption { + return o +} + +func (b *SSHMatchBlock) GetType() SSHType { + return SSHTypeMatch +} + +func (b *SSHMatchBlock) GetOption() *SSHOption { + return b.MatchOption +} + +func (b *SSHHostBlock) GetType() SSHType { + return SSHTypeHost +} + +func (b *SSHHostBlock) GetOption() *SSHOption { + return b.HostOption +} diff --git a/handlers/ssh_config/match-parser/Match.g4 b/handlers/ssh_config/match-parser/Match.g4 new file mode 100644 index 0000000..5b1744d --- /dev/null +++ b/handlers/ssh_config/match-parser/Match.g4 @@ -0,0 +1,45 @@ +grammar Match; + +root + : matchEntry? (WHITESPACE matchEntry?)* EOF + ; + +matchEntry + : criteria separator? values? + ; + +separator + : WHITESPACE + ; + +criteria + : string + ; + +values + : value? (COMMA value?)* + ; + +value + : string + ; + +string + : (QUOTED_STRING | STRING) + ; + +COMMA + : ',' + ; + +STRING + : ~(' ' | '\t' | '\r' | '\n' | '#' | ',')+ + ; + +WHITESPACE + : [ \t]+ + ; + +QUOTED_STRING + : '"' WHITESPACE? (STRING WHITESPACE)* STRING? ('"')? + ; diff --git a/handlers/ssh_config/match-parser/error-listener.go b/handlers/ssh_config/match-parser/error-listener.go new file mode 100644 index 0000000..e7264b0 --- /dev/null +++ b/handlers/ssh_config/match-parser/error-listener.go @@ -0,0 +1,45 @@ +package matchparser + +import ( + "config-lsp/common" + + "github.com/antlr4-go/antlr/v4" +) + +type errorListenerContext struct { + line uint32 +} + +func createErrorListener( + line uint32, +) errorListener { + return errorListener{ + Errors: make([]common.LSPError, 0), + context: errorListenerContext{ + line: line, + }, + } +} + +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, + }, + }) +} diff --git a/handlers/ssh_config/match-parser/listener.go b/handlers/ssh_config/match-parser/listener.go new file mode 100644 index 0000000..ddf7640 --- /dev/null +++ b/handlers/ssh_config/match-parser/listener.go @@ -0,0 +1,132 @@ +package matchparser + +import ( + "config-lsp/common" + commonparser "config-lsp/common/parser" + parser "config-lsp/handlers/ssh_config/match-parser/parser" + "config-lsp/utils" + "errors" + "fmt" + "strings" +) + +func createMatchListenerContext( + line uint32, + startCharacter uint32, +) *matchListenerContext { + return &matchListenerContext{ + currentEntry: nil, + line: line, + startCharacter: startCharacter, + } +} + +type matchListenerContext struct { + currentEntry *MatchEntry + line uint32 + startCharacter uint32 +} + +func createListener( + match *Match, + context *matchListenerContext, +) matchParserListener { + return matchParserListener{ + match: match, + Errors: make([]common.LSPError, 0), + matchContext: context, + } +} + +type matchParserListener struct { + *parser.BaseMatchListener + match *Match + Errors []common.LSPError + matchContext *matchListenerContext +} + +func (s *matchParserListener) EnterMatchEntry(ctx *parser.MatchEntryContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext).ShiftHorizontal(s.matchContext.startCharacter) + location.ChangeBothLines(s.matchContext.line) + + entry := &MatchEntry{ + LocationRange: location, + Value: commonparser.ParseRawString(ctx.GetText(), commonparser.FullFeatures), + } + + s.match.Entries = append(s.match.Entries, entry) + s.matchContext.currentEntry = entry +} + +func (s *matchParserListener) ExitMatchEntry(ctx *parser.MatchEntryContext) { + s.matchContext.currentEntry = nil +} + +var availableCriteria = map[string]MatchCriteriaType{ + string(MatchCriteriaTypeCanonical): MatchCriteriaTypeCanonical, + string(MatchCriteriaTypeFinal): MatchCriteriaTypeFinal, + string(MatchCriteriaTypeExec): MatchCriteriaTypeExec, + string(MatchCriteriaTypeLocalNetwork): MatchCriteriaTypeLocalNetwork, + string(MatchCriteriaTypeHost): MatchCriteriaTypeHost, + string(MatchCriteriaTypeOriginalHost): MatchCriteriaTypeOriginalHost, + string(MatchCriteriaTypeTagged): MatchCriteriaTypeTagged, + string(MatchCriteriaTypeUser): MatchCriteriaTypeUser, + string(MatchCriteriaTypeLocalUser): MatchCriteriaTypeLocalUser, +} + +func (s *matchParserListener) EnterCriteria(ctx *parser.CriteriaContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext).ShiftHorizontal(s.matchContext.startCharacter) + location.ChangeBothLines(s.matchContext.line) + + value := commonparser.ParseRawString(ctx.GetText(), commonparser.FullFeatures) + + criteria, found := availableCriteria[value.Value] + + if !found { + s.Errors = append(s.Errors, common.LSPError{ + Range: location, + Err: errors.New(fmt.Sprintf("Unknown criteria: %s; It must be one of: %s", ctx.GetText(), strings.Join(utils.KeysOfMap(availableCriteria), ", "))), + }) + return + } + + s.matchContext.currentEntry.Criteria = MatchCriteria{ + LocationRange: location, + Type: criteria, + Value: value, + } +} + +func (s *matchParserListener) EnterSeparator(ctx *parser.SeparatorContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext).ShiftHorizontal(s.matchContext.startCharacter) + location.ChangeBothLines(s.matchContext.line) + + value := commonparser.ParseRawString(ctx.GetText(), commonparser.FullFeatures) + + s.matchContext.currentEntry.Separator = &MatchSeparator{ + LocationRange: location, + Value: value, + } +} + +func (s *matchParserListener) EnterValues(ctx *parser.ValuesContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext).ShiftHorizontal(s.matchContext.startCharacter) + location.ChangeBothLines(s.matchContext.line) + + s.matchContext.currentEntry.Values = &MatchValues{ + LocationRange: location, + Values: make([]*MatchValue, 0), + } +} + +func (s *matchParserListener) EnterValue(ctx *parser.ValueContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext).ShiftHorizontal(s.matchContext.startCharacter) + location.ChangeBothLines(s.matchContext.line) + + value := &MatchValue{ + LocationRange: location, + Value: commonparser.ParseRawString(ctx.GetText(), commonparser.FullFeatures), + } + + s.matchContext.currentEntry.Values.Values = append(s.matchContext.currentEntry.Values.Values, value) +} diff --git a/handlers/ssh_config/match-parser/match_ast.go b/handlers/ssh_config/match-parser/match_ast.go new file mode 100644 index 0000000..a15c242 --- /dev/null +++ b/handlers/ssh_config/match-parser/match_ast.go @@ -0,0 +1,56 @@ +package matchparser + +import ( + "config-lsp/common" + commonparser "config-lsp/common/parser" +) + +type Match struct { + Entries []*MatchEntry +} + +type MatchCriteriaType string + +const ( + MatchCriteriaTypeCanonical MatchCriteriaType = "canonical" + MatchCriteriaTypeFinal MatchCriteriaType = "final" + MatchCriteriaTypeExec MatchCriteriaType = "exec" + MatchCriteriaTypeLocalNetwork MatchCriteriaType = "localnetwork" + MatchCriteriaTypeHost MatchCriteriaType = "host" + MatchCriteriaTypeOriginalHost MatchCriteriaType = "originalhost" + MatchCriteriaTypeTagged MatchCriteriaType = "tagged" + MatchCriteriaTypeUser MatchCriteriaType = "user" + MatchCriteriaTypeLocalUser MatchCriteriaType = "localuser" +) + +type MatchCriteria struct { + common.LocationRange + + Type MatchCriteriaType + Value commonparser.ParsedString +} + +type MatchSeparator struct { + common.LocationRange + Value commonparser.ParsedString +} + +type MatchValues struct { + common.LocationRange + + Values []*MatchValue +} + +type MatchEntry struct { + common.LocationRange + Value commonparser.ParsedString + + Criteria MatchCriteria + Separator *MatchSeparator + Values *MatchValues +} + +type MatchValue struct { + common.LocationRange + Value commonparser.ParsedString +} diff --git a/handlers/ssh_config/match-parser/match_fields.go b/handlers/ssh_config/match-parser/match_fields.go new file mode 100644 index 0000000..ab30390 --- /dev/null +++ b/handlers/ssh_config/match-parser/match_fields.go @@ -0,0 +1,62 @@ +package matchparser + +import ( + "config-lsp/common" + "slices" +) + +func (m Match) GetEntryAtPosition(position common.Position) *MatchEntry { + index, found := slices.BinarySearchFunc( + m.Entries, + position, + func(current *MatchEntry, target common.Position) int { + if current.IsPositionAfterEnd(target) { + return -1 + } + + if current.IsPositionBeforeStart(target) { + return 1 + } + + return 0 + }, + ) + + if !found { + return nil + } + + entry := m.Entries[index] + + return entry +} + +func (e MatchEntry) GetValueAtPosition(position common.Position) *MatchValue { + if e.Values == nil { + return nil + } + + index, found := slices.BinarySearchFunc( + e.Values.Values, + position, + func(current *MatchValue, target common.Position) int { + if current.IsPositionAfterEnd(target) { + return -1 + } + + if current.IsPositionBeforeStart(target) { + return 1 + } + + return 0 + }, + ) + + if !found { + return nil + } + + value := e.Values.Values[index] + + return value +} diff --git a/handlers/ssh_config/match-parser/parser.go b/handlers/ssh_config/match-parser/parser.go new file mode 100644 index 0000000..8007a99 --- /dev/null +++ b/handlers/ssh_config/match-parser/parser.go @@ -0,0 +1,54 @@ +package matchparser + +import ( + "config-lsp/common" + "config-lsp/handlers/ssh_config/match-parser/parser" + + "github.com/antlr4-go/antlr/v4" +) + +func NewMatch() *Match { + match := new(Match) + match.Clear() + + return match +} + +func (m *Match) Clear() { + m.Entries = make([]*MatchEntry, 0) +} + +func (m *Match) Parse( + input string, + line uint32, + startCharacter uint32, +) []common.LSPError { + context := createMatchListenerContext(line, startCharacter) + + stream := antlr.NewInputStream(input) + + lexerErrorListener := createErrorListener(context.line) + lexer := parser.NewMatchLexer(stream) + lexer.RemoveErrorListeners() + lexer.AddErrorListener(&lexerErrorListener) + + tokenStream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel) + + parserErrorListener := createErrorListener(context.line) + antlrParser := parser.NewMatchParser(tokenStream) + antlrParser.RemoveErrorListeners() + antlrParser.AddErrorListener(&parserErrorListener) + + listener := createListener(m, context) + antlr.ParseTreeWalkerDefault.Walk( + &listener, + antlrParser.Root(), + ) + + errors := lexerErrorListener.Errors + errors = append(errors, parserErrorListener.Errors...) + errors = append(errors, listener.Errors...) + + return errors + +} diff --git a/handlers/ssh_config/match-parser/parser/Match.interp b/handlers/ssh_config/match-parser/parser/Match.interp new file mode 100644 index 0000000..2843738 --- /dev/null +++ b/handlers/ssh_config/match-parser/parser/Match.interp @@ -0,0 +1,26 @@ +token literal names: +null +',' +null +null +null + +token symbolic names: +null +COMMA +STRING +WHITESPACE +QUOTED_STRING + +rule names: +root +matchEntry +separator +criteria +values +value +string + + +atn: +[4, 1, 4, 56, 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, 1, 0, 3, 0, 16, 8, 0, 1, 0, 1, 0, 3, 0, 20, 8, 0, 5, 0, 22, 8, 0, 10, 0, 12, 0, 25, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 3, 1, 31, 8, 1, 1, 1, 3, 1, 34, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 3, 4, 41, 8, 4, 1, 4, 1, 4, 3, 4, 45, 8, 4, 5, 4, 47, 8, 4, 10, 4, 12, 4, 50, 9, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 0, 0, 7, 0, 2, 4, 6, 8, 10, 12, 0, 1, 2, 0, 2, 2, 4, 4, 56, 0, 15, 1, 0, 0, 0, 2, 28, 1, 0, 0, 0, 4, 35, 1, 0, 0, 0, 6, 37, 1, 0, 0, 0, 8, 40, 1, 0, 0, 0, 10, 51, 1, 0, 0, 0, 12, 53, 1, 0, 0, 0, 14, 16, 3, 2, 1, 0, 15, 14, 1, 0, 0, 0, 15, 16, 1, 0, 0, 0, 16, 23, 1, 0, 0, 0, 17, 19, 5, 3, 0, 0, 18, 20, 3, 2, 1, 0, 19, 18, 1, 0, 0, 0, 19, 20, 1, 0, 0, 0, 20, 22, 1, 0, 0, 0, 21, 17, 1, 0, 0, 0, 22, 25, 1, 0, 0, 0, 23, 21, 1, 0, 0, 0, 23, 24, 1, 0, 0, 0, 24, 26, 1, 0, 0, 0, 25, 23, 1, 0, 0, 0, 26, 27, 5, 0, 0, 1, 27, 1, 1, 0, 0, 0, 28, 30, 3, 6, 3, 0, 29, 31, 3, 4, 2, 0, 30, 29, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 33, 1, 0, 0, 0, 32, 34, 3, 8, 4, 0, 33, 32, 1, 0, 0, 0, 33, 34, 1, 0, 0, 0, 34, 3, 1, 0, 0, 0, 35, 36, 5, 3, 0, 0, 36, 5, 1, 0, 0, 0, 37, 38, 3, 12, 6, 0, 38, 7, 1, 0, 0, 0, 39, 41, 3, 10, 5, 0, 40, 39, 1, 0, 0, 0, 40, 41, 1, 0, 0, 0, 41, 48, 1, 0, 0, 0, 42, 44, 5, 1, 0, 0, 43, 45, 3, 10, 5, 0, 44, 43, 1, 0, 0, 0, 44, 45, 1, 0, 0, 0, 45, 47, 1, 0, 0, 0, 46, 42, 1, 0, 0, 0, 47, 50, 1, 0, 0, 0, 48, 46, 1, 0, 0, 0, 48, 49, 1, 0, 0, 0, 49, 9, 1, 0, 0, 0, 50, 48, 1, 0, 0, 0, 51, 52, 3, 12, 6, 0, 52, 11, 1, 0, 0, 0, 53, 54, 7, 0, 0, 0, 54, 13, 1, 0, 0, 0, 8, 15, 19, 23, 30, 33, 40, 44, 48] \ No newline at end of file diff --git a/handlers/ssh_config/match-parser/parser/Match.tokens b/handlers/ssh_config/match-parser/parser/Match.tokens new file mode 100644 index 0000000..c1682a1 --- /dev/null +++ b/handlers/ssh_config/match-parser/parser/Match.tokens @@ -0,0 +1,5 @@ +COMMA=1 +STRING=2 +WHITESPACE=3 +QUOTED_STRING=4 +','=1 diff --git a/handlers/ssh_config/match-parser/parser/MatchLexer.interp b/handlers/ssh_config/match-parser/parser/MatchLexer.interp new file mode 100644 index 0000000..c2bc7b6 --- /dev/null +++ b/handlers/ssh_config/match-parser/parser/MatchLexer.interp @@ -0,0 +1,29 @@ +token literal names: +null +',' +null +null +null + +token symbolic names: +null +COMMA +STRING +WHITESPACE +QUOTED_STRING + +rule names: +COMMA +STRING +WHITESPACE +QUOTED_STRING + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[4, 0, 4, 39, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 1, 0, 1, 0, 1, 1, 4, 1, 13, 8, 1, 11, 1, 12, 1, 14, 1, 2, 4, 2, 18, 8, 2, 11, 2, 12, 2, 19, 1, 3, 1, 3, 3, 3, 24, 8, 3, 1, 3, 1, 3, 1, 3, 5, 3, 29, 8, 3, 10, 3, 12, 3, 32, 9, 3, 1, 3, 3, 3, 35, 8, 3, 1, 3, 3, 3, 38, 8, 3, 0, 0, 4, 1, 1, 3, 2, 5, 3, 7, 4, 1, 0, 2, 5, 0, 9, 10, 13, 13, 32, 32, 35, 35, 44, 44, 2, 0, 9, 9, 32, 32, 44, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 1, 9, 1, 0, 0, 0, 3, 12, 1, 0, 0, 0, 5, 17, 1, 0, 0, 0, 7, 21, 1, 0, 0, 0, 9, 10, 5, 44, 0, 0, 10, 2, 1, 0, 0, 0, 11, 13, 8, 0, 0, 0, 12, 11, 1, 0, 0, 0, 13, 14, 1, 0, 0, 0, 14, 12, 1, 0, 0, 0, 14, 15, 1, 0, 0, 0, 15, 4, 1, 0, 0, 0, 16, 18, 7, 1, 0, 0, 17, 16, 1, 0, 0, 0, 18, 19, 1, 0, 0, 0, 19, 17, 1, 0, 0, 0, 19, 20, 1, 0, 0, 0, 20, 6, 1, 0, 0, 0, 21, 23, 5, 34, 0, 0, 22, 24, 3, 5, 2, 0, 23, 22, 1, 0, 0, 0, 23, 24, 1, 0, 0, 0, 24, 30, 1, 0, 0, 0, 25, 26, 3, 3, 1, 0, 26, 27, 3, 5, 2, 0, 27, 29, 1, 0, 0, 0, 28, 25, 1, 0, 0, 0, 29, 32, 1, 0, 0, 0, 30, 28, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 34, 1, 0, 0, 0, 32, 30, 1, 0, 0, 0, 33, 35, 3, 3, 1, 0, 34, 33, 1, 0, 0, 0, 34, 35, 1, 0, 0, 0, 35, 37, 1, 0, 0, 0, 36, 38, 5, 34, 0, 0, 37, 36, 1, 0, 0, 0, 37, 38, 1, 0, 0, 0, 38, 8, 1, 0, 0, 0, 7, 0, 14, 19, 23, 30, 34, 37, 0] \ No newline at end of file diff --git a/handlers/ssh_config/match-parser/parser/MatchLexer.tokens b/handlers/ssh_config/match-parser/parser/MatchLexer.tokens new file mode 100644 index 0000000..c1682a1 --- /dev/null +++ b/handlers/ssh_config/match-parser/parser/MatchLexer.tokens @@ -0,0 +1,5 @@ +COMMA=1 +STRING=2 +WHITESPACE=3 +QUOTED_STRING=4 +','=1 diff --git a/handlers/ssh_config/match-parser/parser/match_base_listener.go b/handlers/ssh_config/match-parser/parser/match_base_listener.go new file mode 100644 index 0000000..4e4375d --- /dev/null +++ b/handlers/ssh_config/match-parser/parser/match_base_listener.go @@ -0,0 +1,64 @@ +// Code generated from Match.g4 by ANTLR 4.13.0. DO NOT EDIT. + +package parser // Match + +import "github.com/antlr4-go/antlr/v4" + +// BaseMatchListener is a complete listener for a parse tree produced by MatchParser. +type BaseMatchListener struct{} + +var _ MatchListener = &BaseMatchListener{} + +// VisitTerminal is called when a terminal node is visited. +func (s *BaseMatchListener) VisitTerminal(node antlr.TerminalNode) {} + +// VisitErrorNode is called when an error node is visited. +func (s *BaseMatchListener) VisitErrorNode(node antlr.ErrorNode) {} + +// EnterEveryRule is called when any rule is entered. +func (s *BaseMatchListener) EnterEveryRule(ctx antlr.ParserRuleContext) {} + +// ExitEveryRule is called when any rule is exited. +func (s *BaseMatchListener) ExitEveryRule(ctx antlr.ParserRuleContext) {} + +// EnterRoot is called when production root is entered. +func (s *BaseMatchListener) EnterRoot(ctx *RootContext) {} + +// ExitRoot is called when production root is exited. +func (s *BaseMatchListener) ExitRoot(ctx *RootContext) {} + +// EnterMatchEntry is called when production matchEntry is entered. +func (s *BaseMatchListener) EnterMatchEntry(ctx *MatchEntryContext) {} + +// ExitMatchEntry is called when production matchEntry is exited. +func (s *BaseMatchListener) ExitMatchEntry(ctx *MatchEntryContext) {} + +// EnterSeparator is called when production separator is entered. +func (s *BaseMatchListener) EnterSeparator(ctx *SeparatorContext) {} + +// ExitSeparator is called when production separator is exited. +func (s *BaseMatchListener) ExitSeparator(ctx *SeparatorContext) {} + +// EnterCriteria is called when production criteria is entered. +func (s *BaseMatchListener) EnterCriteria(ctx *CriteriaContext) {} + +// ExitCriteria is called when production criteria is exited. +func (s *BaseMatchListener) ExitCriteria(ctx *CriteriaContext) {} + +// EnterValues is called when production values is entered. +func (s *BaseMatchListener) EnterValues(ctx *ValuesContext) {} + +// ExitValues is called when production values is exited. +func (s *BaseMatchListener) ExitValues(ctx *ValuesContext) {} + +// EnterValue is called when production value is entered. +func (s *BaseMatchListener) EnterValue(ctx *ValueContext) {} + +// ExitValue is called when production value is exited. +func (s *BaseMatchListener) ExitValue(ctx *ValueContext) {} + +// EnterString is called when production string is entered. +func (s *BaseMatchListener) EnterString(ctx *StringContext) {} + +// ExitString is called when production string is exited. +func (s *BaseMatchListener) ExitString(ctx *StringContext) {} diff --git a/handlers/ssh_config/match-parser/parser/match_lexer.go b/handlers/ssh_config/match-parser/parser/match_lexer.go new file mode 100644 index 0000000..e4ab2ba --- /dev/null +++ b/handlers/ssh_config/match-parser/parser/match_lexer.go @@ -0,0 +1,118 @@ +// Code generated from Match.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 MatchLexer struct { + *antlr.BaseLexer + channelNames []string + modeNames []string + // TODO: EOF string +} + +var MatchLexerLexerStaticData 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 matchlexerLexerInit() { + staticData := &MatchLexerLexerStaticData + staticData.ChannelNames = []string{ + "DEFAULT_TOKEN_CHANNEL", "HIDDEN", + } + staticData.ModeNames = []string{ + "DEFAULT_MODE", + } + staticData.LiteralNames = []string{ + "", "','", + } + staticData.SymbolicNames = []string{ + "", "COMMA", "STRING", "WHITESPACE", "QUOTED_STRING", + } + staticData.RuleNames = []string{ + "COMMA", "STRING", "WHITESPACE", "QUOTED_STRING", + } + staticData.PredictionContextCache = antlr.NewPredictionContextCache() + staticData.serializedATN = []int32{ + 4, 0, 4, 39, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 1, + 0, 1, 0, 1, 1, 4, 1, 13, 8, 1, 11, 1, 12, 1, 14, 1, 2, 4, 2, 18, 8, 2, + 11, 2, 12, 2, 19, 1, 3, 1, 3, 3, 3, 24, 8, 3, 1, 3, 1, 3, 1, 3, 5, 3, 29, + 8, 3, 10, 3, 12, 3, 32, 9, 3, 1, 3, 3, 3, 35, 8, 3, 1, 3, 3, 3, 38, 8, + 3, 0, 0, 4, 1, 1, 3, 2, 5, 3, 7, 4, 1, 0, 2, 5, 0, 9, 10, 13, 13, 32, 32, + 35, 35, 44, 44, 2, 0, 9, 9, 32, 32, 44, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, + 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 1, 9, 1, 0, 0, 0, 3, 12, 1, 0, 0, + 0, 5, 17, 1, 0, 0, 0, 7, 21, 1, 0, 0, 0, 9, 10, 5, 44, 0, 0, 10, 2, 1, + 0, 0, 0, 11, 13, 8, 0, 0, 0, 12, 11, 1, 0, 0, 0, 13, 14, 1, 0, 0, 0, 14, + 12, 1, 0, 0, 0, 14, 15, 1, 0, 0, 0, 15, 4, 1, 0, 0, 0, 16, 18, 7, 1, 0, + 0, 17, 16, 1, 0, 0, 0, 18, 19, 1, 0, 0, 0, 19, 17, 1, 0, 0, 0, 19, 20, + 1, 0, 0, 0, 20, 6, 1, 0, 0, 0, 21, 23, 5, 34, 0, 0, 22, 24, 3, 5, 2, 0, + 23, 22, 1, 0, 0, 0, 23, 24, 1, 0, 0, 0, 24, 30, 1, 0, 0, 0, 25, 26, 3, + 3, 1, 0, 26, 27, 3, 5, 2, 0, 27, 29, 1, 0, 0, 0, 28, 25, 1, 0, 0, 0, 29, + 32, 1, 0, 0, 0, 30, 28, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 34, 1, 0, 0, + 0, 32, 30, 1, 0, 0, 0, 33, 35, 3, 3, 1, 0, 34, 33, 1, 0, 0, 0, 34, 35, + 1, 0, 0, 0, 35, 37, 1, 0, 0, 0, 36, 38, 5, 34, 0, 0, 37, 36, 1, 0, 0, 0, + 37, 38, 1, 0, 0, 0, 38, 8, 1, 0, 0, 0, 7, 0, 14, 19, 23, 30, 34, 37, 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) + } +} + +// MatchLexerInit initializes any static state used to implement MatchLexer. By default the +// static state used to implement the lexer is lazily initialized during the first call to +// NewMatchLexer(). You can call this function if you wish to initialize the static state ahead +// of time. +func MatchLexerInit() { + staticData := &MatchLexerLexerStaticData + staticData.once.Do(matchlexerLexerInit) +} + +// NewMatchLexer produces a new lexer instance for the optional input antlr.CharStream. +func NewMatchLexer(input antlr.CharStream) *MatchLexer { + MatchLexerInit() + l := new(MatchLexer) + l.BaseLexer = antlr.NewBaseLexer(input) + staticData := &MatchLexerLexerStaticData + 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 = "Match.g4" + // TODO: l.EOF = antlr.TokenEOF + + return l +} + +// MatchLexer tokens. +const ( + MatchLexerCOMMA = 1 + MatchLexerSTRING = 2 + MatchLexerWHITESPACE = 3 + MatchLexerQUOTED_STRING = 4 +) diff --git a/handlers/ssh_config/match-parser/parser/match_listener.go b/handlers/ssh_config/match-parser/parser/match_listener.go new file mode 100644 index 0000000..496f3a7 --- /dev/null +++ b/handlers/ssh_config/match-parser/parser/match_listener.go @@ -0,0 +1,52 @@ +// Code generated from Match.g4 by ANTLR 4.13.0. DO NOT EDIT. + +package parser // Match + +import "github.com/antlr4-go/antlr/v4" + +// MatchListener is a complete listener for a parse tree produced by MatchParser. +type MatchListener interface { + antlr.ParseTreeListener + + // EnterRoot is called when entering the root production. + EnterRoot(c *RootContext) + + // EnterMatchEntry is called when entering the matchEntry production. + EnterMatchEntry(c *MatchEntryContext) + + // EnterSeparator is called when entering the separator production. + EnterSeparator(c *SeparatorContext) + + // EnterCriteria is called when entering the criteria production. + EnterCriteria(c *CriteriaContext) + + // EnterValues is called when entering the values production. + EnterValues(c *ValuesContext) + + // EnterValue is called when entering the value production. + EnterValue(c *ValueContext) + + // EnterString is called when entering the string production. + EnterString(c *StringContext) + + // ExitRoot is called when exiting the root production. + ExitRoot(c *RootContext) + + // ExitMatchEntry is called when exiting the matchEntry production. + ExitMatchEntry(c *MatchEntryContext) + + // ExitSeparator is called when exiting the separator production. + ExitSeparator(c *SeparatorContext) + + // ExitCriteria is called when exiting the criteria production. + ExitCriteria(c *CriteriaContext) + + // ExitValues is called when exiting the values production. + ExitValues(c *ValuesContext) + + // ExitValue is called when exiting the value production. + ExitValue(c *ValueContext) + + // ExitString is called when exiting the string production. + ExitString(c *StringContext) +} diff --git a/handlers/ssh_config/match-parser/parser/match_parser.go b/handlers/ssh_config/match-parser/parser/match_parser.go new file mode 100644 index 0000000..1ba0752 --- /dev/null +++ b/handlers/ssh_config/match-parser/parser/match_parser.go @@ -0,0 +1,1087 @@ +// Code generated from Match.g4 by ANTLR 4.13.0. DO NOT EDIT. + +package parser // Match + +import ( + "fmt" + "strconv" + "sync" + + "github.com/antlr4-go/antlr/v4" +) + +// Suppress unused import errors +var _ = fmt.Printf +var _ = strconv.Itoa +var _ = sync.Once{} + +type MatchParser struct { + *antlr.BaseParser +} + +var MatchParserStaticData struct { + once sync.Once + serializedATN []int32 + LiteralNames []string + SymbolicNames []string + RuleNames []string + PredictionContextCache *antlr.PredictionContextCache + atn *antlr.ATN + decisionToDFA []*antlr.DFA +} + +func matchParserInit() { + staticData := &MatchParserStaticData + staticData.LiteralNames = []string{ + "", "','", + } + staticData.SymbolicNames = []string{ + "", "COMMA", "STRING", "WHITESPACE", "QUOTED_STRING", + } + staticData.RuleNames = []string{ + "root", "matchEntry", "separator", "criteria", "values", "value", "string", + } + staticData.PredictionContextCache = antlr.NewPredictionContextCache() + staticData.serializedATN = []int32{ + 4, 1, 4, 56, 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, 1, 0, 3, 0, 16, 8, 0, 1, 0, 1, 0, 3, 0, 20, 8, + 0, 5, 0, 22, 8, 0, 10, 0, 12, 0, 25, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 3, 1, + 31, 8, 1, 1, 1, 3, 1, 34, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 3, 4, 41, + 8, 4, 1, 4, 1, 4, 3, 4, 45, 8, 4, 5, 4, 47, 8, 4, 10, 4, 12, 4, 50, 9, + 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 0, 0, 7, 0, 2, 4, 6, 8, 10, 12, 0, 1, + 2, 0, 2, 2, 4, 4, 56, 0, 15, 1, 0, 0, 0, 2, 28, 1, 0, 0, 0, 4, 35, 1, 0, + 0, 0, 6, 37, 1, 0, 0, 0, 8, 40, 1, 0, 0, 0, 10, 51, 1, 0, 0, 0, 12, 53, + 1, 0, 0, 0, 14, 16, 3, 2, 1, 0, 15, 14, 1, 0, 0, 0, 15, 16, 1, 0, 0, 0, + 16, 23, 1, 0, 0, 0, 17, 19, 5, 3, 0, 0, 18, 20, 3, 2, 1, 0, 19, 18, 1, + 0, 0, 0, 19, 20, 1, 0, 0, 0, 20, 22, 1, 0, 0, 0, 21, 17, 1, 0, 0, 0, 22, + 25, 1, 0, 0, 0, 23, 21, 1, 0, 0, 0, 23, 24, 1, 0, 0, 0, 24, 26, 1, 0, 0, + 0, 25, 23, 1, 0, 0, 0, 26, 27, 5, 0, 0, 1, 27, 1, 1, 0, 0, 0, 28, 30, 3, + 6, 3, 0, 29, 31, 3, 4, 2, 0, 30, 29, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, + 33, 1, 0, 0, 0, 32, 34, 3, 8, 4, 0, 33, 32, 1, 0, 0, 0, 33, 34, 1, 0, 0, + 0, 34, 3, 1, 0, 0, 0, 35, 36, 5, 3, 0, 0, 36, 5, 1, 0, 0, 0, 37, 38, 3, + 12, 6, 0, 38, 7, 1, 0, 0, 0, 39, 41, 3, 10, 5, 0, 40, 39, 1, 0, 0, 0, 40, + 41, 1, 0, 0, 0, 41, 48, 1, 0, 0, 0, 42, 44, 5, 1, 0, 0, 43, 45, 3, 10, + 5, 0, 44, 43, 1, 0, 0, 0, 44, 45, 1, 0, 0, 0, 45, 47, 1, 0, 0, 0, 46, 42, + 1, 0, 0, 0, 47, 50, 1, 0, 0, 0, 48, 46, 1, 0, 0, 0, 48, 49, 1, 0, 0, 0, + 49, 9, 1, 0, 0, 0, 50, 48, 1, 0, 0, 0, 51, 52, 3, 12, 6, 0, 52, 11, 1, + 0, 0, 0, 53, 54, 7, 0, 0, 0, 54, 13, 1, 0, 0, 0, 8, 15, 19, 23, 30, 33, + 40, 44, 48, + } + 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) + } +} + +// MatchParserInit initializes any static state used to implement MatchParser. By default the +// static state used to implement the parser is lazily initialized during the first call to +// NewMatchParser(). You can call this function if you wish to initialize the static state ahead +// of time. +func MatchParserInit() { + staticData := &MatchParserStaticData + staticData.once.Do(matchParserInit) +} + +// NewMatchParser produces a new parser instance for the optional input antlr.TokenStream. +func NewMatchParser(input antlr.TokenStream) *MatchParser { + MatchParserInit() + this := new(MatchParser) + this.BaseParser = antlr.NewBaseParser(input) + staticData := &MatchParserStaticData + 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 = "Match.g4" + + return this +} + +// MatchParser tokens. +const ( + MatchParserEOF = antlr.TokenEOF + MatchParserCOMMA = 1 + MatchParserSTRING = 2 + MatchParserWHITESPACE = 3 + MatchParserQUOTED_STRING = 4 +) + +// MatchParser rules. +const ( + MatchParserRULE_root = 0 + MatchParserRULE_matchEntry = 1 + MatchParserRULE_separator = 2 + MatchParserRULE_criteria = 3 + MatchParserRULE_values = 4 + MatchParserRULE_value = 5 + MatchParserRULE_string = 6 +) + +// IRootContext is an interface to support dynamic dispatch. +type IRootContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + EOF() antlr.TerminalNode + AllMatchEntry() []IMatchEntryContext + MatchEntry(i int) IMatchEntryContext + AllWHITESPACE() []antlr.TerminalNode + WHITESPACE(i int) antlr.TerminalNode + + // IsRootContext differentiates from other interfaces. + IsRootContext() +} + +type RootContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyRootContext() *RootContext { + var p = new(RootContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MatchParserRULE_root + return p +} + +func InitEmptyRootContext(p *RootContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MatchParserRULE_root +} + +func (*RootContext) IsRootContext() {} + +func NewRootContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *RootContext { + var p = new(RootContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = MatchParserRULE_root + + return p +} + +func (s *RootContext) GetParser() antlr.Parser { return s.parser } + +func (s *RootContext) EOF() antlr.TerminalNode { + return s.GetToken(MatchParserEOF, 0) +} + +func (s *RootContext) AllMatchEntry() []IMatchEntryContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IMatchEntryContext); ok { + len++ + } + } + + tst := make([]IMatchEntryContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IMatchEntryContext); ok { + tst[i] = t.(IMatchEntryContext) + i++ + } + } + + return tst +} + +func (s *RootContext) MatchEntry(i int) IMatchEntryContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IMatchEntryContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IMatchEntryContext) +} + +func (s *RootContext) AllWHITESPACE() []antlr.TerminalNode { + return s.GetTokens(MatchParserWHITESPACE) +} + +func (s *RootContext) WHITESPACE(i int) antlr.TerminalNode { + return s.GetToken(MatchParserWHITESPACE, i) +} + +func (s *RootContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *RootContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *RootContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MatchListener); ok { + listenerT.EnterRoot(s) + } +} + +func (s *RootContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MatchListener); ok { + listenerT.ExitRoot(s) + } +} + +func (p *MatchParser) Root() (localctx IRootContext) { + localctx = NewRootContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 0, MatchParserRULE_root) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(15) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == MatchParserSTRING || _la == MatchParserQUOTED_STRING { + { + p.SetState(14) + p.MatchEntry() + } + + } + p.SetState(23) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for _la == MatchParserWHITESPACE { + { + p.SetState(17) + p.Match(MatchParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(19) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == MatchParserSTRING || _la == MatchParserQUOTED_STRING { + { + p.SetState(18) + p.MatchEntry() + } + + } + + p.SetState(25) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(26) + p.Match(MatchParserEOF) + 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 +} + +// IMatchEntryContext is an interface to support dynamic dispatch. +type IMatchEntryContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Criteria() ICriteriaContext + Separator() ISeparatorContext + Values() IValuesContext + + // IsMatchEntryContext differentiates from other interfaces. + IsMatchEntryContext() +} + +type MatchEntryContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyMatchEntryContext() *MatchEntryContext { + var p = new(MatchEntryContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MatchParserRULE_matchEntry + return p +} + +func InitEmptyMatchEntryContext(p *MatchEntryContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MatchParserRULE_matchEntry +} + +func (*MatchEntryContext) IsMatchEntryContext() {} + +func NewMatchEntryContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *MatchEntryContext { + var p = new(MatchEntryContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = MatchParserRULE_matchEntry + + return p +} + +func (s *MatchEntryContext) GetParser() antlr.Parser { return s.parser } + +func (s *MatchEntryContext) Criteria() ICriteriaContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ICriteriaContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ICriteriaContext) +} + +func (s *MatchEntryContext) Separator() ISeparatorContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ISeparatorContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ISeparatorContext) +} + +func (s *MatchEntryContext) Values() IValuesContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IValuesContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IValuesContext) +} + +func (s *MatchEntryContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *MatchEntryContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *MatchEntryContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MatchListener); ok { + listenerT.EnterMatchEntry(s) + } +} + +func (s *MatchEntryContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MatchListener); ok { + listenerT.ExitMatchEntry(s) + } +} + +func (p *MatchParser) MatchEntry() (localctx IMatchEntryContext) { + localctx = NewMatchEntryContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 2, MatchParserRULE_matchEntry) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(28) + p.Criteria() + } + p.SetState(30) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 3, p.GetParserRuleContext()) == 1 { + { + p.SetState(29) + p.Separator() + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(33) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 4, p.GetParserRuleContext()) == 1 { + { + p.SetState(32) + p.Values() + } + + } else if p.HasError() { // JIM + 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 +} + +// ISeparatorContext is an interface to support dynamic dispatch. +type ISeparatorContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + WHITESPACE() antlr.TerminalNode + + // IsSeparatorContext differentiates from other interfaces. + IsSeparatorContext() +} + +type SeparatorContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptySeparatorContext() *SeparatorContext { + var p = new(SeparatorContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MatchParserRULE_separator + return p +} + +func InitEmptySeparatorContext(p *SeparatorContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MatchParserRULE_separator +} + +func (*SeparatorContext) IsSeparatorContext() {} + +func NewSeparatorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *SeparatorContext { + var p = new(SeparatorContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = MatchParserRULE_separator + + return p +} + +func (s *SeparatorContext) GetParser() antlr.Parser { return s.parser } + +func (s *SeparatorContext) WHITESPACE() antlr.TerminalNode { + return s.GetToken(MatchParserWHITESPACE, 0) +} + +func (s *SeparatorContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *SeparatorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *SeparatorContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MatchListener); ok { + listenerT.EnterSeparator(s) + } +} + +func (s *SeparatorContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MatchListener); ok { + listenerT.ExitSeparator(s) + } +} + +func (p *MatchParser) Separator() (localctx ISeparatorContext) { + localctx = NewSeparatorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 4, MatchParserRULE_separator) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(35) + p.Match(MatchParserWHITESPACE) + 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 +} + +// ICriteriaContext is an interface to support dynamic dispatch. +type ICriteriaContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + String_() IStringContext + + // IsCriteriaContext differentiates from other interfaces. + IsCriteriaContext() +} + +type CriteriaContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyCriteriaContext() *CriteriaContext { + var p = new(CriteriaContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MatchParserRULE_criteria + return p +} + +func InitEmptyCriteriaContext(p *CriteriaContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MatchParserRULE_criteria +} + +func (*CriteriaContext) IsCriteriaContext() {} + +func NewCriteriaContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *CriteriaContext { + var p = new(CriteriaContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = MatchParserRULE_criteria + + return p +} + +func (s *CriteriaContext) GetParser() antlr.Parser { return s.parser } + +func (s *CriteriaContext) String_() IStringContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IStringContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IStringContext) +} + +func (s *CriteriaContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *CriteriaContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *CriteriaContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MatchListener); ok { + listenerT.EnterCriteria(s) + } +} + +func (s *CriteriaContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MatchListener); ok { + listenerT.ExitCriteria(s) + } +} + +func (p *MatchParser) Criteria() (localctx ICriteriaContext) { + localctx = NewCriteriaContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 6, MatchParserRULE_criteria) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(37) + p.String_() + } + +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 +} + +// IValuesContext is an interface to support dynamic dispatch. +type IValuesContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllValue() []IValueContext + Value(i int) IValueContext + AllCOMMA() []antlr.TerminalNode + COMMA(i int) antlr.TerminalNode + + // IsValuesContext differentiates from other interfaces. + IsValuesContext() +} + +type ValuesContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyValuesContext() *ValuesContext { + var p = new(ValuesContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MatchParserRULE_values + return p +} + +func InitEmptyValuesContext(p *ValuesContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MatchParserRULE_values +} + +func (*ValuesContext) IsValuesContext() {} + +func NewValuesContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ValuesContext { + var p = new(ValuesContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = MatchParserRULE_values + + return p +} + +func (s *ValuesContext) GetParser() antlr.Parser { return s.parser } + +func (s *ValuesContext) AllValue() []IValueContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IValueContext); ok { + len++ + } + } + + tst := make([]IValueContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IValueContext); ok { + tst[i] = t.(IValueContext) + i++ + } + } + + return tst +} + +func (s *ValuesContext) Value(i int) IValueContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IValueContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IValueContext) +} + +func (s *ValuesContext) AllCOMMA() []antlr.TerminalNode { + return s.GetTokens(MatchParserCOMMA) +} + +func (s *ValuesContext) COMMA(i int) antlr.TerminalNode { + return s.GetToken(MatchParserCOMMA, i) +} + +func (s *ValuesContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ValuesContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ValuesContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MatchListener); ok { + listenerT.EnterValues(s) + } +} + +func (s *ValuesContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MatchListener); ok { + listenerT.ExitValues(s) + } +} + +func (p *MatchParser) Values() (localctx IValuesContext) { + localctx = NewValuesContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 8, MatchParserRULE_values) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(40) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == MatchParserSTRING || _la == MatchParserQUOTED_STRING { + { + p.SetState(39) + p.Value() + } + + } + p.SetState(48) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for _la == MatchParserCOMMA { + { + p.SetState(42) + p.Match(MatchParserCOMMA) + 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 == MatchParserSTRING || _la == MatchParserQUOTED_STRING { + { + p.SetState(43) + p.Value() + } + + } + + p.SetState(50) + 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 +} + +// IValueContext is an interface to support dynamic dispatch. +type IValueContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + String_() IStringContext + + // 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 = MatchParserRULE_value + return p +} + +func InitEmptyValueContext(p *ValueContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MatchParserRULE_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 = MatchParserRULE_value + + return p +} + +func (s *ValueContext) GetParser() antlr.Parser { return s.parser } + +func (s *ValueContext) String_() IStringContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IStringContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IStringContext) +} + +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.(MatchListener); ok { + listenerT.EnterValue(s) + } +} + +func (s *ValueContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MatchListener); ok { + listenerT.ExitValue(s) + } +} + +func (p *MatchParser) Value() (localctx IValueContext) { + localctx = NewValueContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 10, MatchParserRULE_value) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(51) + p.String_() + } + +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 +} + +// IStringContext is an interface to support dynamic dispatch. +type IStringContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + QUOTED_STRING() antlr.TerminalNode + STRING() antlr.TerminalNode + + // IsStringContext differentiates from other interfaces. + IsStringContext() +} + +type StringContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyStringContext() *StringContext { + var p = new(StringContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MatchParserRULE_string + return p +} + +func InitEmptyStringContext(p *StringContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MatchParserRULE_string +} + +func (*StringContext) IsStringContext() {} + +func NewStringContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *StringContext { + var p = new(StringContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = MatchParserRULE_string + + return p +} + +func (s *StringContext) GetParser() antlr.Parser { return s.parser } + +func (s *StringContext) QUOTED_STRING() antlr.TerminalNode { + return s.GetToken(MatchParserQUOTED_STRING, 0) +} + +func (s *StringContext) STRING() antlr.TerminalNode { + return s.GetToken(MatchParserSTRING, 0) +} + +func (s *StringContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *StringContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *StringContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MatchListener); ok { + listenerT.EnterString(s) + } +} + +func (s *StringContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MatchListener); ok { + listenerT.ExitString(s) + } +} + +func (p *MatchParser) String_() (localctx IStringContext) { + localctx = NewStringContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 12, MatchParserRULE_string) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(53) + _la = p.GetTokenStream().LA(1) + + if !(_la == MatchParserSTRING || _la == MatchParserQUOTED_STRING) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + +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/ssh_config/match-parser/parser_test.go b/handlers/ssh_config/match-parser/parser_test.go new file mode 100644 index 0000000..d308bc8 --- /dev/null +++ b/handlers/ssh_config/match-parser/parser_test.go @@ -0,0 +1,143 @@ +package matchparser + +import ( + "testing" +) + +func TestSimpleExample( + t *testing.T, +) { + offset := uint32(5) + input := "originalhost example.com user root" + + match := NewMatch() + errors := match.Parse(input, 32, offset) + + if len(errors) > 0 { + t.Fatalf("Expected no errors, but got %v", errors) + } + + if !(len(match.Entries) == 2) { + t.Errorf("Expected 2 entries, but got %v", len(match.Entries)) + } + + if !(match.Entries[0].Criteria.Type == MatchCriteriaTypeOriginalHost) { + t.Errorf("Expected OriginalHost, but got %v", match.Entries[0]) + } + + if !(match.Entries[0].Values.Values[0].Value.Value == "example.com" && match.Entries[0].Values.Values[0].Start.Character == 13+offset && match.Entries[0].Values.Values[0].End.Character == 23+offset+1) { + t.Errorf("Expected example.com, but got %v", match.Entries[0].Values.Values[0]) + } + + if !(match.Entries[1].Criteria.Type == MatchCriteriaTypeUser) { + t.Errorf("Expected User, but got %v", match.Entries[1]) + } + + if !(match.Entries[1].Values.Values[0].Value.Value == "root" && match.Entries[1].Values.Values[0].Start.Character == 30+offset && match.Entries[1].Values.Values[0].End.Character == 33+offset+1) { + t.Errorf("Expected root, but got %v", match.Entries[1].Values.Values[0]) + } +} + +func TestListExample( + t *testing.T, +) { + offset := uint32(20) + input := "originalhost example.com,example.org,example.net" + + match := NewMatch() + errors := match.Parse(input, 0, offset) + + if len(errors) > 0 { + t.Fatalf("Expected no errors, but got %v", errors) + } + + if !(len(match.Entries) == 1) { + t.Errorf("Expected 1 entries, but got %v", len(match.Entries)) + } + + if !(match.Entries[0].Criteria.Type == MatchCriteriaTypeOriginalHost) { + t.Errorf("Expected Address, but got %v", match.Entries[0]) + } + + if !(len(match.Entries[0].Values.Values) == 3) { + t.Errorf("Expected 3 values, but got %v", len(match.Entries[0].Values.Values)) + } + + if !(match.Entries[0].Values.Values[0].Value.Value == "example.com" && match.Entries[0].Values.Values[0].Start.Character == 13+offset && match.Entries[0].Values.Values[0].End.Character == 23+offset+1) { + t.Errorf("Expected example.com, but got %v", match.Entries[0].Values.Values[0]) + } +} + +func TestComplexExample( + t *testing.T, +) { + input := `originalhost laptop exec "[[ $(/usr/bin/dig +short laptop.lan) == '' ]]"` + + match := NewMatch() + errors := match.Parse(input, 0, 0) + + // TODO: Fix match so that it allows quotes + if len(errors) > 0 { + t.Fatalf("Expected no errors, but got %v", errors) + } + + if !(len(match.Entries) == 2) { + t.Errorf("Expected 2 entries, but got %v", len(match.Entries)) + } +} + +func TestIncompleteBetweenEntriesExample( + t *testing.T, +) { + input := "user root,admin,alice " + + match := NewMatch() + errors := match.Parse(input, 0, 0) + + if len(errors) > 0 { + t.Fatalf("Expected no errors, but got %v", errors) + } + + if !(len(match.Entries) == 1) { + t.Errorf("Expected 1 entries, but got %v", len(match.Entries)) + } + + if !(match.Entries[0].Criteria.Type == MatchCriteriaTypeUser) { + t.Errorf("Expected User, but got %v", match.Entries[0]) + } + + if !(len(match.Entries[0].Values.Values) == 3) { + t.Errorf("Expected 3 values, but got %v", len(match.Entries[0].Values.Values)) + } + + if !(match.Entries[0].Start.Character == 0 && match.Entries[0].End.Character == 21) { + t.Errorf("Expected 0-20, but got %v", match.Entries[0]) + } +} + +func TestIncompleteBetweenValuesExample( + t *testing.T, +) { + input := "user " + + match := NewMatch() + errors := match.Parse(input, 0, 0) + + if len(errors) > 0 { + t.Fatalf("Expected no errors, but got %v", errors) + } + + if !(len(match.Entries) == 1) { + t.Errorf("Expected 1 entries, but got %v", len(match.Entries)) + } + + if !(match.Entries[0].Criteria.Type == MatchCriteriaTypeUser) { + t.Errorf("Expected User, but got %v", match.Entries[0]) + } + + if !(len(match.Entries[0].Values.Values) == 0) { + t.Errorf("Expected 0 values, but got %v", match.Entries[0].Values) + } +} + + diff --git a/handlers/ssh_config/shared.go b/handlers/ssh_config/shared.go new file mode 100644 index 0000000..f701d68 --- /dev/null +++ b/handlers/ssh_config/shared.go @@ -0,0 +1,14 @@ +package sshconfig + +import ( + "config-lsp/handlers/ssh_config/ast" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +type SSHDocument struct { + Config *ast.SSHConfig + Indexes *ast.SSHIndexes +} + +var DocumentParserMap = map[protocol.DocumentUri]*SSHDocument{}