aboutsummaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/lang/compiler/compiler.go231
-rw-r--r--pkg/lang/compiler/scope_chain.go226
-rw-r--r--pkg/lang/compiler/symbol.go31
-rw-r--r--pkg/lang/vm/code/builder.go108
-rw-r--r--pkg/lang/vm/code/builder_test.go31
-rw-r--r--pkg/lang/vm/code/errors.go32
-rw-r--r--pkg/lang/vm/code/marker.go23
-rw-r--r--pkg/lang/vm/text/compiler.go2
8 files changed, 479 insertions, 205 deletions
diff --git a/pkg/lang/compiler/compiler.go b/pkg/lang/compiler/compiler.go
index 116db1c..a31ade7 100644
--- a/pkg/lang/compiler/compiler.go
+++ b/pkg/lang/compiler/compiler.go
@@ -31,7 +31,7 @@ func (comp *Compiler) Compile() (code.Code, error) {
target.AppendOp(code.OpHalt)
- return target.Build(), nil
+ return target.Build()
}
func (comp *Compiler) compileStmt(t *code.Builder, stmt ast.Stmt) error {
@@ -39,6 +39,12 @@ func (comp *Compiler) compileStmt(t *code.Builder, stmt ast.Stmt) error {
switch stmt.Kind {
case ast.StmtKindEmpty:
// Do nothing.
+ case ast.StmtKindUse:
+ panic("use statements not implemented")
+ case ast.StmtKindFnDecl:
+ panic("function declaration statements not implemented")
+ case ast.StmtKindObjectDecl:
+ panic("object declaration statements not implemented")
case ast.StmtKindVarDecl:
decl := stmt.Value.(ast.StmtVarDecl)
err = comp.compileVarDeclStmt(t, decl)
@@ -51,11 +57,21 @@ func (comp *Compiler) compileStmt(t *code.Builder, stmt ast.Stmt) error {
case ast.StmtKindForIn:
forCondIn := stmt.Value.(ast.StmtForIn)
err = comp.compileForInStmt(t, forCondIn)
+ case ast.StmtKindTry:
+ panic("try statements not implemented")
+ case ast.StmtKindReturn:
+ panic("return statements not implemented")
+ case ast.StmtKindContinue:
+ panic("continue statements not implemented")
+ case ast.StmtKindBreak:
+ panic("break statements not implemented")
+ case ast.StmtKindThrow:
+ panic("throw statements not implemented")
case ast.StmtKindExpr:
expr := stmt.Value.(ast.StmtExpr).Value
err = comp.compileExpr(t, expr)
default:
- panic(fmt.Errorf("statement of kind %v not implemented", stmt.Kind))
+ panic(fmt.Errorf("unknown statement kind: %d", stmt.Kind))
}
return err
@@ -90,54 +106,56 @@ func (comp *Compiler) compileIfStmt(t *code.Builder, ifStmt ast.StmtIf) error {
// preventing other CondNodes from running. This is missing from the last CondNode.
// Example: `jmp @end`
- subUnits := make([]code.Builder, 0, len(ifStmt.Conds))
+ // First we create all the markers we'll need for the if statement
+ parentMarker := comp.scopes.CreateAnonymousFunctionSubUnit()
- totalLength := 0
+ endMarker := parentMarker.SubMarker("end")
+
+ condMarkers := make([]code.Marker, 0, len(ifStmt.Conds)-1) // We don't need a marker for the first CondNode.
+ for i := 0; i < len(ifStmt.Conds)-1; i++ {
+ condMarker := parentMarker.SubMarker("cond_%d", i+1)
+ condMarkers = append(condMarkers, condMarker)
+ }
for i, cond := range ifStmt.Conds {
- // Then block
- thenTarget := code.NewBuilder()
- if err := comp.compileBlockNode(&thenTarget, cond.Then); err != nil {
- return err
- }
+ isFirst := i == 0
+ isLast := i == len(ifStmt.Conds)-1
- totalLength += thenTarget.Len()
- if i != len(ifStmt.Conds)-1 {
- totalLength += lengthOfAJumpInstruction
+ // If we aren't in the first CondNode, the node before it needs a marker to here.
+ if !isFirst {
+ marker := condMarkers[i-1]
+ t.PutMarker(marker)
}
- // Condition check
- conditionTarget := code.NewBuilder()
if !cond.Cond.IsEmpty() {
- if err := comp.compileExpr(&conditionTarget, cond.Cond); err != nil {
+ // Condition check
+ if err := comp.compileExpr(t, cond.Cond); err != nil {
return err
}
- totalLength += conditionTarget.Len() + lengthOfAJumpInstruction // condjmp
-
- conditionTarget.AppendOp(code.OpJf)
// Condition jump
- conditionTarget.AppendReferenceToPc(int64(totalLength))
+ t.AppendOp(code.OpJf)
+ if isLast {
+ t.AppendMarkerReference(endMarker)
+ } else {
+ nextCondMarker := condMarkers[i]
+ t.AppendMarkerReference(nextCondMarker)
+ }
}
- subUnit := conditionTarget
- subUnit.AppendBuilder(thenTarget)
- subUnits = append(subUnits, subUnit)
- }
-
- result := code.NewBuilder()
-
- // Then jumps
- for i, subUnit := range subUnits {
- if i != len(ifStmt.Conds)-1 {
- subUnit.AppendOp(code.OpJmp)
- subUnit.AppendReferenceToPc(int64(totalLength))
+ // Then block
+ if err := comp.compileBlockNode(t, cond.Then); err != nil {
+ return err
}
- result.AppendBuilderWithoutAdjustingReferences(subUnit)
+ // Then jump
+ if !isLast {
+ t.AppendOp(code.OpJmp)
+ t.AppendMarkerReference(endMarker)
+ }
}
- t.AppendBuilder(result)
+ t.PutMarker(endMarker)
return nil
}
@@ -149,34 +167,34 @@ func (comp *Compiler) compileForCondStmt(t *code.Builder, forCondStmt ast.StmtFo
// 3. Do block: Does something
// 4. Repeat jump: Jumps back to start
- // Do block
- doTarget := code.NewBuilder()
- if err := comp.compileBlockNode(&doTarget, forCondStmt.Do); err != nil {
- return err
- }
+ parentMarker := comp.scopes.CreateAnonymousFunctionSubUnit()
+
+ startMarker := parentMarker.SubMarker("start")
+ endMarker := parentMarker.SubMarker("end")
+
+ t.PutMarker(startMarker)
- conditionTarget := code.NewBuilder()
if !forCondStmt.Cond.IsEmpty() {
// Condition check
- if err := comp.compileExpr(&conditionTarget, forCondStmt.Cond); err != nil {
+ if err := comp.compileExpr(t, forCondStmt.Cond); err != nil {
return err
}
- endOfFor := conditionTarget.Len() + doTarget.Len() + lengthOfAJumpInstruction*2
-
- // Condition jump
- conditionTarget.AppendOp(code.OpJf)
- conditionTarget.AppendReferenceToPc(int64(endOfFor))
+ // Condition check
+ t.AppendOp(code.OpJf)
+ t.AppendMarkerReference(endMarker)
}
- subUnit := conditionTarget
- subUnit.AppendBuilder(doTarget)
+ // Do block
+ if err := comp.compileBlockNode(t, forCondStmt.Do); err != nil {
+ return err
+ }
// Repeat jump
- subUnit.AppendOp(code.OpJmp)
- subUnit.AppendReferenceToPc(int64(0)) // Start of the for
+ t.AppendOp(code.OpJmp)
+ t.AppendMarkerReference(startMarker)
- t.AppendBuilder(subUnit)
+ t.PutMarker(endMarker)
return nil
}
@@ -216,88 +234,81 @@ func (comp *Compiler) compileForInStmt(t *code.Builder, forInStmt ast.StmtForIn)
// @end:
// halt
+ parentMarker := comp.scopes.CreateAnonymousFunctionSubUnit()
+
+ checkMarker := parentMarker.SubUnit("check")
+ endMarker := parentMarker.SubUnit("end")
+
// Preparation
- preparationTarget := code.NewBuilder()
- if err := comp.compileExpr(&preparationTarget, forInStmt.Collection); err != nil {
+ if err := comp.compileExpr(t, forInStmt.Collection); err != nil {
return err
}
collectionLocal := comp.scopes.DeclareAnonymous()
- preparationTarget.AppendOp(code.OpPushInt)
- preparationTarget.AppendInt(0)
+ t.AppendOp(code.OpPushInt)
+ t.AppendInt(0)
iLocal := comp.scopes.DeclareAnonymous()
- preparationTarget.AppendOp(code.OpPushNull)
+ t.AppendOp(code.OpPushNull)
nameLocal, ok := comp.scopes.Declare(forInStmt.Name.Value)
if !ok {
return fmt.Errorf("variable %s already declared", forInStmt.Name.Value)
}
// Condition check
- conditionTarget := code.NewBuilder()
+ t.PutMarker(checkMarker)
- conditionTarget.AppendOp(code.OpGetLocal)
- conditionTarget.AppendInt(int64(iLocal))
+ t.AppendOp(code.OpGetLocal)
+ t.AppendInt(int64(iLocal))
- conditionTarget.AppendOp(code.OpGetLocal)
- conditionTarget.AppendInt(int64(collectionLocal))
+ t.AppendOp(code.OpGetLocal)
+ t.AppendInt(int64(collectionLocal))
- conditionTarget.AppendOp(code.OpGetMember)
- conditionTarget.AppendString("length")
+ t.AppendOp(code.OpGetMember)
+ t.AppendString("length")
- conditionTarget.AppendOp(code.OpCall)
- conditionTarget.AppendInt(0)
+ t.AppendOp(code.OpCall)
+ t.AppendInt(0)
- conditionTarget.AppendOp(code.OpLt)
+ t.AppendOp(code.OpLt)
- // Do Preparation
- doPreparationTarget := code.NewBuilder()
+ // Condition jump
+ t.AppendOp(code.OpJf)
+ t.AppendMarkerReference(endMarker)
- doPreparationTarget.AppendOp(code.OpGetLocal)
- doPreparationTarget.AppendInt(int64(collectionLocal))
+ // Do Preparation
+ t.AppendOp(code.OpGetLocal)
+ t.AppendInt(int64(collectionLocal))
- doPreparationTarget.AppendOp(code.OpGetLocal)
- doPreparationTarget.AppendInt(int64(iLocal))
+ t.AppendOp(code.OpGetLocal)
+ t.AppendInt(int64(iLocal))
- doPreparationTarget.AppendOp(code.OpIndex)
+ t.AppendOp(code.OpIndex)
- doPreparationTarget.AppendOp(code.OpSetLocal)
- doPreparationTarget.AppendInt(int64(nameLocal))
+ t.AppendOp(code.OpSetLocal)
+ t.AppendInt(int64(nameLocal))
- doPreparationTarget.AppendOp(code.OpGetLocal)
- doPreparationTarget.AppendInt(int64(iLocal))
+ t.AppendOp(code.OpGetLocal)
+ t.AppendInt(int64(iLocal))
- doPreparationTarget.AppendOp(code.OpPushInt)
- doPreparationTarget.AppendInt(1)
+ t.AppendOp(code.OpPushInt)
+ t.AppendInt(1)
- doPreparationTarget.AppendOp(code.OpAdd)
+ t.AppendOp(code.OpAdd)
- doPreparationTarget.AppendOp(code.OpSetLocal)
- doPreparationTarget.AppendInt(int64(iLocal))
+ t.AppendOp(code.OpSetLocal)
+ t.AppendInt(int64(iLocal))
// Do block
- doTarget := code.NewBuilder()
- if err := comp.compileBlockNode(&doTarget, forInStmt.Do); err != nil {
+ if err := comp.compileBlockNode(t, forInStmt.Do); err != nil {
return err
}
- // Condition Jump
-
- endOfFor := preparationTarget.Len() + conditionTarget.Len() + doPreparationTarget.Len() + doTarget.Len() + lengthOfAJumpInstruction*2
-
- conditionTarget.AppendOp(code.OpJf)
- conditionTarget.AppendReferenceToPc(int64(endOfFor))
-
- subUnit := preparationTarget
- subUnit.AppendBuilderWithoutAdjustingReferences(conditionTarget)
- subUnit.AppendBuilder(doPreparationTarget)
- subUnit.AppendBuilder(doTarget)
-
// Repeat jump
- subUnit.AppendOp(code.OpJmp)
- subUnit.AppendReferenceToPc(int64(preparationTarget.Len()))
+ t.AppendOp(code.OpJmp)
+ t.AppendMarkerReference(checkMarker)
- t.AppendBuilder(subUnit)
+ t.PutMarker(endMarker)
return nil
}
@@ -389,17 +400,23 @@ func (comp *Compiler) compileAssignExpr(t *code.Builder, expr ast.ExprBinary) er
}
name := expr.Left.Value.(ast.ExprIdent).Value.Value
- symbol, ok := comp.scopes.Lookup(name)
+ symbolId, ok := comp.scopes.Lookup(name)
if !ok {
return fmt.Errorf("variable %s not declared", name)
}
+ if symbolId.symbolKind != SymbolKindVariable {
+ return fmt.Errorf("can't assign to a %v", symbolId.symbolKind)
+ }
+
+ symbol := comp.scopes.GetVariable(symbolId)
+
if err := comp.compileExpr(t, expr.Right); err != nil {
return err
}
t.AppendOp(code.OpSetLocal)
- t.AppendInt(int64(symbol.localIndex))
+ t.AppendInt(int64(symbol.data.localIndex))
return nil
}
@@ -478,14 +495,20 @@ func (comp *Compiler) compileArrayLitExpr(t *code.Builder, expr ast.ExprArrayLit
}
func (comp *Compiler) compileIdentExpr(t *code.Builder, expr ast.ExprIdent) error {
- symbol, ok := comp.scopes.Lookup(expr.Value.Value)
+ symbolId, ok := comp.scopes.Lookup(expr.Value.Value)
if !ok {
return fmt.Errorf("undefined symbol %s", expr.Value.Value)
}
+ if symbolId.symbolKind != SymbolKindVariable {
+ return fmt.Errorf("%v values are not implemeted yet", symbolId.symbolKind)
+ }
+
+ symbol := comp.scopes.GetVariable(symbolId)
+
// TODO: Add boundries to check how the symbol should be fetched. (local, env, global, etc.)
t.AppendOp(code.OpGetLocal)
- t.AppendInt(int64(symbol.localIndex))
+ t.AppendInt(int64(symbol.data.localIndex))
return nil
}
@@ -531,7 +554,3 @@ func (comp *Compiler) compileBlockNode(t *code.Builder, block ast.BlockNode) err
return nil
}
-
-const (
- lengthOfAJumpInstruction = 9 // The length of a jump Op (jmp, jf, jt) and it's following 64-bit integer.
-)
diff --git a/pkg/lang/compiler/scope_chain.go b/pkg/lang/compiler/scope_chain.go
index 6b7e693..ad176da 100644
--- a/pkg/lang/compiler/scope_chain.go
+++ b/pkg/lang/compiler/scope_chain.go
@@ -1,102 +1,238 @@
package compiler
+import (
+ "fmt"
+ "jinx/pkg/lang/vm/code"
+)
+
type ScopeID int
type ScopeChain struct {
- scopes []Scope
+ nameToSymbol map[string]SymbolID
+ scopes []Scope
}
func NewScopeChain() ScopeChain {
scopes := make([]Scope, 1)
- scopes[0] = Scope{
- kind: ScopeKindGlobal,
- nameToSymbol: make(map[string]int),
- symbols: make([]Symbol, 0),
- }
+ scopes[0] = NewFunctionScope("") // Top-most scope is a function scope, so it can have sub-units
return ScopeChain{
- scopes: scopes,
+ nameToSymbol: make(map[string]SymbolID),
+ scopes: scopes,
}
}
+func (sc *ScopeChain) CurrentScopeID() ScopeID {
+ return ScopeID(len(sc.scopes) - 1)
+}
+
func (sc *ScopeChain) Current() *Scope {
- return &sc.scopes[len(sc.scopes)-1]
+ return &sc.scopes[sc.CurrentScopeID()]
}
-func (sc *ScopeChain) Enter(kind ScopeKind) {
- sc.scopes = append(sc.scopes, Scope{
- kind: kind,
- nameToSymbol: make(map[string]int),
- symbols: make([]Symbol, 0),
- })
+func (sc *ScopeChain) CurrentFunction() *Scope {
+ // TODO: Probably should make this lookup constant by making a seperate array of function scopes
+ for i := len(sc.scopes) - 1; i <= 0; i++ {
+ if sc.scopes[i].kind == ScopeKindFunction {
+ return &sc.scopes[i]
+ }
+ }
+
+ panic("top scope should always be a function scope")
+}
+
+func (sc *ScopeChain) Enter() {
+ sc.scopes = append(sc.scopes, NewNormalScope())
+}
+
+func (sc *ScopeChain) EnterFunction(unitName string) {
+ sc.scopes = append(sc.scopes, NewFunctionScope(unitName))
}
func (sc *ScopeChain) Exit() {
- sc.scopes[len(sc.scopes)-1] = Scope{}
- sc.scopes = sc.scopes[:len(sc.scopes)-1]
+ if sc.CurrentScopeID() == 0 {
+ return
+ }
+
+ sc.scopes[sc.CurrentScopeID()] = Scope{}
+ sc.scopes = sc.scopes[:sc.CurrentScopeID()]
}
func (sc *ScopeChain) Declare(name string) (int, bool) {
// Check whether the symbol is already declared in any of the scopes.
- for _, scope := range sc.scopes {
- if _, ok := scope.nameToSymbol[name]; ok {
- return 0, false
- }
+ if _, ok := sc.nameToSymbol[name]; ok {
+ return 0, false
}
current := sc.Current()
- index := len(current.symbols)
+ indexInScope := len(current.variableSymbols)
+
+ symbolID := SymbolID{
+ symbolKind: SymbolKindVariable,
+ scopeID: sc.CurrentScopeID(),
+ indexInScope: indexInScope,
+ }
// Declare the symbol in the current scope.
- current.symbols = append(current.symbols, Symbol{
- kind: SymbolKindVariable,
- name: name,
- localIndex: index,
+ current.variableSymbols = append(current.variableSymbols, Symbol[SymbolVariable]{
+ name: name,
+ data: SymbolVariable{
+ localIndex: indexInScope,
+ },
+ })
+
+ sc.nameToSymbol[name] = symbolID
+
+ return indexInScope, true
+}
+
+func (sc *ScopeChain) DeclareFunction(name string) (code.Marker, bool) {
+ if _, ok := sc.nameToSymbol[name]; ok {
+ return "", false
+ }
+
+ current := sc.Current()
+ index := len(current.functionSymbols)
+
+ symbolID := SymbolID{
+ symbolKind: SymbolKindFunction,
+ scopeID: sc.CurrentScopeID(),
+ indexInScope: index,
+ }
+
+ unitName := sc.CreateFunctionSubUnit(name)
+
+ current.functionSymbols = append(current.functionSymbols, Symbol[SymbolFunction]{
+ name: name,
+ data: SymbolFunction{
+ marker: unitName,
+ },
})
- current.nameToSymbol[name] = index
+ sc.nameToSymbol[name] = symbolID
- return index, true
+ return unitName, true
}
func (sc *ScopeChain) DeclareAnonymous() int {
current := sc.Current()
- index := len(current.symbols)
+ index := len(current.variableSymbols)
- // Declare the symbol in the current scope.
- current.symbols = append(current.symbols, Symbol{
- kind: SymbolKindVariable,
- name: "",
- localIndex: index,
+ current.variableSymbols = append(current.variableSymbols, Symbol[SymbolVariable]{
+ name: "", // An anonymous symbol has no name.
+ data: SymbolVariable{
+ localIndex: index,
+ },
})
return index
}
func (sc *ScopeChain) DeclareTemporary() int {
- return len(sc.Current().symbols)
+ return len(sc.Current().variableSymbols) // :)
}
-func (sc *ScopeChain) Lookup(name string) (Symbol, bool) {
- for i := len(sc.scopes) - 1; i >= 0; i-- {
- if symbol, ok := sc.scopes[i].nameToSymbol[name]; ok {
- return sc.scopes[i].symbols[symbol], true
- }
+func (sc *ScopeChain) CreateAnonymousFunctionSubUnit() code.Marker {
+ fnScope := sc.CurrentFunction()
+ data := fnScope.data.(ScopeFunction)
+
+ index := data.subUnitCount
+ data.subUnitCount++
+
+ fnScope.data = data
+
+ return sc.CreateFunctionSubUnit(fmt.Sprintf("anon_%d", index))
+}
+
+func (sc *ScopeChain) CreateFunctionSubUnit(subUnitName string) code.Marker {
+ fnScope := sc.CurrentFunction()
+ data := fnScope.data.(ScopeFunction)
+
+ name := data.unitName
+ if name == "" {
+ name = code.Marker(subUnitName)
+ } else {
+ name = name.SubUnit(subUnitName)
+ }
+
+ return name
+}
+
+func (sc *ScopeChain) Lookup(name string) (SymbolID, bool) {
+ if id, ok := sc.nameToSymbol[name]; ok {
+ return id, true
+ }
+
+ return SymbolID{}, false
+}
+
+func (sc *ScopeChain) GetVariable(id SymbolID) Symbol[SymbolVariable] {
+ if id.symbolKind != SymbolKindVariable {
+ panic("incorrect symbol id kind given")
+ }
+
+ return sc.scopes[id.scopeID].variableSymbols[id.indexInScope]
+}
+
+func (sc *ScopeChain) GetFunction(id SymbolID) Symbol[SymbolVariable] {
+ if id.symbolKind != SymbolKindVariable {
+ panic("incorrect symbol id kind given")
}
- return Symbol{}, false
+ return sc.scopes[id.scopeID].variableSymbols[id.indexInScope]
+}
+
+type SymbolID struct {
+ symbolKind SymbolKind
+ scopeID ScopeID
+ indexInScope int
}
type ScopeKind int
const (
- ScopeKindGlobal ScopeKind = iota
+ ScopeKindNormal ScopeKind = iota
ScopeKindFunction
- ScopeKindBlock
+ ScopeKindLoop
)
type Scope struct {
- kind ScopeKind
- nameToSymbol map[string]int
- symbols []Symbol
+ variableSymbols []Symbol[SymbolVariable]
+ functionSymbols []Symbol[SymbolFunction]
+
+ kind ScopeKind
+ data any
+}
+
+func NewNormalScope() Scope {
+ return Scope{
+ variableSymbols: make([]Symbol[SymbolVariable], 0),
+ functionSymbols: make([]Symbol[SymbolFunction], 0),
+ kind: ScopeKindNormal,
+ data: ScopeNormal{},
+ }
+}
+
+func NewFunctionScope(unitName string) Scope {
+ return Scope{
+ variableSymbols: make([]Symbol[SymbolVariable], 0),
+ functionSymbols: make([]Symbol[SymbolFunction], 0),
+ kind: ScopeKindFunction,
+ data: ScopeFunction{
+ unitName: code.Marker(unitName),
+ subUnitCount: 0,
+ },
+ }
+}
+
+type ScopeNormal struct{}
+
+type ScopeFunction struct {
+ unitName code.Marker
+ subUnitCount int
+}
+
+type ScopeLoop struct {
+ breakMarker code.Marker
+ continueMarker code.Marker
}
diff --git a/pkg/lang/compiler/symbol.go b/pkg/lang/compiler/symbol.go
index 03838da..d22cdc0 100644
--- a/pkg/lang/compiler/symbol.go
+++ b/pkg/lang/compiler/symbol.go
@@ -1,13 +1,38 @@
package compiler
+import "jinx/pkg/lang/vm/code"
+
type SymbolKind int
const (
SymbolKindVariable SymbolKind = iota
+ SymbolKindFunction
)
-type Symbol struct {
- kind SymbolKind
- name string
+func (s SymbolKind) String() string {
+ switch s {
+ case SymbolKindVariable:
+ return "variable"
+ case SymbolKindFunction:
+ return "function"
+ default:
+ panic("unknown symbol kind")
+ }
+}
+
+type Symbol[D SymbolData] struct {
+ name string
+ data D
+}
+
+type SymbolData interface {
+ SymbolVariable | SymbolFunction
+}
+
+type SymbolVariable struct {
localIndex int
}
+
+type SymbolFunction struct {
+ marker code.Marker
+}
diff --git a/pkg/lang/vm/code/builder.go b/pkg/lang/vm/code/builder.go
index f027c49..adb2eed 100644
--- a/pkg/lang/vm/code/builder.go
+++ b/pkg/lang/vm/code/builder.go
@@ -2,14 +2,15 @@ package code
import (
"encoding/binary"
- "fmt"
"math"
)
type Builder struct {
- code []byte
- debugInfo DebugInfo
- relativePcRefs map[int][]int
+ code []byte
+ debugInfo DebugInfo
+
+ markers map[Marker]int
+ markerRefs map[int]Marker
currentLine int
lineStart int
@@ -17,11 +18,14 @@ type Builder struct {
func NewBuilder() Builder {
return Builder{
- code: make([]byte, 0, 64),
- debugInfo: NewDebugInfo("unknown file"),
- relativePcRefs: make(map[int][]int),
- lineStart: -1,
- currentLine: -1,
+ code: make([]byte, 0, 64),
+ debugInfo: NewDebugInfo("unknown file"),
+
+ markers: make(map[Marker]int, 0),
+ markerRefs: make(map[int]Marker, 0),
+
+ lineStart: -1,
+ currentLine: -1,
}
}
@@ -43,50 +47,45 @@ func (b *Builder) AppendString(s string) {
b.code = append(b.code, 0)
}
-func (b *Builder) AppendReferenceToPc(pc int64) {
- b.addPcRef(int(pc), b.Len())
- b.AppendInt(pc)
+const UnfilledMarkerReference = 0x11_5AB07A6ED_600D5
+
+func (b *Builder) AppendMarkerReference(marker Marker) {
+ b.markerRefs[b.Len()] = marker
+ b.AppendInt(UnfilledMarkerReference)
}
func (b *Builder) AppendRaw(raw Raw) {
b.code = append(b.code, raw...)
}
-func (b *Builder) AppendBuilder(other Builder) {
- code := other.code
- for pc, refsToPc := range other.relativePcRefs {
- newPc := b.Len() + pc
- for _, ref := range refsToPc {
- valueAtRef := binary.LittleEndian.Uint64(code[ref : ref+8])
- if int(valueAtRef) != pc {
- panic(fmt.Errorf("reference to pc in builder does not actually reference pc. (pc: %d, value at reference: %d)", pc, valueAtRef))
+func (b *Builder) AppendBuilder(other Builder) error {
+ for marker, pc := range other.markers {
+ if parentPc, ok := b.markers[marker]; ok {
+ return ErrBuilderAppendMarkerDefinitionClash{
+ Marker: marker,
+ ParentPc: parentPc,
+ ChildPc: pc,
}
-
- binary.LittleEndian.PutUint64(code[ref:], uint64(newPc))
- b.addPcRef(newPc, ref+b.Len())
}
- }
-
- b.code = append(b.code, code...)
- if other.debugInfo.pcToLine.Len() != 0 || other.currentLine != -1 || other.lineStart != -1 {
- panic("appending debug infos not implemented yet")
+ b.SetMarker(marker, pc)
}
-}
-func (b *Builder) AppendBuilderWithoutAdjustingReferences(other Builder) {
- code := other.code
- for pc, refsToPc := range other.relativePcRefs {
- for _, ref := range refsToPc {
- b.addPcRef(pc, ref+b.Len())
- }
+ for pc, marker := range other.markerRefs {
+ b.markerRefs[b.Len()+pc] = marker
}
- b.code = append(b.code, code...)
+ b.AppendRaw(other.code)
- if other.debugInfo.pcToLine.Len() != 0 || other.currentLine != -1 || other.lineStart != -1 {
- panic("appending debug infos not implemented yet")
- }
+ return nil
+}
+
+func (b *Builder) PutMarker(marker Marker) {
+ b.SetMarker(marker, b.Len())
+}
+
+func (b *Builder) SetMarker(marker Marker, pc int) {
+ b.markers[marker] = pc
}
func (b *Builder) StartLine(line int) {
@@ -127,18 +126,27 @@ func (b *Builder) Len() int {
return len(b.code)
}
-func (b *Builder) Build() Code {
- return New(b.code, b.debugInfo)
-}
+func (b *Builder) Build() (Code, error) {
+ for pc, marker := range b.markerRefs {
+ markerPc, ok := b.markers[marker]
+ if !ok {
+ return Code{}, ErrBuilderUnfulfilledMarker{
+ Marker: marker,
+ Pc: pc,
+ }
+ }
+
+ valueAtRefPc := binary.LittleEndian.Uint64(b.code[pc : pc+8])
+ if valueAtRefPc != UnfilledMarkerReference {
+ return Code{}, ErrBuilderOverwrittenMarkerReference{
+ Marker: marker,
+ Pc: pc,
+ UnexpectedValue: valueAtRefPc,
+ }
+ }
-func (b *Builder) addPcRef(pc int, at int) {
- refs, ok := b.relativePcRefs[pc]
- if ok {
- refs = append(refs, at)
- } else {
- refs = make([]int, 1)
- refs[0] = at
+ b.SetInt(pc, int64(markerPc))
}
- b.relativePcRefs[pc] = refs
+ return New(b.code, b.debugInfo), nil
}
diff --git a/pkg/lang/vm/code/builder_test.go b/pkg/lang/vm/code/builder_test.go
new file mode 100644
index 0000000..29f1974
--- /dev/null
+++ b/pkg/lang/vm/code/builder_test.go
@@ -0,0 +1,31 @@
+package code_test
+
+import (
+ "jinx/pkg/lang/vm/code"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestMarker(t *testing.T) {
+ builder := code.NewBuilder()
+
+ builder.AppendInt(123)
+ builder.AppendMarkerReference("hello world")
+
+ builder.PutMarker("hello world")
+ builder.AppendInt(123)
+
+ result, err := builder.Build()
+ assert.NoError(t, err)
+
+ expectedBuilder := code.NewBuilder()
+ expectedBuilder.AppendInt(123)
+ expectedBuilder.AppendInt(16)
+ expectedBuilder.AppendInt(123)
+
+ expected, err := expectedBuilder.Build()
+ assert.NoError(t, err)
+
+ assert.Equal(t, expected.Code(), result.Code())
+}
diff --git a/pkg/lang/vm/code/errors.go b/pkg/lang/vm/code/errors.go
new file mode 100644
index 0000000..3261dd9
--- /dev/null
+++ b/pkg/lang/vm/code/errors.go
@@ -0,0 +1,32 @@
+package code
+
+import "fmt"
+
+type ErrBuilderAppendMarkerDefinitionClash struct {
+ Marker Marker
+ ParentPc int
+ ChildPc int
+}
+
+func (e ErrBuilderAppendMarkerDefinitionClash) Error() string {
+ return fmt.Sprintf("parent and child builder definitions for marker '%v' clash (parent: %d, child: %d)", e.Marker, e.ParentPc, e.ChildPc)
+}
+
+type ErrBuilderUnfulfilledMarker struct {
+ Marker Marker
+ Pc int
+}
+
+func (e ErrBuilderUnfulfilledMarker) Error() string {
+ return fmt.Sprintf("encountered unfulfilled marker '%v' at pc %d", e.Marker, e.Pc)
+}
+
+type ErrBuilderOverwrittenMarkerReference struct {
+ Marker Marker
+ Pc int
+ UnexpectedValue uint64
+}
+
+func (e ErrBuilderOverwrittenMarkerReference) Error() string {
+ return fmt.Sprintf("marker reference for marker '%v' at pc %d was overwritten (value: %x)", e.Marker, e.Pc, e.UnexpectedValue)
+}
diff --git a/pkg/lang/vm/code/marker.go b/pkg/lang/vm/code/marker.go
new file mode 100644
index 0000000..18b4157
--- /dev/null
+++ b/pkg/lang/vm/code/marker.go
@@ -0,0 +1,23 @@
+package code
+
+import "fmt"
+
+type Marker string
+
+func (m Marker) SubUnit(name string) Marker {
+ return Marker(fmt.Sprintf("%v:%s", m, name))
+}
+
+func (m Marker) SubMarker(format string, args ...any) Marker {
+ part := fmt.Sprintf(format, args...)
+
+ return Marker(fmt.Sprintf("%v_%s", m, part))
+}
+
+func (m Marker) String() string {
+ return string(m)
+}
+
+func (m Marker) IsEmpty() bool {
+ return string(m) == ""
+}
diff --git a/pkg/lang/vm/text/compiler.go b/pkg/lang/vm/text/compiler.go
index 21d8dae..d8b53fd 100644
--- a/pkg/lang/vm/text/compiler.go
+++ b/pkg/lang/vm/text/compiler.go
@@ -56,7 +56,7 @@ func (cpl *Compiler) Compile() (code.Code, error) {
return code.Code{}, err
}
- return builder.Build(), nil
+ return builder.Build()
}
func (cpl *Compiler) compileLine() (code.Raw, error) {