about summary refs log tree commit diff
path: root/pkg/lang/compiler
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2022-08-11 01:25:47 +0000
committerMel <einebeere@gmail.com>2022-08-11 01:25:47 +0000
commit86f31acf6789be116dcc54ed85b069a37c0f7aa8 (patch)
treebc7afd6a8c340825996d29c6cfd392ae42b4fbd5 /pkg/lang/compiler
parentc46b2bc7ce6df1f2c6c9494ef08015ec29992da5 (diff)
downloadjinx-86f31acf6789be116dcc54ed85b069a37c0f7aa8.tar.zst
jinx-86f31acf6789be116dcc54ed85b069a37c0f7aa8.zip
Actual modules and core
Diffstat (limited to 'pkg/lang/compiler')
-rw-r--r--pkg/lang/compiler/compiler.go143
-rw-r--r--pkg/lang/compiler/compiler_test.go27
-rw-r--r--pkg/lang/compiler/scope/scope_chain.go37
-rw-r--r--pkg/lang/compiler/scope/scopes.go2
-rw-r--r--pkg/lang/compiler/scope/symbol.go14
5 files changed, 208 insertions, 15 deletions
diff --git a/pkg/lang/compiler/compiler.go b/pkg/lang/compiler/compiler.go
index 8010b16..d647aff 100644
--- a/pkg/lang/compiler/compiler.go
+++ b/pkg/lang/compiler/compiler.go
@@ -4,31 +4,43 @@ import (
 	"fmt"
 	"jinx/pkg/lang/ast"
 	"jinx/pkg/lang/compiler/scope"
+	"jinx/pkg/lang/modules"
 	"jinx/pkg/lang/vm/code"
 )
 
 type Compiler struct {
-	ast ast.Program
+	name   string
+	author string
+	ast    ast.Program
 
 	funcs []*code.Builder
 
+	globals []string
+	deps    map[modules.ModuleRef]struct{}
+
 	scopes scope.ScopeChain
 }
 
