diff options
| author | Mel <einebeere@gmail.com> | 2022-04-21 03:19:06 +0200 |
|---|---|---|
| committer | Mel <einebeere@gmail.com> | 2022-04-21 03:19:06 +0200 |
| commit | b5a9660b6ac42bce27c746e76013c3ce5992743a (patch) | |
| tree | 0e9bd21a30aae25fe21a7a3994a48ac2cea8c955 /pkg | |
| parent | 5267c0e8653b431cfd2c06212cdba4f22225bd02 (diff) | |
| download | jinx-b5a9660b6ac42bce27c746e76013c3ce5992743a.tar.zst jinx-b5a9660b6ac42bce27c746e76013c3ce5992743a.zip | |
Lang parser prototype
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/lang/ast/op.go | 102 | ||||
| -rw-r--r-- | pkg/lang/parser/exprs.go | 390 | ||||
| -rw-r--r-- | pkg/lang/parser/parser.go | 99 | ||||
| -rw-r--r-- | pkg/lang/parser/parser_test.go | 321 | ||||
| -rw-r--r-- | pkg/lang/parser/stmts.go | 372 | ||||
| -rw-r--r-- | pkg/lang/scanner/token/token.go | 9 |
6 files changed, 1286 insertions, 7 deletions
diff --git a/pkg/lang/ast/op.go b/pkg/lang/ast/op.go index a708c78..f5fce51 100644 --- a/pkg/lang/ast/op.go +++ b/pkg/lang/ast/op.go @@ -1,13 +1,18 @@ package ast +import ( + "fmt" + "jinx/pkg/lang/scanner/token" +) + type BinOp int const ( - BinOpAdd BinOp = iota - BinOpSub - BinOpMul - BinOpDiv - BinOpMod + BinOpPlus BinOp = iota + BinOpMinus + BinOpStar + BinOpSlash + BinOpPercent BinOpAssign BinOpEq @@ -18,9 +23,92 @@ const ( BinOpGte ) +func BinOpFromToken(tok token.Token) (BinOp, bool) { + var op BinOp + switch tok.Kind { + case token.Plus: + op = BinOpPlus + case token.Minus: + op = BinOpMinus + case token.Star: + op = BinOpStar + case token.Slash: + op = BinOpSlash + case token.Percent: + op = BinOpPercent + case token.Assign: + op = BinOpAssign + case token.Eq: + op = BinOpEq + case token.Neq: + op = BinOpNeq + case token.Lt: + op = BinOpLt + case token.Lte: + op = BinOpLte + case token.Gt: + op = BinOpGt + case token.Gte: + op = BinOpGte + default: + return 0, false + } + + return op, true +} + +func (op BinOp) Precedence() int { + switch op { + case BinOpPlus, BinOpMinus: + return 1 + case BinOpStar, BinOpSlash, BinOpPercent: + return 2 + case BinOpAssign: + return 3 + case BinOpEq, BinOpNeq, BinOpLt, BinOpLte, BinOpGt, BinOpGte: + return 4 + default: + panic(fmt.Sprintf("unknown binary operator: %d", op)) + } +} + +type Associativity int + +const ( + AssociativityLeft Associativity = iota + AssociativityRight +) + +func (op BinOp) Associativity() Associativity { + switch op { + case BinOpPlus, BinOpMinus, BinOpStar, BinOpSlash, BinOpPercent: + return AssociativityLeft + case BinOpAssign: + return AssociativityRight + case BinOpEq, BinOpNeq, BinOpLt, BinOpLte, BinOpGt, BinOpGte: + return AssociativityLeft + default: + panic(fmt.Sprintf("unknown binary operator: %d", op)) + } +} + type UnOp int const ( - UnOpNot UnOp = iota - UnOpNeg + UnOpBang UnOp = iota + UnOpMinus ) + +func UnOpFromToken(tok token.Token) (UnOp, bool) { + var op UnOp + switch tok.Kind { + case token.Bang: + op = UnOpBang + case token.Minus: + op = UnOpMinus + default: + return 0, false + } + + return op, true +} 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 +} diff --git a/pkg/lang/scanner/token/token.go b/pkg/lang/scanner/token/token.go index 840a420..1ccd864 100644 --- a/pkg/lang/scanner/token/token.go +++ b/pkg/lang/scanner/token/token.go @@ -20,3 +20,12 @@ func New(kind TokenKind, at Loc, data any) Token { Data: data, } } + +func (t Token) CanEndStmt() bool { + switch t.Kind { + case EOF, EOL, SemiColon: + return true + default: + return false + } +} |
