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 --- 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 +++- 5 files changed, 208 insertions(+), 15 deletions(-) (limited to 'pkg/lang/compiler') 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 -- cgit 1.4.1