-func New(ast ast.Program) *Compiler {
+func New(name, author string, ast ast.Program) *Compiler {
 	return &Compiler{
-		ast:    ast,
-		funcs:  make([]*code.Builder, 0),
+		name:    name,
+		author:  author,
+		ast:     ast,
+		funcs:   make([]*code.Builder, 0),
+		globals: []string{},
+		deps: map[modules.ModuleRef]struct{}{
+			modules.CoreModuleRef: {}, // All modules depend on core
+		},
 		scopes: scope.NewScopeChain(),
 	}
 }
 
-func (comp *Compiler) Compile() (code.Code, error) {
+func (comp *Compiler) Compile() (modules.Module, error) {
 	target := code.NewBuilder()
 
 	for _, stmt := range comp.ast.Stmts {
 		if err := comp.compileStmt(&target, stmt); err != nil {
-			return code.Code{}, err
+			return modules.Module{}, err
 		}
 	}
 
@@ -38,7 +50,23 @@ func (comp *Compiler) Compile() (code.Code, error) {
 		target.AppendBuilder(*function)
 	}
 
-	return target.Build()
+	bc, err := target.Build()
+	if err != nil {
+		return modules.Module{}, err
+	}
+
+	deps := make([]modules.ModuleRef, 0, len(comp.deps))
+	for dep := range comp.deps {
+		deps = append(deps, dep)
+	}
+
+	return modules.NewModule(
+		comp.author,
+		comp.name,
+		&bc,
+		deps,
+		comp.globals,
+	), nil
 }
 
 func (comp *Compiler) compileStmt(t *code.Builder, stmt ast.Stmt) error {
@@ -47,7 +75,11 @@ func (comp *Compiler) compileStmt(t *code.Builder, stmt ast.Stmt) error {
 	case ast.StmtKindEmpty:
 		// Do nothing.
 	case ast.StmtKindUse:
-		panic("use statements not implemented")
+		useStmt := stmt.Value.(ast.StmtUse)
+		err = comp.compileUseStmt(t, useStmt)
+	case ast.StmtKindGlobal:
+		globalStmt := stmt.Value.(ast.StmtGlobal)
+		err = comp.compileGlobalStmt(t, globalStmt)
 	case ast.StmtKindFnDecl:
 		fnDeclStmt := stmt.Value.(ast.StmtFnDecl)
 		err = comp.compileFnDeclStmt(t, fnDeclStmt)
@@ -89,6 +121,57 @@ func (comp *Compiler) compileStmt(t *code.Builder, stmt ast.Stmt) error {
 	return err
 }
 
+func (comp *Compiler) compileUseStmt(t *code.Builder, stmt ast.StmtUse) error {
+	if len(stmt.Globals) == 0 {
+		// TODO: I don't want to have module objects in the VM...
+		// Maybe compileMemberExpr could check whether Obj is a reference to a module and compile the whole thing as a global reference?
+		panic("importing entire module not implemented")
+	}
+
+	// TODO: Try to resolve the module during compilation, so we don't have to fail at runtime.
+
+	for _, global := range stmt.Globals {
+		if ok := comp.scopes.DeclareGlobal(global.Value, stmt.Module.Value, stmt.Author.Value); !ok {
+			return fmt.Errorf("global %s already declared", global.Value)
+		}
+	}
+
+	comp.deps[modules.NewRef(stmt.Author.Value, stmt.Module.Value)] = struct{}{}
+
+	return nil
+}
+
+func (comp *Compiler) compileGlobalStmt(t *code.Builder, stmt ast.StmtGlobal) error {
+	if !comp.scopes.IsRootScope() {
+		return fmt.Errorf("global variables can only be declared at the root scope")
+	}
+
+	symbolId, symbolExists := comp.scopes.Lookup(stmt.Name.Value)
+	if !symbolExists {
+		return fmt.Errorf("variable %s not declared", stmt.Name.Value)
+	}
+
+	if symbolId.SymbolKind() != scope.SymbolKindVariable {
+		return fmt.Errorf("%s is not a variable", stmt.Name.Value)
+	}
+
+	if symbolId.ScopeID() != scope.ScopeID(0) {
+		return fmt.Errorf("%s is not in root scope", stmt.Name.Value)
+	}
+
+	symbol := comp.scopes.GetVariable(symbolId)
+
+	t.AppendOp(code.OpGetLocal)
+	t.AppendInt(int64(symbol.Data().LocalIndex()))
+
+	t.AppendOp(code.OpAddGlobal)
+	t.AppendString(stmt.Name.Value)
+
+	comp.globals = append(comp.globals, stmt.Name.Value)
+
+	return nil
+}
+
 func (comp *Compiler) compileFnDeclStmt(t *code.Builder, fnDeclStmt ast.StmtFnDecl) error {
 	_, ok := comp.scopes.Declare(fnDeclStmt.Name.Value)
 	if !ok {
@@ -198,7 +281,10 @@ func (comp *Compiler) compileTypeDeclStmt(t *code.Builder, typeDeclStmt ast.Stmt
 	}
 
 	if !constructorDeclared {
-		return fmt.Errorf("type %s must have a constructor", typeDeclStmt.Name.Value)
+		// The core module is allowed to have no constructor
+		if !comp.areWeCompilingCore() {
+			return fmt.Errorf("type %s must have a constructor", typeDeclStmt.Name.Value)
+		}
 	}
 
 	// Add methods to the type
@@ -228,6 +314,9 @@ func (comp *Compiler) compileTypeDeclStmt(t *code.Builder, typeDeclStmt ast.Stmt
 
 		t.AppendOp(code.OpCall)
 		t.AppendInt(int64(2))
+
+		t.AppendOp(code.OpDrop)
+		t.AppendInt(int64(1))
 	}
 
 	return nil
@@ -653,8 +742,8 @@ func (comp *Compiler) compileAssignExpr(t *code.Builder, expr ast.ExprBinary) er
 
 			t.AppendOp(code.OpSetEnv)
 			t.AppendInt(int64(symbol.Data().IndexInEnv()))
-		default:
-			panic("unknown symbol kind")
+		case scope.SymbolKindGlobal:
+			return fmt.Errorf("cannot assign to global variable %s", name)
 		}
 	case ast.ExprKindMember:
 		memberExpr := expr.Left.Value.(ast.ExprMember)
@@ -695,6 +784,13 @@ func (comp *Compiler) compileUnaryExpr(t *code.Builder, expr ast.ExprUnary) erro
 }
 
 func (comp *Compiler) compileCallExpr(t *code.Builder, expr ast.ExprCall) error {
+	// Check if call is a native call
+	if expr.Callee.Kind == ast.ExprKindIdent &&
+		expr.Callee.Value.(ast.ExprIdent).Value.Value == "native" &&
+		expr.Args[0].Kind == ast.ExprKindStringLit {
+		return comp.compileNativeCallExpr(t, expr.Args[0].Value.(ast.ExprStringLit).Value, expr.Args[1:])
+	}
+
 	if err := comp.compileExpr(t, expr.Callee); err != nil {
 		return err
 	}
@@ -711,6 +807,22 @@ func (comp *Compiler) compileCallExpr(t *code.Builder, expr ast.ExprCall) error
 	return nil
 }
 
+func (comp *Compiler) compileNativeCallExpr(t *code.Builder, nativeID string, args []ast.Expr) error {
+	t.AppendOp(code.OpGetGlobal)
+	t.AppendString(fmt.Sprintf("%s#native", nativeID))
+
+	for i := 0; i < len(args); i++ {
+		if err := comp.compileExpr(t, args[i]); err != nil {
+			return err
+		}
+	}
+
+	t.AppendOp(code.OpCall)
+	t.AppendInt(int64(len(args)))
+
+	return nil
+}
+
 func (comp *Compiler) compileSubscriptionExpr(t *code.Builder, expr ast.ExprSubscription) error {
 	if err := comp.compileExpr(t, expr.Obj); err != nil {
 		return err
@@ -781,6 +893,11 @@ func (comp *Compiler) compileIdentExpr(t *code.Builder, expr ast.ExprIdent) erro
 
 		t.AppendOp(code.OpGetEnv)
 		t.AppendInt(int64(symbol.Data().IndexInEnv()))
+	case scope.SymbolKindGlobal:
+		symbol := comp.scopes.GetGlobal(symbolId)
+
+		t.AppendOp(code.OpGetGlobal)
+		t.AppendString(symbol.Data().ID())
 	default:
 		panic(fmt.Errorf("unknown symbol kind: %v", symbolId.SymbolKind()))
 	}
@@ -890,3 +1007,7 @@ func (comp *Compiler) exitScopeAndCleanStack(t *code.Builder) {
 		t.AppendInt(int64(stackSpace))
 	}
 }
+
+func (comp *Compiler) areWeCompilingCore() bool {
+	return modules.NewRef(comp.author, comp.name) == modules.CoreModuleRef
+}
diff --git a/pkg/lang/compiler/compiler_test.go b/pkg/lang/compiler/compiler_test.go
index 04bf425..725620c 100644
--- a/pkg/lang/compiler/compiler_test.go
+++ b/pkg/lang/compiler/compiler_test.go
@@ -552,6 +552,7 @@ func TestType(t *testing.T) {
 	set_arg_count 2
 
 	call 2
+	drop 1
 
 	get_local 0
 	get_member "$add_method"
@@ -560,6 +561,7 @@ func TestType(t *testing.T) {
 	push_function @Cat:meow
 
 	call 2
+	drop 1
 
 	get_local 0
 	push_string "Kitty"
@@ -601,6 +603,25 @@ func TestType(t *testing.T) {
 	mustCompileTo(t, src, expected)
 }
 
+func TestGlobals(t *testing.T) {
+	src := `
+	use mul from hello by mel
+	use pi from math
+
+	var result = mul(pi, 2)
+	`
+
+	expected := `
+	get_global "mel:hello:mul"
+	get_global ":math:pi"
+	push_int 2
+	call 2
+	halt
+	`
+
+	mustCompileTo(t, src, expected)
+}
+
 func mustCompileTo(t *testing.T, src, expected string) {
 	scanner := scanner.New(strings.NewReader(src))
 	tokens, err := scanner.Scan()
@@ -610,7 +631,7 @@ func mustCompileTo(t *testing.T, src, expected string) {
 	program, err := parser.Parse()
 	require.NoError(t, err)
 
-	langCompiler := compiler.New(program)
+	langCompiler := compiler.New("test_program", "test", program)
 	testResult, err := langCompiler.Compile()
 	require.NoError(t, err)
 
@@ -618,9 +639,9 @@ func mustCompileTo(t *testing.T, src, expected string) {
 	expectedResult, err := textCompiler.Compile()
 	require.NoError(t, err)
 
-	if !assert.Equal(t, expectedResult.Code(), testResult.Code()) {
+	if !assert.Equal(t, expectedResult.Code(), testResult.Code().Code()) {
 		d1 := text.NewDecompiler(expectedResult)
-		d2 := text.NewDecompiler(testResult)
+		d2 := text.NewDecompiler(*testResult.Code())
 		require.Equal(t, d1.Decompile(), d2.Decompile())
 	}
 }
diff --git a/pkg/lang/compiler/scope/scope_chain.go b/pkg/lang/compiler/scope/scope_chain.go
index f386017..0c8f5cf 100644
--- a/pkg/lang/compiler/scope/scope_chain.go
+++ b/pkg/lang/compiler/scope/scope_chain.go
@@ -160,6 +160,35 @@ func (sc *ScopeChain) DeclareTemporary() int {
 	return len(sc.Current().variableSymbols) // :)
 }
 
+func (sc *ScopeChain) DeclareGlobal(name, module, author string) bool {
+	if _, ok := sc.nameToSymbol[name]; ok {
+		return false
+	}
+
+	current := sc.Current()
+	indexInScope := len(current.globalSymbols)
+
+	symbolID := SymbolID{
+		symbolKind:   SymbolKindGlobal,
+		scopeID:      sc.CurrentScopeID(),
+		indexInScope: indexInScope,
+	}
+
+	globalID := fmt.Sprintf("%s:%s:%s", author, module, name)
+
+	// Declare the symbol in the current scope.
+	current.globalSymbols = append(current.globalSymbols, Symbol[SymbolGlobal]{
+		name: name,
+		data: SymbolGlobal{
+			id: globalID,
+		},
+	})
+
+	sc.nameToSymbol[name] = symbolID
+
+	return true
+}
+
 func (sc *ScopeChain) CreateAnonymousFunctionSubUnit() code.Marker {
 	fnScope := sc.CurrentFunction()
 
@@ -242,3 +271,11 @@ func (sc *ScopeChain) GetEnv(id SymbolID) Symbol[SymbolEnv] {
 		},
 	}
 }
+
+func (sc *ScopeChain) GetGlobal(id SymbolID) Symbol[SymbolGlobal] {
+	if id.symbolKind != SymbolKindGlobal {
+		panic("incorrect symbol id kind given")
+	}
+
+	return sc.symbolScopes[id.scopeID].globalSymbols[id.indexInScope]
+}
diff --git a/pkg/lang/compiler/scope/scopes.go b/pkg/lang/compiler/scope/scopes.go
index 2a9453a..e1a7a9f 100644
--- a/pkg/lang/compiler/scope/scopes.go
+++ b/pkg/lang/compiler/scope/scopes.go
@@ -14,11 +14,13 @@ const (
 
 type SymbolScope struct {
 	variableSymbols []Symbol[SymbolVariable]
+	globalSymbols []Symbol[SymbolGlobal]
 }
 
 func NewSymbolScope() SymbolScope {
 	return SymbolScope{
 		variableSymbols: make([]Symbol[SymbolVariable], 0),
+		globalSymbols:   make([]Symbol[SymbolGlobal], 0),
 	}
 }
 
diff --git a/pkg/lang/compiler/scope/symbol.go b/pkg/lang/compiler/scope/symbol.go
index b87d5aa..8bfe60a 100644
--- a/pkg/lang/compiler/scope/symbol.go
+++ b/pkg/lang/compiler/scope/symbol.go
@@ -26,6 +26,8 @@ const (
 	// An env symbol is bound to a local on the stack, outside of the function's scope.
 	// Emitted at lookup time, so the SymbolScope has no array for them.
 	SymbolKindEnv SymbolKind = iota
+	// A global symbol is bound to a global from a dependency.
+	SymbolKindGlobal SymbolKind = iota
 )
 
 func (s SymbolKind) String() string {
@@ -34,6 +36,8 @@ func (s SymbolKind) String() string {
 		return "variable"
 	case SymbolKindEnv:
 		return "env"
+	case SymbolKindGlobal:
+		return "global"
 	default:
 		panic("unknown symbol kind")
 	}
@@ -49,7 +53,7 @@ func (s Symbol[D]) Data() D {
 }
 
 type SymbolData interface {
-	SymbolVariable | SymbolEnv
+	SymbolVariable | SymbolEnv | SymbolGlobal
 }
 
 type SymbolVariable struct {
@@ -67,3 +71,11 @@ type SymbolEnv struct {
 func (se SymbolEnv) IndexInEnv() int {
 	return se.indexInEnv
 }
+
+type SymbolGlobal struct {
+	id string
+}
+
+func (sg SymbolGlobal) ID() string {
+	return sg.id
+}
\ No newline at end of file