aboutsummaryrefslogtreecommitdiff
path: root/pkg/lang
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2022-07-27 22:53:35 +0000
committerMel <einebeere@gmail.com>2022-07-27 22:53:35 +0000
commitd79973cb9df8660fe89810507557f5ba86256f30 (patch)
tree201774dae1fa63e6b962e7be9aeea20c033bb5e3 /pkg/lang
parent4f23155ca7f8591cae0be6938610386513d24b7f (diff)
downloadjinx-d79973cb9df8660fe89810507557f5ba86256f30.tar.zst
jinx-d79973cb9df8660fe89810507557f5ba86256f30.zip
Parse type declaration statements
Diffstat (limited to 'pkg/lang')
-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) {