package parser_test import ( "jinx/pkg/lang/ast" "jinx/pkg/lang/parser" "jinx/pkg/lang/scanner" "jinx/pkg/libs/source" "strings" "testing" "github.com/stretchr/testify/require" ) func TestIntLit(t *testing.T) { src := `123` p := cheatWithScanner(t, src) 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) { src := `1 + 2` p := cheatWithScanner(t, src) 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: source.NewLoc(0, 4), Kind: ast.ExprKindIntLit, Value: ast.ExprIntLit{Value: 2}, }, }, }, }, }, program.Stmts[0]) } func TestLeftAssocBinaryExpr(t *testing.T) { src := `1 + 2 + 3` p := cheatWithScanner(t, src) 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: source.NewLoc(0, 4), Kind: ast.ExprKindIntLit, Value: ast.ExprIntLit{Value: 2}, }, }, }, Op: ast.BinOpPlus, Right: ast.Expr{ At: source.NewLoc(0, 8), Kind: ast.ExprKindIntLit, Value: ast.ExprIntLit{Value: 3}, }, }, }, }, }, program.Stmts[0]) } func TestRightAssocBinaryExpr(t *testing.T) { src := `x = y = z` p := cheatWithScanner(t, src) 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: source.NewLoc(0, 4), Kind: ast.ExprKindBinary, Value: ast.ExprBinary{ Left: ast.Expr{ At: source.NewLoc(0, 4), Kind: ast.ExprKindIdent, Value: ast.ExprIdent{Value: ast.IdentNode{At: source.NewLoc(0, 4), Value: "y"}}, }, Op: ast.BinOpAssign, Right: ast.Expr{ At: source.NewLoc(0, 8), Kind: ast.ExprKindIdent, Value: ast.ExprIdent{Value: ast.IdentNode{At: source.NewLoc(0, 8), Value: "z"}}, }, }, }, }, }, }, }, program.Stmts[0]) } func TestVarDecl(t *testing.T) { src := `var x = 123` p := cheatWithScanner(t, src) 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: source.NewLoc(0, 4), Value: "x"}, Value: ast.Expr{ At: source.NewLoc(0, 8), Kind: ast.ExprKindIntLit, Value: ast.ExprIntLit{Value: 123}, }, }, }, program.Stmts[0]) } func TestEmptyStmt(t *testing.T) { src := ` ; ` p := cheatWithScanner(t, src) 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: source.NewLoc(3, 1), Kind: ast.StmtKindEmpty, Value: ast.StmtEmpty{}, }, { At: source.NewLoc(3, 2), Kind: ast.StmtKindEmpty, Value: ast.StmtEmpty{}, }, { At: source.NewLoc(6, 1), Kind: ast.StmtKindEmpty, Value: ast.StmtEmpty{}, }, } require.Equal(t, expected, program.Stmts) } func TestMultipleStmts(t *testing.T) { src := sourceify( `var x = 1`, `var y = 2`, `z = 3`, ) p := cheatWithScanner(t, src) 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: source.NewLoc(0, 4), Value: "x"}, Value: ast.Expr{ At: source.NewLoc(0, 8), Kind: ast.ExprKindIntLit, Value: ast.ExprIntLit{Value: 1}, }, }, }, { At: source.NewLoc(1, 0), Kind: ast.StmtKindVarDecl, Value: ast.StmtVarDecl{ Name: ast.IdentNode{At: source.NewLoc(1, 4), Value: "y"}, Value: ast.Expr{ At: source.NewLoc(1, 8), Kind: ast.ExprKindIntLit, Value: ast.ExprIntLit{Value: 2}, }, }, }, { At: source.NewLoc(2, 0), Kind: ast.StmtKindExpr, Value: ast.StmtExpr{ Value: ast.Expr{ At: source.NewLoc(2, 0), Kind: ast.ExprKindBinary, Value: ast.ExprBinary{ Left: ast.Expr{ At: source.NewLoc(2, 0), Kind: ast.ExprKindIdent, Value: ast.ExprIdent{Value: ast.IdentNode{At: source.NewLoc(2, 0), Value: "z"}}, }, Op: ast.BinOpAssign, Right: ast.Expr{ At: source.NewLoc(2, 4), Kind: ast.ExprKindIntLit, Value: ast.ExprIntLit{Value: 3}, }, }, }, }, }, } require.Equal(t, expected, program.Stmts) } func TestIfStmt(t *testing.T) { src := sourceify( `if false {`, ` var x = 2`, `}`, ) p := cheatWithScanner(t, src) 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: source.NewLoc(0, 3), Kind: ast.ExprKindBoolLit, Value: ast.ExprBoolLit{Value: false}, }, Then: ast.BlockNode{ At: source.NewLoc(0, 9), Stmts: []ast.Stmt{ { At: source.NewLoc(0, 10), Kind: ast.StmtKindEmpty, Value: ast.StmtEmpty{}, }, { At: source.NewLoc(1, 1), Kind: ast.StmtKindVarDecl, Value: ast.StmtVarDecl{ Name: ast.IdentNode{At: source.NewLoc(1, 5), Value: "x"}, Value: ast.Expr{ At: source.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) }