aboutsummaryrefslogtreecommitdiff
path: root/pkg/lang/parser
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/lang/parser')
-rw-r--r--pkg/lang/parser/exprs.go390
-rw-r--r--pkg/lang/parser/parser.go99
-rw-r--r--pkg/lang/parser/parser_test.go321
-rw-r--r--pkg/lang/parser/stmts.go372
4 files changed, 1182 insertions, 0 deletions
diff --git a/pkg/lang/parser/exprs.go b/pkg/lang/parser/exprs.go
new file mode 100644
index 0000000..b08864a
--- /dev/null
+++ b/pkg/lang/parser/exprs.go
@@ -0,0 +1,390 @@
+package parser
+
+import (
+ "fmt"
+ "jinx/pkg/lang/ast"
+ "jinx/pkg/lang/scanner/token"
+)
+
+func (p *Parser) parseExpr() (ast.Expr, error) {
+ return p.parseBinaryExpr()
+}
+
+func (p *Parser) parseBinaryExpr() (ast.Expr, error) {
+ // BinaryExpr = UnaryExpr (BinOp BinaryExpr)?
+
+ left, err := p.parseUnaryExpr()
+ if err != nil {
+ return ast.Expr{}, err
+ }
+
+ if op, ok := ast.BinOpFromToken(p.peek()); ok {
+ p.next()
+
+ right, err := p.parseBinaryExpr()
+ if err != nil {
+ return ast.Expr{}, err
+ }
+
+ // Check precedence and associativity.
+ if right.Kind == ast.ExprKindBinary {
+ rightBin := right.Value.(ast.ExprBinary)
+
+ needsSwitch := (op.Precedence() > rightBin.Op.Precedence()) ||
+ (op.Precedence() == rightBin.Op.Precedence() && op.Associativity() == ast.AssociativityLeft)
+
+ if needsSwitch {
+ left = ast.Expr{
+ At: left.At,
+ Kind: ast.ExprKindBinary,
+ Value: ast.ExprBinary{
+ Left: left,
+ Op: op,
+ Right: rightBin.Left,
+ },
+ }
+
+ right = rightBin.Right
+
+ op = rightBin.Op
+ }
+ }
+
+ left = ast.Expr{
+ At: left.At,
+ Kind: ast.ExprKindBinary,
+ Value: ast.ExprBinary{
+ Left: left,
+ Op: op,
+ Right: right,
+ },
+ }
+ }
+
+ return left, nil
+}
+
+func (p *Parser) parseUnaryExpr() (ast.Expr, error) {
+ // UnaryExpr = PostfixExpr (UnOp UnaryExpr)?
+ if op, ok := ast.UnOpFromToken(p.peek()); ok {
+ tok := p.next()
+
+ expr, err := p.parseUnaryExpr()
+ if err != nil {
+ return ast.Expr{}, err
+ }
+
+ return ast.Expr{
+ At: tok.At,
+ Kind: ast.ExprKindUnary,
+ Value: ast.ExprUnary{
+ Op: op,
+ Value: expr,
+ },
+ }, nil
+ }
+
+ return p.parsePostfixExpr()
+}
+
+func (p *Parser) parsePostfixExpr() (ast.Expr, error) {
+ // PostfixExpr = SubscriptionExpr | CallExpr
+
+ obj, err := p.parseUnitExpr()
+ if err != nil {
+ return ast.Expr{}, err
+ }
+
+ switch p.peek().Kind {
+ case token.LBracket:
+ return p.parseArrayIndexExpr(obj)
+ case token.LParen:
+ return p.parseCallExpr(obj)
+ default:
+ return obj, nil
+ }
+}
+
+func (p *Parser) parseArrayIndexExpr(obj ast.Expr) (ast.Expr, error) {
+ // SubscriptionExpr = UnitExpr ( "[" Expr "]" )*
+
+ if _, err := p.expect(token.LBracket); err != nil {
+ return ast.Expr{}, err
+ }
+
+ index, err := p.parseExpr()
+ if err != nil {
+ return ast.Expr{}, err
+ }
+
+ if _, err := p.expect(token.RBracket); err != nil {
+ return ast.Expr{}, err
+ }
+
+ return ast.Expr{
+ At: obj.At,
+ Kind: ast.ExprKindSubscription,
+ Value: ast.ExprSubscription{
+ Obj: obj,
+ Key: index,
+ },
+ }, nil
+
+}
+
+func (p *Parser) parseCallExpr(callee ast.Expr) (ast.Expr, error) {
+ // CallExpr = UnitExpr ( "(" ( Expr ( "," Expr )* )? ")" )*
+
+ if _, err := p.expect(token.LParen); err != nil {
+ return ast.Expr{}, err
+ }
+
+ args := []ast.Expr{}
+ if p.peek().Kind != token.RParen {
+ for {
+ arg, err := p.parseExpr()
+ if err != nil {
+ return ast.Expr{}, err
+ }
+
+ args = append(args, arg)
+
+ if p.peek().Kind == token.RParen {
+ break
+ }
+
+ if _, err := p.expect(token.Comma); err != nil {
+ return ast.Expr{}, err
+ }
+ }
+ }
+
+ if _, err := p.expect(token.RParen); err != nil {
+ return ast.Expr{}, err
+ }
+
+ return ast.Expr{
+ At: callee.At,
+ Kind: ast.ExprKindCall,
+ Value: ast.ExprCall{
+ Callee: callee,
+ Args: args,
+ },
+ }, nil
+
+}
+
+func (p *Parser) parseUnitExpr() (ast.Expr, error) {
+ // UnitExpr = GroupExpr |
+ // FnLitExpr |
+ // ArrayLitExpr |
+ // Ident |
+ // IntLitExpr |
+ // FloatLitExpr |
+ // StringLitExpr |
+ // ValueLitExpr |
+ switch p.peek().Kind {
+ case token.LParen:
+ return p.parseGroupExpr()
+ case token.KwFn:
+ return p.parseFnLitExpr()
+ case token.LBracket:
+ return p.parseArrayLitExpr()
+ case token.Ident:
+ return p.parseIdentExpr()
+ case token.Int:
+ return p.parseIntLitExpr()
+ case token.Float:
+ return p.parseFloatLitExpr()
+ case token.String:
+ return p.parseStringLitExpr()
+ case token.KwTrue, token.KwFalse, token.KwNull, token.KwThis:
+ return p.parseValueLitExpr()
+ default:
+ panic(fmt.Errorf("unexpected token %+v, wanted unit expression start", p.peek()))
+ }
+}
+
+func (p *Parser) parseGroupExpr() (ast.Expr, error) {
+ // GroupExpr = "(" Expr ")"
+
+ if _, err := p.expect(token.LParen); err != nil {
+ return ast.Expr{}, err
+ }
+
+ expr, err := p.parseExpr()
+ if err != nil {
+ return ast.Expr{}, err
+ }
+
+ if _, err := p.expect(token.RParen); err != nil {
+ return ast.Expr{}, err
+ }
+
+ return expr, nil
+}
+
+func (p *Parser) parseFnLitExpr() (ast.Expr, error) {
+ // FnLitExpr = "fn" FnParams (Block | Expr)
+ fnTok, err := p.expect(token.KwFn)
+ if err != nil {
+ return ast.Expr{}, err
+ }
+
+ params, err := p.parseFnParams()
+ if err != nil {
+ return ast.Expr{}, err
+ }
+
+ // TODO: Also parse just an expression
+ block, err := p.parseBlock()
+ if err != nil {
+ return ast.Expr{}, err
+ }
+
+ return ast.Expr{
+ At: fnTok.At,
+ Kind: ast.ExprKindFnLit,
+ Value: ast.ExprFnLit{
+ Args: params,
+ Body: block,
+ },
+ }, nil
+}
+
+func (p *Parser) parseArrayLitExpr() (ast.Expr, error) {
+ // ArrayLitExpr = "[" Expr ("," Expr)* "]"
+ bracketTok, err := p.expect(token.LBracket)
+ if err != nil {
+ return ast.Expr{}, err
+ }
+
+ elems := []ast.Expr{}
+ if p.peek().Kind != token.RBracket {
+ for {
+ elem, err := p.parseExpr()
+ if err != nil {
+ return ast.Expr{}, err
+ }
+
+ elems = append(elems, elem)
+
+ if p.peek().Kind == token.RBracket {
+ break
+ }
+
+ if _, err := p.expect(token.Comma); err != nil {
+ return ast.Expr{}, err
+ }
+ }
+ }
+
+ return ast.Expr{
+ At: bracketTok.At,
+ Kind: ast.ExprKindArrayLit,
+ Value: ast.ExprArrayLit{
+ Values: elems,
+ },
+ }, nil
+}
+
+func (p *Parser) parseIdentExpr() (ast.Expr, error) {
+ // IdentExpr = IDENT
+ node, err := p.parseIdent()
+ if err != nil {
+ return ast.Expr{}, err
+ }
+
+ return ast.Expr{
+ At: node.At,
+ Kind: ast.ExprKindIdent,
+ Value: ast.ExprIdent{
+ Value: node,
+ },
+ }, nil
+}
+
+func (p *Parser) parseIntLitExpr() (ast.Expr, error) {
+ // IntLitExpr = INT
+ tok, err := p.expect(token.Int)
+ if err != nil {
+ return ast.Expr{}, err
+ }
+
+ return ast.Expr{
+ At: tok.At,
+ Kind: ast.ExprKindIntLit,
+ Value: ast.ExprIntLit{
+ Value: tok.Data.(uint64),
+ },
+ }, nil
+}
+
+func (p *Parser) parseFloatLitExpr() (ast.Expr, error) {
+ // FloatLitExpr = FLOAT
+ tok, err := p.expect(token.Float)
+ if err != nil {
+ return ast.Expr{}, err
+ }
+
+ return ast.Expr{
+ At: tok.At,
+ Kind: ast.ExprKindFloatLit,
+ Value: ast.ExprFloatLit{
+ Value: tok.Data.(float64),
+ },
+ }, nil
+}
+
+func (p *Parser) parseStringLitExpr() (ast.Expr, error) {
+ // StringLitExpr = STRING
+ tok, err := p.expect(token.String)
+ if err != nil {
+ return ast.Expr{}, err
+ }
+
+ return ast.Expr{
+ At: tok.At,
+ Kind: ast.ExprKindStringLit,
+ Value: ast.ExprStringLit{
+ Value: tok.Data.(string),
+ },
+ }, nil
+}
+
+func (p *Parser) parseValueLitExpr() (ast.Expr, error) {
+ // ValueLitExpr = "true" | "false" | "null" | "this"
+ tok := p.next()
+ switch tok.Kind {
+ case token.KwTrue:
+ return ast.Expr{
+ At: tok.At,
+ Kind: ast.ExprKindBoolLit,
+ Value: ast.ExprBoolLit{
+ Value: true,
+ },
+ }, nil
+ case token.KwFalse:
+ return ast.Expr{
+ At: tok.At,
+ Kind: ast.ExprKindBoolLit,
+ Value: ast.ExprBoolLit{
+ Value: false,
+ },
+ }, nil
+ case token.KwNull:
+ return ast.Expr{
+ At: tok.At,
+ Kind: ast.ExprKindNullLit,
+ Value: ast.ExprNullLit{},
+ }, nil
+ case token.KwThis:
+ return ast.Expr{
+ At: tok.At,
+ Kind: ast.ExprKindThis,
+ Value: ast.ExprThis{},
+ }, nil
+ default:
+ panic(fmt.Errorf("unexpected token %+v, wanted value literal", tok))
+ }
+}
diff --git a/pkg/lang/parser/parser.go b/pkg/lang/parser/parser.go
new file mode 100644
index 0000000..1a9d1d0
--- /dev/null
+++ b/pkg/lang/parser/parser.go
@@ -0,0 +1,99 @@
+package parser
+
+import (
+ "fmt"
+ "jinx/pkg/lang/ast"
+ "jinx/pkg/lang/scanner/token"
+)
+
+type Parser struct {
+ tokens []token.Token
+ pos int
+}
+
+func New(tokens []token.Token) *Parser {
+ return &Parser{tokens: tokens, pos: 0}
+}
+
+func (p *Parser) Parse() (ast.Program, error) {
+ program := ast.Program{}
+
+ for p.pos < len(p.tokens) {
+ stmt, err := p.parseStmt()
+ if err != nil {
+ return ast.Program{}, err
+ }
+
+ if err = p.parseStmtEnd(); err != nil {
+ return ast.Program{}, err
+ }
+
+ program.Stmts = append(program.Stmts, stmt)
+ }
+
+ return program, nil
+}
+
+func (p *Parser) parseBlock() (ast.BlockNode, error) {
+ braceTok, err := p.expect(token.LBrace)
+ if err != nil {
+ return ast.BlockNode{}, err
+ }
+
+ stmts := []ast.Stmt{}
+ for {
+ stmt, err := p.parseStmt()
+ if err != nil {
+ return ast.BlockNode{}, err
+ }
+
+ stmts = append(stmts, stmt)
+
+ if err = p.parseStmtEnd(); err != nil {
+ return ast.BlockNode{}, err
+ }
+
+ if p.peek().Kind == token.RBrace {
+ break
+ }
+ }
+
+ if _, err := p.expect(token.RBrace); err != nil {
+ return ast.BlockNode{}, err
+ }
+
+ return ast.BlockNode{
+ At: braceTok.At,
+ Stmts: stmts,
+ }, nil
+}
+
+func (p *Parser) parseIdent() (ast.IdentNode, error) {
+ tok, err := p.expect(token.Ident)
+ if err != nil {
+ return ast.IdentNode{}, err
+ }
+
+ return ast.IdentNode{
+ At: tok.At,
+ Value: tok.Data.(string),
+ }, nil
+}
+
+func (p *Parser) next() token.Token {
+ p.pos++
+ return p.tokens[p.pos-1]
+}
+
+func (p *Parser) expect(kind token.TokenKind) (token.Token, error) {
+ tok := p.next()
+ if tok.Kind != kind {
+ return token.Token{}, fmt.Errorf("expected %v, got %v", kind, tok.Kind)
+ }
+
+ return tok, nil
+}
+
+func (p *Parser) peek() token.Token {
+ return p.tokens[p.pos]
+}
diff --git a/pkg/lang/parser/parser_test.go b/pkg/lang/parser/parser_test.go
new file mode 100644
index 0000000..969e878
--- /dev/null
+++ b/pkg/lang/parser/parser_test.go
@@ -0,0 +1,321 @@
+package parser_test
+
+import (
+ "jinx/pkg/lang/ast"
+ "jinx/pkg/lang/parser"
+ "jinx/pkg/lang/scanner"
+ "jinx/pkg/lang/scanner/token"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestIntLit(t *testing.T) {
+ source := `123`
+ p := cheatWithScanner(t, source)
+ program, err := p.Parse()
+ require.NoError(t, err)
+
+ require.Equal(t, 1, len(program.Stmts))
+ require.Equal(t, ast.Stmt{
+ Kind: ast.StmtKindExpr,
+ Value: ast.StmtExpr{
+ Value: ast.Expr{
+ Kind: ast.ExprKindIntLit,
+ Value: ast.ExprIntLit{Value: 123},
+ },
+ },
+ }, program.Stmts[0])
+}
+
+func TestSimpleBinaryExpr(t *testing.T) {
+ source := `1 + 2`
+ p := cheatWithScanner(t, source)
+ program, err := p.Parse()
+ require.NoError(t, err)
+
+ require.Equal(t, 1, len(program.Stmts))
+ require.Equal(t, ast.Stmt{
+ Kind: ast.StmtKindExpr,
+ Value: ast.StmtExpr{
+ Value: ast.Expr{
+ Kind: ast.ExprKindBinary,
+ Value: ast.ExprBinary{
+ Left: ast.Expr{
+ Kind: ast.ExprKindIntLit,
+ Value: ast.ExprIntLit{Value: 1},
+ },
+ Op: ast.BinOpPlus,
+ Right: ast.Expr{
+ At: token.NewLoc(0, 4),
+ Kind: ast.ExprKindIntLit,
+ Value: ast.ExprIntLit{Value: 2},
+ },
+ },
+ },
+ },
+ }, program.Stmts[0])
+}
+
+func TestLeftAssocBinaryExpr(t *testing.T) {
+ source := `1 + 2 + 3`
+ p := cheatWithScanner(t, source)
+ program, err := p.Parse()
+ require.NoError(t, err)
+
+ require.Equal(t, 1, len(program.Stmts))
+ require.Equal(t, ast.Stmt{
+ Kind: ast.StmtKindExpr,
+ Value: ast.StmtExpr{
+ Value: ast.Expr{
+ Kind: ast.ExprKindBinary,
+ Value: ast.ExprBinary{
+ Left: ast.Expr{
+ Kind: ast.ExprKindBinary,
+ Value: ast.ExprBinary{
+ Left: ast.Expr{
+ Kind: ast.ExprKindIntLit,
+ Value: ast.ExprIntLit{Value: 1},
+ },
+ Op: ast.BinOpPlus,
+ Right: ast.Expr{
+ At: token.NewLoc(0, 4),
+ Kind: ast.ExprKindIntLit,
+ Value: ast.ExprIntLit{Value: 2},
+ },
+ },
+ },
+ Op: ast.BinOpPlus,
+ Right: ast.Expr{
+ At: token.NewLoc(0, 8),
+ Kind: ast.ExprKindIntLit,
+ Value: ast.ExprIntLit{Value: 3},
+ },
+ },
+ },
+ },
+ }, program.Stmts[0])
+}
+
+func TestRightAssocBinaryExpr(t *testing.T) {
+ source := `x = y = z`
+ p := cheatWithScanner(t, source)
+ program, err := p.Parse()
+ require.NoError(t, err)
+
+ require.Equal(t, 1, len(program.Stmts))
+ require.Equal(t, ast.Stmt{
+ Kind: ast.StmtKindExpr,
+ Value: ast.StmtExpr{
+ Value: ast.Expr{
+ Kind: ast.ExprKindBinary,
+ Value: ast.ExprBinary{
+ Left: ast.Expr{
+ Kind: ast.ExprKindIdent,
+ Value: ast.ExprIdent{Value: ast.IdentNode{Value: "x"}},
+ },
+ Op: ast.BinOpAssign,
+ Right: ast.Expr{
+ At: token.NewLoc(0, 4),
+ Kind: ast.ExprKindBinary,
+ Value: ast.ExprBinary{
+ Left: ast.Expr{
+ At: token.NewLoc(0, 4),
+ Kind: ast.ExprKindIdent,
+ Value: ast.ExprIdent{Value: ast.IdentNode{At: token.NewLoc(0, 4), Value: "y"}},
+ },
+ Op: ast.BinOpAssign,
+ Right: ast.Expr{
+ At: token.NewLoc(0, 8),
+ Kind: ast.ExprKindIdent,
+ Value: ast.ExprIdent{Value: ast.IdentNode{At: token.NewLoc(0, 8), Value: "z"}},
+ },
+ },
+ },
+ },
+ },
+ },
+ }, program.Stmts[0])
+}
+
+func TestVarDecl(t *testing.T) {
+ source := `var x = 123`
+ p := cheatWithScanner(t, source)
+ program, err := p.Parse()
+ require.NoError(t, err)
+
+ require.Equal(t, 1, len(program.Stmts))
+ require.Equal(t, ast.Stmt{
+ Kind: ast.StmtKindVarDecl,
+ Value: ast.StmtVarDecl{
+ Name: ast.IdentNode{At: token.NewLoc(0, 4), Value: "x"},
+ Value: ast.Expr{
+ At: token.NewLoc(0, 8),
+ Kind: ast.ExprKindIntLit,
+ Value: ast.ExprIntLit{Value: 123},
+ },
+ },
+ }, program.Stmts[0])
+}
+
+func TestEmptyStmt(t *testing.T) {
+ source := `
+
+
+ ;
+
+
+ `
+ p := cheatWithScanner(t, source)
+ program, err := p.Parse()
+ require.NoError(t, err)
+
+ require.Equal(t, 4, len(program.Stmts))
+
+ expected := []ast.Stmt{
+ {
+ Kind: ast.StmtKindEmpty,
+ Value: ast.StmtEmpty{},
+ },
+ {
+ At: token.NewLoc(3, 1),
+ Kind: ast.StmtKindEmpty,
+ Value: ast.StmtEmpty{},
+ },
+ {
+ At: token.NewLoc(3, 2),
+ Kind: ast.StmtKindEmpty,
+ Value: ast.StmtEmpty{},
+ },
+ {
+ At: token.NewLoc(6, 1),
+ Kind: ast.StmtKindEmpty,
+ Value: ast.StmtEmpty{},
+ },
+ }
+
+ require.Equal(t, expected, program.Stmts)
+}
+
+func TestMultipleStmts(t *testing.T) {
+ source := sourceify(
+ `var x = 1`,
+ `var y = 2`,
+ `z = 3`,
+ )
+
+ p := cheatWithScanner(t, source)
+ program, err := p.Parse()
+ require.NoError(t, err)
+
+ require.Equal(t, 3, len(program.Stmts))
+
+ expected := []ast.Stmt{
+ {
+ Kind: ast.StmtKindVarDecl,
+ Value: ast.StmtVarDecl{
+ Name: ast.IdentNode{At: token.NewLoc(0, 4), Value: "x"},
+ Value: ast.Expr{
+ At: token.NewLoc(0, 8),
+ Kind: ast.ExprKindIntLit,
+ Value: ast.ExprIntLit{Value: 1},
+ },
+ },
+ },
+ {
+ At: token.NewLoc(1, 0),
+ Kind: ast.StmtKindVarDecl,
+ Value: ast.StmtVarDecl{
+ Name: ast.IdentNode{At: token.NewLoc(1, 4), Value: "y"},
+ Value: ast.Expr{
+ At: token.NewLoc(1, 8),
+ Kind: ast.ExprKindIntLit,
+ Value: ast.ExprIntLit{Value: 2},
+ },
+ },
+ },
+ {
+ At: token.NewLoc(2, 0),
+ Kind: ast.StmtKindExpr,
+ Value: ast.StmtExpr{
+ Value: ast.Expr{
+ At: token.NewLoc(2, 0),
+ Kind: ast.ExprKindBinary,
+ Value: ast.ExprBinary{
+ Left: ast.Expr{
+ At: token.NewLoc(2, 0),
+ Kind: ast.ExprKindIdent,
+ Value: ast.ExprIdent{Value: ast.IdentNode{At: token.NewLoc(2, 0), Value: "z"}},
+ },
+ Op: ast.BinOpAssign,
+ Right: ast.Expr{
+ At: token.NewLoc(2, 4),
+ Kind: ast.ExprKindIntLit,
+ Value: ast.ExprIntLit{Value: 3},
+ },
+ },
+ },
+ },
+ },
+ }
+ require.Equal(t, expected, program.Stmts)
+}
+
+func TestIfStmt(t *testing.T) {
+ source := sourceify(
+ `if false {`,
+ ` var x = 2`,
+ `}`,
+ )
+ p := cheatWithScanner(t, source)
+ program, err := p.Parse()
+ require.NoError(t, err)
+
+ require.Equal(t, 1, len(program.Stmts))
+ require.Equal(t, ast.Stmt{
+ Kind: ast.StmtKindIf,
+ Value: ast.StmtIf{
+ Cond: ast.Expr{
+ At: token.NewLoc(0, 3),
+ Kind: ast.ExprKindBoolLit,
+ Value: ast.ExprBoolLit{Value: false},
+ },
+ Then: ast.BlockNode{
+ At: token.NewLoc(0, 9),
+ Stmts: []ast.Stmt{
+ {
+ At: token.NewLoc(0, 10),
+ Kind: ast.StmtKindEmpty,
+ Value: ast.StmtEmpty{},
+ },
+ {
+ At: token.NewLoc(1, 1),
+ Kind: ast.StmtKindVarDecl,
+ Value: ast.StmtVarDecl{
+ Name: ast.IdentNode{At: token.NewLoc(1, 5), Value: "x"},
+ Value: ast.Expr{
+ At: token.NewLoc(1, 9),
+ Kind: ast.ExprKindIntLit,
+ Value: ast.ExprIntLit{Value: 2},
+ },
+ },
+ },
+ },
+ },
+ Elifs: []ast.CondNode{},
+ },
+ }, program.Stmts[0])
+}
+
+func sourceify(lines ...string) string {
+ return strings.Join(lines, "\n")
+}
+
+func cheatWithScanner(t *testing.T, source string) *parser.Parser {
+ s := scanner.New(strings.NewReader(source))
+ tokens, err := s.Scan()
+ require.NoError(t, err, "scanner error occurred while testing parser :(")
+
+ return parser.New(tokens)
+}
diff --git a/pkg/lang/parser/stmts.go b/pkg/lang/parser/stmts.go
new file mode 100644
index 0000000..52e590c
--- /dev/null
+++ b/pkg/lang/parser/stmts.go
@@ -0,0 +1,372 @@
+package parser
+
+import (
+ "fmt"
+ "jinx/pkg/lang/ast"
+ "jinx/pkg/lang/scanner/token"
+)
+
+func (p *Parser) parseStmt() (ast.Stmt, error) {
+ switch p.peek().Kind {
+ case token.KwUse:
+ return p.parseUseStmt()
+ case token.KwFn:
+ return p.parseFnDeclStmt()
+ case token.KwObject:
+ return p.parseObjectDeclStmt()
+ case token.KwVar:
+ return p.parseVarDeclStmt()
+ case token.KwIf:
+ return p.parseIfStmt()
+ case token.KwTry:
+ return p.parseTryStmt()
+ case token.KwReturn:
+ return p.parseReturnStmt()
+ case token.KwContinue:
+ return p.parseContinueStmt()
+ case token.KwBreak:
+ return p.parseBreakStmt()
+ case token.KwThrow:
+ return p.parseThrowStmt()
+ default:
+ if p.peek().CanEndStmt() {
+ return p.parseEmptyStmt()
+ } else {
+ return p.parseExprStmt()
+ }
+ }
+}
+
+func (p *Parser) parseUseStmt() (ast.Stmt, error) {
+ panic("not implemented")
+}
+
+func (p *Parser) parseFnDeclStmt() (ast.Stmt, error) {
+ fnTok, err := p.expect(token.KwFn)
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ name, err := p.parseIdent()
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ params, err := p.parseFnParams()
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ block, err := p.parseBlock()
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ return ast.Stmt{
+ At: fnTok.At,
+ Kind: ast.StmtKindFnDecl,
+ Value: ast.StmtFnDecl{
+ Name: name,
+ Args: params,
+ Body: block,
+ },
+ }, nil
+}
+
+func (p *Parser) parseFnParams() ([]ast.IdentNode, error) {
+ // FnParams = "(" ( IDENT ("," IDENT)* )? ")"
+
+ if _, err := p.expect(token.LParen); err != nil {
+ return nil, err
+ }
+
+ params := []ast.IdentNode{}
+
+ if p.peek().Kind != token.RParen {
+ for {
+ param, err := p.parseIdent()
+ if err != nil {
+ return nil, err
+ }
+
+ params = append(params, param)
+
+ if p.peek().Kind == token.RParen {
+ break
+ }
+
+ if _, err := p.expect(token.Comma); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ if _, err := p.expect(token.RParen); err != nil {
+ return nil, err
+ }
+
+ return params, nil
+}
+
+func (p *Parser) parseObjectDeclStmt() (ast.Stmt, error) {
+ panic("not implemented")
+}
+
+func (p *Parser) parseVarDeclStmt() (ast.Stmt, error) {
+ // VarDeclStmt = "var" IDENT "=" Expr
+
+ varTok, err := p.expect(token.KwVar)
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ name, err := p.parseIdent()
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ if _, err := p.expect(token.Assign); err != nil {
+ return ast.Stmt{}, err
+ }
+
+ expr, err := p.parseExpr()
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ return ast.Stmt{
+ At: varTok.At,
+ Kind: ast.StmtKindVarDecl,
+ Value: ast.StmtVarDecl{
+ Name: name,
+ Value: expr,
+ },
+ }, nil
+}
+
+func (p *Parser) parseIfStmt() (ast.Stmt, error) {
+ // IfStmt = "if" Expr Block ("elif" Expr Block)* ("else" Block)?
+ ifTok, err := p.expect(token.KwIf)
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ cond, err := p.parseExpr()
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ then, err := p.parseBlock()
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ elifs := []ast.CondNode{}
+
+ for p.peek().Kind == token.KwElif {
+ elifTok, err := p.expect(token.KwElif)
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ elifCond, err := p.parseExpr()
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ elifThen, err := p.parseBlock()
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ elifs = append(elifs, ast.CondNode{
+ At: elifTok.At,
+ Cond: elifCond,
+ Then: elifThen,
+ })
+ }
+
+ elseThen := ast.BlockNode{}
+
+ if p.peek().Kind == token.KwElse {
+ _, err := p.expect(token.KwElse)
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ elseThen, err = p.parseBlock()
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+ }
+
+ return ast.Stmt{
+ At: ifTok.At,
+ Kind: ast.StmtKindIf,
+ Value: ast.StmtIf{
+ Cond: cond,
+ Then: then,
+ Elifs: elifs,
+ Else: elseThen,
+ },
+ }, nil
+}
+
+func (p *Parser) parseTryStmt() (ast.Stmt, error) {
+ // TryStmt = "try" Block "catch" Ident ("finally" Block)?
+
+ tryTok, err := p.expect(token.KwTry)
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ tryBlock, err := p.parseBlock()
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ if _, err = p.expect(token.KwCatch); err != nil {
+ return ast.Stmt{}, err
+ }
+
+ catchName, err := p.parseIdent()
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ catchBlock, err := p.parseBlock()
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ finallyBlock := ast.BlockNode{}
+ if p.peek().Kind == token.KwFinally {
+ _, err := p.expect(token.KwFinally)
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ finallyBlock, err = p.parseBlock()
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+ }
+
+ return ast.Stmt{
+ At: tryTok.At,
+ Kind: ast.StmtKindTry,
+ Value: ast.StmtTry{
+ Try: tryBlock,
+ CatchedName: catchName,
+ Catch: catchBlock,
+ Finally: finallyBlock,
+ },
+ }, nil
+}
+
+func (p *Parser) parseReturnStmt() (ast.Stmt, error) {
+ // ReturnStmt = "return" (Expr)?
+ returnTok, err := p.expect(token.KwReturn)
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ expr := ast.Expr{}
+ if !p.peek().CanEndStmt() {
+ expr, err = p.parseExpr()
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+ }
+
+ return ast.Stmt{
+ At: returnTok.At,
+ Kind: ast.StmtKindReturn,
+ Value: ast.StmtReturn{
+ Value: expr,
+ },
+ }, nil
+}
+
+func (p *Parser) parseContinueStmt() (ast.Stmt, error) {
+ // ContinueStmt = "continue"
+ continueTok, err := p.expect(token.KwContinue)
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ return ast.Stmt{
+ At: continueTok.At,
+ Kind: ast.StmtKindContinue,
+ Value: ast.StmtContinue{},
+ }, nil
+}
+
+func (p *Parser) parseBreakStmt() (ast.Stmt, error) {
+ // BreakStmt = "break"
+
+ breakTok, err := p.expect(token.KwBreak)
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ return ast.Stmt{
+ At: breakTok.At,
+ Kind: ast.StmtKindBreak,
+ Value: ast.StmtBreak{},
+ }, nil
+}
+
+func (p *Parser) parseThrowStmt() (ast.Stmt, error) {
+ // ThrowStmt = "throw" Expr
+
+ throwTok, err := p.expect(token.KwThrow)
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ expr, err := p.parseExpr()
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ return ast.Stmt{
+ At: throwTok.At,
+ Kind: ast.StmtKindThrow,
+ Value: ast.StmtThrow{
+ Value: expr,
+ },
+ }, nil
+}
+
+func (p *Parser) parseExprStmt() (ast.Stmt, error) {
+ expr, err := p.parseExpr()
+ if err != nil {
+ return ast.Stmt{}, err
+ }
+
+ return ast.Stmt{
+ At: expr.At,
+ Kind: ast.StmtKindExpr,
+ Value: ast.StmtExpr{
+ Value: expr,
+ },
+ }, nil
+}
+
+func (p *Parser) parseEmptyStmt() (ast.Stmt, error) {
+ return ast.Stmt{
+ At: p.peek().At,
+ Kind: ast.StmtKindEmpty,
+ Value: ast.StmtEmpty{},
+ }, nil
+}
+
+func (p *Parser) parseStmtEnd() error {
+ tok := p.peek()
+ if !tok.CanEndStmt() {
+ panic(fmt.Errorf("wanted statement end, received: %+v", tok))
+ }
+ p.next()
+ return nil
+}