about summary refs log tree commit diff
path: root/pkg
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2022-08-22 23:41:49 +0000
committerMel <einebeere@gmail.com>2022-08-22 23:41:49 +0000
commite22df631c4428f8dea4d5784a8a7840d2f84c6be (patch)
tree084c233741374cdabde8d70b49988b3dca686719 /pkg
parent24183cd5363b96d83292df17d44fa79d8b47a89f (diff)
downloadjinx-e22df631c4428f8dea4d5784a8a7840d2f84c6be.tar.zst
jinx-e22df631c4428f8dea4d5784a8a7840d2f84c6be.zip
Generate runtime debug info with source locations
Diffstat (limited to 'pkg')
-rw-r--r--pkg/lang/compiler/compiler.go46
-rw-r--r--pkg/lang/vm/code/builder.go6
-rw-r--r--pkg/lang/vm/code/debug.go29
-rw-r--r--pkg/lang/vm/vm.go8
-rw-r--r--pkg/libs/rangemap/rangemap.go67
-rw-r--r--pkg/libs/rangemap/rangemap_test.go76
6 files changed, 205 insertions, 27 deletions
diff --git a/pkg/lang/compiler/compiler.go b/pkg/lang/compiler/compiler.go
index b08a215..b1d4dfb 100644
--- a/pkg/lang/compiler/compiler.go
+++ b/pkg/lang/compiler/compiler.go
@@ -70,6 +70,8 @@ func (comp *Compiler) Compile() (modules.Module, error) {
 }
 
 func (comp *Compiler) compileStmt(t *code.Builder, stmt ast.Stmt) error {
+	startPc := t.Len()
+
 	var err error
 	switch stmt.Kind {
 	case ast.StmtKindEmpty:
@@ -119,15 +121,24 @@ func (comp *Compiler) compileStmt(t *code.Builder, stmt ast.Stmt) error {
 	}
 
 	// Wrap errors in a compiler error, if it's not already one.
-	if err != nil && !isCompilerError(err) {
-		err = Error{
-			Module: comp.name,
-			Loc:    stmt.At,
-			Err:    err,
+	if err != nil {
+		if !isCompilerError(err) {
+			err = Error{
+				Module: comp.name,
+				Loc:    stmt.At,
+				Err:    err,
+			}
 		}
+		return err
 	}
 
-	return err
+	// Add debug info if needed.
+	endPc := t.Len()
+	if startPc != endPc {
+		t.AddDebugInfo(startPc, endPc-1, stmt.At)
+	}
+
+	return nil
 }
 
 func (comp *Compiler) compileUseStmt(t *code.Builder, stmt ast.StmtUse) error {
@@ -645,6 +656,8 @@ func (comp *Compiler) compileExprStmt(t *code.Builder, exprStmt ast.StmtExpr) er
 }
 
 func (comp *Compiler) compileExpr(t *code.Builder, expr ast.Expr) error {
+	startPc := t.Len()
+
 	var err error
 	switch expr.Kind {
 	case ast.ExprKindBinary:
@@ -683,15 +696,24 @@ func (comp *Compiler) compileExpr(t *code.Builder, expr ast.Expr) error {
 	}
 
 	// If the error was not a compiler error, we need to wrap it in one.
-	if err != nil && !isCompilerError(err) {
-		err = Error{
-			Module: comp.name,
-			Loc:    expr.At,
-			Err:    err,
+	if err != nil {
+		if !isCompilerError(err) {
+			err = Error{
+				Module: comp.name,
+				Loc:    expr.At,
+				Err:    err,
+			}
 		}
+
+		return err
 	}
 
-	return err
+	endPc := t.Len()
+	if startPc != endPc {
+		t.AddDebugInfo(startPc, endPc-1, expr.At)
+	}
+
+	return nil
 }
 
 func (comp *Compiler) compileBinaryExpr(t *code.Builder, expr ast.ExprBinary) error {
diff --git a/pkg/lang/vm/code/builder.go b/pkg/lang/vm/code/builder.go
index f413ba1..618608a 100644
--- a/pkg/lang/vm/code/builder.go
+++ b/pkg/lang/vm/code/builder.go
@@ -2,6 +2,7 @@ package code
 
 import (
 	"encoding/binary"
+	"jinx/pkg/libs/source"
 	"math"
 )
 
@@ -76,6 +77,7 @@ func (b *Builder) AppendBuilder(other Builder) error {
 	}
 
 	b.AppendRaw(other.code)
+	b.debugInfo.AppendOther(other.debugInfo)
 
 	return nil
 }
@@ -114,6 +116,10 @@ func (b *Builder) EndLine() {
 	b.debugInfo.AppendLine(b.Len()-1, b.currentLine)
 }
 
+func (b *Builder) AddDebugInfo(fromPc, toPc int, loc source.Loc) {
+	b.debugInfo.FillInfo(fromPc, toPc, loc)
+}
+
 func (b *Builder) SetInt(at int, x int64) {
 	binary.LittleEndian.PutUint64(b.code[at:], uint64(x))
 }
diff --git a/pkg/lang/vm/code/debug.go b/pkg/lang/vm/code/debug.go
index ebf0ea1..efb788c 100644
--- a/pkg/lang/vm/code/debug.go
+++ b/pkg/lang/vm/code/debug.go
@@ -1,16 +1,19 @@
 package code
 
-import "jinx/pkg/libs/rangemap"
+import (
+	"jinx/pkg/libs/rangemap"
+	"jinx/pkg/libs/source"
+)
 
 type DebugInfo struct {
 	file     string
-	pcToLine rangemap.RangeMap[int]
+	pcToLine rangemap.RangeMap[source.Loc]
 }
 
 func NewDebugInfo(file string) DebugInfo {
 	return DebugInfo{
 		file:     file,
-		pcToLine: rangemap.New[int](),
+		pcToLine: rangemap.New[source.Loc](),
 	}
 }
 
@@ -18,14 +21,22 @@ func (di *DebugInfo) File() string {
 	return di.file
 }
 
-func (di *DebugInfo) PCToLine(pc int) int {
-	line := di.pcToLine.Get(pc)
-	if line == nil {
-		return -1
+func (di *DebugInfo) PCToLoc(pc int) (source.Loc, bool) {
+	loc := di.pcToLine.Get(pc)
+	if loc == nil {
+		return source.Loc{}, false
 	}
-	return *line
+	return *loc, true
 }
 
 func (di *DebugInfo) AppendLine(uptoPc, line int) {
-	di.pcToLine.AppendToLast(uptoPc, line)
+	di.pcToLine.AppendToLast(uptoPc, source.Loc{Row: line, Col: 0})
+}
+
+func (di *DebugInfo) FillInfo(from, to int, loc source.Loc) {
+	di.pcToLine.Fill(from, to, loc)
+}
+
+func (di *DebugInfo) AppendOther(other DebugInfo) {
+	di.pcToLine.AppendRanges(other.pcToLine)
 }
diff --git a/pkg/lang/vm/vm.go b/pkg/lang/vm/vm.go
index 4d4d5ca..b18e7cb 100644
--- a/pkg/lang/vm/vm.go
+++ b/pkg/lang/vm/vm.go
@@ -121,10 +121,16 @@ func (vm *VM) executeModule(moduleID int) error {
 		vm.advancePC(advance)
 
 		if decision, err := vm.step(code, op); err != nil {
+			loc, ok := code.Debug().PCToLoc(vm.pc())
+			line := loc.Row
+			if !ok {
+				line = -1
+			}
+
 			return Error{
 				Pos:    vm.pos,
 				Module: module.Name(),
-				Line:   code.Debug().PCToLine(vm.pc()),
+				Line:   line,
 				Err:    err,
 			}
 		} else if decision == stepDecisionHalt {
diff --git a/pkg/libs/rangemap/rangemap.go b/pkg/libs/rangemap/rangemap.go
index 650deec..367483a 100644
--- a/pkg/libs/rangemap/rangemap.go
+++ b/pkg/libs/rangemap/rangemap.go
@@ -73,6 +73,54 @@ func (rm *RangeMap[D]) Set(from, to int, data D) {
 	rm.ranges = newRanges
 }
 
+func (rm *RangeMap[D]) Fill(from, to int, data D) {
+	if to < from {
+		return
+	}
+
+	leftIndex := rm.entry(from)
+	rightIndex := rm.entry(to)
+
+	for i := leftIndex; i <= rightIndex; i++ {
+		r := rm.ranges[i]
+		// Range is not inside fill zone or is already filled.
+		if r.from > to || (r.to < from && r.to != -1) || r.data != nil {
+			continue
+		}
+
+		if r.from >= from && r.to <= to && r.to != -1 {
+			// The empty range is completely contained in the range to fill, overwrite it.
+			rm.ranges[i].data = &data
+		} else {
+			// The empty range is partially contained in the range to fill, split it.
+			rm.Set(max(r.from, from), min(r.to, to), data)
+		}
+	}
+}
+
+func (rm *RangeMap[D]) AppendRanges(other RangeMap[D]) {
+	newRanges := make([]rangeEntry[D], 0, len(rm.ranges)+len(other.ranges))
+	offset := rm.ranges[len(rm.ranges)-1].from
+
+	// No need to copy the sentinel.
+	newRanges = append(newRanges, rm.ranges[:len(rm.ranges)-1]...)
+
+	for _, otherEntry := range other.ranges {
+		to := otherEntry.to + offset
+		if otherEntry.to == -1 {
+			to = -1 // Sentinel not affected by offset.
+		}
+
+		newRanges = append(newRanges, rangeEntry[D]{
+			from: otherEntry.from + offset,
+			to:   to,
+			data: otherEntry.data,
+		})
+	}
+
+	rm.ranges = newRanges
+}
+
 func (rm *RangeMap[D]) Get(i int) *D {
 	return rm.ranges[rm.entry(i)].data
 }
@@ -100,11 +148,6 @@ func (rm *RangeMap[D]) entry(i int) int {
 		return 0
 	}
 
-	lastRange := rm.ranges[len(rm.ranges)-1]
-	if i >= lastRange.to && lastRange.to != -1 {
-		return len(rm.ranges) - 1
-	}
-
 	left := 0
 	right := len(rm.ranges) - 1
 	for left <= right {
@@ -127,3 +170,17 @@ type rangeEntry[D any] struct {
 	to   int
 	data *D
 }
+
+func min(a, b int) int {
+	if a < b && a != -1 {
+		return a
+	}
+	return b
+}
+
+func max(a, b int) int {
+	if a > b && b != -1 {
+		return a
+	}
+	return b
+}
diff --git a/pkg/libs/rangemap/rangemap_test.go b/pkg/libs/rangemap/rangemap_test.go
index deb6dc7..7ea7f52 100644
--- a/pkg/libs/rangemap/rangemap_test.go
+++ b/pkg/libs/rangemap/rangemap_test.go
@@ -112,3 +112,79 @@ func TestSpaceBetween(t *testing.T) {
 	// Since our search can never fail, we pad the space with a sentinel value.
 	assert.Equal(t, "[{0..2: a}, {3..3}, {4..6: x}, {7..8}, {9..9: b}, {10..-1}]", rm.String())
 }
+
+func TestAppendRanges(t *testing.T) {
+	rm1 := rangemap.New[string]()
+
+	rm1.Set(0, 2, "a")
+	rm1.Set(3, 5, "b")
+
+	rm2 := rangemap.New[string]()
+	rm2.Set(0, 2, "c")
+	rm2.Set(3, 3, "d")
+
+	assert.Equal(t, "[{0..2: a}, {3..5: b}, {6..-1}]", rm1.String())
+	assert.Equal(t, "[{0..2: c}, {3..3: d}, {4..-1}]", rm2.String())
+
+	rm1.AppendRanges(rm2)
+
+	assert.Equal(t, "[{0..2: a}, {3..5: b}, {6..8: c}, {9..9: d}, {10..-1}]", rm1.String())
+}
+
+func TestAppendRangesSpaceBetween(t *testing.T) {
+	rm1 := rangemap.New[string]()
+
+	rm1.Set(1, 2, "a")
+	rm1.Set(4, 4, "b")
+
+	rm2 := rangemap.New[string]()
+
+	rm2.Set(2, 4, "c")
+	rm2.Set(6, 6, "d")
+
+	rm1.AppendRanges(rm2)
+
+	assert.Equal(t, "[{0..0}, {1..2: a}, {3..3}, {4..4: b}, {5..6}, {7..9: c}, {10..10}, {11..11: d}, {12..-1}]", rm1.String())
+}
+
+func TestFill(t *testing.T) {
+	rm := rangemap.New[string]()
+
+	rm.Set(0, 1, "a")
+	rm.Set(3, 5, "b")
+	rm.Set(9, 10, "c")
+
+	rm.Fill(2, 10, "x")
+
+	assert.Equal(t, "[{0..1: a}, {2..2: x}, {3..5: b}, {6..8: x}, {9..10: c}, {11..-1}]", rm.String())
+}
+
+func TestFillFromMiddle(t *testing.T) {
+	rm := rangemap.New[string]()
+
+	rm.Set(0, 2, "a")
+	rm.Set(9, 10, "c")
+
+	rm.Fill(5, 6, "x")
+
+	assert.Equal(t, "[{0..2: a}, {3..4}, {5..6: x}, {7..8}, {9..10: c}, {11..-1}]", rm.String())
+}
+
+func TestFillEdge(t *testing.T) {
+	rm := rangemap.New[string]()
+
+	rm.Set(0, 2, "a")
+	rm.Set(9, 10, "c")
+
+	rm.Fill(2, 6, "x")
+
+	assert.Equal(t, "[{0..2: a}, {3..6: x}, {7..8}, {9..10: c}, {11..-1}]", rm.String())
+}
+
+func TestFillFromEmpty(t *testing.T) {
+	rm := rangemap.New[string]()
+	rm.Fill(2, 4, "x")
+	rm.Fill(8, 10, "x")
+
+	assert.Equal(t, "[{0..1}, {2..4: x}, {5..7}, {8..10: x}, {11..-1}]", rm.String())
+}