Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 27 additions & 12 deletions doc_test.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
package lua_test

import (
"fmt"

"github.com/Shopify/go-lua"
)

// This example receives a variable number of numerical arguments and returns their average and sum.
func ExampleFunction(l *lua.State) int {
n := l.Top() // Number of arguments.
var sum float64
for i := 1; i <= n; i++ {
f, ok := l.ToNumber(i)
if !ok {
l.PushString("incorrect argument")
l.Error()
func ExampleFunction() {
averageAndSum := func(l *lua.State) int {
n := l.Top() // Number of arguments.
var sum float64
for i := 1; i <= n; i++ {
f, ok := l.ToNumber(i)
if !ok {
l.PushString("incorrect argument")
l.Error()
}
sum += f
}
sum += f
l.PushNumber(sum / float64(n)) // First result.
l.PushNumber(sum) // Second result.
return 2 // Result count.
}
l.PushNumber(sum / float64(n)) // First result.
l.PushNumber(sum) // Second result.
return 2 // Result count.

l := lua.NewState()
l.PushGoFunction(averageAndSum)
l.PushNumber(1)
l.PushNumber(2)
l.PushNumber(3)
l.Call(3, 2)
average, _ := l.ToNumber(-2)
sum, _ := l.ToNumber(-1)
fmt.Println(average, sum)
// Output: 2 6
}
101 changes: 101 additions & 0 deletions pow_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package lua

import (
"fmt"
"math"
"testing"
)

func TestPowerConsistentAcrossEvaluationPaths(t *testing.T) {
engines := []struct {
name string
run func(*State)
}{
{"function table", (*State).executeFunctionTable},
{"switch", (*State).executeSwitch},
}
cases := []struct {
base, exponent string
baseNumber, exponentNumber float64
}{
{"10", "-33", 10, -33},
{"10", "33", 10, 33},
{"10", "100", 10, 100},
{"10", "308", 10, 308},
{"10", "309", 10, 309},
{"10", "-323", 10, -323},
{"10", "-324", 10, -324},
{"-2", "3", -2, 3},
{"-2", "-3", -2, -3},
{"-2", "0.5", -2, 0.5},
{"0", "0", 0, 0},
{"0", "-3", 0, -3},
{"-0.0", "3", math.Copysign(0, -1), 3},
{"-0.0", "-3", math.Copysign(0, -1), -3},
{"-0.0", "2", math.Copysign(0, -1), 2},
{"-0.0", "-2", math.Copysign(0, -1), -2},
{"2", "0.5", 2, 0.5},
}
for _, engine := range engines {
for _, tc := range cases {
t.Run(engine.name+"/"+tc.base+"^"+tc.exponent, func(t *testing.T) {
l := NewState()
OpenLibraries(l)
source := fmt.Sprintf(`
local base, exponent = (%s), (%s)
local folded = (%s)^(%s)
local runtime = base^exponent
local coerced = %q^%q
local library = math.pow(base, exponent)
local keyMatches = true
if folded == folded then
local keys = {[folded] = "found"}
keyMatches = keys[runtime] == "found" and keys[coerced] == "found" and keys[library] == "found"
end
return folded, runtime, coerced, library, keyMatches
`, tc.base, tc.exponent, tc.base, tc.exponent, tc.base, tc.exponent)
if err := LoadString(l, source); err != nil {
t.Fatal(err)
}
if err := l.protectedCall(func() {
if l.preCall(l.top-1, MultipleReturns) {
t.Fatal("expected a Lua function")
}
engine.run(l)
}, l.top-1, 0); err != nil {
t.Fatal(err)
}
// Use the runtime's numeric primitive, not a decimal approximation
// or the result from a different host math library.
want := math.Pow(tc.baseNumber, tc.exponentNumber)
for index, name := range []string{"folded", "runtime", "coerced", "math.pow"} {
got, ok := l.ToNumber(index + 1)
if !ok || !samePowerNumber(got, want) {
t.Errorf("%s = %g (%016x), want %g (%016x)", name, got, math.Float64bits(got), want, math.Float64bits(want))
}
}
if !l.ToBoolean(5) {
t.Error("folded and runtime powers did not address the same table key")
}
for _, coerce := range []bool{false, true} {
api := NewState()
if coerce {
api.PushString(tc.base)
api.PushString(tc.exponent)
} else {
api.PushNumber(tc.baseNumber)
api.PushNumber(tc.exponentNumber)
}
api.Arith(OpPow)
if got, ok := api.ToNumber(-1); !ok || !samePowerNumber(got, want) {
t.Errorf("Arith(OpPow), coerce=%v: %g, want %g", coerce, got, want)
}
}
})
}
}
}

func samePowerNumber(a, b float64) bool {
return math.Float64bits(a) == math.Float64bits(b) || math.IsNaN(a) && math.IsNaN(b)
}
8 changes: 7 additions & 1 deletion tag_methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,17 @@ func (l *State) tagMethodByObject(o value, event tm) value {
}

func (l *State) callTagMethod(f, p1, p2 value) value {
top := l.top
if l.callInfo.isLua() && l.top < l.callInfo.top {
l.top = l.callInfo.top
}
l.push(f)
l.push(p1)
l.push(p2)
l.call(l.top-3, 1, l.callInfo.isLua())
return l.pop()
v := l.pop()
l.top = top
return v
}

func (l *State) callTagMethodV(f, p1, p2, p3 value) {
Expand Down
4 changes: 0 additions & 4 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,6 @@ func arith(op Operator, v1, v2 float64) float64 {
case OpMod:
return v1 - math.Floor(v1/v2)*v2
case OpPow:
// Golang bug: math.Pow(10.0, 33.0) is incorrect by 1 bit.
if v1 == 10.0 && float64(int(v2)) == v2 {
return math.Pow10(int(v2))
}
return math.Pow(v1, v2)
case OpUnaryMinus:
return -v1
Expand Down
21 changes: 21 additions & 0 deletions vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,27 @@ func TestVarArgMeta(t *testing.T) {
testString(t, s)
}

func TestConcatWithMetatableIndexInTableConstructor(t *testing.T) {
testString(t, `local meta = {
__index = function(self, key)
return "ASD"
end
}

TEST = {}
setmetatable(TEST, meta)
assert('b ' .. TEST.B .. ' b' == 'b ASD b')

local x = {
'a ' .. TEST.A .. ' a',
'b ' .. TEST.B .. ' b'
}

assert(x[1] == 'a ASD a')
assert(x[2] == 'b ASD b', 'got ' .. tostring(x[2]))
assert('c ' .. TEST.C == 'c ASD')`)
}

func TestCanRemoveNilObjectFromStack(t *testing.T) {
defer func() {
if r := recover(); r != nil {
Expand Down
Loading