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