about summary refs log tree commit diff
path: root/pkg/lang/compiler
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2022-06-12 14:54:56 +0000
committerGitHub <noreply@github.com>2022-06-12 14:54:56 +0000
commit14bdb59c24aef85f2a7c69f03ddacb7f56445e9e (patch)
tree4d6ef5b36847cfee7e54b97a4f0ed14f1bb91aa4 /pkg/lang/compiler
parent565b141c38da7c9c86499c67bf62ac7dd69a5aae (diff)
downloadjinx-14bdb59c24aef85f2a7c69f03ddacb7f56445e9e.tar.zst
jinx-14bdb59c24aef85f2a7c69f03ddacb7f56445e9e.zip
Create basic Expr compiler for Lang
Diffstat (limited to 'pkg/lang/compiler')
-rw-r--r--pkg/lang/compiler/compiler.go188
-rw-r--r--pkg/lang/compiler/compiler_test.go83
2 files changed, 260 insertions, 11 deletions
diff --git a/pkg/lang/compiler/compiler.go b/pkg/lang/compiler/compiler.go
index bf34487..e299fdc 100644
--- a/pkg/lang/compiler/compiler.go
+++ b/pkg/lang/compiler/compiler.go
@@ -6,36 +6,202 @@ import (
 )
 
 type Compiler struct {
-	ast ast.Program
+	ast     ast.Program
+	builder code.Builder
 }
 
 func New(ast ast.Program) *Compiler {
 	return &Compiler{
-		ast: ast,
+		ast:     ast,
+		builder: code.NewBuilder(),
 	}
 }
 
 func (comp *Compiler) Compile() (code.Code, error) {
-	bc := make([]byte, 0, 1024)
-
 	for _, stmt := range comp.ast.Stmts {
+		if stmt.Kind == ast.StmtKindEmpty {
+			continue
+		}
+
 		if stmt.Kind != ast.StmtKindExpr {
-			panic("statements other than expressions not implemented")
+			panic("statements other than expressions and empties not implemented")
 		}
 
 		expr := stmt.Value.(ast.StmtExpr).Value
 
-		res, err := comp.compileExpr(expr)
-		if err != nil {
+		if err := comp.compileExpr(expr); err != nil {
 			return code.Code{}, err
 		}
+	}
+
+	return comp.builder.Build(), nil
+}
+
+func (comp *Compiler) compileExpr(expr ast.Expr) error {
+	switch expr.Kind {
+	case ast.ExprKindBinary:
+		return comp.compileBinaryExpr(expr.Value.(ast.ExprBinary))
+	case ast.ExprKindUnary:
+		return comp.compileUnaryExpr(expr.Value.(ast.ExprUnary))
+	case ast.ExprKindCall:
+		return comp.compileCallExpr(expr.Value.(ast.ExprCall))
+	case ast.ExprKindSubscription:
+		return comp.compileSubscriptionExpr(expr.Value.(ast.ExprSubscription))
+
+	case ast.ExprKindGroup:
+		return comp.compileGroupExpr(expr.Value.(ast.ExprGroup))
+	case ast.ExprKindFnLit:
+		panic("not implemented")
+	case ast.ExprKindArrayLit:
+		panic("not implemented")
+	case ast.ExprKindIdent:
+		panic("not implemented")
+	case ast.ExprKindIntLit:
+		return comp.compileIntLitExpr(expr.Value.(ast.ExprIntLit))
+	case ast.ExprKindFloatLit:
+		return comp.compileFloatLitExpr(expr.Value.(ast.ExprFloatLit))
+	case ast.ExprKindStringLit:
+		return comp.compileStringLitExpr(expr.Value.(ast.ExprStringLit))
+	case ast.ExprKindBoolLit:
+		return comp.compileBoolLitExpr(expr.Value.(ast.ExprBoolLit))
+	case ast.ExprKindNullLit:
+		return comp.compileNullLitExpr(expr.Value.(ast.ExprNullLit))
+	case ast.ExprKindThis:
+		panic("not implemented")
+	default:
+		panic("unknown expression kind")
+	}
+}
+
+func (comp *Compiler) compileBinaryExpr(expr ast.ExprBinary) error {
+	if err := comp.compileExpr(expr.Right); err != nil {
+		return err
+	}
+
+	if err := comp.compileExpr(expr.Left); err != nil {
+		return err
+	}
+
+	switch expr.Op {
+	case ast.BinOpPlus:
+		comp.builder.AppendOp(code.OpAdd)
+	case ast.BinOpMinus:
+		comp.builder.AppendOp(code.OpSub)
+	case ast.BinOpStar:
+		// comp.builder.AppendOp(code.OpMul)
+		panic("not implemented")
+	case ast.BinOpSlash:
+		// comp.builder.AppendOp(code.OpDiv)
+		panic("not implemented")
+	case ast.BinOpPercent:
+		// comp.builder.AppendOp(code.OpMod)
+		panic("not implemented")
+
+	case ast.BinOpAssign:
+		panic("not implemented")
+	case ast.BinOpEq:
+		// comp.builder.AppendOp(code.OpEq)
+		panic("not implemented")
+	case ast.BinOpNeq:
+		// comp.builder.AppendOp(code.OpNeq)
+		panic("not implemented")
+	case ast.BinOpLt:
+		// comp.builder.AppendOp(code.OpLt)
+		panic("not implemented")
+	case ast.BinOpLte:
+		comp.builder.AppendOp(code.OpLte)
+	case ast.BinOpGt:
+		// comp.builder.AppendOp(code.OpGt)
+		panic("not implemented")
+	case ast.BinOpGte:
+		// comp.builder.AppendOp(code.OpGte)
+		panic("not implemented")
+	default:
+		panic("unknown binary operator")
+	}
+
+	return nil
+}
+
+func (comp *Compiler) compileUnaryExpr(expr ast.ExprUnary) error {
+	if err := comp.compileExpr(expr.Value); err != nil {
+		return err
+	}
+
+	switch expr.Op {
+	case ast.UnOpBang:
+		panic("not implemented")
+	case ast.UnOpMinus:
+		panic("not implemented")
+	default:
+		panic("unknown unary operator")
+	}
+
+	return nil
+}
+
+func (comp *Compiler) compileCallExpr(expr ast.ExprCall) error {
+	for i := len(expr.Args) - 1; i >= 0; i-- {
+		if err := comp.compileExpr(expr.Args[i]); err != nil {
+			return err
+		}
+	}
+
+	if err := comp.compileExpr(expr.Callee); err != nil {
+		return err
+	}
+
+	comp.builder.AppendOp(code.OpCall)
+	comp.builder.AppendInt(int64(len(expr.Args)))
+
+	return nil
+}
+
+func (comp *Compiler) compileSubscriptionExpr(expr ast.ExprSubscription) error {
+	if err := comp.compileExpr(expr.Key); err != nil {
+		return err
+	}
 
-		bc = append(bc, res...)
+	if err := comp.compileExpr(expr.Obj); err != nil {
+		return err
 	}
 
-	return code.New(bc, code.NewDebugInfo("unknown file")), nil
+	comp.builder.AppendOp(code.OpIndex)
+	return nil
+}
+
+func (comp *Compiler) compileGroupExpr(expr ast.ExprGroup) error {
+	return comp.compileExpr(expr.Value)
+}
+
+func (comp *Compiler) compileIntLitExpr(expr ast.ExprIntLit) error {
+	comp.builder.AppendOp(code.OpPushInt)
+	comp.builder.AppendInt(int64(expr.Value))
+	return nil
+}
+
+func (comp *Compiler) compileFloatLitExpr(expr ast.ExprFloatLit) error {
+	comp.builder.AppendOp(code.OpPushFloat)
+	comp.builder.AppendFloat(expr.Value)
+	return nil
+}
+
+func (comp *Compiler) compileStringLitExpr(expr ast.ExprStringLit) error {
+	comp.builder.AppendOp(code.OpPushString)
+	comp.builder.AppendString(expr.Value)
+	return nil
+}
+
+func (comp *Compiler) compileBoolLitExpr(expr ast.ExprBoolLit) error {
+	if expr.Value {
+		comp.builder.AppendOp(code.OpPushTrue)
+	} else {
+		comp.builder.AppendOp(code.OpPushFalse)
+	}
+	return nil
 }
 
-func (comp *Compiler) compileExpr(expr ast.Expr) ([]byte, error) {
-	panic("not implemented")
+func (comp *Compiler) compileNullLitExpr(expr ast.ExprNullLit) error {
+	comp.builder.AppendOp(code.OpPushNull)
+	return nil
 }
diff --git a/pkg/lang/compiler/compiler_test.go b/pkg/lang/compiler/compiler_test.go
new file mode 100644
index 0000000..2477b21
--- /dev/null
+++ b/pkg/lang/compiler/compiler_test.go
@@ -0,0 +1,83 @@
+package compiler_test
+
+import (
+	"jinx/pkg/lang/compiler"
+	"jinx/pkg/lang/parser"
+	"jinx/pkg/lang/scanner"
+	"jinx/pkg/lang/vm/text"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestSimpleAddExpr(t *testing.T) {
+	src := `
+	1 + 2
+	`
+
+	expected := `
+	push_int 2
+	push_int 1
+	add
+	`
+
+	mustCompileTo(t, src, expected)
+}
+
+func TestNestedExpr(t *testing.T) {
+	// Yeah, we don't compile identifiers yet, so here we call and index directly on literals.
+	src := `
+	1 + 2 - 3 - (123("a", "b", "c") + 456[321])
+	`
+
+	expected := `
+	push_int 321
+	push_int 456
+	index
+
+	push_string "c"
+	push_string "b"
+	push_string "a"
+	push_int 123
+	call
+
+	add
+
+	push_int 3
+	sub
+
+	push_int 2
+	sub
+
+	push_int 1
+	add
+	`
+
+	mustCompileTo(t, src, expected)
+}
+
+func mustCompileTo(t *testing.T, src, expected string) {
+	scanner := scanner.New(strings.NewReader(src))
+	tokens, err := scanner.Scan()
+	require.NoError(t, err)
+
+	parser := parser.New(tokens)
+	program, err := parser.Parse()
+	require.NoError(t, err)
+
+	langCompiler := compiler.New(program)
+	testResult, err := langCompiler.Compile()
+	require.NoError(t, err)
+
+	textCompiler := text.NewCompiler(strings.NewReader(expected))
+	expectedResult, err := textCompiler.Compile()
+	require.NoError(t, err)
+
+	if !assert.Equal(t, expectedResult.Code(), testResult.Code()) {
+		d1 := text.NewDecompiler(expectedResult)
+		d2 := text.NewDecompiler(testResult)
+		require.Equal(t, d1.Decompile(), d2.Decompile())
+	}
+}