diff --git a/internal/lsp/server.go b/internal/lsp/server.go index f626eda..2e65ca8 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -311,13 +311,18 @@ func handleDidOpen(params DidOpenTextDocumentParams) { documents[params.TextDocument.URI] = params.TextDocument.Text p := parser.NewParser(params.TextDocument.Text) config, err := p.Parse() + if err != nil { publishParserError(params.TextDocument.URI, err) - return + } else { + publishParserError(params.TextDocument.URI, nil) + } + + if config != nil { + tree.AddFile(path, config) + tree.ResolveReferences() + runValidation(params.TextDocument.URI) } - tree.AddFile(path, config) - tree.ResolveReferences() - runValidation(params.TextDocument.URI) } func handleDidChange(params DidChangeTextDocumentParams) { @@ -329,13 +334,18 @@ func handleDidChange(params DidChangeTextDocumentParams) { path := uriToPath(params.TextDocument.URI) p := parser.NewParser(text) config, err := p.Parse() + if err != nil { publishParserError(params.TextDocument.URI, err) - return + } else { + publishParserError(params.TextDocument.URI, nil) + } + + if config != nil { + tree.AddFile(path, config) + tree.ResolveReferences() + runValidation(params.TextDocument.URI) } - tree.AddFile(path, config) - tree.ResolveReferences() - runValidation(params.TextDocument.URI) } func handleFormatting(params DocumentFormattingParams) []TextEdit { @@ -426,6 +436,19 @@ func runValidation(uri string) { } func publishParserError(uri string, err error) { + if err == nil { + notification := JsonRpcMessage{ + Jsonrpc: "2.0", + Method: "textDocument/publishDiagnostics", + Params: mustMarshal(PublishDiagnosticsParams{ + URI: uri, + Diagnostics: []LSPDiagnostic{}, + }), + } + send(notification) + return + } + var line, col int var msg string // Try parsing "line:col: message" diff --git a/internal/parser/parser.go b/internal/parser/parser.go index ba8e6a8..f464dd7 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -11,6 +11,7 @@ type Parser struct { buf []Token comments []Comment pragmas []Pragma + errors []error } func NewParser(input string) *Parser { @@ -19,6 +20,10 @@ func NewParser(input string) *Parser { } } +func (p *Parser) addError(pos Position, msg string) { + p.errors = append(p.errors, fmt.Errorf("%d:%d: %s", pos.Line, pos.Column, msg)) +} + func (p *Parser) next() Token { if len(p.buf) > 0 { t := p.buf[0] @@ -71,72 +76,82 @@ func (p *Parser) Parse() (*Configuration, error) { continue } - def, err := p.parseDefinition() - if err != nil { - return nil, err + def, ok := p.parseDefinition() + if ok { + config.Definitions = append(config.Definitions, def) + } else { + // Synchronization: skip token if not consumed to make progress + if p.peek() == tok { + p.next() + } } - config.Definitions = append(config.Definitions, def) } config.Comments = p.comments config.Pragmas = p.pragmas - return config, nil + + var err error + if len(p.errors) > 0 { + err = p.errors[0] + } + return config, err } -func (p *Parser) parseDefinition() (Definition, error) { +func (p *Parser) parseDefinition() (Definition, bool) { tok := p.next() switch tok.Type { case TokenIdentifier: - // Could be Field = Value OR Node = { ... } name := tok.Value - if p.next().Type != TokenEqual { - return nil, fmt.Errorf("%d:%d: expected =", tok.Position.Line, tok.Position.Column) + if p.peek().Type != TokenEqual { + p.addError(tok.Position, "expected =") + return nil, false } + p.next() // Consume = - // Disambiguate based on RHS nextTok := p.peek() if nextTok.Type == TokenLBrace { - // Check if it looks like a Subnode (contains definitions) or Array (contains values) if p.isSubnodeLookahead() { - sub, err := p.parseSubnode() - if err != nil { - return nil, err + sub, ok := p.parseSubnode() + if !ok { + return nil, false } return &ObjectNode{ Position: tok.Position, Name: name, Subnode: sub, - }, nil + }, true } } - // Default to Field - val, err := p.parseValue() - if err != nil { - return nil, err + val, ok := p.parseValue() + if !ok { + return nil, false } return &Field{ Position: tok.Position, Name: name, Value: val, - }, nil + }, true case TokenObjectIdentifier: - // node = subnode name := tok.Value - if p.next().Type != TokenEqual { - return nil, fmt.Errorf("%d:%d: expected =", tok.Position.Line, tok.Position.Column) + if p.peek().Type != TokenEqual { + p.addError(tok.Position, "expected =") + return nil, false } - sub, err := p.parseSubnode() - if err != nil { - return nil, err + p.next() // Consume = + + sub, ok := p.parseSubnode() + if !ok { + return nil, false } return &ObjectNode{ Position: tok.Position, Name: name, Subnode: sub, - }, nil + }, true default: - return nil, fmt.Errorf("%d:%d: unexpected token %v", tok.Position.Line, tok.Position.Column, tok.Value) + p.addError(tok.Position, fmt.Sprintf("unexpected token %v", tok.Value)) + return nil, false } } @@ -176,10 +191,11 @@ func (p *Parser) isSubnodeLookahead() bool { return false } -func (p *Parser) parseSubnode() (Subnode, error) { +func (p *Parser) parseSubnode() (Subnode, bool) { tok := p.next() if tok.Type != TokenLBrace { - return Subnode{}, fmt.Errorf("%d:%d: expected {", tok.Position.Line, tok.Position.Column) + p.addError(tok.Position, "expected {") + return Subnode{}, false } sub := Subnode{Position: tok.Position} for { @@ -190,18 +206,22 @@ func (p *Parser) parseSubnode() (Subnode, error) { break } if t.Type == TokenEOF { - return sub, fmt.Errorf("%d:%d: unexpected EOF, expected }", t.Position.Line, t.Position.Column) + p.addError(t.Position, "unexpected EOF, expected }") + return sub, false } - def, err := p.parseDefinition() - if err != nil { - return sub, err + def, ok := p.parseDefinition() + if ok { + sub.Definitions = append(sub.Definitions, def) + } else { + if p.peek() == t { + p.next() + } } - sub.Definitions = append(sub.Definitions, def) } - return sub, nil + return sub, true } -func (p *Parser) parseValue() (Value, error) { +func (p *Parser) parseValue() (Value, bool) { tok := p.next() switch tok.Type { case TokenString: @@ -209,24 +229,21 @@ func (p *Parser) parseValue() (Value, error) { Position: tok.Position, Value: strings.Trim(tok.Value, "\""), Quoted: true, - }, nil + }, true case TokenNumber: - // Simplistic handling if strings.Contains(tok.Value, ".") || strings.Contains(tok.Value, "e") { f, _ := strconv.ParseFloat(tok.Value, 64) - return &FloatValue{Position: tok.Position, Value: f, Raw: tok.Value}, nil + return &FloatValue{Position: tok.Position, Value: f, Raw: tok.Value}, true } i, _ := strconv.ParseInt(tok.Value, 0, 64) - return &IntValue{Position: tok.Position, Value: i, Raw: tok.Value}, nil + return &IntValue{Position: tok.Position, Value: i, Raw: tok.Value}, true case TokenBool: return &BoolValue{Position: tok.Position, Value: tok.Value == "true"}, - nil + true case TokenIdentifier: - // reference? - return &ReferenceValue{Position: tok.Position, Value: tok.Value}, nil + return &ReferenceValue{Position: tok.Position, Value: tok.Value}, true case TokenLBrace: - // array arr := &ArrayValue{Position: tok.Position} for { t := p.peek() @@ -239,14 +256,15 @@ func (p *Parser) parseValue() (Value, error) { p.next() continue } - val, err := p.parseValue() - if err != nil { - return nil, err + val, ok := p.parseValue() + if !ok { + return nil, false } arr.Elements = append(arr.Elements, val) } - return arr, nil + return arr, true default: - return nil, fmt.Errorf("%d:%d: unexpected value token %v", tok.Position.Line, tok.Position.Column, tok.Value) + p.addError(tok.Position, fmt.Sprintf("unexpected value token %v", tok.Value)) + return nil, false } }