about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--pkg/lang/ast/nodes.go9
-rw-r--r--pkg/lang/ast/op.go8
-rw-r--r--pkg/lang/ast/stmt.go5
-rw-r--r--pkg/lang/parser/exprs.go6
-rw-r--r--pkg/lang/parser/parser_test.go109
-rw-r--r--pkg/lang/parser/stmts.go126
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) {