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.
This commit is contained in:
Ayke van Laethem
2021-05-20 14:25:25 +02:00
committed by Ron Evans
parent a38b5c4e01
commit 3339d0f47e
2 changed files with 59 additions and 38 deletions
+51 -30
View File
@@ -11,14 +11,30 @@ import (
"strings" "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. // parseConst parses the given string as a C constant.
func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) { func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
t := newTokenizer(pos, fset, value) t := newTokenizer(pos, fset, value)
expr, err := parseConstExpr(t) expr, err := parseConstExpr(t)
t.Next()
if t.token != token.EOF { if t.token != token.EOF {
return nil, &scanner.Error{ return nil, &scanner.Error{
Pos: t.fset.Position(t.pos), 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 return expr, err
@@ -26,14 +42,46 @@ func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *sc
// parseConstExpr parses a stream of C tokens to a Go expression. // parseConstExpr parses a stream of C tokens to a Go expression.
func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) { func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
switch t.token { if t.token == token.EOF {
case token.LPAREN: return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: "empty constant",
}
}
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 lparen := t.pos
t.Next() t.Next()
x, err := parseConstExpr(t) x, err := parseConstExpr(t)
if err != nil { if err != nil {
return nil, err return nil, err
} }
t.Next()
if t.token != token.RPAREN { if t.token != token.RPAREN {
return nil, unexpectedToken(t, token.RPAREN) return nil, unexpectedToken(t, token.RPAREN)
} }
@@ -42,34 +90,7 @@ func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
X: x, X: x,
Rparen: t.pos, Rparen: t.pos,
} }
t.Next()
return expr, nil 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:
return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: "empty constant",
}
default:
return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: fmt.Sprintf("unexpected token %s", t.token),
}
}
} }
// unexpectedToken returns an error of the form "unexpected token FOO, expected // unexpectedToken returns an error of the form "unexpected token FOO, expected
+2 -2
View File
@@ -18,7 +18,7 @@ func TestParseConst(t *testing.T) {
{`(5)`, `(5)`}, {`(5)`, `(5)`},
{`(((5)))`, `(5)`}, {`(((5)))`, `(5)`},
{`)`, `error: 1:1: unexpected token )`}, {`)`, `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 )`}, {" \t)", `error: 1:4: unexpected token )`},
{`5.8f`, `5.8`}, {`5.8f`, `5.8`},
{`foo`, `C.foo`}, {`foo`, `C.foo`},
@@ -30,7 +30,7 @@ func TestParseConst(t *testing.T) {
{`'a'`, `'a'`}, {`'a'`, `'a'`},
{`0b10`, `0b10`}, {`0b10`, `0b10`},
{`0x1234_5678`, `0x1234_5678`}, {`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() fset := token.NewFileSet()
startPos := fset.AddFile("", -1, 1000).Pos(0) startPos := fset.AddFile("", -1, 1000).Pos(0)