Skip to content

Commit

Permalink
compile: add line numbers to compiler and create line number table (l…
Browse files Browse the repository at this point in the history
…notab)
  • Loading branch information
ncw committed Jul 7, 2015
1 parent bbab811 commit 97e4a0f
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 14 deletions.
25 changes: 21 additions & 4 deletions compile/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const (
type compiler struct {
Code *py.Code // code being built up
Filename string
Lineno int // current line number
OpCodes Instructions
loops loopstack
SymTable *symtable.SymTable
Expand Down Expand Up @@ -144,6 +145,11 @@ func (c *compiler) panicSyntaxErrorf(Ast ast.Ast, format string, a ...interface{
panic(err)
}

// Sets Lineno from an ast node
func (c *compiler) SetLineno(node ast.Ast) {
c.Lineno = node.GetLineno()
}

// Create a new compiler object at Ast, using private for name mangling
func (c *compiler) newCompilerScope(compilerScope compilerScopeType, Ast ast.Ast, private string) (newC *compiler) {
newSymTable := c.SymTable.FindChild(Ast)
Expand Down Expand Up @@ -186,6 +192,7 @@ func (c *compiler) compileAst(Ast ast.Ast, filename string, futureFlags int, don
code.Freevars = append(code.Freevars, SymTable.Find(symtable.ScopeFree, symtable.DefFreeClass)...)
code.Flags = c.codeFlags(SymTable) | int32(futureFlags&py.CO_COMPILER_FLAGS_MASK)
valueOnStack := false
c.SetLineno(Ast)
switch node := Ast.(type) {
case *ast.Module:
c.Stmts(c.docString(node.Body, false))
Expand Down Expand Up @@ -283,6 +290,7 @@ func (c *compiler) compileAst(Ast ast.Ast, filename string, futureFlags int, don
code.Code = c.OpCodes.Assemble()
code.Stacksize = int32(c.OpCodes.StackDepth())
code.Nlocals = int32(len(code.Varnames))
code.Lnotab = string(c.OpCodes.Lnotab())
return nil
}

Expand Down Expand Up @@ -377,15 +385,19 @@ func (c *compiler) OpArg(Op vm.OpCode, Arg uint32) {
if !Op.HAS_ARG() {
panic("OpArg called with an instruction which doesn't take an Arg")
}
c.OpCodes.Add(&OpArg{Op: Op, Arg: Arg})
instr := &OpArg{Op: Op, Arg: Arg}
instr.SetLineno(c.Lineno)
c.OpCodes.Add(instr)
}

// Compiles an instruction without an argument
func (c *compiler) Op(op vm.OpCode) {
if op.HAS_ARG() {
panic("Op called with an instruction which takes an Arg")
}
c.OpCodes.Add(&Op{Op: op})
instr := &Op{Op: op}
instr.SetLineno(c.Lineno)
c.OpCodes.Add(instr)
}

// Inserts an existing label
Expand All @@ -402,14 +414,17 @@ func (c *compiler) NewLabel() *Label {

// Compiles a jump instruction
func (c *compiler) Jump(Op vm.OpCode, Dest *Label) {
var instr Instruction
switch Op {
case vm.JUMP_IF_FALSE_OR_POP, vm.JUMP_IF_TRUE_OR_POP, vm.JUMP_ABSOLUTE, vm.POP_JUMP_IF_FALSE, vm.POP_JUMP_IF_TRUE, vm.CONTINUE_LOOP: // Absolute
c.OpCodes.Add(&JumpAbs{OpArg: OpArg{Op: Op}, Dest: Dest})
instr = &JumpAbs{OpArg: OpArg{Op: Op}, Dest: Dest}
case vm.JUMP_FORWARD, vm.SETUP_WITH, vm.FOR_ITER, vm.SETUP_LOOP, vm.SETUP_EXCEPT, vm.SETUP_FINALLY:
c.OpCodes.Add(&JumpRel{OpArg: OpArg{Op: Op}, Dest: Dest})
instr = &JumpRel{OpArg: OpArg{Op: Op}, Dest: Dest}
default:
panic("Jump called with non jump instruction")
}
instr.SetLineno(c.Lineno)
c.OpCodes.Add(instr)
}

/* The test for LOCAL must come before the test for FREE in order to
Expand Down Expand Up @@ -963,6 +978,7 @@ func (c *compiler) Stmts(stmts []ast.Stmt) {

// Compile statement
func (c *compiler) Stmt(stmt ast.Stmt) {
c.SetLineno(stmt)
switch node := stmt.(type) {
case *ast.FunctionDef:
// Name Identifier
Expand Down Expand Up @@ -1578,6 +1594,7 @@ func (c *compiler) Exprs(exprs []ast.Expr) {

// Compile and expression
func (c *compiler) Expr(expr ast.Expr) {
c.SetLineno(expr)
switch node := expr.(type) {
case *ast.BoolOp:
// Op BoolOpNumber
Expand Down
37 changes: 36 additions & 1 deletion compile/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,39 @@ func EqString(t *testing.T, name string, a, b string) {
}
}

// Compact the lnotab as python3 seems to generate inefficient ones
// with spurious zero line number increments.
func lnotabCompact(t *testing.T, pxs *[]byte) {
xs := *pxs
newxs := make([]byte, 0, len(xs))
carry := 0
for i := 0; i < len(xs); i += 2 {
d_offset, d_lineno := xs[i], xs[i+1]
if d_lineno == 0 {
carry += int(d_offset)
continue
}
// FIXME ignoring d_offset overflow
d_offset += byte(carry)
carry = 0
newxs = append(newxs, byte(d_offset), d_lineno)
}
// if string(newxs) != string(xs) {
// t.Logf("Compacted\n% x\n% x", xs, newxs)
// }
*pxs = newxs
}

func EqLnotab(t *testing.T, name string, aStr, bStr string) {
a := []byte(aStr)
b := []byte(bStr)
lnotabCompact(t, &a)
lnotabCompact(t, &b)
if string(a) != string(b) {
t.Errorf("%s want % x, got % x", name, a, b)
}
}

func EqInt32(t *testing.T, name string, a, b int32) {
if a != b {
t.Errorf("%s want %d, got %d", name, a, b)
Expand Down Expand Up @@ -105,7 +138,9 @@ func EqCode(t *testing.T, name string, a, b *py.Code) {
EqCodeCode(t, name+": Code", a.Code, b.Code)
EqString(t, name+": Filename", a.Filename, b.Filename)
EqString(t, name+": Name", a.Name, b.Name)
// FIXME EqString(t, name+": Lnotab", a.Lnotab, b.Lnotab)
// The Lnotabs are mostly the same but not entirely
// So it is probably not profitable to test them exactly
// EqLnotab(t, name+": Lnotab", a.Lnotab, b.Lnotab)

// []string
EqStrings(t, name+": Names", a.Names, b.Names)
Expand Down
59 changes: 54 additions & 5 deletions compile/instructions.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package compile

import (
"github.com/ncw/gpython/vm"
)
import "github.com/ncw/gpython/vm"

// FIXME detect if label is not in the instruction stream by setting
// Pos to 0xFFFF say by default, ie we made a label but forgot to add
Expand Down Expand Up @@ -300,6 +298,8 @@ func (is Instructions) StackDepth() int {
type Instruction interface {
Pos() uint32
Number() int
Lineno() int
SetLineno(int)
SetPos(int, uint32) bool
Size() uint32
Output() []byte
Expand All @@ -312,8 +312,9 @@ type Resolver interface {

// Position
type pos struct {
n uint32
p uint32
n uint32
p uint32
lineno int
}

// Read instruction number
Expand All @@ -326,6 +327,16 @@ func (p *pos) Pos() uint32 {
return p.p
}

// Read lineno
func (p *pos) Lineno() int {
return p.lineno
}

// Set lineno
func (p *pos) SetLineno(lineno int) {
p.lineno = lineno
}

// Set Position - returns changed
func (p *pos) SetPos(number int, newPos uint32) bool {
p.n = uint32(number)
Expand Down Expand Up @@ -439,3 +450,41 @@ func (o *JumpRel) Resolve() {
panic("FIXME compile: JUMP_FOWARDS size changed")
}
}

// Creates the lnotab from the instruction stream
//
// See Objects/lnotab_notes.txt for the description of the line number table.
func (is Instructions) Lnotab() []byte {
var lnotab []byte
old_offset := uint32(0)
old_lineno := 1
for _, instr := range is {
if instr.Size() == 0 {
continue
}
lineno := instr.Lineno()
offset := instr.Pos()
d_lineno := lineno - old_lineno
if d_lineno <= 0 {
continue
}
d_bytecode := offset - old_offset
for d_bytecode > 255 {
lnotab = append(lnotab, 255, 0)
d_bytecode -= 255
}
for d_lineno > 255 {
lnotab = append(lnotab, byte(d_bytecode), 255)
d_bytecode = 0
d_lineno -= 255
}
if d_bytecode > 0 {
lnotab = append(lnotab, byte(d_bytecode), byte(d_lineno))
} else { /* First line of a block; def stmt, etc. */
lnotab = append(lnotab, 0, byte(d_lineno))
}
old_lineno = lineno
old_offset = offset
}
return lnotab
}
56 changes: 56 additions & 0 deletions compile/instructions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package compile

import (
"bytes"
"testing"
)

func TestLnotab(t *testing.T) {
for i, test := range []struct {
instrs Instructions
want []byte
}{
{
instrs: Instructions{},
want: []byte{},
},
{
instrs: Instructions{
&Op{pos: pos{n: 1, p: 10, lineno: 1}},
&Op{pos: pos{n: 0, p: 10, lineno: 0}},
&Op{pos: pos{n: 1, p: 102, lineno: 1}},
},
want: []byte{},
},
{
instrs: Instructions{
&Op{pos: pos{n: 1, p: 0, lineno: 1}},
&Op{pos: pos{n: 1, p: 1, lineno: 2}},
&Op{pos: pos{n: 1, p: 2, lineno: 3}},
},
want: []byte{1, 1, 1, 1},
},
{
// Example from lnotab.txt
instrs: Instructions{
&Op{pos: pos{n: 1, p: 0, lineno: 1}},
&Op{pos: pos{n: 1, p: 6, lineno: 2}},
&Op{pos: pos{n: 1, p: 50, lineno: 7}},
&Op{pos: pos{n: 1, p: 350, lineno: 307}},
&Op{pos: pos{n: 1, p: 361, lineno: 308}},
},
want: []byte{
6, 1,
44, 5,
255, 0,
45, 255,
0, 45,
11, 1},
},
} {
got := test.instrs.Lnotab()
if bytes.Compare(test.want, got) != 0 {
t.Errorf("%d: want %d got %d", i, test.want, got)
}
}
}
5 changes: 1 addition & 4 deletions notes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Things to do before release
===========================

* Line numbers
* frame.Lasti is pointing past the instruction which puts tracebacks out
* Subclass builtins
* pygen

Expand All @@ -39,10 +40,6 @@ Failes on /usr/lib/python3/dist-packages/mpmath/usertools.py
Limitations & Missing parts
===========================
* string keys only in dictionaries
* line numbers missing in SyntaxErrors
* now need to show them when printing SyntaxErrors
* line numbers missing in Tracebacks
* lnotab etc
* \N{...} escapes not implemented
* lots of builtins still to implement
* FIXME eq && ne should throw an error for a type which doesn' have eq implemented
Expand Down

0 comments on commit 97e4a0f

Please sign in to comment.