about summary refs log tree commit diff
path: root/pkg
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
parentc46b2bc7ce6df1f2c6c9494ef08015ec29992da5 (diff)
downloadjinx-86f31acf6789be116dcc54ed85b069a37c0f7aa8.tar.zst
jinx-86f31acf6789be116dcc54ed85b069a37c0f7aa8.zip
Actual modules and core
Diffstat (limited to 'pkg')
-rw-r--r--pkg/bot/cmds.go11
-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
-rw-r--r--pkg/lang/modules/core/compiled.go22
-rw-r--r--pkg/lang/modules/core/core.lang78
-rw-r--r--pkg/lang/modules/core/natives.go166
-rw-r--r--pkg/lang/modules/module.go104
-rw-r--r--pkg/lang/modules/natives/natives.go39
-rw-r--r--pkg/lang/vm/core.go146
-rw-r--r--pkg/lang/vm/errors.go11
-rw-r--r--pkg/lang/vm/exe.go62
-rw-r--r--pkg/lang/vm/exec.go53
-rw-r--r--pkg/lang/vm/executor/executor.go17
-rw-r--r--pkg/lang/vm/executor/nativefunc.go5
-rw-r--r--pkg/lang/vm/setup.go58
-rw-r--r--pkg/lang/vm/utils.go13
-rw-r--r--pkg/lang/vm/value/core_ptrs.go98
-rw-r--r--pkg/lang/vm/value/data.go22
-rw-r--r--pkg/lang/vm/value/value.go18
-rw-r--r--pkg/lang/vm/vm.go43
-rw-r--r--pkg/lang/vm/vm_test.go93
24 files changed, 1002 insertions, 280 deletions
diff --git a/pkg/bot/cmds.go b/pkg/bot/cmds.go
index b5ded6f..32b1bfe 100644
--- a/pkg/bot/cmds.go
+++ b/pkg/bot/cmds.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"jinx/pkg/discord/events"
 	"jinx/pkg/lang/compiler"
+	"jinx/pkg/lang/modules"
 	"jinx/pkg/lang/parser"
 	"jinx/pkg/lang/scanner"
 	"jinx/pkg/lang/vm"
@@ -35,7 +36,7 @@ func vmCmd(b *Bot, content string, msg events.Message) error {
 		return err
 	}
 
