From 86f31acf6789be116dcc54ed85b069a37c0f7aa8 Mon Sep 17 00:00:00 2001 From: Mel Date: Thu, 11 Aug 2022 01:25:47 +0000 Subject: Actual modules and core --- cmd/lang/main.go | 11 +-- cmd/vm/main.go | 3 +- pkg/bot/cmds.go | 11 ++- pkg/lang/compiler/compiler.go | 143 +++++++++++++++++++++++++--- pkg/lang/compiler/compiler_test.go | 27 +++++- pkg/lang/compiler/scope/scope_chain.go | 37 ++++++++ pkg/lang/compiler/scope/scopes.go | 2 + pkg/lang/compiler/scope/symbol.go | 14 ++- pkg/lang/modules/core/compiled.go | 22 +++++ pkg/lang/modules/core/core.lang | 78 ++++++++++++++++ pkg/lang/modules/core/natives.go | 166 +++++++++++++++++++++++++++++++++ pkg/lang/modules/module.go | 104 +++++++++++++++++++++ pkg/lang/modules/natives/natives.go | 39 ++++++++ pkg/lang/vm/core.go | 146 ----------------------------- pkg/lang/vm/errors.go | 11 ++- pkg/lang/vm/exe.go | 62 ++++++++++++ pkg/lang/vm/exec.go | 53 ++++++++--- pkg/lang/vm/executor/executor.go | 17 ++++ pkg/lang/vm/executor/nativefunc.go | 5 + pkg/lang/vm/setup.go | 58 +++++++----- pkg/lang/vm/utils.go | 13 ++- pkg/lang/vm/value/core_ptrs.go | 98 ++++++++++++++++--- pkg/lang/vm/value/data.go | 22 ++++- pkg/lang/vm/value/value.go | 18 ++-- pkg/lang/vm/vm.go | 43 ++++++--- pkg/lang/vm/vm_test.go | 93 +++++++++++------- 26 files changed, 1008 insertions(+), 288 deletions(-) create mode 100644 pkg/lang/modules/core/compiled.go create mode 100644 pkg/lang/modules/core/core.lang create mode 100644 pkg/lang/modules/core/natives.go create mode 100644 pkg/lang/modules/module.go create mode 100644 pkg/lang/modules/natives/natives.go delete mode 100644 pkg/lang/vm/core.go create mode 100644 pkg/lang/vm/exe.go create mode 100644 pkg/lang/vm/executor/executor.go create mode 100644 pkg/lang/vm/executor/nativefunc.go diff --git a/cmd/lang/main.go b/cmd/lang/main.go index 0a8eee8..f5046a7 100644 --- a/cmd/lang/main.go +++ b/cmd/lang/main.go @@ -7,7 +7,6 @@ import ( "jinx/pkg/lang/parser" "jinx/pkg/lang/scanner" "jinx/pkg/lang/vm" - "jinx/pkg/lang/vm/code" "os" ) @@ -32,8 +31,6 @@ func main() { } defer file.Close() - var bc code.Code - scanner := scanner.New(file) tokens, err := scanner.Scan() if err != nil { @@ -46,8 +43,8 @@ func main() { exit("error during parsing: %v", err) } - comp := compiler.New(program) - bc, err = comp.Compile() + comp := compiler.New("program", "noone", program) + module, err := comp.Compile() if err != nil { exit("compilation failed: %v", err) } @@ -59,13 +56,13 @@ func main() { } defer output.Close() - if _, err := output.Write(bc.Code()); err != nil { + if _, err := output.Write(module.Code().Code()); err != nil { exit("could not write to file: %v", err) } } if *run { - vm := vm.New(&bc, nil) + vm := vm.New(module) if err := vm.Run(); err != nil { exit("execution failed: %v", err) } diff --git a/cmd/vm/main.go b/cmd/vm/main.go index 5ee954e..ecebcbf 100644 --- a/cmd/vm/main.go +++ b/cmd/vm/main.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "io" + "jinx/pkg/lang/modules" "jinx/pkg/lang/vm" "jinx/pkg/lang/vm/code" "jinx/pkg/lang/vm/text" @@ -90,7 +91,7 @@ func main() { } if *run { - vm := vm.New(&bc, nil) + vm := vm.New(modules.NewUnknownModule(&bc)) if err := vm.Run(); err != nil { exit("execution failed: %v", err) } 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) -- cgit 1.4.1