Skip to content

Commit 86ad9d2

Browse files
Use the v2 scanner as the default and remove the old one (#144)
* Parse tests passing * Fix a header edge case * Fix bad resolver test case * All working! * Remove token.URL and ast.KindURL * Fix pathological parser recovery fuzz test * oops lint * Add todo
1 parent 594f0b5 commit 86ad9d2

File tree

135 files changed

+1373
-2920
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

135 files changed

+1373
-2920
lines changed

internal/syntax/ast/ast_test.go

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ func TestNode(t *testing.T) {
3636
},
3737
{
3838
name: "url literal",
39-
node: ast.URL{
39+
node: ast.TextLiteral{
4040
Value: "https://example.com",
41-
Token: token.Token{Kind: token.URL, Start: 0, End: 19},
42-
Type: ast.KindURL,
41+
Token: token.Token{Kind: token.Text, Start: 0, End: 19},
42+
Type: ast.KindTextLiteral,
4343
},
44-
start: token.Token{Kind: token.URL, Start: 0, End: 19},
45-
end: token.Token{Kind: token.URL, Start: 0, End: 19},
46-
kind: ast.KindURL,
44+
start: token.Token{Kind: token.Text, Start: 0, End: 19},
45+
end: token.Token{Kind: token.Text, Start: 0, End: 19},
46+
kind: ast.KindTextLiteral,
4747
},
4848
{
4949
name: "ident",
@@ -240,7 +240,7 @@ func TestNode(t *testing.T) {
240240
},
241241
URL: ast.TextLiteral{
242242
Value: "https://example.com",
243-
Token: token.Token{Kind: token.URL, Start: 9, End: 28},
243+
Token: token.Token{Kind: token.Text, Start: 9, End: 28},
244244
Type: ast.KindTextLiteral,
245245
},
246246
Method: ast.Method{
@@ -260,7 +260,7 @@ func TestNode(t *testing.T) {
260260
Body: nil,
261261
URL: ast.TextLiteral{
262262
Value: "https://example.com",
263-
Token: token.Token{Kind: token.URL, Start: 9, End: 28},
263+
Token: token.Token{Kind: token.Text, Start: 9, End: 28},
264264
Type: ast.KindTextLiteral,
265265
},
266266
Method: ast.Method{
@@ -271,7 +271,7 @@ func TestNode(t *testing.T) {
271271
Type: ast.KindRequest,
272272
},
273273
start: token.Token{Kind: token.Separator, Start: 0, End: 3},
274-
end: token.Token{Kind: token.URL, Start: 9, End: 28},
274+
end: token.Token{Kind: token.Text, Start: 9, End: 28},
275275
kind: ast.KindRequest,
276276
},
277277
{
@@ -308,7 +308,7 @@ func TestNode(t *testing.T) {
308308
},
309309
URL: ast.TextLiteral{
310310
Value: "https://example.com",
311-
Token: token.Token{Kind: token.URL, Start: 9, End: 28},
311+
Token: token.Token{Kind: token.Text, Start: 9, End: 28},
312312
Type: ast.KindTextLiteral,
313313
},
314314
Method: ast.Method{
@@ -340,7 +340,7 @@ func TestNode(t *testing.T) {
340340
},
341341
URL: ast.TextLiteral{
342342
Value: "https://example.com",
343-
Token: token.Token{Kind: token.URL, Start: 9, End: 28},
343+
Token: token.Token{Kind: token.Text, Start: 9, End: 28},
344344
Type: ast.KindTextLiteral,
345345
},
346346
Method: ast.Method{
@@ -375,10 +375,10 @@ func TestNode(t *testing.T) {
375375
// |------ left ------|-- interp --|- right -|
376376
name: "full interp",
377377
node: ast.InterpolatedExpression{
378-
Left: ast.URL{
378+
Left: ast.TextLiteral{
379379
Value: "https://example.com/",
380-
Token: token.Token{Kind: token.URL, Start: 0, End: 20},
381-
Type: ast.KindURL,
380+
Token: token.Token{Kind: token.Text, Start: 0, End: 20},
381+
Type: ast.KindTextLiteral,
382382
},
383383
Interp: ast.Interp{
384384
Expr: ast.Ident{
@@ -390,15 +390,15 @@ func TestNode(t *testing.T) {
390390
Close: token.Token{Kind: token.CloseInterp, Start: 31, End: 33},
391391
Type: ast.KindInterp,
392392
},
393-
Right: ast.URL{
393+
Right: ast.TextLiteral{
394394
Value: "/items/123",
395-
Token: token.Token{Kind: token.URL, Start: 33, End: 43},
396-
Type: ast.KindURL,
395+
Token: token.Token{Kind: token.Text, Start: 33, End: 43},
396+
Type: ast.KindTextLiteral,
397397
},
398398
Type: ast.KindInterpolatedExpression,
399399
},
400-
start: token.Token{Kind: token.URL, Start: 0, End: 20},
401-
end: token.Token{Kind: token.URL, Start: 33, End: 43},
400+
start: token.Token{Kind: token.Text, Start: 0, End: 20},
401+
end: token.Token{Kind: token.Text, Start: 33, End: 43},
402402
kind: ast.KindInterpolatedExpression,
403403
},
404404
{
@@ -417,26 +417,26 @@ func TestNode(t *testing.T) {
417417
Close: token.Token{Kind: token.CloseInterp, Start: 11, End: 13},
418418
Type: ast.KindInterp,
419419
},
420-
Right: ast.URL{
420+
Right: ast.TextLiteral{
421421
Value: "/items/3",
422-
Token: token.Token{Kind: token.URL, Start: 13, End: 21},
423-
Type: ast.KindURL,
422+
Token: token.Token{Kind: token.Text, Start: 13, End: 21},
423+
Type: ast.KindTextLiteral,
424424
},
425425
Type: ast.KindInterpolatedExpression,
426426
},
427427
start: token.Token{Kind: token.OpenInterp, Start: 0, End: 2},
428-
end: token.Token{Kind: token.URL, Start: 13, End: 21},
428+
end: token.Token{Kind: token.Text, Start: 13, End: 21},
429429
kind: ast.KindInterpolatedExpression,
430430
},
431431
{
432432
// https://example/com/{{ endpoint }}
433433
// |------ left ------|-- interp ---|
434434
name: "interp no right",
435435
node: ast.InterpolatedExpression{
436-
Left: ast.URL{
436+
Left: ast.TextLiteral{
437437
Value: "https://example.com/",
438-
Token: token.Token{Kind: token.URL, Start: 0, End: 20},
439-
Type: ast.KindURL,
438+
Token: token.Token{Kind: token.Text, Start: 0, End: 20},
439+
Type: ast.KindTextLiteral,
440440
},
441441
Interp: ast.Interp{
442442
Expr: ast.Ident{
@@ -451,7 +451,7 @@ func TestNode(t *testing.T) {
451451
Right: nil,
452452
Type: ast.KindInterpolatedExpression,
453453
},
454-
start: token.Token{Kind: token.URL, Start: 0, End: 20},
454+
start: token.Token{Kind: token.Text, Start: 0, End: 20},
455455
end: token.Token{Kind: token.CloseInterp, Start: 32, End: 34},
456456
kind: ast.KindInterpolatedExpression,
457457
},

internal/syntax/ast/expression.go

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -72,38 +72,6 @@ func (t TextLiteral) Kind() Kind {
7272
// expressionNode marks a [TextLiteral] as an [ast.Expression].
7373
func (t TextLiteral) expressionNode() {}
7474

75-
// URL is a literal URL expression.
76-
type URL struct {
77-
// The url value (unquoted)
78-
Value string `yaml:"value"`
79-
80-
// The [token.URL] token.
81-
Token token.Token `yaml:"token"`
82-
83-
// Type is [KindURL].
84-
Type Kind `yaml:"type"`
85-
}
86-
87-
// Start returns the first token of the URL, which is
88-
// obviously just the [token.URL].
89-
func (u URL) Start() token.Token {
90-
return u.Token
91-
}
92-
93-
// End returns the last token in the URL, which is also
94-
// the [token.URL].
95-
func (u URL) End() token.Token {
96-
return u.Token
97-
}
98-
99-
// Kind returns [KindURL].
100-
func (u URL) Kind() Kind {
101-
return u.Type
102-
}
103-
104-
// expressionNode marks a [URL] as an [ast.Expression].
105-
func (u URL) expressionNode() {}
106-
10775
// Interp is a text interpolation expression.
10876
type Interp struct {
10977
// Expr is the expression inside the interpolation.

internal/syntax/ast/kind.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ const (
1212
KindVarStatement // VarStatement
1313
KindIdent // Ident
1414
KindTextLiteral // TextLiteral
15-
KindURL // URL
1615
KindInterp // Interp
1716
KindPrompt // Prompt
1817
KindRequest // Request

internal/syntax/ast/kind_string.go

Lines changed: 14 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/syntax/parser/parser.go

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,15 @@ import (
2323
"go.followtheprocess.codes/zap/internal/syntax/token"
2424
)
2525

26-
// ErrParse is a generic parsing error, details on the error are passed
27-
// to the parser's [syntax.ErrorHandler] at the moment it occurs.
28-
var ErrParse = errors.New("parse error")
26+
var (
27+
// ErrParse is a generic parsing error, details on the error are passed
28+
// to the parser's [syntax.ErrorHandler] at the moment it occurs.
29+
ErrParse = errors.New("parse error")
30+
31+
// ErrAbort is a fatal parsing error, this means the parser encountered
32+
// pathological syntax it was not able to recover from and has aborted.
33+
ErrAbort = errors.New("fatal parse error, aborting")
34+
)
2935

3036
// Parser is the http file parser.
3137
type Parser struct {
@@ -72,14 +78,20 @@ func (p *Parser) Parse() (ast.File, error) {
7278
for !p.current.Is(token.EOF) {
7379
if p.current.Is(token.Error) {
7480
p.error("Error token from scanner")
75-
p.synchronise()
81+
82+
if abortErr := p.synchronise(); abortErr != nil {
83+
return file, fmt.Errorf("%w: %w", ErrAbort, abortErr)
84+
}
7685

7786
continue
7887
}
7988

8089
statement, err := p.parseStatement()
8190
if err != nil {
82-
p.synchronise()
91+
if abortErr := p.synchronise(); abortErr != nil {
92+
return file, fmt.Errorf("%w: %w", ErrAbort, abortErr)
93+
}
94+
8395
continue
8496
}
8597

@@ -226,14 +238,34 @@ func (p *Parser) bytes() []byte {
226238
//
227239
// synchronise discards tokens until it sees the next Separator, EOF after which
228240
// point the parser should be back in sync and can continue normally.
229-
func (p *Parser) synchronise() {
241+
//
242+
// It does this up to a maximum limit, which if reached will cause synchronise
243+
// to return a non-nil error, indicating that no progress has been made while
244+
// synchronising and the parser should abort.
245+
func (p *Parser) synchronise() error {
246+
p.hadErrors = true
247+
248+
// We try a max of 5 times to synchronise, if we haven't found
249+
// something good by then, bail out.
250+
const maxAttempts = 5
251+
252+
attempt := 0
253+
230254
for {
231255
p.advance()
232256

233-
if p.current.Is(token.Separator, token.EOF) {
257+
attempt++
258+
259+
if p.current.Is(token.Separator, token.EOF) || attempt >= maxAttempts {
234260
break
235261
}
236262
}
263+
264+
if attempt >= maxAttempts {
265+
return fmt.Errorf("%w: could not synchronise parser after %d attempts", ErrAbort, maxAttempts)
266+
}
267+
268+
return nil
237269
}
238270

239271
// parseStatement parses a statement.
@@ -438,7 +470,7 @@ func (p *Parser) parseRequest() (ast.Request, error) {
438470

439471
result.Method = method
440472

441-
if err = p.expect(token.URL, token.OpenInterp); err != nil {
473+
if err = p.expect(token.Text, token.OpenInterp); err != nil {
442474
return result, err
443475
}
444476

@@ -491,6 +523,9 @@ func (p *Parser) parseRequest() (ast.Request, error) {
491523
}
492524

493525
result.Body = bodyFile
526+
case token.Error:
527+
p.error("parse error while parsing request body")
528+
return result, ErrParse
494529
default:
495530
// Nothing, not all requests have a body
496531
}
@@ -548,8 +583,6 @@ func (p *Parser) parseExpression(precedence int) (ast.Expression, error) {
548583
expr, err = p.parseInterpolatedExpression(nil)
549584
case token.Ident:
550585
expr = p.parseIdent()
551-
case token.URL:
552-
expr = p.parseURL()
553586
case token.Body:
554587
expr, err = p.parseBody()
555588
default:
@@ -619,7 +652,7 @@ func (p *Parser) parseInterpolatedExpression(left ast.Expression) (ast.Interpola
619652

620653
precedence := p.current.Precedence()
621654

622-
if p.next.Is(token.Text, token.OpenInterp, token.Ident, token.URL, token.Body) && p.shouldParseRHS(left) {
655+
if p.next.Is(token.Text, token.OpenInterp, token.Body) && p.shouldParseRHS(left) {
623656
p.advance()
624657

625658
right, err := p.parseExpression(precedence)
@@ -663,8 +696,6 @@ func (p *Parser) shouldParseRHS(left ast.Expression) bool {
663696
return p.next.Is(token.Text)
664697
case ast.KindIdent:
665698
return p.next.Is(token.Ident)
666-
case ast.KindURL:
667-
return p.next.Is(token.URL)
668699
case ast.KindBody:
669700
return p.next.Is(token.Body)
670701
default:
@@ -683,17 +714,6 @@ func (p *Parser) parseTextLiteral() ast.TextLiteral {
683714
return text
684715
}
685716

686-
// parseURL parses a URL literal.
687-
func (p *Parser) parseURL() ast.URL {
688-
result := ast.URL{
689-
Value: p.text(),
690-
Token: p.current,
691-
Type: ast.KindURL,
692-
}
693-
694-
return result
695-
}
696-
697717
// parseHeader parses a Header statement.
698718
func (p *Parser) parseHeader() (ast.Header, error) {
699719
result := ast.Header{
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
go test fuzz v1
2+
[]byte("### Body\nPOST https://api.somewhere.com/items/1\n\n{\n \"somethi\xfeS\xe7C\xb3\x8f?ng\": \"here\"\n}\n\n<> response.json\n")
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
# TODO: The positions are off by 1 or 2 in some places
2+
13
-- src.http --
24
### Bad
35
GET https://blowup.com
46

57
< £$%^&
68
-- want.txt --
79
bad-body-file.txtar:4:1-2: Error token from scanner
8-
bad-body-file.txtar:4:5: unrecognised character: '£'
10+
bad-body-file.txtar:4:4: unexpected character: '£'

internal/syntax/parser/testdata/invalid/bad-headers.txtar

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ GET https://api.something.com/v1
66
Content-Type application/json
77
-- want.txt --
88
bad-headers.txtar:3:1-13: Error token from scanner
9-
bad-headers.txtar:3:13: expected ':', got ' '
9+
bad-headers.txtar:3:12: invalid header, expected ':', got ' '

0 commit comments

Comments
 (0)