aboutsummaryrefslogtreecommitdiff
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
parent565b141c38da7c9c86499c67bf62ac7dd69a5aae (diff)
downloadjinx-14bdb59c24aef85f2a7c69f03ddacb7f56445e9e.tar.zst
jinx-14bdb59c24aef85f2a7c69f03ddacb7f56445e9e.zip
Create basic Expr compiler for Lang
-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())
+ }
+}