-	vm := vm.New(&bc, nil)
+	vm := vm.New(modules.NewUnknownModule(&bc))
 
 	if err := vm.Run(); err != nil {
 		return err
@@ -66,8 +67,8 @@ func langCmd(b *Bot, content string, msg events.Message) error {
 		return err
 	}
 
-	comp := compiler.New(program)
-	bc, err := comp.Compile()
+	comp := compiler.New("chat program", msg.Author.Username, program)
+	module, err := comp.Compile()
 	if err != nil {
 		b.logger.Error().Err(err).Msg("error compiling code")
 		return err
@@ -75,12 +76,12 @@ func langCmd(b *Bot, content string, msg events.Message) error {
 
 	b.client.SendMessage(msg.ChannelID, "successfully compiled code")
 
-	decomp := text.NewDecompiler(bc)
+	decomp := text.NewDecompiler(*module.Code())
 	decompRes := decomp.Decompile()
 
 	b.client.SendMessage(msg.ChannelID, fmt.Sprintf("resulting decompiled bytecode:\n```\n%s\n```", decompRes))
 
-	vm := vm.New(&bc, nil)
+	vm := vm.New(module)
 
 	b.client.SendMessage(msg.ChannelID, "executing code...")
 
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
diff --git a/pkg/lang/modules/core/compiled.go b/pkg/lang/modules/core/compiled.go
new file mode 100644
index 0000000..ae34cf4
--- /dev/null
+++ b/pkg/lang/modules/core/compiled.go
@@ -0,0 +1,22 @@
+package core
+
+import (
+	"jinx/pkg/lang/modules"
+	"jinx/pkg/lang/vm/code"
+)
+
+var moduleCompiled = []byte{
+	0xb, 0x54, 0x79, 0x70, 0x65, 0x0, 0xe, 0x3a, 0x63, 0x6f, 0x72, 0x65, 0x3a, 0x73, 0x65, 0x74, 0x75, 0x70, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x23, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x54, 0x79, 0x70, 0x65, 0x0, 0xb, 0x4e, 0x75, 0x6c, 0x6c, 0x0, 0x10, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x24, 0x61, 0x64, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x0, 0x4, 0x24, 0x69, 0x6e, 0x69, 0x74, 0x0, 0x9, 0x5a, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0x49, 0x6e, 0x74, 0x0, 0x10, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x24, 0x61, 0x64, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x0, 0x4, 0x24, 0x69, 0x6e, 0x69, 0x74, 0x0, 0x9, 0x9a, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x0, 0x10, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x24, 0x61, 0x64, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x0, 0x4, 0x24, 0x69, 0x6e, 0x69, 0x74, 0x0, 0x9, 0xd9, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x0, 0x10, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x24, 0x61, 0x64, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x0, 0x4, 0x24, 0x69, 0x6e, 0x69, 0x74, 0x0, 0x9, 0x1a, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0x42, 0x6f, 0x6f, 0x6c, 0x0, 0x10, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x24, 0x61, 0x64, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x0, 0x4, 0x24, 0x69, 0x6e, 0x69, 0x74, 0x0, 0x9, 0x5c, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0x41, 0x72, 0x72, 0x61, 0x79, 0x0, 0x10, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x24, 0x61, 0x64, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x0, 0x4, 0x24, 0x69, 0x6e, 0x69, 0x74, 0x0, 0x9, 0x9c, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x24, 0x61, 0x64, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x0, 0x4, 0x70, 0x75, 0x73, 0x68, 0x0, 0x9, 0xdd, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x24, 0x61, 0x64, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x0, 0x4, 0x70, 0x6f, 0x70, 0x0, 0x9, 0x1c, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x24, 0x61, 0x64, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x0, 0x4, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x0, 0x9, 0x47, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0, 0x10, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x24, 0x61, 0x64, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x0, 0x4, 0x24, 0x69, 0x6e, 0x69, 0x74, 0x0, 0x9, 0x75, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xb9, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0xe8, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x4e, 0x75, 0x6c, 0x6c, 0x0, 0x10, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x49, 0x6e, 0x74, 0x0, 0x10, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x0, 0x10, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x0, 0x10, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x42, 0x6f, 0x6f, 0x6c, 0x0, 0x10, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x41, 0x72, 0x72, 0x61, 0x79, 0x0, 0x10, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0, 0x10, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x73, 0x61, 0x79, 0x0, 0x10, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x70, 0x69, 0x6e, 0x67, 0x0, 0x1, 0xa, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, 0xe, 0x3a, 0x63, 0x6f, 0x72, 0x65, 0x3a, 0x4e, 0x75, 0x6c, 0x6c, 0x3a, 0x24, 0x69, 0x6e, 0x69, 0x74, 0x23, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x0, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x28, 0xa, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, 0xe, 0x3a, 0x63, 0x6f, 0x72, 0x65, 0x3a, 0x49, 0x6e, 0x74, 0x3a, 0x24, 0x69, 0x6e, 0x69, 0x74, 0x23, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x0, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x28, 0xa, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, 0xe, 0x3a, 0x63, 0x6f, 0x72, 0x65, 0x3a, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x24, 0x69, 0x6e, 0x69, 0x74, 0x23, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x0, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x28, 0xa, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, 0xe, 0x3a, 0x63, 0x6f, 0x72, 0x65, 0x3a, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3a, 0x24, 0x69, 0x6e, 0x69, 0x74, 0x23, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x0, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x28, 0xa, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, 0xe, 0x3a, 0x63, 0x6f, 0x72, 0x65, 0x3a, 0x42, 0x6f, 0x6f, 0x6c, 0x3a, 0x24, 0x69, 0x6e, 0x69, 0x74, 0x23, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x0, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x28, 0xa, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, 0xe, 0x3a, 0x63, 0x6f, 0x72, 0x65, 0x3a, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3a, 0x24, 0x69, 0x6e, 0x69, 0x74, 0x23, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x0, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x28, 0xe, 0x3a, 0x63, 0x6f, 0x72, 0x65, 0x3a, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3a, 0x70, 0x75, 0x73, 0x68, 0x23, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x28, 0xe, 0x3a, 0x63, 0x6f, 0x72, 0x65, 0x3a, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3a, 0x70, 0x6f, 0x70, 0x23, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x28, 0xe, 0x3a, 0x63, 0x6f, 0x72, 0x65, 0x3a, 0x41, 0x72, 0x72, 0x61, 0x79, 0x3a, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x23, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x28, 0xa, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, 0xe, 0x3a, 0x63, 0x6f, 0x72, 0x65, 0x3a, 0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x24, 0x69, 0x6e, 0x69, 0x74, 0x23, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x0, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x28, 0xe, 0x3a, 0x63, 0x6f, 0x72, 0x65, 0x3a, 0x73, 0x61, 0x79, 0x23, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x28, 0x4, 0x50, 0x6f, 0x6e, 0x67, 0x21, 0x0, 0x28, 
+}
+
+var moduleCode = code.New(code.Raw(moduleCompiled), code.NewDebugInfo("core"))
+
+var Module = modules.NewModule(
+	"",
+	"core",
+	&moduleCode,
+	[]modules.ModuleRef{},
+	[]string{
+		"Type", "Null", "Int", "Float", "String", "Bool", "Array", "Function", "say", "ping", 
+	},
+)
diff --git a/pkg/lang/modules/core/core.lang b/pkg/lang/modules/core/core.lang
new file mode 100644
index 0000000..e6be2d5
--- /dev/null
+++ b/pkg/lang/modules/core/core.lang
@@ -0,0 +1,78 @@
+type Type {
+}
+
+native(":core:setup_type", Type)
+global Type
+
+type Null {
+    () {
+        native(":core:Null:$init")
+    }
+}
+
+type Int {
+    () {
+        native(":core:Int:$init")
+    }
+}
+
+type Float {
+    () {
+        native(":core:Float:$init")
+    }
+}
+
+type String {
+    () {
+        native(":core:String:$init")
+    }
+}
+
+type Bool {
+    () {
+        native(":core:Bool:$init")
+    }
+}
+
+type Array {
+    () {
+        native(":core:Array:$init")
+    }
+
+    fn push(this, element) {
+        native(":core:Array:push", this, element)
+    }
+
+    fn pop(this) {
+        return native(":core:Array:pop", this)
+    }
+
+    fn length(this) {
+        return native(":core:Array:length", this)
+    }
+}
+
+type Function {
+    () {
+        native(":core:Function:$init")
+    }
+}
+
+fn say(message) {
+    native(":core:say", message)
+}
+
+fn ping() {
+    return "Pong!"
+}
+
+global Null
+global Int
+global Float
+global String
+global Bool
+global Array
+global Function
+
+global say
+global ping
\ No newline at end of file
diff --git a/pkg/lang/modules/core/natives.go b/pkg/lang/modules/core/natives.go
new file mode 100644
index 0000000..3035b80
--- /dev/null
+++ b/pkg/lang/modules/core/natives.go
@@ -0,0 +1,166 @@
+package core
+
+import (
+	"fmt"
+	"jinx/pkg/lang/vm/code"
+	"jinx/pkg/lang/vm/executor"
+	"jinx/pkg/lang/vm/value"
+)
+
+var Natives = []any{
+	n(":core:setup_type", 1, func(exe executor.Exectutor, args []value.Value) (value.Value, error) {
+		// Add the very important "$add_method" method to the Type type.
+		// Without it no other methods can be created.
+		typeRef, err := ensureType[value.TypeRefData](args[0], value.TypeRefType)
+		if err != nil {
+			return value.Value{}, err
+		}
+
+		addMethodGlobal, ok, err := exe.GetGlobal(":core:Type:$add_method#native")
+		if err != nil {
+			return value.Value{}, err
+		}
+
+		if !ok {
+			panic("missing Type:$add_method implementation")
+		}
+
+		addMethodFn, err := ensureType[value.FunctionData](addMethodGlobal, value.FunctionType)
+		if err != nil {
+			return value.Value{}, err
+		}
+
+		typeRef.AddMethod(
+			exe.Mem(),
+			"$add_method",
+			value.NewNativeFunction(addMethodFn.Native(), 2).Data().(value.FunctionData),
+		)
+
+		return value.Value{}, nil
+	}),
+	n(":core:Type:$add_method", 2, func(exe executor.Exectutor, args []value.Value) (value.Value, error) {
+		ref, err := ensureTypeThis[value.TypeRefData](exe, value.TypeRefType)
+		if err != nil {
+			return value.Value{}, err
+		}
+
+		fn, err := ensureType[value.FunctionData](args[0], value.FunctionType)
+		if err != nil {
+			return value.Value{}, err
+		}
+
+		nameData, err := ensureType[value.StringData](args[1], value.StringType)
+		if err != nil {
+			return value.Value{}, err
+		}
+		name, err := nameData.RawString(exe.Mem())
+		if err != nil {
+			return value.Value{}, err
+		}
+
+		return value.Value{}, ref.AddMethod(exe.Mem(), name, fn)
+	}),
+
+	n(":core:Int:$init", 0, func(exe executor.Exectutor, args []value.Value) (value.Value, error) {
+		return value.NewInt(0), nil
+	}),
+	n(":core:Float:$init", 0, func(exe executor.Exectutor, args []value.Value) (value.Value, error) {
+		return value.NewFloat(0.0), nil
+	}),
+	n(":core:String:$init", 0, func(exe executor.Exectutor, args []value.Value) (value.Value, error) {
+		return value.NewString(exe.Mem(), "")
+	}),
+	n(":core:Bool:$init", 0, func(exe executor.Exectutor, args []value.Value) (value.Value, error) {
+		return value.NewBool(false), nil
+	}),
+	n(":core:Array:$init", 0, func(exe executor.Exectutor, args []value.Value) (value.Value, error) {
+		return value.NewArray(exe.Mem(), args)
+	}),
+	n(":core:Function:$init", 0, func(exe executor.Exectutor, args []value.Value) (value.Value, error) {
+		return value.NewFunction(code.NewPos(0, 0), 0), nil
+	}),
+	n(":core:Type:$init", 0, func(exe executor.Exectutor, args []value.Value) (value.Value, error) {
+		return value.NewType(exe.Mem(), "")
+	}),
+
+	n(":core:Array:push", 2, func(exe executor.Exectutor, args []value.Value) (value.Value, error) {
+		array, err := ensureType[value.ArrayData](args[0], value.ArrayType)
+		if err != nil {
+			return value.Value{}, err
+		}
+
+		element := args[1]
+		return value.Value{}, array.Push(exe.Mem(), element)
+	}),
+	n(":core:Array:pop", 1, func(exe executor.Exectutor, args []value.Value) (value.Value, error) {
+		array, err := ensureType[value.ArrayData](args[0], value.ArrayType)
+		if err != nil {
+			return value.Value{}, err
+		}
+
+		return array.Pop(exe.Mem())
+	}),
+	n(":core:Array:length", 1, func(exe executor.Exectutor, args []value.Value) (value.Value, error) {
+		array, err := ensureType[value.ArrayData](args[0], value.ArrayType)
+		if err != nil {
+			return value.Value{}, err
+		}
+
+		length, err := array.Len(exe.Mem())
+		if err != nil {
+			return value.Value{}, err
+		}
+
+		return value.NewInt(int64(length)), nil
+	}),
+
+	n(":core:say", 1, func(exe executor.Exectutor, args []value.Value) (value.Value, error) {
+		message := args[0]
+		s, err := message.Data().String(exe.Mem())
+		if err != nil {
+			return value.Value{}, err
+		}
+
+		fmt.Println(s)
+		return value.Value{}, nil
+	}),
+}
+
+type native struct {
+	name     string
+	argCount int
+	fn       executor.NativeFunc
+}
+
+func n(name string, argCount int, fn executor.NativeFunc) native {
+	return native{name, argCount, fn}
+}
+
+func (n native) Name() string {
+	return n.name
+}
+
+func (n native) ArgCount() int {
+	return n.argCount
+}
+
+func (n native) Fn() executor.NativeFunc {
+	return n.fn
+}
+
+func ensureTypeThis[D value.Data](exe executor.Exectutor, t value.TypeKind) (D, error) {
+	this, err := exe.GetThis()
+	if err != nil {
+		return *new(D), err
+	}
+
+	return ensureType[D](this, t)
+}
+
+func ensureType[D value.Data](val value.Value, t value.TypeKind) (D, error) {
+	if val.Type() != t {
+		return *new(D), fmt.Errorf("expected type %s, got %s", t, val.Type())
+	}
+
+	return val.Data().(D), nil
+}
diff --git a/pkg/lang/modules/module.go b/pkg/lang/modules/module.go
new file mode 100644
index 0000000..08b9987
--- /dev/null
+++ b/pkg/lang/modules/module.go
@@ -0,0 +1,104 @@
+package modules
+
+import (
+	"fmt"
+	"jinx/pkg/lang/vm/code"
+)
+
+type Module struct {
+	author  string
+	name    string
+	code    *code.Code
+	deps    []ModuleRef
+	globals []string
+}
+
+func NewModule(author, name string, code *code.Code, deps []ModuleRef, globals []string) Module {
+	return Module{
+		author:  author,
+		name:    name,
+		code:    code,
+		deps:    deps,
+		globals: globals,
+	}
+}
+
+func NewUnknownModule(code *code.Code) Module {
+	return Module{
+		author: "noone",
+		name:   "program",
+		code:   code,
+		deps: []ModuleRef{
+			CoreModuleRef,
+		},
+		globals: []string{},
+	}
+}
+
+func (m Module) Author() string {
+	return m.author
+}
+
+func (m Module) Name() string {
+	return m.name
+}
+
+func (m Module) Code() *code.Code {
+	return m.code
+}
+
+func (m Module) Deps() []ModuleRef {
+	return m.deps
+}
+
+func (m Module) Globals() []string {
+	return m.globals
+}
+
+func (m Module) IsCore() bool {
+	return m.author == CoreModuleRef.author && m.name == CoreModuleRef.name
+}
+
+func (m Module) GetGlobal(name string) (string, error) {
+	// TODO: Don't use loop
+	found := false
+	for _, global := range m.globals {
+		if global == name {
+			found = true
+			break
+		}
+	}
+
+	if !found {
+		return "", fmt.Errorf("global %s not in module", name)
+	}
+
+	return fmt.Sprintf("%s:%s:%s", m.author, m.name, name), nil
+}
+
+type ModuleRef struct {
+	author string
+	name   string
+}
+
+func NewRef(author, name string) ModuleRef {
+	return ModuleRef{
+		author: author,
+		name:   name,
+	}
+}
+
+func (m ModuleRef) Author() string {
+	return m.author
+}
+
+func (m ModuleRef) Name() string {
+	return m.name
+}
+
+var (
+	CoreModuleRef = ModuleRef{
+		author: "",
+		name:   "core",
+	}
+)
diff --git a/pkg/lang/modules/natives/natives.go b/pkg/lang/modules/natives/natives.go
new file mode 100644
index 0000000..a1b09b6
--- /dev/null
+++ b/pkg/lang/modules/natives/natives.go
@@ -0,0 +1,39 @@
+package natives
+
+import (
+	"jinx/pkg/lang/modules/core"
+	"jinx/pkg/lang/vm/executor"
+)
+
+var Natives = makeNatives(
+	core.Natives,
+)
+
+func makeNatives(moduleNativesArrays ...[]any) []Native {
+	natives := make([]Native, 0)
+
+	for _, moduleNatives := range moduleNativesArrays {
+		for _, moduleNativeI := range moduleNatives {
+			moduleNative := moduleNativeI.(moduleNative)
+
+			natives = append(natives, Native{
+				Name:     moduleNative.Name(),
+				ArgCount: moduleNative.ArgCount(),
+				Fn:       moduleNative.Fn(),
+			})
+		}
+	}
+	return natives
+}
+
+type Native struct {
+	Name     string
+	ArgCount int
+	Fn       executor.NativeFunc
+}
+
+type moduleNative interface {
+	Name() string
+	ArgCount() int
+	Fn() executor.NativeFunc
+}
diff --git a/pkg/lang/vm/core.go b/pkg/lang/vm/core.go
deleted file mode 100644
index 477cee8..0000000
--- a/pkg/lang/vm/core.go
+++ /dev/null
@@ -1,146 +0,0 @@
-package vm
-
-import (
-	"jinx/pkg/lang/vm/code"
-	"jinx/pkg/lang/vm/value"
-)
-
-func (vm *VM) createCoreNullType() value.Type {
-	return value.Type{
-		Kind: value.NullType,
-	}
-}
-
-func (vm *VM) createCoreIntType() value.Type {
-	return value.Type{
-		Kind: value.IntType,
-	}
-}
-
-func (vm *VM) createCoreFloatType() value.Type {
-	return value.Type{
-		Kind: value.FloatType,
-	}
-}
-
-func (vm *VM) createCoreStringType() value.Type {
-	return value.Type{
-		Kind: value.StringType,
-	}
-}
-
-func (vm *VM) createCoreBoolType() value.Type {
-	return value.Type{
-		Kind: value.BoolType,
-	}
-}
-
-func (vm *VM) createCoreArrayType() value.Type {
-	return value.Type{
-		Kind: value.ArrayType,
-		Methods: map[string]value.FunctionData{
-			"length": makeCoreFn(vm.coreArrayLength, 0),
-			"push": makeCoreFn(vm.coreArrayPush, 1),
-		},
-	}
-}
-
-func (vm *VM) createCoreFunctionType() value.Type {
-	return value.Type{
-		Kind: value.FunctionType,
-	}
-}
-
-func (vm *VM) createCoreTypeRefType() value.Type {
-	return value.Type{
-		Kind: value.TypeRefType,
-		Methods: map[string]value.FunctionData{
-			"$add_method": makeCoreFn(vm.coreTypeRefIntrAddMethod, 2),
-		},
-	}
-}
-
-func (vm *VM) coreArrayLength(args []value.Value) (value.Value, error) {
-	a, err := ensureTypeThis[value.ArrayData](vm, value.ArrayType)
-	if err != nil {
-		return value.Value{}, err
-	}
-
-	len, err := a.Len(vm.memory)
-	if err != nil {
-		return value.Value{}, err
-	}
-	res := value.NewInt(int64(len))
-	return res, nil
-}
-
-func (vm *VM) coreArrayPush(args []value.Value) (value.Value, error) {
-	a, err := ensureTypeThis[value.ArrayData](vm, value.ArrayType)
-	if err != nil {
-		return value.Value{}, err
-	}
-
-	return value.Value{}, a.Push(vm.memory, args[0])
-}
-
-func (vm *VM) coreTypeRefIntrAddMethod(args []value.Value) (value.Value, error) {
-	ref, err := ensureTypeThis[value.TypeRefData](vm, value.TypeRefType)
-	if err != nil {
-		return value.Value{}, err
-	}
-
-	fn, err := ensureType[value.FunctionData](args[0], value.FunctionType)
-	if err != nil {
-		return value.Value{}, err
-	}
-
-	nameData, err := ensureType[value.StringData](args[1], value.StringType)
-	if err != nil {
-		return value.Value{}, err
-	}
-	name, err := nameData.RawString(vm.memory)
-	if err != nil {
-		return value.Value{}, err
-	}
-
-	return value.Value{}, ref.AddMethod(vm.memory, name, fn)
-}
-
-func ensureTypeThis[D value.Data](vm *VM, t value.TypeKind) (D, error) {
-	this, err := vm.getThis()
-	if err != nil {
-		return *new(D), err
-	}
-
-	return ensureType[D](this, t)
-}
-
-func ensureType[D value.Data](val value.Value, t value.TypeKind) (D, error) {
-	if val.Type() != t {
-		return *new(D), ErrInvalidOperandType{
-			Op: code.OpNop, // TODO: Add error not dependent on op.
-			X:  val.Type(),
-		}
-	}
-
-	return val.Data().(D), nil
-}
-
-func makeCoreFn(f value.NativeFunc, argCount uint) value.FunctionData {
-	return value.NewNativeFunction(f, argCount).Data().(value.FunctionData)
-}
-
-func (vm *VM) getThis() (value.Value, error) {
-	err := vm.execGetEnv(0)
-	if err != nil {
-		return value.Value{}, err
-	}
-
-	// Take it back!
-	this, err := vm.popAndDrop()
-	if err != nil {
-		return value.Value{}, err
-	}
-
-	return this, nil
-}
diff --git a/pkg/lang/vm/errors.go b/pkg/lang/vm/errors.go
index 0dd73e7..0c1b916 100644
--- a/pkg/lang/vm/errors.go
+++ b/pkg/lang/vm/errors.go
@@ -9,16 +9,17 @@ import (
 )
 
 type Error struct {
-	Pos  code.Pos
-	Line int
-	Err  error
+	Pos    code.Pos
+	Module string
+	Line   int
+	Err    error
 }
 
 func (e Error) Error() string {
 	if e.Line == -1 {
-		return fmt.Sprintf("vm error in module '%d' at pc %d, unknown line: %v", e.Pos.Module, e.Pos.PC, e.Err)
+		return fmt.Sprintf("vm error in module '%s' at pc %d, unknown line: %v", e.Module, e.Pos.PC, e.Err)
 	}
-	return fmt.Sprintf("vm error in module '%d' at pc %d, line %d: %v", e.Pos.Module, e.Pos.PC, e.Line, e.Err)
+	return fmt.Sprintf("vm error in module '%s' at pc %d, line %d: %v", e.Module, e.Pos.PC, e.Line, e.Err)
 }
 
 // Fatal errors
diff --git a/pkg/lang/vm/exe.go b/pkg/lang/vm/exe.go
new file mode 100644
index 0000000..7d6fc61
--- /dev/null
+++ b/pkg/lang/vm/exe.go
@@ -0,0 +1,62 @@
+package vm
+
+import (
+	"jinx/pkg/lang/vm/mem"
+	"jinx/pkg/lang/vm/stack"
+	"jinx/pkg/lang/vm/value"
+)
+
+func (vm *VM) Mem() mem.Mem {
+	return vm.memory
+}
+
+func (vm *VM) Stack() stack.Stack {
+	return vm.stack
+}
+
+func (vm *VM) GetThis() (value.Value, error) {
+	err := vm.execGetEnv(0)
+	if err != nil {
+		return value.Value{}, err
+	}
+
+	// Take it back!
+	this, err := vm.popAndDrop()
+	if err != nil {
+		return value.Value{}, err
+	}
+
+	return this, nil
+}
+
+func (vm *VM) AddGlobal(name string, v value.Value) error {
+	globalPtr, err := vm.memory.Allocate(mem.CellKindGlobal)
+	if err != nil {
+		return err
+	}
+
+	globalCell := value.GlobalCell(v)
+	if err := vm.memory.Set(globalPtr, globalCell); err != nil {
+		return err
+	}
+
+	vm.globals[name] = globalPtr
+
+	return nil
+}
+
+func (vm *VM) GetGlobal(name string) (value.Value, bool, error) {
+	ptr, ok := vm.globals[name]
+	if !ok {
+		return value.Value{}, false, nil
+	}
+
+	cell, err := vm.getMemCell(ptr, mem.CellKindGlobal, false)
+	if err != nil {
+		return value.Value{}, false, err
+	}
+
+	v := cell.(value.GlobalCell).Get()
+
+	return v, true, nil
+}
diff --git a/pkg/lang/vm/exec.go b/pkg/lang/vm/exec.go
index eeb8710..ade6cc0 100644
--- a/pkg/lang/vm/exec.go
+++ b/pkg/lang/vm/exec.go
@@ -44,7 +44,7 @@ func (vm *VM) execPushArray() error {
 
 func (vm *VM) execPushFunction(pc int) {
 	// TODO: Make push ops into functions, where the argCount can be passed.
-	vm.stack.Push(value.NewFunction(code.NewPos(vm.module(), pc), 0))
+	vm.stack.Push(value.NewFunction(code.NewPos(vm.moduleID(), pc), 0))
 }
 
 func (vm *VM) execPushObject() error {
@@ -71,35 +71,56 @@ func (vm *VM) execAddGlobal(name string) error {
 		return err
 	}
 
-	globalPtr, err := vm.memory.Allocate(mem.CellKindGlobal)
+	module := vm.module()
+	qualifiedName, err := module.GetGlobal(name)
 	if err != nil {
 		return err
 	}
 
-	globalCell := value.GlobalCell(v)
-	if err := vm.memory.Set(globalPtr, globalCell); err != nil {
-		return err
+	// Fill the core ptrs if we got a core global.
+	if module.IsCore() {
+		if v.Type() == value.TypeRefType {
+			ptr := v.Data().(value.TypeRefData).TypeRef()
+
+			switch qualifiedName {
+			case ":core:Type":
+				vm.corePtrs.SetTypeRef(ptr)
+			case ":core:Int":
+				vm.corePtrs.SetInt(ptr)
+			case ":core:Float":
+				vm.corePtrs.SetFloat(ptr)
+			case ":core:Bool":
+				vm.corePtrs.SetBool(ptr)
+			case ":core:Null":
+				vm.corePtrs.SetNull(ptr)
+			case ":core:Array":
+				vm.corePtrs.SetArray(ptr)
+			case ":core:Function":
+				vm.corePtrs.SetFunction(ptr)
+			case ":core:String":
+				vm.corePtrs.SetString(ptr)
+			}
+		}
 	}
 
-	vm.globals[name] = globalPtr
+	if err := vm.AddGlobal(qualifiedName, v); err != nil {
+		return err
+	}
 
 	return nil
 }
 
 func (vm *VM) execGetGlobal(name string) error {
-	ptr, ok := vm.globals[name]
-	if !ok {
-		return ErrNoSuchGlobal{GlobalName: name}
-	}
-
-	cell, err := vm.getMemCell(ptr, mem.CellKindGlobal, false)
+	v, ok, err := vm.GetGlobal(name)
 	if err != nil {
 		return err
 	}
 
-	v := cell.(value.GlobalCell).Get()
-	v = v.Clone(vm.memory)
+	if !ok {
+		return ErrNoSuchGlobal{GlobalName: name}
+	}
 
+	v = v.Clone(vm.memory)
 	vm.stack.Push(v)
 	return nil
 }
@@ -234,7 +255,7 @@ func (vm *VM) execGetMember(name string) error {
 		}
 	}
 
-	typeCell, err := vm.getMemCell(parent.TypePtr(), mem.CellKindType, false)
+	typeCell, err := vm.getMemCell(parent.TypePtr(&vm.corePtrs), mem.CellKindType, false)
 	if err != nil {
 		return err
 	}
@@ -889,6 +910,8 @@ func (vm *VM) execCall(argCount uint) error {
 
 		if !val.IsEmpty() {
 			vm.stack.Push(val)
+		} else {
+			vm.stack.Push(value.NewNull())
 		}
 
 		return nil
diff --git a/pkg/lang/vm/executor/executor.go b/pkg/lang/vm/executor/executor.go
new file mode 100644
index 0000000..0b7ccef
--- /dev/null
+++ b/pkg/lang/vm/executor/executor.go
@@ -0,0 +1,17 @@
+package executor
+
+import (
+	"jinx/pkg/lang/vm/mem"
+	"jinx/pkg/lang/vm/stack"
+	"jinx/pkg/lang/vm/value"
+)
+
+type Exectutor interface {
+	Mem() mem.Mem
+	Stack() stack.Stack
+
+	GetThis() (value.Value, error)
+
+	AddGlobal(name string, v value.Value) error
+	GetGlobal(name string) (value.Value, bool, error)
+}
diff --git a/pkg/lang/vm/executor/nativefunc.go b/pkg/lang/vm/executor/nativefunc.go
new file mode 100644
index 0000000..2188bfb
--- /dev/null
+++ b/pkg/lang/vm/executor/nativefunc.go
@@ -0,0 +1,5 @@
+package executor
+
+import "jinx/pkg/lang/vm/value"
+
+type NativeFunc func(executor Exectutor, args []value.Value) (value.Value, error)
diff --git a/pkg/lang/vm/setup.go b/pkg/lang/vm/setup.go
index db44b8a..201538d 100644
--- a/pkg/lang/vm/setup.go
+++ b/pkg/lang/vm/setup.go
@@ -1,44 +1,54 @@
 package vm
 
 import (
-	"jinx/pkg/lang/vm/mem"
+	"fmt"
+	"jinx/pkg/lang/modules"
+	"jinx/pkg/lang/modules/core"
+	"jinx/pkg/lang/modules/natives"
 	"jinx/pkg/lang/vm/value"
 )
 
 func (vm *VM) setup() error {
-	if err := vm.setupCoreLib(); err != nil {
-		return err
-	}
+	vm.modules = make([]modules.Module, 0, len(vm.main.Deps()))
 
-	return nil
-}
+	// Add all natives to the VM as globals.
+	for _, native := range natives.Natives {
+		decollidedName := fmt.Sprintf("%s#native", native.Name)
 
-func (vm *VM) setupCoreLib() error {
-	type allocationJob struct {
-		at mem.Ptr
-		t  value.Type
-	}
+		if _, ok := vm.globals[decollidedName]; ok {
+			return fmt.Errorf("native %s already exists", decollidedName)
+		}
 
-	toAllocate := []allocationJob{
-		{at: value.CORE_TYPE_NULL, t:  vm.createCoreNullType()},
-		{at: value.CORE_TYPE_INT, t:  vm.createCoreIntType()},
-		{at: value.CORE_TYPE_FLOAT, t:  vm.createCoreFloatType()},
-		{at: value.CORE_TYPE_STRING, t:  vm.createCoreStringType()},
-		{at: value.CORE_TYPE_BOOL, t:  vm.createCoreBoolType()},
-		{at: value.CORE_TYPE_ARRAY, t:  vm.createCoreArrayType()},
-		{at: value.CORE_TYPE_FUNCTION, t:  vm.createCoreFunctionType()},
-		{at: value.CORE_TYPE_TYPE_REF, t:  vm.createCoreTypeRefType()},
-	}
+		nativeFunc := native.Fn // Capture the native function, because Go is fun.
+		wrappedFunc := func(args []value.Value) (value.Value, error) {
+			return nativeFunc(vm, args)
+		}
 
-	for _, job := range toAllocate {
-		if err := vm.memory.AllocateAt(job.at, mem.CellKindType); err != nil {
+		nativeFunction := value.NewNativeFunction(wrappedFunc, uint(native.ArgCount))
+		if err := vm.AddGlobal(decollidedName, nativeFunction); err != nil {
 			return err
 		}
+	}
 
-		if err := vm.memory.Set(job.at, value.TypeCell(job.t)); err != nil {
+	for _, depRef := range vm.main.Deps() {
+		dep, err := vm.resolveModule(depRef)
+		if err != nil {
 			return err
 		}
+
+		vm.modules = append(vm.modules, dep)
 	}
 
 	return nil
 }
+
+// TODO: Make an actual module resolver.
+func (vm *VM) resolveModule(ref modules.ModuleRef) (modules.Module, error) {
+	// TODO: Support other modules than core.
+	switch ref.Name() {
+	case "core":
+		return core.Module, nil
+	default:
+		return modules.Module{}, fmt.Errorf("unknown module: %s", ref.Name())
+	}
+}
diff --git a/pkg/lang/vm/utils.go b/pkg/lang/vm/utils.go
index 2b7fa6f..975d31b 100644
--- a/pkg/lang/vm/utils.go
+++ b/pkg/lang/vm/utils.go
@@ -1,6 +1,7 @@
 package vm
 
 import (
+	"jinx/pkg/lang/modules"
 	"jinx/pkg/lang/vm/code"
 	"jinx/pkg/lang/vm/mem"
 	"jinx/pkg/lang/vm/value"
@@ -35,7 +36,7 @@ func (vm *VM) getMemCell(ptr mem.Ptr, kind mem.CellKind, allowNil bool) (mem.Cel
 	}
 
 	if !vm.memory.Is(ptr, kind) {
-		return nil, ErrUnexpectedMemCell{Ptr: ptr, Expected: mem.CellKindEnv, Got: vm.memory.Kind(ptr)}
+		return nil, ErrUnexpectedMemCell{Ptr: ptr, Expected: kind, Got: vm.memory.Kind(ptr)}
 	}
 
 	cell, err := vm.memory.Get(ptr)
@@ -75,10 +76,18 @@ func (vm *VM) getMemCell(ptr mem.Ptr, kind mem.CellKind, allowNil bool) (mem.Cel
 	return cell, nil
 }
 
-func (vm *VM) module() int {
+func (vm *VM) moduleID() int {
 	return vm.pos.Module
 }
 
+func (vm *VM) module() modules.Module {
+	if vm.moduleID() == -1 {
+		return vm.main
+	} else {
+		return vm.modules[vm.moduleID()]
+	}
+}
+
 func (vm *VM) pc() int {
 	return vm.pos.PC
 }
diff --git a/pkg/lang/vm/value/core_ptrs.go b/pkg/lang/vm/value/core_ptrs.go
index a1f0365..adb9a6b 100644
--- a/pkg/lang/vm/value/core_ptrs.go
+++ b/pkg/lang/vm/value/core_ptrs.go
@@ -4,14 +4,90 @@ import (
 	"jinx/pkg/lang/vm/mem"
 )
 
-const (
-	CORE_TYPE_NULL = mem.Ptr(iota + 1)
-	CORE_TYPE_INT
-	CORE_TYPE_FLOAT
-	CORE_TYPE_STRING
-	CORE_TYPE_BOOL
-	CORE_TYPE_ARRAY
-	CORE_TYPE_FUNCTION
-
-	CORE_TYPE_TYPE_REF
-)
+// All CorePtrs are unitialized until the core module is loaded.
+type CorePtrs struct {
+	CoreTypeNull     mem.Ptr
+	CoreTypeInt      mem.Ptr
+	CoreTypeFloat    mem.Ptr
+	CoreTypeString   mem.Ptr
+	CoreTypeBool     mem.Ptr
+	CoreTypeArray    mem.Ptr
+	CoreTypeFunction mem.Ptr
+
+	CoreTypeTypeRef mem.Ptr
+}
+
+func (c *CorePtrs) SetNull(p mem.Ptr) {
+	if c.CoreTypeNull != mem.NullPtr {
+		panic("core ptr null already set")
+	}
+
+	c.CoreTypeNull = p
+}
+
+func (c *CorePtrs) SetInt(p mem.Ptr) {
+	if c.CoreTypeInt != mem.NullPtr {
+		panic("core ptr int already set")
+	}
+
+	c.CoreTypeInt = p
+}
+
+func (c *CorePtrs) SetFloat(p mem.Ptr) {
+	if c.CoreTypeFloat != mem.NullPtr {
+		panic("core ptr float already set")
+	}
+
+	c.CoreTypeFloat = p
+}
+
+func (c *CorePtrs) SetString(p mem.Ptr) {
+	if c.CoreTypeString != mem.NullPtr {
+		panic("core ptr string already set")
+	}
+
+	c.CoreTypeString = p
+}
+
+func (c *CorePtrs) SetBool(p mem.Ptr) {
+	if c.CoreTypeBool != mem.NullPtr {
+		panic("core ptr bool already set")
+	}
+
+	c.CoreTypeBool = p
+}
+
+func (c *CorePtrs) SetArray(p mem.Ptr) {
+	if c.CoreTypeArray != mem.NullPtr {
+		panic("core ptr array already set")
+	}
+
+	c.CoreTypeArray = p
+}
+
+func (c *CorePtrs) SetFunction(p mem.Ptr) {
+	if c.CoreTypeFunction != mem.NullPtr {
+		panic("core ptr function already set")
+	}
+
+	c.CoreTypeFunction = p
+}
+
+func (c *CorePtrs) SetTypeRef(p mem.Ptr) {
+	if c.CoreTypeTypeRef != mem.NullPtr {
+		panic("core ptr type ref already set")
+	}
+
+	c.CoreTypeTypeRef = p
+}
+
+func (c *CorePtrs) Complete() bool {
+	return c.CoreTypeNull != mem.NullPtr &&
+		c.CoreTypeInt != mem.NullPtr &&
+		c.CoreTypeFloat != mem.NullPtr &&
+		c.CoreTypeString != mem.NullPtr &&
+		c.CoreTypeBool != mem.NullPtr &&
+		c.CoreTypeArray != mem.NullPtr &&
+		c.CoreTypeFunction != mem.NullPtr &&
+		c.CoreTypeTypeRef != mem.NullPtr
+}
diff --git a/pkg/lang/vm/value/data.go b/pkg/lang/vm/value/data.go
index 39193d5..1cd258b 100644
--- a/pkg/lang/vm/value/data.go
+++ b/pkg/lang/vm/value/data.go
@@ -116,11 +116,31 @@ func (a ArrayData) Push(m mem.Mem, v Value) error {
 
 	arr := data.(ArrayCell).Get()
 	arr = append(arr, v)
-	m.Set(a.data, ArrayCell(arr))
 
+	if err := m.Set(a.data, ArrayCell(arr)); err != nil {
+		return err
+	}
 	return nil
 }
 
+func (a ArrayData) Pop(m mem.Mem) (Value, error) {
+	data, err := m.Get(a.data)
+	if err != nil {
+		return Value{}, err
+	}
+
+	arr := data.(ArrayCell).Get()
+
+	popped := arr[len(arr)-1]
+	arr = arr[:len(arr)-1]
+
+	if err := m.Set(a.data, ArrayCell(arr)); err != nil {
+		return Value{}, err
+	}
+
+	return popped, nil
+}
+
 type NullData struct{}
 
 func (n NullData) String(_ mem.Mem) (string, error) {
diff --git a/pkg/lang/vm/value/value.go b/pkg/lang/vm/value/value.go
index 5ef3f07..cce2f5e 100644
--- a/pkg/lang/vm/value/value.go
+++ b/pkg/lang/vm/value/value.go
@@ -93,24 +93,24 @@ func (v Value) Type() TypeKind {
 	return v.t
 }
 
-func (v Value) TypePtr() mem.Ptr {
+func (v Value) TypePtr(corePtrs *CorePtrs) mem.Ptr {
 	switch v.t {
 	case IntType:
-		return CORE_TYPE_INT
+		return corePtrs.CoreTypeInt
 	case FloatType:
-		return CORE_TYPE_FLOAT
+		return corePtrs.CoreTypeFloat
 	case StringType:
-		return CORE_TYPE_STRING
+		return corePtrs.CoreTypeFloat
 	case BoolType:
-		return CORE_TYPE_BOOL
+		return corePtrs.CoreTypeBool
 	case ArrayType:
-		return CORE_TYPE_ARRAY
+		return corePtrs.CoreTypeArray
 	case NullType:
-		return CORE_TYPE_NULL
+		return corePtrs.CoreTypeNull
 	case FunctionType:
-		return CORE_TYPE_FUNCTION
+		return corePtrs.CoreTypeFunction
 	case TypeRefType:
-		return CORE_TYPE_TYPE_REF
+		return corePtrs.CoreTypeTypeRef
 	case ObjectType:
 		return v.d.(ObjectData).t
 	default:
diff --git a/pkg/lang/vm/vm.go b/pkg/lang/vm/vm.go
index 4a4aa68..4b40633 100644
--- a/pkg/lang/vm/vm.go
+++ b/pkg/lang/vm/vm.go
@@ -1,34 +1,42 @@
 package vm
 
 import (
+	"jinx/pkg/lang/modules"
 	"jinx/pkg/lang/vm/code"
 	"jinx/pkg/lang/vm/mem"
 	"jinx/pkg/lang/vm/stack"
+	"jinx/pkg/lang/vm/value"
 )
 
 type VM struct {
 	pos code.Pos
 
-	modules []*code.Code
+	main    modules.Module
+	modules []modules.Module
 
 	stack  stack.Stack
 	memory mem.Mem
 
 	canAddGlobals bool
 	globals       map[string]mem.Ptr
+
+	corePtrs value.CorePtrs
 }
 
-func New(main *code.Code, deps []*code.Code) *VM {
+func New(main modules.Module) *VM {
 	vm := &VM{
-		pos: code.NewPos(0, 0),
+		pos: code.NewPos(-1, 0),
 
-		modules: append([]*code.Code{main}, deps...),
+		main:    main,
+		modules: []modules.Module{},
 
 		stack:  stack.New(),
 		memory: mem.New(),
 
 		canAddGlobals: false,
 		globals:       make(map[string]mem.Ptr),
+
+		corePtrs: value.CorePtrs{},
 	}
 
 	if err := vm.setup(); err != nil {
@@ -57,7 +65,7 @@ func (vm *VM) GetResult() (string, error) {
 
 func (vm *VM) Run() error {
 	vm.canAddGlobals = true
-	for i := 1; i < len(vm.modules); i++ {
+	for i := 0; i < len(vm.modules); i++ {
 		if err := vm.executeModule(i); err != nil {
 			return err
 		}
@@ -77,8 +85,13 @@ func (vm *VM) Run() error {
 		}
 	}
 
+	// All the core ptrs should be initialized once core is loaded
+	if !vm.corePtrs.Complete() {
+		panic("core ptrs not initialized after loading core (perhaps core is missing?)")
+	}
+
 	vm.canAddGlobals = false
-	if err := vm.executeModule(0); err != nil {
+	if err := vm.executeModule(-1); err != nil {
 		return err
 	}
 
@@ -93,27 +106,27 @@ func (vm *VM) executeModule(moduleID int) error {
 	}
 
 	for {
-		module := vm.modules[vm.module()]
+		module := vm.module()
+		code := module.Code()
 
-		if vm.pc() >= module.Len() {
+		if vm.pc() >= code.Len() {
 			return nil
 		}
 
-		op, advance := module.GetOp(vm.pc())
+		op, advance := code.GetOp(vm.pc())
 		vm.advancePC(advance)
 
-		if decision, err := vm.step(module, op); err != nil {
+		if decision, err := vm.step(code, op); err != nil {
 			return Error{
-				Pos:  vm.pos,
-				Line: module.Debug().PCToLine(vm.pc()),
-				Err:  err,
+				Pos:    vm.pos,
+				Module: module.Name(),
+				Line:   code.Debug().PCToLine(vm.pc()),
+				Err:    err,
 			}
 		} else if decision == stepDecisionHalt {
 			return nil
 		}
 	}
-
-	return nil
 }
 
 type stepDecision int
diff --git a/pkg/lang/vm/vm_test.go b/pkg/lang/vm/vm_test.go
index b25a446..17edb2e 100644
--- a/pkg/lang/vm/vm_test.go
+++ b/pkg/lang/vm/vm_test.go
@@ -1,9 +1,11 @@
 package vm_test
 
 import (
+	"jinx/pkg/lang/modules"
 	"jinx/pkg/lang/vm"
 	"jinx/pkg/lang/vm/code"
 	"jinx/pkg/lang/vm/text"
+	"os"
 	"strings"
 	"testing"
 
@@ -294,6 +296,7 @@ func TestTypeConstruct(t *testing.T) {
 	set_arg_count 2
 	
 	call 2
+	drop 1
 
 	get_local 0
 	get_member "$add_method"
@@ -302,6 +305,7 @@ func TestTypeConstruct(t *testing.T) {
 	push_function @Cat:meow
 
 	call 2
+	drop 1
 	
 	# Create a new instance of Cat
 	get_local 0
@@ -441,49 +445,74 @@ func TestPrimes(t *testing.T) {
 }
 
 func TestModules(t *testing.T) {
-	mainSrc := `
-	get_global "add_one"
-	get_global "x"
-	call 1
-	halt
-	`
-
-	librarySrc1 := `
-	push_int 41
-	add_global "x"
-	halt
-	`
+	t.Skip("We can't pass in random modules anymore, they have to go through the resolver")
+
+	// mainSrc := `
+	// get_global "add_one"
+	// get_global "x"
+	// call 1
+	// halt
+	// `
+
+	// librarySrc1 := `
+	// push_int 41
+	// add_global "x"
+	// halt
+	// `
+
+	// librarySrc2 := `
+	// push_function @add_one
+	// set_arg_count 1
+	// add_global "add_one"
+	// halt
+
+	// @add_one:
+	// get_local 0
+	// push_int 1
+	// add
+	// ret
+	// `
+
+	// main := compile(t, mainSrc)
+	// library1 := compile(t, librarySrc1)
+	// library2 := compile(t, librarySrc2)
+
+	// vm := vm.New(&main, []*code.Code{&library1, &library2})
+	// err := vm.Run()
+	// require.NoError(t, err)
+
+	// res, err := vm.GetResult()
+	// require.NoError(t, err)
+
+	// require.Equal(t, "42", res)
+}
 
-	librarySrc2 := `
-	push_function @add_one
-	set_arg_count 1
-	add_global "add_one"
+func TestCoreSay(t *testing.T) {
+	src := `
+	get_global ":core:say"
+	push_string "Meow!!"
+	call 1
+	push_int 0
 	halt
-
-	@add_one:
-	get_local 0
-	push_int 1
-	add
-	ret
 	`
 
-	main := compile(t, mainSrc)
-	library1 := compile(t, librarySrc1)
-	library2 := compile(t, librarySrc2)
+	previous := os.Stdout
+	r, w, _ := os.Pipe()
+	defer w.Close()
+	os.Stdout = w
+	test(t, src, "0")
+	os.Stdout = previous
 
-	vm := vm.New(&main, []*code.Code{&library1, &library2})
-	err := vm.Run()
-	require.NoError(t, err)
-
-	res, err := vm.GetResult()
+	result := make([]byte, 8)
+	_, err := r.Read(result)
 	require.NoError(t, err)
 
-	require.Equal(t, "42", res)
+	require.Equal(t, "\"Meow!!\"", string(result))
 }
 
 func test(t *testing.T, src string, expected string) {
 	bc := compile(t, src)
-	vm := vm.New(&bc, nil)
+	vm := vm.New(modules.NewUnknownModule(&bc))
 	err := vm.Run()
 	require.NoError(t, err)