From 3339d0f47e3083fba80a7335c3401eb0f7a93298 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 20 May 2021 14:25:25 +0200 Subject: [PATCH] cgo: create skeleton of a Pratt parser This converts the existing const parser to the basics of a Pratt parser, following the book "Writing An Interpreter In Go" by Thorsten Ball. It doesn't really do anything interesting yet, it simply converts the existing code (with existing tests) to the new structure. --- cgo/const.go | 93 +++++++++++++++++++++++++++++------------------ cgo/const_test.go | 4 +- 2 files changed, 59 insertions(+), 38 deletions(-) diff --git a/cgo/const.go b/cgo/const.go index c2451bc0..6e7d7ade 100644 --- a/cgo/const.go +++ b/cgo/const.go @@ -11,14 +11,30 @@ import ( "strings" ) +var prefixParseFns map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error) + +func init() { + // This must be done in an init function to avoid an initialization order + // failure. + prefixParseFns = map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error){ + token.IDENT: parseIdent, + token.INT: parseBasicLit, + token.FLOAT: parseBasicLit, + token.STRING: parseBasicLit, + token.CHAR: parseBasicLit, + token.LPAREN: parseParenExpr, + } +} + // parseConst parses the given string as a C constant. func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) { t := newTokenizer(pos, fset, value) expr, err := parseConstExpr(t) + t.Next() if t.token != token.EOF { return nil, &scanner.Error{ Pos: t.fset.Position(t.pos), - Msg: "unexpected token " + t.token.String(), + Msg: "unexpected token " + t.token.String() + ", expected end of expression", } } return expr, err @@ -26,50 +42,55 @@ func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *sc // parseConstExpr parses a stream of C tokens to a Go expression. func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) { - switch t.token { - case token.LPAREN: - lparen := t.pos - t.Next() - x, err := parseConstExpr(t) - if err != nil { - return nil, err - } - if t.token != token.RPAREN { - return nil, unexpectedToken(t, token.RPAREN) - } - expr := &ast.ParenExpr{ - Lparen: lparen, - X: x, - Rparen: t.pos, - } - t.Next() - return expr, nil - case token.INT, token.FLOAT, token.STRING, token.CHAR: - expr := &ast.BasicLit{ - ValuePos: t.pos, - Kind: t.token, - Value: t.value, - } - t.Next() - return expr, nil - case token.IDENT: - expr := &ast.Ident{ - NamePos: t.pos, - Name: "C." + t.value, - } - t.Next() - return expr, nil - case token.EOF: + if t.token == token.EOF { return nil, &scanner.Error{ Pos: t.fset.Position(t.pos), Msg: "empty constant", } - default: + } + prefix := prefixParseFns[t.token] + if prefix == nil { return nil, &scanner.Error{ Pos: t.fset.Position(t.pos), Msg: fmt.Sprintf("unexpected token %s", t.token), } } + leftExpr, err := prefix(t) + return leftExpr, err +} + +func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) { + return &ast.Ident{ + NamePos: t.pos, + Name: "C." + t.value, + }, nil +} + +func parseBasicLit(t *tokenizer) (ast.Expr, *scanner.Error) { + return &ast.BasicLit{ + ValuePos: t.pos, + Kind: t.token, + Value: t.value, + }, nil +} + +func parseParenExpr(t *tokenizer) (ast.Expr, *scanner.Error) { + lparen := t.pos + t.Next() + x, err := parseConstExpr(t) + if err != nil { + return nil, err + } + t.Next() + if t.token != token.RPAREN { + return nil, unexpectedToken(t, token.RPAREN) + } + expr := &ast.ParenExpr{ + Lparen: lparen, + X: x, + Rparen: t.pos, + } + return expr, nil } // unexpectedToken returns an error of the form "unexpected token FOO, expected diff --git a/cgo/const_test.go b/cgo/const_test.go index cfba4507..1402c18e 100644 --- a/cgo/const_test.go +++ b/cgo/const_test.go @@ -18,7 +18,7 @@ func TestParseConst(t *testing.T) { {`(5)`, `(5)`}, {`(((5)))`, `(5)`}, {`)`, `error: 1:1: unexpected token )`}, - {`5)`, `error: 1:2: unexpected token )`}, + {`5)`, `error: 1:2: unexpected token ), expected end of expression`}, {" \t)", `error: 1:4: unexpected token )`}, {`5.8f`, `5.8`}, {`foo`, `C.foo`}, @@ -30,7 +30,7 @@ func TestParseConst(t *testing.T) { {`'a'`, `'a'`}, {`0b10`, `0b10`}, {`0x1234_5678`, `0x1234_5678`}, - {`5 5`, `error: 1:3: unexpected token INT`}, // test for a bugfix + {`5 5`, `error: 1:3: unexpected token INT, expected end of expression`}, // test for a bugfix } { fset := token.NewFileSet() startPos := fset.AddFile("", -1, 1000).Pos(0)