diff options
Diffstat (limited to 'pkg/lang')
| -rw-r--r-- | pkg/lang/ast/nodes.go | 9 | ||||
| -rw-r--r-- | pkg/lang/ast/op.go | 8 | ||||
| -rw-r--r-- | pkg/lang/ast/stmt.go | 5 | ||||
| -rw-r--r-- | pkg/lang/parser/exprs.go | 6 | ||||
| -rw-r--r-- | pkg/lang/parser/parser_test.go | 109 | ||||
| -rw-r--r-- | pkg/lang/parser/stmts.go | 126 |
6 files changed, 252 insertions, 11 deletions
diff --git a/pkg/lang/ast/nodes.go b/pkg/lang/ast/nodes.go index 117d8bf..74ebd2a 100644 --- a/pkg/lang/ast/nodes.go +++ b/pkg/lang/ast/nodes.go @@ -17,3 +17,12 @@ type CondNode struct { Cond Expr Then BlockNode } + +type TypeMethodNode struct { + At source.Loc + HasThis bool + IsConstructor bool + Name IdentNode + Args []IdentNode + Body BlockNode +} diff --git a/pkg/lang/ast/op.go b/pkg/lang/ast/op.go index 6f48e0c..063a309 100644 --- a/pkg/lang/ast/op.go +++ b/pkg/lang/ast/op.go @@ -21,6 +21,8 @@ const ( BinOpLte BinOpGt BinOpGte + + BinOpDot ) func BinOpFromToken(tok token.Token) (BinOp, bool) { @@ -50,6 +52,8 @@ func BinOpFromToken(tok token.Token) (BinOp, bool) { op = BinOpGt case token.Gte: op = BinOpGte + case token.Dot: + op = BinOpDot default: return 0, false } @@ -67,6 +71,8 @@ func (op BinOp) Precedence() int { return 3 case BinOpStar, BinOpSlash, BinOpPercent: return 4 + case BinOpDot: + return 5 default: panic(fmt.Sprintf("unknown binary operator: %d", op)) @@ -88,6 +94,8 @@ func (op BinOp) Associativity() Associativity { return AssociativityRight case BinOpEq, BinOpNeq, BinOpLt, BinOpLte, BinOpGt, BinOpGte: return AssociativityLeft + case BinOpDot: + return AssociativityLeft default: panic(fmt.Sprintf("unknown binary operator: %d", op)) } diff --git a/pkg/lang/ast/stmt.go b/pkg/lang/ast/stmt.go index 8a538f2..7545bee 100644 --- a/pkg/lang/ast/stmt.go +++ b/pkg/lang/ast/stmt.go @@ -41,7 +41,10 @@ type StmtFnDecl struct { Body BlockNode } -type StmtTypeDecl struct {} +type StmtTypeDecl struct { + Name IdentNode + Methods []TypeMethodNode +} type StmtVarDecl struct { Name IdentNode diff --git a/pkg/lang/parser/exprs.go b/pkg/lang/parser/exprs.go index d0725f6..80b0fd6 100644 --- a/pkg/lang/parser/exprs.go +++ b/pkg/lang/parser/exprs.go @@ -213,11 +213,15 @@ func (p *Parser) parseFnLitExpr() (ast.Expr, error) { return ast.Expr{}, err } - params, err := p.parseFnParams() + hasThis, params, err := p.parseFnParams() if err != nil { return ast.Expr{}, err } + if hasThis { + return ast.Expr{}, fmt.Errorf("function literal cannot have 'this' parameter") + } + // TODO: Also parse just an expression block, err := p.parseBlock() if err != nil { diff --git a/pkg/lang/parser/parser_test.go b/pkg/lang/parser/parser_test.go index 8cecfa8..de97928 100644 --- a/pkg/lang/parser/parser_test.go +++ b/pkg/lang/parser/parser_test.go @@ -662,6 +662,115 @@ func TestForInStmt(t *testing.T) { }, program.Stmts[0]) } +func TestTypeDeclStmt(t *testing.T) { + src := sourceify( + `type Foo {`, + ` (bar) {`, + ` this.bar = bar`, + ` }`, + ` `, + ` fn baz(this) {`, + ` return this.bar`, + ` }`, + `}`, + ) + + p := cheatWithScanner(t, src) + program, err := p.Parse() + require.NoError(t, err) + + require.Equal(t, 1, len(program.Stmts)) + require.Equal(t, ast.Stmt{ + At: source.NewLoc(0, 0), + Kind: ast.StmtKindTypeDecl, + Value: ast.StmtTypeDecl{ + Name: ast.IdentNode{At: source.NewLoc(0, 5), Value: "Foo"}, + Methods: []ast.TypeMethodNode{ + { + At: source.NewLoc(1, 1), + HasThis: false, + IsConstructor: true, + Name: ast.IdentNode{}, + Args: []ast.IdentNode{ + {At: source.NewLoc(1, 2), Value: "bar"}, + }, + Body: ast.BlockNode{ + At: source.NewLoc(1, 7), + Stmts: []ast.Stmt{ + {At: source.NewLoc(1, 8), Kind: 0, Value: ast.StmtEmpty{}}, + { + At: source.NewLoc(2, 2), + Kind: ast.StmtKindExpr, + Value: ast.StmtExpr{ + Value: ast.Expr{ + At: source.NewLoc(2, 2), + Kind: ast.ExprKindBinary, + Value: ast.ExprBinary{ + Left: ast.Expr{ + At: source.NewLoc(2, 2), + Kind: ast.ExprKindBinary, + Value: ast.ExprBinary{ + Left: ast.Expr{At: source.NewLoc(2, 2), Kind: ast.ExprKindThis, Value: ast.ExprThis{}}, + Op: ast.BinOpDot, + Right: ast.Expr{ + At: source.NewLoc(2, 7), + Kind: ast.ExprKindIdent, + Value: ast.ExprIdent{Value: ast.IdentNode{At: source.NewLoc(2, 7), Value: "bar"}}, + }, + }, + }, + Op: ast.BinOpAssign, + Right: ast.Expr{ + At: source.NewLoc(2, 13), + Kind: ast.ExprKindIdent, + Value: ast.ExprIdent{ + Value: ast.IdentNode{At: source.NewLoc(2, 13), Value: "bar"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + At: source.NewLoc(5, 1), + HasThis: true, + IsConstructor: false, + Name: ast.IdentNode{At: source.NewLoc(5, 4), Value: "baz"}, + Args: []ast.IdentNode{}, + Body: ast.BlockNode{ + At: source.NewLoc(5, 14), + Stmts: []ast.Stmt{ + {At: source.NewLoc(5, 15), Kind: ast.StmtKindEmpty, Value: ast.StmtEmpty{}}, + { + At: source.NewLoc(6, 2), + Kind: ast.StmtKindReturn, + Value: ast.StmtReturn{ + Value: ast.Expr{ + At: source.NewLoc(6, 9), + Kind: ast.ExprKindBinary, + Value: ast.ExprBinary{ + Left: ast.Expr{At: source.NewLoc(6, 9), Kind: ast.ExprKindThis, Value: ast.ExprThis{}}, + Op: ast.BinOpDot, + Right: ast.Expr{ + At: source.NewLoc(6, 14), + Kind: ast.ExprKindIdent, + Value: ast.ExprIdent{Value: ast.IdentNode{At: source.NewLoc(6, 14), Value: "bar"}}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, program.Stmts[0]) +} + func sourceify(lines ...string) string { return strings.Join(lines, "\n") } diff --git a/pkg/lang/parser/stmts.go b/pkg/lang/parser/stmts.go index 7d10e61..a0bba28 100644 --- a/pkg/lang/parser/stmts.go +++ b/pkg/lang/parser/stmts.go @@ -54,11 +54,15 @@ func (p *Parser) parseFnDeclStmt() (ast.Stmt, error) { return ast.Stmt{}, err } - params, err := p.parseFnParams() + hasThis, params, err := p.parseFnParams() if err != nil { return ast.Stmt{}, err } + if hasThis { + return ast.Stmt{}, fmt.Errorf("function cannot have 'this' as a parameter") + } + block, err := p.parseBlock() if err != nil { return ast.Stmt{}, err @@ -75,20 +79,36 @@ func (p *Parser) parseFnDeclStmt() (ast.Stmt, error) { }, nil } -func (p *Parser) parseFnParams() ([]ast.IdentNode, error) { - // FnParams = "(" ( IDENT ("," IDENT)* )? ")" +func (p *Parser) parseFnParams() (bool, []ast.IdentNode, error) { + // FnParams = "(" ( (IDENT | "this") ("," IDENT)* )? ")" if _, err := p.expect(token.LParen); err != nil { - return nil, err + return false, nil, err } + hasThis := false params := []ast.IdentNode{} if p.peek().Kind != token.RParen { for { + if p.peek().Kind == token.KwThis { + hasThis = true + if _, err := p.expect(token.KwThis); err != nil { + return false, nil, err + } + + if p.peek().Kind != token.Comma { + break + } else { + if _, err := p.expect(token.Comma); err != nil { + return false, nil, err + } + } + } + param, err := p.parseIdent() if err != nil { - return nil, err + return false, nil, err } params = append(params, param) @@ -98,20 +118,108 @@ func (p *Parser) parseFnParams() ([]ast.IdentNode, error) { } if _, err := p.expect(token.Comma); err != nil { - return nil, err + return false, nil, err } } } if _, err := p.expect(token.RParen); err != nil { - return nil, err + return false, nil, err } - return params, nil + return hasThis, params, nil } func (p *Parser) parseTypeDeclStmt() (ast.Stmt, error) { - panic("not implemented") + // TypeDeclStmt = "type" IDENT "{" (FnDeclStmt)* "}" + + typeTok, err := p.expect(token.KwType) + if err != nil { + return ast.Stmt{}, err + } + + name, err := p.parseIdent() + if err != nil { + return ast.Stmt{}, err + } + + if _, err := p.expect(token.LBrace); err != nil { + return ast.Stmt{}, err + } + + if err := p.parseStmtEnd(); err != nil { + return ast.Stmt{}, err + } + + methods := make([]ast.TypeMethodNode, 0) + + for p.peek().Kind != token.RBrace { + node, err := p.parseTypeMethodNode() + if err != nil { + return ast.Stmt{}, err + } + + methods = append(methods, node) + + if err := p.parseStmtEnd(); err != nil { + return ast.Stmt{}, err + } + } + + if _, err := p.expect(token.RBrace); err != nil { + return ast.Stmt{}, err + } + + return ast.Stmt{ + At: typeTok.At, + Kind: ast.StmtKindTypeDecl, + Value: ast.StmtTypeDecl{ + Name: name, + Methods: methods, + }, + }, nil + +} + +func (p *Parser) parseTypeMethodNode() (ast.TypeMethodNode, error) { + var err error + + startTok := p.peek() + isConstructor := false + name := ast.IdentNode{} + + if p.peek().Kind == token.KwFn { + startTok, err = p.expect(token.KwFn) + if err != nil { + return ast.TypeMethodNode{}, err + } + + name, err = p.parseIdent() + if err != nil { + return ast.TypeMethodNode{}, err + } + } else { + isConstructor = true + } + + hasThis, params, err := p.parseFnParams() + if err != nil { + return ast.TypeMethodNode{}, err + } + + block, err := p.parseBlock() + if err != nil { + return ast.TypeMethodNode{}, err + } + + return ast.TypeMethodNode{ + At: startTok.At, + HasThis: hasThis, + IsConstructor: isConstructor, + Name: name, + Args: params, + Body: block, + }, nil } func (p *Parser) parseVarDeclStmt() (ast.Stmt, error) { |
