<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package lxc_test

import (
	"fmt"
	"path/filepath"
	stdtesting "testing"

	. "launchpad.net/gocheck"
	"launchpad.net/juju-core/container/lxc"
	_ "launchpad.net/juju-core/environs/dummy"
	"launchpad.net/juju-core/instance"
	jujutesting "launchpad.net/juju-core/juju/testing"
	"launchpad.net/juju-core/state"
	"launchpad.net/juju-core/state/api"
	"launchpad.net/juju-core/testing"
	"launchpad.net/juju-core/utils"
	"launchpad.net/juju-core/version"
)

func TestPackage(t *stdtesting.T) {
	testing.MgoTestPackage(t)
}

type LxcSuite struct {
	testing.LoggingSuite
	testing.MgoSuite
	home               *testing.FakeHome
	containerDir       string
	lxcDir             string
	oldContainerDir    string
	oldLxcContainerDir string
}

var _ = Suite(&LxcSuite{})

func (s *LxcSuite) SetUpSuite(c *C) {
	s.LoggingSuite.SetUpSuite(c)
	s.MgoSuite.SetUpSuite(c)
}

func (s *LxcSuite) TearDownSuite(c *C) {
	s.MgoSuite.TearDownSuite(c)
	s.LoggingSuite.TearDownSuite(c)
}

func (s *LxcSuite) SetUpTest(c *C) {
	s.LoggingSuite.SetUpTest(c)
	s.MgoSuite.SetUpTest(c)
	s.home = testing.MakeSampleHome(c)
	s.containerDir = c.MkDir()
	s.oldContainerDir = lxc.SetContainerDir(s.containerDir)
	s.lxcDir = c.MkDir()
	s.oldLxcContainerDir = lxc.SetLxcContainerDir(s.lxcDir)
}

func (s *LxcSuite) TearDownTest(c *C) {
	lxc.SetContainerDir(s.oldContainerDir)
	lxc.SetLxcContainerDir(s.oldLxcContainerDir)
	s.home.Restore()
	s.MgoSuite.TearDownTest(c)
	s.LoggingSuite.TearDownTest(c)
}

func (s *LxcSuite) TestNewContainer(c *C) {
	factory := lxc.NewFactory(MockFactory())
	container, err := factory.NewContainer("2/lxc/0")
	c.Assert(err, IsNil)
	c.Assert(container.Id(), Equals, instance.Id("machine-2-lxc-0"))
	machineId, ok := lxc.GetMachineId(container)
	c.Assert(ok, Equals, true)
	c.Assert(machineId, Equals, "2/lxc/0")
}

func (s *LxcSuite) TestNewFromExisting(c *C) {
	mock := MockFactory()
	mockLxc := mock.New("machine-1-lxc-0")
	factory := lxc.NewFactory(mock)
	container, err := factory.NewFromExisting(mockLxc)
	c.Assert(err, IsNil)
	c.Assert(container.Id(), Equals, instance.Id("machine-1-lxc-0"))
	machineId, ok := lxc.GetMachineId(container)
	c.Assert(ok, Equals, true)
	c.Assert(machineId, Equals, "1/lxc/0")
}

func (s *LxcSuite) TestContainerCreate(c *C) {

	machineId := "1/lxc/0"
	config := testing.EnvironConfig(c)
	stateInfo := jujutesting.FakeStateInfo(machineId)
	apiInfo := jujutesting.FakeAPIInfo(machineId)

	factory := lxc.NewFactory(MockFactory())
	container, err := factory.NewContainer(machineId)
	c.Assert(err, IsNil)

	series := "series"
	nonce := "fake-nonce"
	tools := &state.Tools{
		Binary: version.MustParseBinary("2.3.4-foo-bar"),
		URL:    "http://tools.example.com/2.3.4-foo-bar.tgz",
	}

	err = container.Create(series, nonce, tools, config, stateInfo, apiInfo)
	c.Assert(err, IsNil)

	name := string(container.Id())
	// Check our container config files.
	testing.AssertNonEmptyFileExists(c, filepath.Join(s.containerDir, name, "lxc.conf"))
	testing.AssertNonEmptyFileExists(c, filepath.Join(s.containerDir, name, "cloud-init"))
	// Check the mount point has been created inside the container.
	testing.AssertDirectoryExists(c, filepath.Join(s.lxcDir, name, "rootfs/var/log/juju"))
}
<commit_msg>Simplify the test suite.<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package lxc_test

import (
	"path/filepath"
	stdtesting "testing"

	. "launchpad.net/gocheck"
	"launchpad.net/juju-core/container/lxc"
	_ "launchpad.net/juju-core/environs/dummy"
	"launchpad.net/juju-core/instance"
	jujutesting "launchpad.net/juju-core/juju/testing"
	"launchpad.net/juju-core/state"
	"launchpad.net/juju-core/testing"
	"launchpad.net/juju-core/version"
)

func Test(t *stdtesting.T) {
	TestingT(t)
}

type LxcSuite struct {
	testing.LoggingSuite
	containerDir       string
	lxcDir             string
	oldContainerDir    string
	oldLxcContainerDir string
}

var _ = Suite(&LxcSuite{})

func (s *LxcSuite) SetUpSuite(c *C) {
	s.LoggingSuite.SetUpSuite(c)
}

func (s *LxcSuite) TearDownSuite(c *C) {
	s.LoggingSuite.TearDownSuite(c)
}

func (s *LxcSuite) SetUpTest(c *C) {
	s.LoggingSuite.SetUpTest(c)
	s.containerDir = c.MkDir()
	s.oldContainerDir = lxc.SetContainerDir(s.containerDir)
	s.lxcDir = c.MkDir()
	s.oldLxcContainerDir = lxc.SetLxcContainerDir(s.lxcDir)
}

func (s *LxcSuite) TearDownTest(c *C) {
	lxc.SetContainerDir(s.oldContainerDir)
	lxc.SetLxcContainerDir(s.oldLxcContainerDir)
	s.LoggingSuite.TearDownTest(c)
}

func (s *LxcSuite) TestNewContainer(c *C) {
	factory := lxc.NewFactory(MockFactory())
	container, err := factory.NewContainer("2/lxc/0")
	c.Assert(err, IsNil)
	c.Assert(container.Id(), Equals, instance.Id("machine-2-lxc-0"))
	machineId, ok := lxc.GetMachineId(container)
	c.Assert(ok, Equals, true)
	c.Assert(machineId, Equals, "2/lxc/0")
}

func (s *LxcSuite) TestNewFromExisting(c *C) {
	mock := MockFactory()
	mockLxc := mock.New("machine-1-lxc-0")
	factory := lxc.NewFactory(mock)
	container, err := factory.NewFromExisting(mockLxc)
	c.Assert(err, IsNil)
	c.Assert(container.Id(), Equals, instance.Id("machine-1-lxc-0"))
	machineId, ok := lxc.GetMachineId(container)
	c.Assert(ok, Equals, true)
	c.Assert(machineId, Equals, "1/lxc/0")
}

func (s *LxcSuite) TestContainerCreate(c *C) {

	machineId := "1/lxc/0"
	config := testing.EnvironConfig(c)
	stateInfo := jujutesting.FakeStateInfo(machineId)
	apiInfo := jujutesting.FakeAPIInfo(machineId)

	factory := lxc.NewFactory(MockFactory())
	container, err := factory.NewContainer(machineId)
	c.Assert(err, IsNil)

	series := "series"
	nonce := "fake-nonce"
	tools := &state.Tools{
		Binary: version.MustParseBinary("2.3.4-foo-bar"),
		URL:    "http://tools.example.com/2.3.4-foo-bar.tgz",
	}

	err = container.Create(series, nonce, tools, config, stateInfo, apiInfo)
	c.Assert(err, IsNil)

	name := string(container.Id())
	// Check our container config files.
	testing.AssertNonEmptyFileExists(c, filepath.Join(s.containerDir, name, "lxc.conf"))
	testing.AssertNonEmptyFileExists(c, filepath.Join(s.containerDir, name, "cloud-init"))
	// Check the mount point has been created inside the container.
	testing.AssertDirectoryExists(c, filepath.Join(s.lxcDir, name, "rootfs/var/log/juju"))
}
<|endoftext|>


<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package lxc_test

import (
	stdtesting "testing"

	. "launchpad.net/gocheck"
)

func Test(t *stdtesting.T) { TestingT(t) }

type LxcSuite struct{}

var _ = Suite(&LxcSuite{})
<commit_msg>Test for factory creation.<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package lxc_test

import (
	stdtesting "testing"

	. "launchpad.net/gocheck"
	"launchpad.net/juju-core/container/lxc"
	"launchpad.net/juju-core/instance"
	"launchpad.net/juju-core/testing"
)

func Test(t *stdtesting.T) { TestingT(t) }

type LxcSuite struct {
	testing.LoggingSuite
}

var _ = Suite(&LxcSuite{})

func (s *LxcSuite) TestNewContainer(c *C) {
	factory := lxc.NewFactory(MockFactory())
	container, err := factory.NewContainer("2/lxc/0")
	c.Assert(err, IsNil)
	c.Assert(container.Id(), Equals, instance.Id("machine-2-lxc-0"))
}
<|endoftext|>


<commit_before>package rel

import (
	"bytes"
	"log"
	"runtime/debug"
	"strings"
)

type ToSqlVisitor struct {
	conn *Connection
}

const (
	WHERE    = " WHERE "
	SPACE    = " "
	COMMA    = ", "
	GROUP_BY = " GROUP BY "
	ORDER_BY = " ORDER BY "
	WINDOW   = " WINDOW "
	AND      = " AND "
	DISTINCT = "DISTINCT"
)

func NewToSqlVisitor(c *Connection) ToSqlVisitor {
	return ToSqlVisitor{conn: c}
}

func (v ToSqlVisitor) Accept(a Visitable) string {
	return v.Visit(a)
}

func (v ToSqlVisitor) Visit(a Visitable) string {
	ret := ""
	switch val := a.(type) {
	case SelectStatementNode:
		ret = v.VisitSelectStatementNode(val)
	case AndNode:
		ret = v.VisitAndNode(val)
	case InNode:
		ret = v.VisitInNode(val)
	case SqlLiteralNode:
		ret = v.VisitSqlLiteralNode(val)
	case JoinSource:
		ret = v.VisitJoinSourceNode(val)
	case EqualityNode:
		ret = v.VisitEqualityNode(val)
	case HavingNode:
		ret = v.VisitHavingNode(val)
	case AttributeNode:
		ret = v.VisitAttributeNode(val)
	case GroupNode:
		ret = v.VisitGroupNode(val)
	case ExistsNode:
		ret = v.VisitExistsNode(val)
	case AsNode:
		ret = v.VisitAsNode(val)
	case Table:
		ret = v.VisitTable(val)
	case *Table:
		ret = v.VisitTable(*val)
	case LessThanNode:
		ret = v.VisitLessThanNode(val)
	case UnionNode:
		ret = v.VisitUnionNode(val)
	case UnionAllNode:
		ret = v.VisitUnionAllNode(val)
	case SelectManager:
		ret = v.VisitSelectManager(val)
	case GreaterThanNode:
		ret = v.VisitGreaterThanNode(val)
	default:
		debug.PrintStack()
		log.Fatalf("ToSqlVisitor#Visit %T not handled", a)
	}
	return ret
}

func (v ToSqlVisitor) VisitTopNode(a TopNode) string {
	return "TopNode"
}

func (v ToSqlVisitor) VisitLimitNode(a LimitNode) string {
	var buf bytes.Buffer
	buf.WriteString("LIMIT ")
	buf.WriteString(v.Visit(a.Expr))
	return buf.String()
}

func (v ToSqlVisitor) VisitLockNode(a LockNode) string {
	return "LockNode"
}

func (v ToSqlVisitor) VisitOffsetNode(n OffsetNode) string {
	return "OFFSET " + v.Visit(n.Expr)
}

func (v ToSqlVisitor) VisitDistinctOnNode(a DistinctOnNode) string {
	return "DistinctOnNode"
}

func (v ToSqlVisitor) VisitAndNode(a AndNode) string {
	return "AndNode"
}

func (v ToSqlVisitor) VisitInNode(a InNode) string {
	return "InNode"
}

func (v ToSqlVisitor) VisitOrderingNode(a OrderingNode) string {
	return "OrderingNode"
}

func (v ToSqlVisitor) VisitSelectManager(a SelectManager) string {
	var buf bytes.Buffer
	buf.WriteString("(")
	buf.WriteString(a.ToSql())
	buf.WriteString(")")
	return buf.String()
}

func (v ToSqlVisitor) VisitUnionNode(a UnionNode) string {
	var buf bytes.Buffer
	buf.WriteString("( ")
	buf.WriteString(v.Visit(a.Left))
	buf.WriteString(" UNION ")
	buf.WriteString(v.Visit(a.Right))
	buf.WriteString(" )")
	return buf.String()
}

func (v ToSqlVisitor) VisitUnionAllNode(a UnionAllNode) string {
	var buf bytes.Buffer
	buf.WriteString("( ")
	buf.WriteString(v.Visit(a.Left))
	buf.WriteString(" UNION ALL ")
	buf.WriteString(v.Visit(a.Right))
	buf.WriteString(" )")
	return buf.String()
}

func (v ToSqlVisitor) VisitLessThanNode(a LessThanNode) string {
	var buf bytes.Buffer
	buf.WriteString(v.Visit(a.Left))
	buf.WriteString(" < ")
	buf.WriteString(v.Visit(a.Right))
	return buf.String()
}

func (v ToSqlVisitor) VisitGreaterThanNode(a GreaterThanNode) string {
	var buf bytes.Buffer
	buf.WriteString(v.Visit(a.Left))
	buf.WriteString(" > ")
	buf.WriteString(v.Visit(a.Right))
	return buf.String()
}

func (v ToSqlVisitor) VisitAsNode(a AsNode) string {
	var buf bytes.Buffer
	buf.WriteString(v.Visit(a.Left))
	buf.WriteString(" AS ")
	buf.WriteString(v.Visit(*a.Right))
	return buf.String()
}

func (v ToSqlVisitor) VisitGroupNode(n GroupNode) string {
	return v.Visit(n.Expr)
}

func (v ToSqlVisitor) VisitHavingNode(n HavingNode) string {
	var buf bytes.Buffer
	buf.WriteString("HAVING ")
	buf.WriteString(v.Visit(n.Expr))
	return buf.String()
}

func (v ToSqlVisitor) VisitExistsNode(n ExistsNode) string {
	var buf bytes.Buffer
	buf.WriteString("EXISTS (")
	buf.WriteString(v.Visit(n.Expressions))
	buf.WriteString(")")
	if n.Alias != nil {
		buf.WriteString(" AS ")
		buf.WriteString(v.Visit(n.Alias))
	}
	return buf.String()
}

func (v ToSqlVisitor) VisitAttributeNode(n AttributeNode) string {
	var buf bytes.Buffer
	buf.WriteString(v.QuoteTableName(n.Table.Name))
	buf.WriteString(".")
	buf.WriteString(v.QuoteColumnName(n.Name))
	return buf.String()
}

func (v ToSqlVisitor) VisitEqualityNode(n EqualityNode) string {
	var buf bytes.Buffer
	if n.Right == nil {
		buf.WriteString(v.Visit(n.Left))
		buf.WriteString(" IS NULL")
	} else {
		buf.WriteString(v.Visit(n.Left))
		buf.WriteString(" = ")
		buf.WriteString(v.Visit(*n.Right))
	}
	return buf.String()
}

func (v ToSqlVisitor) VisitTable(t Table) string {
	var buf bytes.Buffer
	if t.TableAlias != "" {
		buf.WriteString(v.QuoteTableName(t.Name))
		buf.WriteString(SPACE)
		buf.WriteString(v.QuoteTableName(t.TableAlias))
	} else {
		buf.WriteString(v.QuoteTableName(t.Name))
	}
	return buf.String()
}

func (v ToSqlVisitor) QuoteTableName(name string) string {
	return v.conn.QuoteTableName(name)
}

func (v ToSqlVisitor) QuoteColumnName(name string) string {
	return v.conn.QuoteColumnName(name)
}

func (v ToSqlVisitor) VisitJoinSourceNode(a JoinSource) string {
	var buf bytes.Buffer
	if a.Left != nil {
		buf.WriteString(v.Visit(a.Left))
	}
	return buf.String()
}

func (v ToSqlVisitor) VisitSqlLiteralNode(a SqlLiteralNode) string {
	if a.Raw != "" {
		return a.Raw
	} else {
		return ""
	}
}

func (v ToSqlVisitor) VisitSelectCoreNode(s SelectCoreNode) string {
	var buf bytes.Buffer

	buf.WriteString("SELECT")

	// Add TOP statement to the buffer
	if s.Top != nil {
		buf.WriteString(SPACE)
		buf.WriteString(v.VisitTopNode(*s.Top))
	}

	if s.SetQuanifier != nil {
		buf.WriteString(SPACE)
		buf.WriteString(v.Visit(*s.SetQuanifier))
	}

	// add select projections
	if s.Projections != nil {
		claused := false
		for i, projection := range *s.Projections {

			if projection != nil {
				if !claused {
					buf.WriteString(SPACE)
					claused = true
				}
				buf.WriteString(v.Visit(projection))
				if (len(*s.Projections) - 1) != i {
					buf.WriteString(COMMA)
				}
			}
		}
	}

	// add FROM statement to the buffer
	if s.Source != nil && s.Source.Left != nil {
		if t, ok := s.Source.Left.(Table); ok && t.Name != "" {
			buf.WriteString(" FROM ")
			buf.WriteString(v.Visit(*s.Source))
		} else if t, ok := s.Source.Left.(*Table); ok && t.Name != "" {
			buf.WriteString(" FROM ")
			buf.WriteString(v.Visit(*s.Source))
		}
	}

	// add WHERE statement to the buffer
	if s.Wheres != nil {
		claused := false
		for i, where := range *s.Wheres {
			// add WHERE clause if it hasn't already been added
			if !claused {
				buf.WriteString(WHERE)
				claused = true
			}
			buf.WriteString(v.Visit(where))
			if (len(*s.Wheres) - 1) != i {
				buf.WriteString(COMMA)
			}
		}
	}

	// add GROUP BY statement to the buffer
	if s.Groups != nil {
		claused := false
		for i, group := range *s.Groups {
			// add GROUP BY clause if it hasn't already been added
			if !claused {
				buf.WriteString(GROUP_BY)
				claused = true
			}
			buf.WriteString(v.Visit(group))
			if (len(*s.Groups) - 1) != i {
				buf.WriteString(COMMA)
			}
		}
	}

	// add HAVING statement to the buffer
	if s.Having != nil {
		buf.WriteString(SPACE)
		buf.WriteString(v.VisitHavingNode(*s.Having))
	}

	// add WINDOW statements to the buffer
	if s.Windows != nil {
		claused := false
		for i, window := range *s.Windows {
			// add WINDOW clause if is hasn't already been added
			if !claused {
				buf.WriteString(WINDOW)
				claused = true
			}
			buf.WriteString(v.Visit(window))
			if (len(*s.Windows) - 1) != i {
				buf.WriteString(COMMA)
			}
		}
	}

	return buf.String()
}

func (v ToSqlVisitor) VisitSelectStatementNode(s SelectStatementNode) string {
	var buf bytes.Buffer

	// add WITH statement to the buffer
	if s.With != nil {
		buf.WriteString(v.Visit(s.With))
	}

	// add SELECT core to the buffer
	if s.Cores != nil {
		for _, core := range s.Cores {
			if core != nil {
				buf.WriteString(v.VisitSelectCoreNode(*core))
			}
		}
	}

	// add ORDER BY clauses to the buffer
	if s.Orders != nil {
		buf.WriteString(ORDER_BY)
		for i, order := range *s.Orders {
			buf.WriteString(v.Visit(order))
			if (len(*s.Orders) - 1) != i {
				buf.WriteString(COMMA)
			}
		}
	}

	// add LIMIT clause to the buffer
	if s.Limit != nil {
		buf.WriteString(SPACE)
		buf.WriteString(v.VisitLimitNode(*s.Limit))
	}

	// add OFFSET clause to the buffer
	if s.Offset != nil {
		buf.WriteString(SPACE)
		buf.WriteString(v.VisitOffsetNode(*s.Offset))
	}

	// add LOCK clause to the buffer
	if s.Lock != nil {
		buf.WriteString(SPACE)
		buf.WriteString(v.VisitLockNode(*s.Lock))
	}

	return strings.TrimSpace(buf.String())
}
<commit_msg>Added ToSqlVisitor#VisitIntersectNode<commit_after>package rel

import (
	"bytes"
	"log"
	"runtime/debug"
	"strings"
)

type ToSqlVisitor struct {
	conn *Connection
}

const (
	WHERE    = " WHERE "
	SPACE    = " "
	COMMA    = ", "
	GROUP_BY = " GROUP BY "
	ORDER_BY = " ORDER BY "
	WINDOW   = " WINDOW "
	AND      = " AND "
	DISTINCT = "DISTINCT"
)

func NewToSqlVisitor(c *Connection) ToSqlVisitor {
	return ToSqlVisitor{conn: c}
}

func (v ToSqlVisitor) Accept(a Visitable) string {
	return v.Visit(a)
}

func (v ToSqlVisitor) Visit(a Visitable) string {
	ret := ""
	switch val := a.(type) {
	case SelectStatementNode:
		ret = v.VisitSelectStatementNode(val)
	case AndNode:
		ret = v.VisitAndNode(val)
	case InNode:
		ret = v.VisitInNode(val)
	case SqlLiteralNode:
		ret = v.VisitSqlLiteralNode(val)
	case JoinSource:
		ret = v.VisitJoinSourceNode(val)
	case EqualityNode:
		ret = v.VisitEqualityNode(val)
	case HavingNode:
		ret = v.VisitHavingNode(val)
	case AttributeNode:
		ret = v.VisitAttributeNode(val)
	case GroupNode:
		ret = v.VisitGroupNode(val)
	case ExistsNode:
		ret = v.VisitExistsNode(val)
	case AsNode:
		ret = v.VisitAsNode(val)
	case Table:
		ret = v.VisitTable(val)
	case *Table:
		ret = v.VisitTable(*val)
	case LessThanNode:
		ret = v.VisitLessThanNode(val)
	case UnionNode:
		ret = v.VisitUnionNode(val)
	case UnionAllNode:
		ret = v.VisitUnionAllNode(val)
	case SelectManager:
		ret = v.VisitSelectManager(val)
	case GreaterThanNode:
		ret = v.VisitGreaterThanNode(val)
	case IntersectNode:
		ret = v.VisitIntersectNode(val)
	default:
		debug.PrintStack()
		log.Fatalf("ToSqlVisitor#Visit %T not handled", a)
	}
	return ret
}

func (v ToSqlVisitor) VisitTopNode(a TopNode) string {
	return "TopNode"
}

func (v ToSqlVisitor) VisitLimitNode(a LimitNode) string {
	var buf bytes.Buffer
	buf.WriteString("LIMIT ")
	buf.WriteString(v.Visit(a.Expr))
	return buf.String()
}

func (v ToSqlVisitor) VisitLockNode(a LockNode) string {
	return "LockNode"
}

func (v ToSqlVisitor) VisitOffsetNode(n OffsetNode) string {
	return "OFFSET " + v.Visit(n.Expr)
}

func (v ToSqlVisitor) VisitDistinctOnNode(a DistinctOnNode) string {
	return "DistinctOnNode"
}

func (v ToSqlVisitor) VisitAndNode(a AndNode) string {
	return "AndNode"
}

func (v ToSqlVisitor) VisitInNode(a InNode) string {
	return "InNode"
}

func (v ToSqlVisitor) VisitOrderingNode(a OrderingNode) string {
	return "OrderingNode"
}

func (v ToSqlVisitor) VisitIntersectNode(a IntersectNode) string {
	var buf bytes.Buffer
	buf.WriteString("( ")
	buf.WriteString(v.Visit(a.Left))
	buf.WriteString(" INTERSECT ")
	buf.WriteString(v.Visit(a.Right))
	buf.WriteString(" )")
	return buf.String()
}

func (v ToSqlVisitor) VisitSelectManager(a SelectManager) string {
	var buf bytes.Buffer
	buf.WriteString("(")
	buf.WriteString(a.ToSql())
	buf.WriteString(")")
	return buf.String()
}

func (v ToSqlVisitor) VisitUnionNode(a UnionNode) string {
	var buf bytes.Buffer
	buf.WriteString("( ")
	buf.WriteString(v.Visit(a.Left))
	buf.WriteString(" UNION ")
	buf.WriteString(v.Visit(a.Right))
	buf.WriteString(" )")
	return buf.String()
}

func (v ToSqlVisitor) VisitUnionAllNode(a UnionAllNode) string {
	var buf bytes.Buffer
	buf.WriteString("( ")
	buf.WriteString(v.Visit(a.Left))
	buf.WriteString(" UNION ALL ")
	buf.WriteString(v.Visit(a.Right))
	buf.WriteString(" )")
	return buf.String()
}

func (v ToSqlVisitor) VisitLessThanNode(a LessThanNode) string {
	var buf bytes.Buffer
	buf.WriteString(v.Visit(a.Left))
	buf.WriteString(" < ")
	buf.WriteString(v.Visit(a.Right))
	return buf.String()
}

func (v ToSqlVisitor) VisitGreaterThanNode(a GreaterThanNode) string {
	var buf bytes.Buffer
	buf.WriteString(v.Visit(a.Left))
	buf.WriteString(" > ")
	buf.WriteString(v.Visit(a.Right))
	return buf.String()
}

func (v ToSqlVisitor) VisitAsNode(a AsNode) string {
	var buf bytes.Buffer
	buf.WriteString(v.Visit(a.Left))
	buf.WriteString(" AS ")
	buf.WriteString(v.Visit(*a.Right))
	return buf.String()
}

func (v ToSqlVisitor) VisitGroupNode(n GroupNode) string {
	return v.Visit(n.Expr)
}

func (v ToSqlVisitor) VisitHavingNode(n HavingNode) string {
	var buf bytes.Buffer
	buf.WriteString("HAVING ")
	buf.WriteString(v.Visit(n.Expr))
	return buf.String()
}

func (v ToSqlVisitor) VisitExistsNode(n ExistsNode) string {
	var buf bytes.Buffer
	buf.WriteString("EXISTS (")
	buf.WriteString(v.Visit(n.Expressions))
	buf.WriteString(")")
	if n.Alias != nil {
		buf.WriteString(" AS ")
		buf.WriteString(v.Visit(n.Alias))
	}
	return buf.String()
}

func (v ToSqlVisitor) VisitAttributeNode(n AttributeNode) string {
	var buf bytes.Buffer
	buf.WriteString(v.QuoteTableName(n.Table.Name))
	buf.WriteString(".")
	buf.WriteString(v.QuoteColumnName(n.Name))
	return buf.String()
}

func (v ToSqlVisitor) VisitEqualityNode(n EqualityNode) string {
	var buf bytes.Buffer
	if n.Right == nil {
		buf.WriteString(v.Visit(n.Left))
		buf.WriteString(" IS NULL")
	} else {
		buf.WriteString(v.Visit(n.Left))
		buf.WriteString(" = ")
		buf.WriteString(v.Visit(*n.Right))
	}
	return buf.String()
}

func (v ToSqlVisitor) VisitTable(t Table) string {
	var buf bytes.Buffer
	if t.TableAlias != "" {
		buf.WriteString(v.QuoteTableName(t.Name))
		buf.WriteString(SPACE)
		buf.WriteString(v.QuoteTableName(t.TableAlias))
	} else {
		buf.WriteString(v.QuoteTableName(t.Name))
	}
	return buf.String()
}

func (v ToSqlVisitor) QuoteTableName(name string) string {
	return v.conn.QuoteTableName(name)
}

func (v ToSqlVisitor) QuoteColumnName(name string) string {
	return v.conn.QuoteColumnName(name)
}

func (v ToSqlVisitor) VisitJoinSourceNode(a JoinSource) string {
	var buf bytes.Buffer
	if a.Left != nil {
		buf.WriteString(v.Visit(a.Left))
	}
	return buf.String()
}

func (v ToSqlVisitor) VisitSqlLiteralNode(a SqlLiteralNode) string {
	if a.Raw != "" {
		return a.Raw
	} else {
		return ""
	}
}

func (v ToSqlVisitor) VisitSelectCoreNode(s SelectCoreNode) string {
	var buf bytes.Buffer

	buf.WriteString("SELECT")

	// Add TOP statement to the buffer
	if s.Top != nil {
		buf.WriteString(SPACE)
		buf.WriteString(v.VisitTopNode(*s.Top))
	}

	if s.SetQuanifier != nil {
		buf.WriteString(SPACE)
		buf.WriteString(v.Visit(*s.SetQuanifier))
	}

	// add select projections
	if s.Projections != nil {
		claused := false
		for i, projection := range *s.Projections {

			if projection != nil {
				if !claused {
					buf.WriteString(SPACE)
					claused = true
				}
				buf.WriteString(v.Visit(projection))
				if (len(*s.Projections) - 1) != i {
					buf.WriteString(COMMA)
				}
			}
		}
	}

	// add FROM statement to the buffer
	if s.Source != nil && s.Source.Left != nil {
		if t, ok := s.Source.Left.(Table); ok && t.Name != "" {
			buf.WriteString(" FROM ")
			buf.WriteString(v.Visit(*s.Source))
		} else if t, ok := s.Source.Left.(*Table); ok && t.Name != "" {
			buf.WriteString(" FROM ")
			buf.WriteString(v.Visit(*s.Source))
		}
	}

	// add WHERE statement to the buffer
	if s.Wheres != nil {
		claused := false
		for i, where := range *s.Wheres {
			// add WHERE clause if it hasn't already been added
			if !claused {
				buf.WriteString(WHERE)
				claused = true
			}
			buf.WriteString(v.Visit(where))
			if (len(*s.Wheres) - 1) != i {
				buf.WriteString(COMMA)
			}
		}
	}

	// add GROUP BY statement to the buffer
	if s.Groups != nil {
		claused := false
		for i, group := range *s.Groups {
			// add GROUP BY clause if it hasn't already been added
			if !claused {
				buf.WriteString(GROUP_BY)
				claused = true
			}
			buf.WriteString(v.Visit(group))
			if (len(*s.Groups) - 1) != i {
				buf.WriteString(COMMA)
			}
		}
	}

	// add HAVING statement to the buffer
	if s.Having != nil {
		buf.WriteString(SPACE)
		buf.WriteString(v.VisitHavingNode(*s.Having))
	}

	// add WINDOW statements to the buffer
	if s.Windows != nil {
		claused := false
		for i, window := range *s.Windows {
			// add WINDOW clause if is hasn't already been added
			if !claused {
				buf.WriteString(WINDOW)
				claused = true
			}
			buf.WriteString(v.Visit(window))
			if (len(*s.Windows) - 1) != i {
				buf.WriteString(COMMA)
			}
		}
	}

	return buf.String()
}

func (v ToSqlVisitor) VisitSelectStatementNode(s SelectStatementNode) string {
	var buf bytes.Buffer

	// add WITH statement to the buffer
	if s.With != nil {
		buf.WriteString(v.Visit(s.With))
	}

	// add SELECT core to the buffer
	if s.Cores != nil {
		for _, core := range s.Cores {
			if core != nil {
				buf.WriteString(v.VisitSelectCoreNode(*core))
			}
		}
	}

	// add ORDER BY clauses to the buffer
	if s.Orders != nil {
		buf.WriteString(ORDER_BY)
		for i, order := range *s.Orders {
			buf.WriteString(v.Visit(order))
			if (len(*s.Orders) - 1) != i {
				buf.WriteString(COMMA)
			}
		}
	}

	// add LIMIT clause to the buffer
	if s.Limit != nil {
		buf.WriteString(SPACE)
		buf.WriteString(v.VisitLimitNode(*s.Limit))
	}

	// add OFFSET clause to the buffer
	if s.Offset != nil {
		buf.WriteString(SPACE)
		buf.WriteString(v.VisitOffsetNode(*s.Offset))
	}

	// add LOCK clause to the buffer
	if s.Lock != nil {
		buf.WriteString(SPACE)
		buf.WriteString(v.VisitLockNode(*s.Lock))
	}

	return strings.TrimSpace(buf.String())
}
<|endoftext|>


<commit_before>package sarama

import (
	"errors"
	"fmt"
)

// ErrOutOfBrokers is the error returned when the client has run out of brokers to talk to because all of them errored
// or otherwise failed to respond.
var ErrOutOfBrokers = errors.New("kafka: client has run out of available brokers to talk to (Is your cluster reachable?)")

// ErrClosedClient is the error returned when a method is called on a client that has been closed.
var ErrClosedClient = errors.New("kafka: tried to use a client that was closed")

// ErrIncompleteResponse is the error returned when the server returns a syntactically valid response, but it does
// not contain the expected information.
var ErrIncompleteResponse = errors.New("kafka: response did not contain all the expected topic/partition blocks")

// ErrInvalidPartition is the error returned when a partitioner returns an invalid partition index
// (meaning one outside of the range [0...numPartitions-1]).
var ErrInvalidPartition = errors.New("kafka: partitioner returned an invalid partition index")

// ErrAlreadyConnected is the error returned when calling Open() on a Broker that is already connected or connecting.
var ErrAlreadyConnected = errors.New("kafka: broker connection already initiated")

// ErrNotConnected is the error returned when trying to send or call Close() on a Broker that is not connected.
var ErrNotConnected = errors.New("kafka: broker not connected")

// ErrInsufficientData is returned when decoding and the packet is truncated. This can be expected
// when requesting messages, since as an optimization the server is allowed to return a partial message at the end
// of the message set.
var ErrInsufficientData = errors.New("kafka: insufficient data to decode packet, more bytes expected")

// ErrShuttingDown is returned when a producer receives a message during shutdown.
var ErrShuttingDown = errors.New("kafka: message received by producer in process of shutting down")

// ErrMessageTooLarge is returned when the next message to consume is larger than the configured Consumer.Fetch.Max
var ErrMessageTooLarge = errors.New("kafka: message is larger than Consumer.Fetch.Max")

// ErrConsumerOffsetNotAdvanced is returned when a partition consumer didn't advance its offset after parsing
// a RecordBatch.
var ErrConsumerOffsetNotAdvanced = errors.New("kafka: consumer offset was not advanced after a RecordBatch")

// ErrControllerNotAvailable is returned when server didn't give correct controller id. May be kafka server's version
// is lower than 0.10.0.0.
var ErrControllerNotAvailable = errors.New("kafka: controller is not available")

// ErrNoTopicsToUpdateMetadata is returned when Meta.Full is set to false but no specific topics were found to update
// the metadata.
var ErrNoTopicsToUpdateMetadata = errors.New("kafka: no specific topics to update metadata")

// PacketEncodingError is returned from a failure while encoding a Kafka packet. This can happen, for example,
// if you try to encode a string over 2^15 characters in length, since Kafka's encoding rules do not permit that.
type PacketEncodingError struct {
	Info string
}

func (err PacketEncodingError) Error() string {
	return fmt.Sprintf("kafka: error encoding packet: %s", err.Info)
}

// PacketDecodingError is returned when there was an error (other than truncated data) decoding the Kafka broker's response.
// This can be a bad CRC or length field, or any other invalid value.
type PacketDecodingError struct {
	Info string
}

func (err PacketDecodingError) Error() string {
	return fmt.Sprintf("kafka: error decoding packet: %s", err.Info)
}

// ConfigurationError is the type of error returned from a constructor (e.g. NewClient, or NewConsumer)
// when the specified configuration is invalid.
type ConfigurationError string

func (err ConfigurationError) Error() string {
	return "kafka: invalid configuration (" + string(err) + ")"
}

// KError is the type of error that can be returned directly by the Kafka broker.
// See https://cwiki.apache.org/confluence/display/KAFKA/A+Guide+To+The+Kafka+Protocol#AGuideToTheKafkaProtocol-ErrorCodes
type KError int16

// Numeric error codes returned by the Kafka server.
const (
	ErrNoError                            KError = 0
	ErrUnknown                            KError = -1
	ErrOffsetOutOfRange                   KError = 1
	ErrInvalidMessage                     KError = 2
	ErrUnknownTopicOrPartition            KError = 3
	ErrInvalidMessageSize                 KError = 4
	ErrLeaderNotAvailable                 KError = 5
	ErrNotLeaderForPartition              KError = 6
	ErrRequestTimedOut                    KError = 7
	ErrBrokerNotAvailable                 KError = 8
	ErrReplicaNotAvailable                KError = 9
	ErrMessageSizeTooLarge                KError = 10
	ErrStaleControllerEpochCode           KError = 11
	ErrOffsetMetadataTooLarge             KError = 12
	ErrNetworkException                   KError = 13
	ErrOffsetsLoadInProgress              KError = 14
	ErrConsumerCoordinatorNotAvailable    KError = 15
	ErrNotCoordinatorForConsumer          KError = 16
	ErrInvalidTopic                       KError = 17
	ErrMessageSetSizeTooLarge             KError = 18
	ErrNotEnoughReplicas                  KError = 19
	ErrNotEnoughReplicasAfterAppend       KError = 20
	ErrInvalidRequiredAcks                KError = 21
	ErrIllegalGeneration                  KError = 22
	ErrInconsistentGroupProtocol          KError = 23
	ErrInvalidGroupId                     KError = 24
	ErrUnknownMemberId                    KError = 25
	ErrInvalidSessionTimeout              KError = 26
	ErrRebalanceInProgress                KError = 27
	ErrInvalidCommitOffsetSize            KError = 28
	ErrTopicAuthorizationFailed           KError = 29
	ErrGroupAuthorizationFailed           KError = 30
	ErrClusterAuthorizationFailed         KError = 31
	ErrInvalidTimestamp                   KError = 32
	ErrUnsupportedSASLMechanism           KError = 33
	ErrIllegalSASLState                   KError = 34
	ErrUnsupportedVersion                 KError = 35
	ErrTopicAlreadyExists                 KError = 36
	ErrInvalidPartitions                  KError = 37
	ErrInvalidReplicationFactor           KError = 38
	ErrInvalidReplicaAssignment           KError = 39
	ErrInvalidConfig                      KError = 40
	ErrNotController                      KError = 41
	ErrInvalidRequest                     KError = 42
	ErrUnsupportedForMessageFormat        KError = 43
	ErrPolicyViolation                    KError = 44
	ErrOutOfOrderSequenceNumber           KError = 45
	ErrDuplicateSequenceNumber            KError = 46
	ErrInvalidProducerEpoch               KError = 47
	ErrInvalidTxnState                    KError = 48
	ErrInvalidProducerIDMapping           KError = 49
	ErrInvalidTransactionTimeout          KError = 50
	ErrConcurrentTransactions             KError = 51
	ErrTransactionCoordinatorFenced       KError = 52
	ErrTransactionalIDAuthorizationFailed KError = 53
	ErrSecurityDisabled                   KError = 54
	ErrOperationNotAttempted              KError = 55
	ErrKafkaStorageError                  KError = 56
	ErrLogDirNotFound                     KError = 57
	ErrSASLAuthenticationFailed           KError = 58
	ErrUnknownProducerID                  KError = 59
	ErrReassignmentInProgress             KError = 60
)

func (err KError) Error() string {
	// Error messages stolen/adapted from
	// https://kafka.apache.org/protocol#protocol_error_codes
	switch err {
	case ErrNoError:
		return "kafka server: Not an error, why are you printing me?"
	case ErrUnknown:
		return "kafka server: Unexpected (unknown?) server error."
	case ErrOffsetOutOfRange:
		return "kafka server: The requested offset is outside the range of offsets maintained by the server for the given topic/partition."
	case ErrInvalidMessage:
		return "kafka server: Message contents does not match its CRC."
	case ErrUnknownTopicOrPartition:
		return "kafka server: Request was for a topic or partition that does not exist on this broker."
	case ErrInvalidMessageSize:
		return "kafka server: The message has a negative size."
	case ErrLeaderNotAvailable:
		return "kafka server: In the middle of a leadership election, there is currently no leader for this partition and hence it is unavailable for writes."
	case ErrNotLeaderForPartition:
		return "kafka server: Tried to send a message to a replica that is not the leader for some partition. Your metadata is out of date."
	case ErrRequestTimedOut:
		return "kafka server: Request exceeded the user-specified time limit in the request."
	case ErrBrokerNotAvailable:
		return "kafka server: Broker not available. Not a client facing error, we should never receive this!!!"
	case ErrReplicaNotAvailable:
		return "kafka server: Replica information not available, one or more brokers are down."
	case ErrMessageSizeTooLarge:
		return "kafka server: Message was too large, server rejected it to avoid allocation error."
	case ErrStaleControllerEpochCode:
		return "kafka server: StaleControllerEpochCode (internal error code for broker-to-broker communication)."
	case ErrOffsetMetadataTooLarge:
		return "kafka server: Specified a string larger than the configured maximum for offset metadata."
	case ErrNetworkException:
		return "kafka server: The server disconnected before a response was received."
	case ErrOffsetsLoadInProgress:
		return "kafka server: The broker is still loading offsets after a leader change for that offset's topic partition."
	case ErrConsumerCoordinatorNotAvailable:
		return "kafka server: Offset's topic has not yet been created."
	case ErrNotCoordinatorForConsumer:
		return "kafka server: Request was for a consumer group that is not coordinated by this broker."
	case ErrInvalidTopic:
		return "kafka server: The request attempted to perform an operation on an invalid topic."
	case ErrMessageSetSizeTooLarge:
		return "kafka server: The request included message batch larger than the configured segment size on the server."
	case ErrNotEnoughReplicas:
		return "kafka server: Messages are rejected since there are fewer in-sync replicas than required."
	case ErrNotEnoughReplicasAfterAppend:
		return "kafka server: Messages are written to the log, but to fewer in-sync replicas than required."
	case ErrInvalidRequiredAcks:
		return "kafka server: The number of required acks is invalid (should be either -1, 0, or 1)."
	case ErrIllegalGeneration:
		return "kafka server: The provided generation id is not the current generation."
	case ErrInconsistentGroupProtocol:
		return "kafka server: The provider group protocol type is incompatible with the other members."
	case ErrInvalidGroupId:
		return "kafka server: The provided group id was empty."
	case ErrUnknownMemberId:
		return "kafka server: The provided member is not known in the current generation."
	case ErrInvalidSessionTimeout:
		return "kafka server: The provided session timeout is outside the allowed range."
	case ErrRebalanceInProgress:
		return "kafka server: A rebalance for the group is in progress. Please re-join the group."
	case ErrInvalidCommitOffsetSize:
		return "kafka server: The provided commit metadata was too large."
	case ErrTopicAuthorizationFailed:
		return "kafka server: The client is not authorized to access this topic."
	case ErrGroupAuthorizationFailed:
		return "kafka server: The client is not authorized to access this group."
	case ErrClusterAuthorizationFailed:
		return "kafka server: The client is not authorized to send this request type."
	case ErrInvalidTimestamp:
		return "kafka server: The timestamp of the message is out of acceptable range."
	case ErrUnsupportedSASLMechanism:
		return "kafka server: The broker does not support the requested SASL mechanism."
	case ErrIllegalSASLState:
		return "kafka server: Request is not valid given the current SASL state."
	case ErrUnsupportedVersion:
		return "kafka server: The version of API is not supported."
	case ErrTopicAlreadyExists:
		return "kafka server: Topic with this name already exists."
	case ErrInvalidPartitions:
		return "kafka server: Number of partitions is invalid."
	case ErrInvalidReplicationFactor:
		return "kafka server: Replication-factor is invalid."
	case ErrInvalidReplicaAssignment:
		return "kafka server: Replica assignment is invalid."
	case ErrInvalidConfig:
		return "kafka server: Configuration is invalid."
	case ErrNotController:
		return "kafka server: This is not the correct controller for this cluster."
	case ErrInvalidRequest:
		return "kafka server: This most likely occurs because of a request being malformed by the client library or the message was sent to an incompatible broker. See the broker logs for more details."
	case ErrUnsupportedForMessageFormat:
		return "kafka server: The requested operation is not supported by the message format version."
	case ErrPolicyViolation:
		return "kafka server: Request parameters do not satisfy the configured policy."
	case ErrOutOfOrderSequenceNumber:
		return "kafka server: The broker received an out of order sequence number."
	case ErrDuplicateSequenceNumber:
		return "kafka server: The broker received a duplicate sequence number."
	case ErrInvalidProducerEpoch:
		return "kafka server: Producer attempted an operation with an old epoch."
	case ErrInvalidTxnState:
		return "kafka server: The producer attempted a transactional operation in an invalid state."
	case ErrInvalidProducerIDMapping:
		return "kafka server: The producer attempted to use a producer id which is not currently assigned to its transactional id."
	case ErrInvalidTransactionTimeout:
		return "kafka server: The transaction timeout is larger than the maximum value allowed by the broker (as configured by max.transaction.timeout.ms)."
	case ErrConcurrentTransactions:
		return "kafka server: The producer attempted to update a transaction while another concurrent operation on the same transaction was ongoing."
	case ErrTransactionCoordinatorFenced:
		return "kafka server: The transaction coordinator sending a WriteTxnMarker is no longer the current coordinator for a given producer."
	case ErrTransactionalIDAuthorizationFailed:
		return "kafka server: Transactional ID authorization failed."
	case ErrSecurityDisabled:
		return "kafka server: Security features are disabled."
	case ErrOperationNotAttempted:
		return "kafka server: The broker did not attempt to execute this operation."
	case ErrKafkaStorageError:
		return "kafka server: Disk error when trying to access log file on the disk."
	case ErrLogDirNotFound:
		return "kafka server: The specified log directory is not found in the broker config."
	case ErrSASLAuthenticationFailed:
		return "kafka server: SASL Authentication failed."
	case ErrUnknownProducerID:
		return "kafka server: The broker could not locate the producer metadata associated with the Producer ID."
	case ErrReassignmentInProgress:
		return "kafka server: A partition reassignment is in progress."
	}

	return fmt.Sprintf("Unknown error, how did this happen? Error code = %d", err)
}
<commit_msg>add error codes 61-72<commit_after>package sarama

import (
	"errors"
	"fmt"
)

// ErrOutOfBrokers is the error returned when the client has run out of brokers to talk to because all of them errored
// or otherwise failed to respond.
var ErrOutOfBrokers = errors.New("kafka: client has run out of available brokers to talk to (Is your cluster reachable?)")

// ErrClosedClient is the error returned when a method is called on a client that has been closed.
var ErrClosedClient = errors.New("kafka: tried to use a client that was closed")

// ErrIncompleteResponse is the error returned when the server returns a syntactically valid response, but it does
// not contain the expected information.
var ErrIncompleteResponse = errors.New("kafka: response did not contain all the expected topic/partition blocks")

// ErrInvalidPartition is the error returned when a partitioner returns an invalid partition index
// (meaning one outside of the range [0...numPartitions-1]).
var ErrInvalidPartition = errors.New("kafka: partitioner returned an invalid partition index")

// ErrAlreadyConnected is the error returned when calling Open() on a Broker that is already connected or connecting.
var ErrAlreadyConnected = errors.New("kafka: broker connection already initiated")

// ErrNotConnected is the error returned when trying to send or call Close() on a Broker that is not connected.
var ErrNotConnected = errors.New("kafka: broker not connected")

// ErrInsufficientData is returned when decoding and the packet is truncated. This can be expected
// when requesting messages, since as an optimization the server is allowed to return a partial message at the end
// of the message set.
var ErrInsufficientData = errors.New("kafka: insufficient data to decode packet, more bytes expected")

// ErrShuttingDown is returned when a producer receives a message during shutdown.
var ErrShuttingDown = errors.New("kafka: message received by producer in process of shutting down")

// ErrMessageTooLarge is returned when the next message to consume is larger than the configured Consumer.Fetch.Max
var ErrMessageTooLarge = errors.New("kafka: message is larger than Consumer.Fetch.Max")

// ErrConsumerOffsetNotAdvanced is returned when a partition consumer didn't advance its offset after parsing
// a RecordBatch.
var ErrConsumerOffsetNotAdvanced = errors.New("kafka: consumer offset was not advanced after a RecordBatch")

// ErrControllerNotAvailable is returned when server didn't give correct controller id. May be kafka server's version
// is lower than 0.10.0.0.
var ErrControllerNotAvailable = errors.New("kafka: controller is not available")

// ErrNoTopicsToUpdateMetadata is returned when Meta.Full is set to false but no specific topics were found to update
// the metadata.
var ErrNoTopicsToUpdateMetadata = errors.New("kafka: no specific topics to update metadata")

// PacketEncodingError is returned from a failure while encoding a Kafka packet. This can happen, for example,
// if you try to encode a string over 2^15 characters in length, since Kafka's encoding rules do not permit that.
type PacketEncodingError struct {
	Info string
}

func (err PacketEncodingError) Error() string {
	return fmt.Sprintf("kafka: error encoding packet: %s", err.Info)
}

// PacketDecodingError is returned when there was an error (other than truncated data) decoding the Kafka broker's response.
// This can be a bad CRC or length field, or any other invalid value.
type PacketDecodingError struct {
	Info string
}

func (err PacketDecodingError) Error() string {
	return fmt.Sprintf("kafka: error decoding packet: %s", err.Info)
}

// ConfigurationError is the type of error returned from a constructor (e.g. NewClient, or NewConsumer)
// when the specified configuration is invalid.
type ConfigurationError string

func (err ConfigurationError) Error() string {
	return "kafka: invalid configuration (" + string(err) + ")"
}

// KError is the type of error that can be returned directly by the Kafka broker.
// See https://cwiki.apache.org/confluence/display/KAFKA/A+Guide+To+The+Kafka+Protocol#AGuideToTheKafkaProtocol-ErrorCodes
type KError int16

// Numeric error codes returned by the Kafka server.
const (
	ErrNoError                            KError = 0
	ErrUnknown                            KError = -1
	ErrOffsetOutOfRange                   KError = 1
	ErrInvalidMessage                     KError = 2
	ErrUnknownTopicOrPartition            KError = 3
	ErrInvalidMessageSize                 KError = 4
	ErrLeaderNotAvailable                 KError = 5
	ErrNotLeaderForPartition              KError = 6
	ErrRequestTimedOut                    KError = 7
	ErrBrokerNotAvailable                 KError = 8
	ErrReplicaNotAvailable                KError = 9
	ErrMessageSizeTooLarge                KError = 10
	ErrStaleControllerEpochCode           KError = 11
	ErrOffsetMetadataTooLarge             KError = 12
	ErrNetworkException                   KError = 13
	ErrOffsetsLoadInProgress              KError = 14
	ErrConsumerCoordinatorNotAvailable    KError = 15
	ErrNotCoordinatorForConsumer          KError = 16
	ErrInvalidTopic                       KError = 17
	ErrMessageSetSizeTooLarge             KError = 18
	ErrNotEnoughReplicas                  KError = 19
	ErrNotEnoughReplicasAfterAppend       KError = 20
	ErrInvalidRequiredAcks                KError = 21
	ErrIllegalGeneration                  KError = 22
	ErrInconsistentGroupProtocol          KError = 23
	ErrInvalidGroupId                     KError = 24
	ErrUnknownMemberId                    KError = 25
	ErrInvalidSessionTimeout              KError = 26
	ErrRebalanceInProgress                KError = 27
	ErrInvalidCommitOffsetSize            KError = 28
	ErrTopicAuthorizationFailed           KError = 29
	ErrGroupAuthorizationFailed           KError = 30
	ErrClusterAuthorizationFailed         KError = 31
	ErrInvalidTimestamp                   KError = 32
	ErrUnsupportedSASLMechanism           KError = 33
	ErrIllegalSASLState                   KError = 34
	ErrUnsupportedVersion                 KError = 35
	ErrTopicAlreadyExists                 KError = 36
	ErrInvalidPartitions                  KError = 37
	ErrInvalidReplicationFactor           KError = 38
	ErrInvalidReplicaAssignment           KError = 39
	ErrInvalidConfig                      KError = 40
	ErrNotController                      KError = 41
	ErrInvalidRequest                     KError = 42
	ErrUnsupportedForMessageFormat        KError = 43
	ErrPolicyViolation                    KError = 44
	ErrOutOfOrderSequenceNumber           KError = 45
	ErrDuplicateSequenceNumber            KError = 46
	ErrInvalidProducerEpoch               KError = 47
	ErrInvalidTxnState                    KError = 48
	ErrInvalidProducerIDMapping           KError = 49
	ErrInvalidTransactionTimeout          KError = 50
	ErrConcurrentTransactions             KError = 51
	ErrTransactionCoordinatorFenced       KError = 52
	ErrTransactionalIDAuthorizationFailed KError = 53
	ErrSecurityDisabled                   KError = 54
	ErrOperationNotAttempted              KError = 55
	ErrKafkaStorageError                  KError = 56
	ErrLogDirNotFound                     KError = 57
	ErrSASLAuthenticationFailed           KError = 58
	ErrUnknownProducerID                  KError = 59
	ErrReassignmentInProgress             KError = 60
	ErrDelegationTokenAuthDisabled        KError = 61
	ErrDelegationTokenNotFound            KError = 62
	ErrDelegationTokenOwnerMismatch       KError = 63
	ErrDelegationTokenRequestNotAllowed   KError = 64
	ErrDelegationTokenAuthorizationFailed KError = 65
	ErrDelegationTokenExpired             KError = 66
	ErrInvalidPrincipalType               KError = 67
	ErrNonEmptyGroup                      KError = 68
	ErrGroupIDNotFound                    KError = 69
	ErrFetchSessionIDNotFound             KError = 70
	ErrInvalidFetchSessionEpoch           KError = 71
	ErrListenerNotFound                   KError = 72
)

func (err KError) Error() string {
	// Error messages stolen/adapted from
	// https://kafka.apache.org/protocol#protocol_error_codes
	switch err {
	case ErrNoError:
		return "kafka server: Not an error, why are you printing me?"
	case ErrUnknown:
		return "kafka server: Unexpected (unknown?) server error."
	case ErrOffsetOutOfRange:
		return "kafka server: The requested offset is outside the range of offsets maintained by the server for the given topic/partition."
	case ErrInvalidMessage:
		return "kafka server: Message contents does not match its CRC."
	case ErrUnknownTopicOrPartition:
		return "kafka server: Request was for a topic or partition that does not exist on this broker."
	case ErrInvalidMessageSize:
		return "kafka server: The message has a negative size."
	case ErrLeaderNotAvailable:
		return "kafka server: In the middle of a leadership election, there is currently no leader for this partition and hence it is unavailable for writes."
	case ErrNotLeaderForPartition:
		return "kafka server: Tried to send a message to a replica that is not the leader for some partition. Your metadata is out of date."
	case ErrRequestTimedOut:
		return "kafka server: Request exceeded the user-specified time limit in the request."
	case ErrBrokerNotAvailable:
		return "kafka server: Broker not available. Not a client facing error, we should never receive this!!!"
	case ErrReplicaNotAvailable:
		return "kafka server: Replica information not available, one or more brokers are down."
	case ErrMessageSizeTooLarge:
		return "kafka server: Message was too large, server rejected it to avoid allocation error."
	case ErrStaleControllerEpochCode:
		return "kafka server: StaleControllerEpochCode (internal error code for broker-to-broker communication)."
	case ErrOffsetMetadataTooLarge:
		return "kafka server: Specified a string larger than the configured maximum for offset metadata."
	case ErrNetworkException:
		return "kafka server: The server disconnected before a response was received."
	case ErrOffsetsLoadInProgress:
		return "kafka server: The broker is still loading offsets after a leader change for that offset's topic partition."
	case ErrConsumerCoordinatorNotAvailable:
		return "kafka server: Offset's topic has not yet been created."
	case ErrNotCoordinatorForConsumer:
		return "kafka server: Request was for a consumer group that is not coordinated by this broker."
	case ErrInvalidTopic:
		return "kafka server: The request attempted to perform an operation on an invalid topic."
	case ErrMessageSetSizeTooLarge:
		return "kafka server: The request included message batch larger than the configured segment size on the server."
	case ErrNotEnoughReplicas:
		return "kafka server: Messages are rejected since there are fewer in-sync replicas than required."
	case ErrNotEnoughReplicasAfterAppend:
		return "kafka server: Messages are written to the log, but to fewer in-sync replicas than required."
	case ErrInvalidRequiredAcks:
		return "kafka server: The number of required acks is invalid (should be either -1, 0, or 1)."
	case ErrIllegalGeneration:
		return "kafka server: The provided generation id is not the current generation."
	case ErrInconsistentGroupProtocol:
		return "kafka server: The provider group protocol type is incompatible with the other members."
	case ErrInvalidGroupId:
		return "kafka server: The provided group id was empty."
	case ErrUnknownMemberId:
		return "kafka server: The provided member is not known in the current generation."
	case ErrInvalidSessionTimeout:
		return "kafka server: The provided session timeout is outside the allowed range."
	case ErrRebalanceInProgress:
		return "kafka server: A rebalance for the group is in progress. Please re-join the group."
	case ErrInvalidCommitOffsetSize:
		return "kafka server: The provided commit metadata was too large."
	case ErrTopicAuthorizationFailed:
		return "kafka server: The client is not authorized to access this topic."
	case ErrGroupAuthorizationFailed:
		return "kafka server: The client is not authorized to access this group."
	case ErrClusterAuthorizationFailed:
		return "kafka server: The client is not authorized to send this request type."
	case ErrInvalidTimestamp:
		return "kafka server: The timestamp of the message is out of acceptable range."
	case ErrUnsupportedSASLMechanism:
		return "kafka server: The broker does not support the requested SASL mechanism."
	case ErrIllegalSASLState:
		return "kafka server: Request is not valid given the current SASL state."
	case ErrUnsupportedVersion:
		return "kafka server: The version of API is not supported."
	case ErrTopicAlreadyExists:
		return "kafka server: Topic with this name already exists."
	case ErrInvalidPartitions:
		return "kafka server: Number of partitions is invalid."
	case ErrInvalidReplicationFactor:
		return "kafka server: Replication-factor is invalid."
	case ErrInvalidReplicaAssignment:
		return "kafka server: Replica assignment is invalid."
	case ErrInvalidConfig:
		return "kafka server: Configuration is invalid."
	case ErrNotController:
		return "kafka server: This is not the correct controller for this cluster."
	case ErrInvalidRequest:
		return "kafka server: This most likely occurs because of a request being malformed by the client library or the message was sent to an incompatible broker. See the broker logs for more details."
	case ErrUnsupportedForMessageFormat:
		return "kafka server: The requested operation is not supported by the message format version."
	case ErrPolicyViolation:
		return "kafka server: Request parameters do not satisfy the configured policy."
	case ErrOutOfOrderSequenceNumber:
		return "kafka server: The broker received an out of order sequence number."
	case ErrDuplicateSequenceNumber:
		return "kafka server: The broker received a duplicate sequence number."
	case ErrInvalidProducerEpoch:
		return "kafka server: Producer attempted an operation with an old epoch."
	case ErrInvalidTxnState:
		return "kafka server: The producer attempted a transactional operation in an invalid state."
	case ErrInvalidProducerIDMapping:
		return "kafka server: The producer attempted to use a producer id which is not currently assigned to its transactional id."
	case ErrInvalidTransactionTimeout:
		return "kafka server: The transaction timeout is larger than the maximum value allowed by the broker (as configured by max.transaction.timeout.ms)."
	case ErrConcurrentTransactions:
		return "kafka server: The producer attempted to update a transaction while another concurrent operation on the same transaction was ongoing."
	case ErrTransactionCoordinatorFenced:
		return "kafka server: The transaction coordinator sending a WriteTxnMarker is no longer the current coordinator for a given producer."
	case ErrTransactionalIDAuthorizationFailed:
		return "kafka server: Transactional ID authorization failed."
	case ErrSecurityDisabled:
		return "kafka server: Security features are disabled."
	case ErrOperationNotAttempted:
		return "kafka server: The broker did not attempt to execute this operation."
	case ErrKafkaStorageError:
		return "kafka server: Disk error when trying to access log file on the disk."
	case ErrLogDirNotFound:
		return "kafka server: The specified log directory is not found in the broker config."
	case ErrSASLAuthenticationFailed:
		return "kafka server: SASL Authentication failed."
	case ErrUnknownProducerID:
		return "kafka server: The broker could not locate the producer metadata associated with the Producer ID."
	case ErrReassignmentInProgress:
		return "kafka server: A partition reassignment is in progress."
	case ErrDelegationTokenAuthDisabled:
		return "kafka server: Delegation Token feature is not enabled."
	case ErrDelegationTokenNotFound:
		return "kafka server: Delegation Token is not found on server."
	case ErrDelegationTokenOwnerMismatch:
		return "kafka server: Specified Principal is not valid Owner/Renewer."
	case ErrDelegationTokenRequestNotAllowed:
		return "kafka server: Delegation Token requests are not allowed on PLAINTEXT/1-way SSL channels and on delegation token authenticated channels."
	case ErrDelegationTokenAuthorizationFailed:
		return "kafka server: Delegation Token authorization failed."
	case ErrDelegationTokenExpired:
		return "kafka server: Delegation Token is expired."
	case ErrInvalidPrincipalType:
		return "kafka server: Supplied principalType is not supported."
	case ErrNonEmptyGroup:
		return "kafka server: The group is not empty."
	case ErrGroupIDNotFound:
		return "kafka server: The group id does not exist."
	case ErrFetchSessionIDNotFound:
		return "kafka server: The fetch session ID was not found."
	case ErrInvalidFetchSessionEpoch:
		return "kafka server: The fetch session epoch is invalid."
	case ErrListenerNotFound:
		return "kafka server: There is no listener on the leader broker that matches the listener on which metadata request was processed."
	}

	return fmt.Sprintf("Unknown error, how did this happen? Error code = %d", err)
}
<|endoftext|>


<commit_before>package gocassa

import (
	"fmt"
	"strings"
)

// RowNotFoundError is returned by Reads if the Row is not found.
type RowNotFoundError struct {
	file string
	line int
}

func (r RowNotFoundError) Error() string {
	ss := strings.Split(r.file, "/")
	f := ""
	if len(ss) > 0 {
		f = ss[len(ss)-1]
	}
	return fmt.Sprintf("%v:%v: No rows returned", f, r.line)
}
<commit_msg>Re-add errOp<commit_after>package gocassa

import (
	"fmt"
	"strings"
)

// RowNotFoundError is returned by Reads if the Row is not found.
type RowNotFoundError struct {
	file string
	line int
}

func (r RowNotFoundError) Error() string {
	ss := strings.Split(r.file, "/")
	f := ""
	if len(ss) > 0 {
		f = ss[len(ss)-1]
	}
	return fmt.Sprintf("%v:%v: No rows returned", f, r.line)
}

// errOp is an Op which represents a known error, which will always return during preflighting (preventing any execution
// in a multiOp scenario)
type errOp struct{ err error }

func (o errOp) Run() error               { return o.err }
func (o errOp) RunAtomically() error     { return o.err }
func (o errOp) Add(ops ...Op) Op         { return multiOp{o}.Add(ops...) }
func (o errOp) WithOptions(_ Options) Op { return o }
func (o errOp) Preflight() error         { return o.err }
<|endoftext|>


<commit_before>package rst

import (
	"bytes"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"runtime"
	"strings"

	"github.com/mohamedattahri/rst/internal/assets"
)

// ErrorHandler is a wrapper that allows any Go error to implement the
// http.Handler interface.
func ErrorHandler(err error) http.Handler {
	if e, ok := err.(*Error); ok {
		return e
	}
	// panic will be intercepted in the main mux handler, and will write a
	// response which may display debugging info or hide them depending on the
	// Debug variable set in the mux.
	panic(err)
}

// BadRequest is returned when the request could not be understood by the
// server due to malformed syntax.
func BadRequest(reason, description string) *Error {
	if reason == "" {
		reason = http.StatusText(http.StatusBadRequest)
	}
	if description == "" {
		description = "Request could not be understood due to malformed syntax."
	}

	return NewError(http.StatusBadRequest, reason, description)
}

// Unauthorized is returned when authentication is required for the server
// to process the request.
func Unauthorized() *Error {
	err := NewError(
		http.StatusUnauthorized,
		"Authentication is required",
		"Authentication is required and has failed or has not yet been provided.",
	)
	return err
}

// Forbidden is returned when a resource is protected and inaccessible.
func Forbidden() *Error {
	err := NewError(
		http.StatusForbidden,
		"Request will not be fullfilled",
		"The request was a valid request, but the server is refusing to respond to it. Authenticating will make no difference.",
	)
	return err
}

// NotFound is returned when the server has not found a resource matching the
// Request-URI.
func NotFound() *Error {
	return NewError(
		http.StatusNotFound,
		http.StatusText(http.StatusNotFound),
		"No resource could be found at the requested URI.",
	)
}

// MethodNotAllowed is returned when the method specified in a request is
// not allowed by the resource identified by the request-URI.
func MethodNotAllowed(forbidden string, allowed []string) *Error {
	methods := strings.Join(allowed, ", ")
	err := NewError(
		http.StatusMethodNotAllowed,
		fmt.Sprintf("%s method is not allowed for this resource", forbidden),
		fmt.Sprintf("This ressource only allows the following methods: %s.", methods),
	)
	err.Header.Set("Allow", methods)
	return err
}

// NotAcceptable is returned when the resource identified by the request
// is only capable of generating response entities which have content
// characteristics not acceptable according to the accept headers sent in the
// request.
func NotAcceptable() *Error {
	err := NewError(
		http.StatusNotAcceptable,
		http.StatusText(http.StatusNotAcceptable),
		"Resource is only capable of generating content not acceptable according to the accept headers sent in the request.",
	)
	return err
}

// Conflict is returned when a request can't be processed due to a conflict with
// the current state of the resource.
func Conflict() *Error {
	err := NewError(
		http.StatusConflict,
		"Resource could not be modified",
		"The request could not be processed due to a conflict with the current state of the resource.",
	)
	return err
}

// PreconditionFailed is returned when one of the conditions the request was
// made under has failed.
func PreconditionFailed() *Error {
	err := NewError(
		http.StatusPreconditionFailed,
		"Resource could not be modified",
		"A condition set in the headers of the request could not be matched.",
	)
	return err
}

// UnsupportedMediaType is returned when the entity in the request is in a format
// not support by the server. The supported media MIME type strings can be passed
// to improve the description of the error description.
func UnsupportedMediaType(mimes ...string) *Error {
	description := "The entity in the request is in a format not supported by this resource."
	if len(mimes) > 0 {
		description += fmt.Sprintf(" Supported types: %s", strings.Join(mimes, ", "))
	}
	err := NewError(
		http.StatusUnsupportedMediaType,
		"Entity inside request could not be processed",
		description,
	)
	return err
}

// RequestedRangeNotSatisfiable is returned when the range in the Range header
// does not overlap the current extent of the requested resource.
func RequestedRangeNotSatisfiable(cr *ContentRange) *Error {
	err := NewError(
		http.StatusRequestedRangeNotSatisfiable,
		http.StatusText(http.StatusRequestedRangeNotSatisfiable),
		"The requested range is not available and cannot be served.",
	)
	err.Header.Set("Content-Range", cr.String())
	err.Header.Add("Vary", "Range")
	return err
}

type stackRecord struct {
	Filename string `json:"file" xml:"File"`
	Line     int    `json:"line" xml:"Line"`
	Funcname string `json:"func" xml:"Func"`
}

func (r *stackRecord) String() string {
	return fmt.Sprintf("Line %d: %s - %s", r.Line, r.Filename, r.Funcname)
}

// InternalServerError represents an error with status code 500.
//
// When captureStack is true, the stack trace will be captured and displayed in
// the HTML projection of the returned error if mux.Debug is true.
func InternalServerError(reason, description string, captureStack bool) *Error {
	err := NewError(http.StatusInternalServerError, reason, description)
	if captureStack {
		var stack []*stackRecord
		for skip := 2; ; skip++ {
			pc, file, line, ok := runtime.Caller(skip)
			if !ok {
				break
			}
			if !strings.HasSuffix(file, ".go") || strings.HasSuffix(file, "runtime/panic.go") {
				continue
			}
			stack = append(stack, &stackRecord{
				Filename: file,
				Line:     line,
				Funcname: runtime.FuncForPC(pc).Name(),
			})
		}
		err.Stack = stack
	}
	return err
}

// Error represents an HTTP error, with a status code, a reason and a
// description.
// Error implements both the error and http.Handler interfaces.
//
// Header can be used to specify headers that will be written in the HTTP
// response generated from this error.
type Error struct {
	Code        int            `json:"-" xml:"-"`
	Header      http.Header    `json:"-" xml:"-"`
	Reason      string         `json:"message" xml:"Message"`
	Description string         `json:"description,omitempty" xml:"Description,omitempty"`
	Stack       []*stackRecord `json:"stack,omitempty" xml:"Stack,omitempty"`
}

func (e *Error) Error() string {
	return fmt.Sprintf("%d (%s) - %s\n%s", e.Code, http.StatusText(e.Code), e.Reason, e.Description)
}

func (e *Error) String() string {
	s := fmt.Sprintf("%d (%s) - %s", e.Code, http.StatusText(e.Code), e.Reason)

	if e.Description != "" {
		s += fmt.Sprintf("\n%s", e.Description)
	}

	if e.Stack != nil && len(e.Stack) > 0 {
		s += "\n"
		for _, r := range e.Stack {
			s += fmt.Sprintf("\n- %s", r)
		}
	}
	return s
}

// StatusText returns a text for the HTTP status code of this error. It returns
// the empty string if the code is unknown.
func (e *Error) StatusText() string {
	return http.StatusText(e.Code)
}

// MarshalRST is implemented to generate an HTML rendering of the error.
func (e *Error) MarshalRST(r *http.Request) (string, []byte, error) {
	accept := ParseAccept(r.Header.Get("Accept"))
	ct := accept.Negotiate("text/html", "*/*")
	if strings.Contains(ct, "html") || ct == "*/*" {
		buffer := &bytes.Buffer{}
		var data = struct {
			Request *http.Request
			*Error
		}{Request: r, Error: e}
		if err := errorTemplate.Execute(buffer, &data); err != nil {
			return "", nil, err
		}
		return "text/html; charset=utf-8", buffer.Bytes(), nil
	}
	return MarshalResource(e, r)
}

// ServeHTTP implements the http.Handler interface.
func (e *Error) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ct, b, err := Marshal(e, r)
	if err != nil {
		ct = "text/plain; charset=utf-8"
		b = []byte(e.String())
	}

	for key, values := range e.Header {
		for _, value := range values {
			w.Header().Add(key, value)
		}
	}

	// Remove headers which might have been set by a previous assumption of
	// success.
	w.Header().Del("Last-Modified")
	w.Header().Del("ETag")
	w.Header().Del("Expires")

	w.Header().Set("Content-Type", ct)
	w.Header().Add("Vary", "Accept")
	if e.Code != http.StatusNotFound && e.Code != http.StatusGone {
		w.Header().Add("Cache-Control", "no-cache, no-store, must-revalidate")
	}
	w.WriteHeader(e.Code)
	w.Write(b)
}

// NewError returns a new error with the given code, reason and description.
// It will panic if code < 400.
func NewError(code int, reason, description string) *Error {
	if code < 400 {
		panic(fmt.Errorf("%d is not a valid HTTP status code for an error", code))
	}
	return &Error{
		Code:        code,
		Reason:      reason,
		Description: description,
		Header:      make(http.Header),
	}
}

var errorTemplate *template.Template

func init() {
	// errorTemplate is based on data embedded in interal/assets/assets.go
	// using go generate and https://github.com/mjibson/esc.
	f, err := assets.FS(false).Open("/internal/assets/error.html")
	if err != nil {
		log.Fatal(err)
	}
	b, err := ioutil.ReadAll(f)
	if err != nil {
		log.Fatal(err)
	}
	errorTemplate, err = template.New("internal/assets/error.html").Parse(string(b))
	if err != nil {
		log.Fatal(err)
	}
}
<commit_msg>Fixed a bug that would have allowed "Cache-Control" header to be duplicated.<commit_after>package rst

import (
	"bytes"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"runtime"
	"strings"

	"github.com/mohamedattahri/rst/internal/assets"
)

// ErrorHandler is a wrapper that allows any Go error to implement the
// http.Handler interface.
func ErrorHandler(err error) http.Handler {
	if e, ok := err.(*Error); ok {
		return e
	}
	// panic will be intercepted in the main mux handler, and will write a
	// response which may display debugging info or hide them depending on the
	// Debug variable set in the mux.
	panic(err)
}

// BadRequest is returned when the request could not be understood by the
// server due to malformed syntax.
func BadRequest(reason, description string) *Error {
	if reason == "" {
		reason = http.StatusText(http.StatusBadRequest)
	}
	if description == "" {
		description = "Request could not be understood due to malformed syntax."
	}

	return NewError(http.StatusBadRequest, reason, description)
}

// Unauthorized is returned when authentication is required for the server
// to process the request.
func Unauthorized() *Error {
	err := NewError(
		http.StatusUnauthorized,
		"Authentication is required",
		"Authentication is required and has failed or has not yet been provided.",
	)
	return err
}

// Forbidden is returned when a resource is protected and inaccessible.
func Forbidden() *Error {
	err := NewError(
		http.StatusForbidden,
		"Request will not be fullfilled",
		"The request was a valid request, but the server is refusing to respond to it. Authenticating will make no difference.",
	)
	return err
}

// NotFound is returned when the server has not found a resource matching the
// Request-URI.
func NotFound() *Error {
	return NewError(
		http.StatusNotFound,
		http.StatusText(http.StatusNotFound),
		"No resource could be found at the requested URI.",
	)
}

// MethodNotAllowed is returned when the method specified in a request is
// not allowed by the resource identified by the request-URI.
func MethodNotAllowed(forbidden string, allowed []string) *Error {
	methods := strings.Join(allowed, ", ")
	err := NewError(
		http.StatusMethodNotAllowed,
		fmt.Sprintf("%s method is not allowed for this resource", forbidden),
		fmt.Sprintf("This ressource only allows the following methods: %s.", methods),
	)
	err.Header.Set("Allow", methods)
	return err
}

// NotAcceptable is returned when the resource identified by the request
// is only capable of generating response entities which have content
// characteristics not acceptable according to the accept headers sent in the
// request.
func NotAcceptable() *Error {
	err := NewError(
		http.StatusNotAcceptable,
		http.StatusText(http.StatusNotAcceptable),
		"Resource is only capable of generating content not acceptable according to the accept headers sent in the request.",
	)
	return err
}

// Conflict is returned when a request can't be processed due to a conflict with
// the current state of the resource.
func Conflict() *Error {
	err := NewError(
		http.StatusConflict,
		"Resource could not be modified",
		"The request could not be processed due to a conflict with the current state of the resource.",
	)
	return err
}

// PreconditionFailed is returned when one of the conditions the request was
// made under has failed.
func PreconditionFailed() *Error {
	err := NewError(
		http.StatusPreconditionFailed,
		"Resource could not be modified",
		"A condition set in the headers of the request could not be matched.",
	)
	return err
}

// UnsupportedMediaType is returned when the entity in the request is in a format
// not support by the server. The supported media MIME type strings can be passed
// to improve the description of the error description.
func UnsupportedMediaType(mimes ...string) *Error {
	description := "The entity in the request is in a format not supported by this resource."
	if len(mimes) > 0 {
		description += fmt.Sprintf(" Supported types: %s", strings.Join(mimes, ", "))
	}
	err := NewError(
		http.StatusUnsupportedMediaType,
		"Entity inside request could not be processed",
		description,
	)
	return err
}

// RequestedRangeNotSatisfiable is returned when the range in the Range header
// does not overlap the current extent of the requested resource.
func RequestedRangeNotSatisfiable(cr *ContentRange) *Error {
	err := NewError(
		http.StatusRequestedRangeNotSatisfiable,
		http.StatusText(http.StatusRequestedRangeNotSatisfiable),
		"The requested range is not available and cannot be served.",
	)
	err.Header.Set("Content-Range", cr.String())
	err.Header.Add("Vary", "Range")
	return err
}

type stackRecord struct {
	Filename string `json:"file" xml:"File"`
	Line     int    `json:"line" xml:"Line"`
	Funcname string `json:"func" xml:"Func"`
}

func (r *stackRecord) String() string {
	return fmt.Sprintf("Line %d: %s - %s", r.Line, r.Filename, r.Funcname)
}

// InternalServerError represents an error with status code 500.
//
// When captureStack is true, the stack trace will be captured and displayed in
// the HTML projection of the returned error if mux.Debug is true.
func InternalServerError(reason, description string, captureStack bool) *Error {
	err := NewError(http.StatusInternalServerError, reason, description)
	if captureStack {
		var stack []*stackRecord
		for skip := 2; ; skip++ {
			pc, file, line, ok := runtime.Caller(skip)
			if !ok {
				break
			}
			if !strings.HasSuffix(file, ".go") || strings.HasSuffix(file, "runtime/panic.go") {
				continue
			}
			stack = append(stack, &stackRecord{
				Filename: file,
				Line:     line,
				Funcname: runtime.FuncForPC(pc).Name(),
			})
		}
		err.Stack = stack
	}
	return err
}

// Error represents an HTTP error, with a status code, a reason and a
// description.
// Error implements both the error and http.Handler interfaces.
//
// Header can be used to specify headers that will be written in the HTTP
// response generated from this error.
type Error struct {
	Code        int            `json:"-" xml:"-"`
	Header      http.Header    `json:"-" xml:"-"`
	Reason      string         `json:"message" xml:"Message"`
	Description string         `json:"description,omitempty" xml:"Description,omitempty"`
	Stack       []*stackRecord `json:"stack,omitempty" xml:"Stack,omitempty"`
}

func (e *Error) Error() string {
	return fmt.Sprintf("%d (%s) - %s\n%s", e.Code, http.StatusText(e.Code), e.Reason, e.Description)
}

func (e *Error) String() string {
	s := fmt.Sprintf("%d (%s) - %s", e.Code, http.StatusText(e.Code), e.Reason)

	if e.Description != "" {
		s += fmt.Sprintf("\n%s", e.Description)
	}

	if e.Stack != nil && len(e.Stack) > 0 {
		s += "\n"
		for _, r := range e.Stack {
			s += fmt.Sprintf("\n- %s", r)
		}
	}
	return s
}

// StatusText returns a text for the HTTP status code of this error. It returns
// the empty string if the code is unknown.
func (e *Error) StatusText() string {
	return http.StatusText(e.Code)
}

// MarshalRST is implemented to generate an HTML rendering of the error.
func (e *Error) MarshalRST(r *http.Request) (string, []byte, error) {
	accept := ParseAccept(r.Header.Get("Accept"))
	ct := accept.Negotiate("text/html", "*/*")
	if strings.Contains(ct, "html") || ct == "*/*" {
		buffer := &bytes.Buffer{}
		var data = struct {
			Request *http.Request
			*Error
		}{Request: r, Error: e}
		if err := errorTemplate.Execute(buffer, &data); err != nil {
			return "", nil, err
		}
		return "text/html; charset=utf-8", buffer.Bytes(), nil
	}
	return MarshalResource(e, r)
}

// ServeHTTP implements the http.Handler interface.
func (e *Error) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ct, b, err := Marshal(e, r)
	if err != nil {
		ct = "text/plain; charset=utf-8"
		b = []byte(e.String())
	}

	for key, values := range e.Header {
		for _, value := range values {
			w.Header().Add(key, value)
		}
	}

	// Remove headers which might have been set by a previous assumption of
	// success.
	w.Header().Del("Last-Modified")
	w.Header().Del("ETag")
	w.Header().Del("Expires")

	w.Header().Set("Content-Type", ct)
	w.Header().Add("Vary", "Accept")
	if e.Code != http.StatusNotFound && e.Code != http.StatusGone {
		w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
	}
	w.WriteHeader(e.Code)
	w.Write(b)
}

// NewError returns a new error with the given code, reason and description.
// It will panic if code < 400.
func NewError(code int, reason, description string) *Error {
	if code < 400 {
		panic(fmt.Errorf("%d is not a valid HTTP status code for an error", code))
	}
	return &Error{
		Code:        code,
		Reason:      reason,
		Description: description,
		Header:      make(http.Header),
	}
}

var errorTemplate *template.Template

func init() {
	// errorTemplate is based on data embedded in interal/assets/assets.go
	// using go generate and https://github.com/mjibson/esc.
	f, err := assets.FS(false).Open("/internal/assets/error.html")
	if err != nil {
		log.Fatal(err)
	}
	b, err := ioutil.ReadAll(f)
	if err != nil {
		log.Fatal(err)
	}
	errorTemplate, err = template.New("internal/assets/error.html").Parse(string(b))
	if err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"runtime"
	"strconv"
)

var (
	ErrUnknownToken = errors.New("Unknown token returned by lex")
)

type ParseError struct {
	Err      error
	Fallback string
}

func (e ParseError) Error() string {
	if e.Err != nil {
		return e.Err.Error()
	}
	return e.Fallback
}

// ArithNode is an implementation of the symbols described
// in Top Down Operator Precedence; Vaughn Pratt; 1973
type ArithNode interface {
	nud() int64
	led(int64) int64
	lbp() int
}

func Parse(s string) (i int64, err error) {
	defer func() {
		if r := recover(); r != nil {
			if _, ok := r.(runtime.Error); ok {
				panic(r)
			}
			switch r.(type) {
			case string:
				err = ParseError{Fallback: r.(string)}
			case error:
				err = ParseError{Err: r.(error)}
			}
			return
		}
	}()
	ap := &ArithParser{lexer: NewArithLexer(s)}
	ap.next()
	parser = ap
	return parser.expression(0), nil
}

var parser *ArithParser

type ArithParser struct {
	lastNode  ArithNode
	lastToken ArithToken
	lexer     *ArithLexer
}

func (ap *ArithParser) expression(rbp int) int64 {
	n := ap.lastNode
	ap.next()
	left := n.nud()
	for rbp < ap.lastNode.lbp() {
		n = ap.lastNode
		ap.next()
		left = n.led(left)
	}
	return left
}

func (ap *ArithParser) consume(t ArithToken) {
	if t != ap.lastToken {
		panic("Expected '" + t.String() + "'")
	}
	ap.next()
}

func (ap *ArithParser) next() {
	tok, val := ap.lexer.Lex()
	switch {
	case TokenIsBinaryOp(tok):
		ap.lastNode = InfixNode{T: tok}
	case TokenIsAssignmentOp(tok) || TokenIs(tok, ArithAssignment):
		ap.lastNode = InfixAssignNode{T: tok, V: ap.lastNode}
	case TokenIs(tok, ArithAdd, ArithOr):
		ap.lastNode = InfixRightNode{T: tok}
	case TokenIs(tok, ArithNumber):
		ap.lastNode = LiteralNode{Val: val.(int64)}
	case TokenIs(tok, ArithVariable):
		ap.lastNode = VariableNode{Val: val.(string)}
	case TokenIs(tok, ArithBinaryNot, ArithNot, ArithLeftParen):
		ap.lastNode = PrefixNode{T: tok}
	case TokenIs(tok, ArithEOF):
		ap.lastNode = EOFNode{}
	case TokenIs(tok, ArithQuestionMark):
		ap.lastNode = TernaryNode{}
	case TokenIs(tok, ArithRightParen, ArithColon):
		ap.lastNode = NoopNode{T: tok}
	default:
		panic(ErrUnknownToken)
	}
	ap.lastToken = tok
}

func (ap *ArithParser) getVariable(name string) int64 {
	v := GlobalScope.Get(name)
	// We dont care if the variable if unset or empty they both
	// count as a zero
	if v.Val == "" {
		return 0
	}
	// ParseInt figures out the format of the variable if is in hex / octal
	// format so we can just perform one conversion.
	i, err := strconv.ParseInt(v.Val, 0, 64)
	if err != nil {
		panic("Variable '" + name + "' cannot be used as a number: " + err.Error())
	}
	return i
}

func (ap *ArithParser) setVariable(name string, val int64) {
	GlobalScope.Set(name, strconv.FormatInt(val, 10))
}

// IsArithBinaryOp checks if a token operates on two values.
// E.g a + b, a << b
func TokenIsBinaryOp(a ArithToken) bool {
	return a <= ArithAdd && a >= ArithLessEqual
}

// IsArithAssignmentOp checks if a token assigns to the lefthand variable.
// E.g a += b, a <<= b
func TokenIsAssignmentOp(a ArithToken) bool {
	return a <= ArithAssignAdd && a >= ArithAssignBinaryAnd
}

// TokenIs checks if the first supplied token is equal to any of the other
// supplied tokens.
func TokenIs(toks ...ArithToken) bool {
	if len(toks) < 2 {
		return false
	}
	have := toks[0]
	toks = toks[1:]
	for _, t := range toks {
		if have == t {
			return true
		}
	}
	return false
}

type EOFNode struct{}

func (n EOFNode) nud() int64      { panic("Nud called on EOFNode") }
func (n EOFNode) led(int64) int64 { panic("Led called on EOFNode") }
func (n EOFNode) lbp() int        { return -1 }

type NoopNode struct {
	T ArithToken
}

func (n NoopNode) nud() int64      { panic("Nud called on NoopNode: " + n.T.String()) }
func (n NoopNode) led(int64) int64 { panic("Led called on NoopNode: " + n.T.String()) }
func (n NoopNode) lbp() int        { return 0 }

type LiteralNode struct {
	Val int64
}

func (n LiteralNode) nud() int64      { return n.Val }
func (n LiteralNode) led(int64) int64 { panic("Led called on LiteralNode") }
func (n LiteralNode) lbp() int        { return 0 }

type VariableNode struct {
	Val string
}

func (n VariableNode) nud() int64      { return parser.getVariable(n.Val) }
func (n VariableNode) led(int64) int64 { panic("Led called on VariableNode") }
func (n VariableNode) lbp() int        { return 0 }

var (
	InfixNudFunctions = map[ArithToken]func() int64{
		ArithAdd:      func() int64 { return parser.expression(150) },
		ArithSubtract: func() int64 { return -parser.expression(150) },
	}
	PrefixNudFunctions = map[ArithToken]func() int64{
		ArithBinaryNot: func() int64 { return -parser.expression(LbpValues[ArithBinaryNot]) - 1 },
		ArithNot:       func() int64 { return BoolToShell(parser.expression(LbpValues[ArithNot]) != ShellTrue) },
		ArithLeftParen: func() int64 {
			e := parser.expression(0)
			parser.consume(ArithRightParen)
			return e
		},
	}
	InfixLedFunctions = map[ArithToken]func(int64, int64) int64{
		ArithLessEqual:    func(l, r int64) int64 { return BoolToShell(l <= r) },
		ArithGreaterEqual: func(l, r int64) int64 { return BoolToShell(l >= r) },
		ArithLessThan:     func(l, r int64) int64 { return BoolToShell(l < r) },
		ArithGreaterThan:  func(l, r int64) int64 { return BoolToShell(l > r) },
		ArithEqual:        func(l, r int64) int64 { return BoolToShell(l == r) },
		ArithNotEqual:     func(l, r int64) int64 { return BoolToShell(l != r) },
		ArithBinaryAnd:    func(l, r int64) int64 { return l & r },
		ArithBinaryOr:     func(l, r int64) int64 { return l | r },
		ArithBinaryXor:    func(l, r int64) int64 { return l ^ r },
		ArithLeftShift:    func(l, r int64) int64 { return LeftShift(l, r) },
		ArithRightShift:   func(l, r int64) int64 { return RightShift(l, r) },
		ArithRemainder:    func(l, r int64) int64 { return l % r },
		ArithMultiply:     func(l, r int64) int64 { return l * r },
		ArithDivide:       func(l, r int64) int64 { return l / r },
		ArithSubtract:     func(l, r int64) int64 { return l - r },
		ArithAdd:          func(l, r int64) int64 { return l + r },
		ArithAssignment:   func(l, r int64) int64 { return r },
	}
	InfixRightLedFunctions = map[ArithToken]func(int64, int64) int64{
		ArithAnd: func(l, r int64) int64 { return BoolToShell((l == ShellTrue) && (r == ShellTrue)) },
		ArithOr:  func(l, r int64) int64 { return BoolToShell((l == ShellTrue) || (r == ShellTrue)) },
	}
	LbpValues = map[ArithToken]int{
		ArithRightParen:   20,
		ArithOr:           30,
		ArithAnd:          40,
		ArithNot:          50,
		ArithLessEqual:    60,
		ArithGreaterEqual: 60,
		ArithLessThan:     60,
		ArithGreaterThan:  60,
		ArithEqual:        60,
		ArithNotEqual:     60,
		ArithAssignment:   60,
		ArithBinaryOr:     70,
		ArithBinaryXor:    80,
		ArithBinaryAnd:    90,
		ArithLeftShift:    100,
		ArithRightShift:   100,
		ArithSubtract:     110,
		ArithAdd:          110,
		ArithMultiply:     120,
		ArithDivide:       120,
		ArithRemainder:    120,
		ArithBinaryNot:    130,
		ArithLeftParen:    140,
	}
)

type InfixAssignNode struct {
	T ArithToken
	V ArithNode
}

func (n InfixAssignNode) nud() int64 { panic("Nud called on InfixAssignNode: " + n.T.String()) }
func (n InfixAssignNode) led(left int64) int64 {
	v, ok := n.V.(VariableNode)
	var f func(int64, int64) int64
	if !ok {
		panic("LHS of assignment '" + n.T.String() + "' is not a variable")
	}

	if n.T == ArithAssignment {
		f = InfixLedFunctions[ArithAssignment]
	} else {
		f, ok = InfixLedFunctions[n.T-ArithAssignDiff]
		if !ok {
			panic("No Led function for InfixAssignNode: " + n.T.String())
		}
	}

	right := parser.expression(0)
	t := f(left, right)
	parser.setVariable(v.Val, t)
	return t
}
func (n InfixAssignNode) lbp() int {
	if n.T == ArithAssignment {
		return LbpValues[n.T]
	}
	return LbpValues[n.T-ArithAssignDiff]
}

type InfixNode struct {
	T ArithToken
}

func (n InfixNode) nud() int64 {
	f, ok := InfixNudFunctions[n.T]
	if !ok {
		panic("No Nud function for InfixNode: " + n.T.String())
	}
	return f()
}
func (n InfixNode) led(left int64) int64 {
	right := parser.expression(n.lbp())
	f, ok := InfixLedFunctions[n.T]
	if !ok {
		panic("No Led function for InfixNode: " + n.T.String())
	}
	return f(left, right)
}
func (n InfixNode) lbp() int { return LbpValues[n.T] }

type InfixRightNode struct {
	T ArithToken
}

func (n InfixRightNode) nud() int64 { panic("Nud called on InfixRightNode: " + n.T.String()) }
func (n InfixRightNode) led(left int64) int64 {
	right := parser.expression(n.lbp() - 1)
	f, ok := InfixRightLedFunctions[n.T]
	if !ok {
		panic("No Led function for InfixRightNode: " + n.T.String())
	}
	return f(left, right)
}
func (n InfixRightNode) lbp() int { return LbpValues[n.T] }

type PrefixNode struct {
	T ArithToken
}

func (n PrefixNode) nud() int64 {
	f, ok := PrefixNudFunctions[n.T]
	if !ok {
		panic("No Nud function for PrefixNode: " + string(n.T))
	}
	return f()
}

func (n PrefixNode) led(int64) int64 { panic("Led called on PrefixNode: " + n.T.String()) }
func (n PrefixNode) lbp() int        { return LbpValues[n.T] }

type TernaryNode struct {
	condition         int64
	valTrue, valFalse int64
}

func (n TernaryNode) nud() int64 { panic("Nud called on TernaryNode") }
func (n TernaryNode) led(left int64) int64 {
	// Somewhat confusingly the shell's ternary operator does not work using
	// the shell's True/False semantics.
	// The actual operation is Given (a ? b : c)
	// if (a != 0)
	//	return b
	// else
	//	return c
	// See the ISO C Standard Section 6.5.15
	//
	// This function evaluates both sides of the ternary no matter
	// what the condition is.
	// This introduces bugs when assignment operators are used alongside
	// the ternary.
	// E.g
	// (0 ? x += 2 : x += 2)
	// will make x = 4
	// and
	// (y ? x = 3 : x = 4)
	// will make x = 4 regardless of the value of y
	// Fixing this is a TODO

	n.condition = left
	n.valTrue = parser.expression(0)
	parser.consume(ArithColon)
	n.valFalse = parser.expression(0)

	if n.condition != 0 {
		return n.valTrue
	}
	return n.valFalse
}
func (n TernaryNode) lbp() int {
	return 20
}
<commit_msg>Rename Parse so we can keep a flat file structre for now<commit_after>package main

import (
	"errors"
	"runtime"
	"strconv"
)

var (
	ErrUnknownToken = errors.New("Unknown token returned by lex")
)

type ParseError struct {
	Err      error
	Fallback string
}

func (e ParseError) Error() string {
	if e.Err != nil {
		return e.Err.Error()
	}
	return e.Fallback
}

// ArithNode is an implementation of the symbols described
// in Top Down Operator Precedence; Vaughn Pratt; 1973
type ArithNode interface {
	nud() int64
	led(int64) int64
	lbp() int
}

func ParseArith(s string) (i int64, err error) {
	defer func() {
		if r := recover(); r != nil {
			if _, ok := r.(runtime.Error); ok {
				panic(r)
			}
			switch r.(type) {
			case string:
				err = ParseError{Fallback: r.(string)}
			case error:
				err = ParseError{Err: r.(error)}
			}
		}
	}()
	ap := &ArithParser{lexer: NewArithLexer(s)}
	ap.next()
	parser = ap
	return parser.expression(0), nil
}

var parser *ArithParser

type ArithParser struct {
	lastNode  ArithNode
	lastToken ArithToken
	lexer     *ArithLexer
	scope     *variables.Scope
}

func (ap *ArithParser) expression(rbp int) int64 {
	n := ap.lastNode
	ap.next()
	left := n.nud()
	for rbp < ap.lastNode.lbp() {
		n = ap.lastNode
		ap.next()
		left = n.led(left)
	}
	return left
}

func (ap *ArithParser) consume(t ArithToken) {
	if t != ap.lastToken {
		panic("Expected '" + t.String() + "'")
	}
	ap.next()
}

func (ap *ArithParser) next() {
	tok, val := ap.lexer.Lex()
	switch {
	case TokenIsBinaryOp(tok):
		ap.lastNode = InfixNode{T: tok}
	case TokenIsAssignmentOp(tok) || TokenIs(tok, ArithAssignment):
		ap.lastNode = InfixAssignNode{T: tok, V: ap.lastNode}
	case TokenIs(tok, ArithAdd, ArithOr):
		ap.lastNode = InfixRightNode{T: tok}
	case TokenIs(tok, ArithNumber):
		ap.lastNode = LiteralNode{Val: val.(int64)}
	case TokenIs(tok, ArithVariable):
		ap.lastNode = VariableNode{Val: val.(string)}
	case TokenIs(tok, ArithBinaryNot, ArithNot, ArithLeftParen):
		ap.lastNode = PrefixNode{T: tok}
	case TokenIs(tok, ArithEOF):
		ap.lastNode = EOFNode{}
	case TokenIs(tok, ArithQuestionMark):
		ap.lastNode = TernaryNode{}
	case TokenIs(tok, ArithRightParen, ArithColon):
		ap.lastNode = NoopNode{T: tok}
	default:
		panic(ErrUnknownToken)
	}
	ap.lastToken = tok
}

func (ap *ArithParser) getVariable(name string) int64 {
	v := GlobalScope.Get(name)
	// We dont care if the variable if unset or empty they both
	// count as a zero
	if v.Val == "" {
		return 0
	}
	// ParseInt figures out the format of the variable if is in hex / octal
	// format so we can just perform one conversion.
	i, err := strconv.ParseInt(v.Val, 0, 64)
	if err != nil {
		panic("Variable '" + name + "' cannot be used as a number: " + err.Error())
	}
	return i
}

func (ap *ArithParser) setVariable(name string, val int64) {
	GlobalScope.Set(name, strconv.FormatInt(val, 10))
}

// IsArithBinaryOp checks if a token operates on two values.
// E.g a + b, a << b
func TokenIsBinaryOp(a ArithToken) bool {
	return a <= ArithAdd && a >= ArithLessEqual
}

// IsArithAssignmentOp checks if a token assigns to the lefthand variable.
// E.g a += b, a <<= b
func TokenIsAssignmentOp(a ArithToken) bool {
	return a <= ArithAssignAdd && a >= ArithAssignBinaryAnd
}

// TokenIs checks if the first supplied token is equal to any of the other
// supplied tokens.
func TokenIs(toks ...ArithToken) bool {
	if len(toks) < 2 {
		return false
	}
	have := toks[0]
	toks = toks[1:]
	for _, t := range toks {
		if have == t {
			return true
		}
	}
	return false
}

type EOFNode struct{}

func (n EOFNode) nud() int64      { panic("Nud called on EOFNode") }
func (n EOFNode) led(int64) int64 { panic("Led called on EOFNode") }
func (n EOFNode) lbp() int        { return -1 }

type NoopNode struct {
	T ArithToken
}

func (n NoopNode) nud() int64      { panic("Nud called on NoopNode: " + n.T.String()) }
func (n NoopNode) led(int64) int64 { panic("Led called on NoopNode: " + n.T.String()) }
func (n NoopNode) lbp() int        { return 0 }

type LiteralNode struct {
	Val int64
}

func (n LiteralNode) nud() int64      { return n.Val }
func (n LiteralNode) led(int64) int64 { panic("Led called on LiteralNode") }
func (n LiteralNode) lbp() int        { return 0 }

type VariableNode struct {
	Val string
}

func (n VariableNode) nud() int64      { return parser.getVariable(n.Val) }
func (n VariableNode) led(int64) int64 { panic("Led called on VariableNode") }
func (n VariableNode) lbp() int        { return 0 }

var (
	InfixNudFunctions = map[ArithToken]func() int64{
		ArithAdd:      func() int64 { return parser.expression(150) },
		ArithSubtract: func() int64 { return -parser.expression(150) },
	}
	PrefixNudFunctions = map[ArithToken]func() int64{
		ArithBinaryNot: func() int64 { return -parser.expression(LbpValues[ArithBinaryNot]) - 1 },
		ArithNot:       func() int64 { return BoolToShell(parser.expression(LbpValues[ArithNot]) != ShellTrue) },
		ArithLeftParen: func() int64 {
			e := parser.expression(0)
			parser.consume(ArithRightParen)
			return e
		},
	}
	InfixLedFunctions = map[ArithToken]func(int64, int64) int64{
		ArithLessEqual:    func(l, r int64) int64 { return BoolToShell(l <= r) },
		ArithGreaterEqual: func(l, r int64) int64 { return BoolToShell(l >= r) },
		ArithLessThan:     func(l, r int64) int64 { return BoolToShell(l < r) },
		ArithGreaterThan:  func(l, r int64) int64 { return BoolToShell(l > r) },
		ArithEqual:        func(l, r int64) int64 { return BoolToShell(l == r) },
		ArithNotEqual:     func(l, r int64) int64 { return BoolToShell(l != r) },
		ArithBinaryAnd:    func(l, r int64) int64 { return l & r },
		ArithBinaryOr:     func(l, r int64) int64 { return l | r },
		ArithBinaryXor:    func(l, r int64) int64 { return l ^ r },
		ArithLeftShift:    func(l, r int64) int64 { return LeftShift(l, r) },
		ArithRightShift:   func(l, r int64) int64 { return RightShift(l, r) },
		ArithRemainder:    func(l, r int64) int64 { return l % r },
		ArithMultiply:     func(l, r int64) int64 { return l * r },
		ArithDivide:       func(l, r int64) int64 { return l / r },
		ArithSubtract:     func(l, r int64) int64 { return l - r },
		ArithAdd:          func(l, r int64) int64 { return l + r },
		ArithAssignment:   func(l, r int64) int64 { return r },
	}
	InfixRightLedFunctions = map[ArithToken]func(int64, int64) int64{
		ArithAnd: func(l, r int64) int64 { return BoolToShell((l == ShellTrue) && (r == ShellTrue)) },
		ArithOr:  func(l, r int64) int64 { return BoolToShell((l == ShellTrue) || (r == ShellTrue)) },
	}
	LbpValues = map[ArithToken]int{
		ArithRightParen:   20,
		ArithOr:           30,
		ArithAnd:          40,
		ArithNot:          50,
		ArithLessEqual:    60,
		ArithGreaterEqual: 60,
		ArithLessThan:     60,
		ArithGreaterThan:  60,
		ArithEqual:        60,
		ArithNotEqual:     60,
		ArithAssignment:   60,
		ArithBinaryOr:     70,
		ArithBinaryXor:    80,
		ArithBinaryAnd:    90,
		ArithLeftShift:    100,
		ArithRightShift:   100,
		ArithSubtract:     110,
		ArithAdd:          110,
		ArithMultiply:     120,
		ArithDivide:       120,
		ArithRemainder:    120,
		ArithBinaryNot:    130,
		ArithLeftParen:    140,
	}
)

type InfixAssignNode struct {
	T ArithToken
	V ArithNode
}

func (n InfixAssignNode) nud() int64 { panic("Nud called on InfixAssignNode: " + n.T.String()) }
func (n InfixAssignNode) led(left int64) int64 {
	v, ok := n.V.(VariableNode)
	var f func(int64, int64) int64
	if !ok {
		panic("LHS of assignment '" + n.T.String() + "' is not a variable")
	}

	if n.T == ArithAssignment {
		f = InfixLedFunctions[ArithAssignment]
	} else {
		f, ok = InfixLedFunctions[n.T-ArithAssignDiff]
		if !ok {
			panic("No Led function for InfixAssignNode: " + n.T.String())
		}
	}

	right := parser.expression(0)
	t := f(left, right)
	parser.setVariable(v.Val, t)
	return t
}
func (n InfixAssignNode) lbp() int {
	if n.T == ArithAssignment {
		return LbpValues[n.T]
	}
	return LbpValues[n.T-ArithAssignDiff]
}

type InfixNode struct {
	T ArithToken
}

func (n InfixNode) nud() int64 {
	f, ok := InfixNudFunctions[n.T]
	if !ok {
		panic("No Nud function for InfixNode: " + n.T.String())
	}
	return f()
}
func (n InfixNode) led(left int64) int64 {
	right := parser.expression(n.lbp())
	f, ok := InfixLedFunctions[n.T]
	if !ok {
		panic("No Led function for InfixNode: " + n.T.String())
	}
	return f(left, right)
}
func (n InfixNode) lbp() int { return LbpValues[n.T] }

type InfixRightNode struct {
	T ArithToken
}

func (n InfixRightNode) nud() int64 { panic("Nud called on InfixRightNode: " + n.T.String()) }
func (n InfixRightNode) led(left int64) int64 {
	right := parser.expression(n.lbp() - 1)
	f, ok := InfixRightLedFunctions[n.T]
	if !ok {
		panic("No Led function for InfixRightNode: " + n.T.String())
	}
	return f(left, right)
}
func (n InfixRightNode) lbp() int { return LbpValues[n.T] }

type PrefixNode struct {
	T ArithToken
}

func (n PrefixNode) nud() int64 {
	f, ok := PrefixNudFunctions[n.T]
	if !ok {
		panic("No Nud function for PrefixNode: " + string(n.T))
	}
	return f()
}

func (n PrefixNode) led(int64) int64 { panic("Led called on PrefixNode: " + n.T.String()) }
func (n PrefixNode) lbp() int        { return LbpValues[n.T] }

type TernaryNode struct {
	condition         int64
	valTrue, valFalse int64
}

func (n TernaryNode) nud() int64 { panic("Nud called on TernaryNode") }
func (n TernaryNode) led(left int64) int64 {
	// Somewhat confusingly the shell's ternary operator does not work using
	// the shell's True/False semantics.
	// The actual operation is Given (a ? b : c)
	// if (a != 0)
	//	return b
	// else
	//	return c
	// See the ISO C Standard Section 6.5.15
	//
	// This function evaluates both sides of the ternary no matter
	// what the condition is.
	// This introduces bugs when assignment operators are used alongside
	// the ternary.
	// E.g
	// (0 ? x += 2 : x += 2)
	// will make x = 4
	// and
	// (y ? x = 3 : x = 4)
	// will make x = 4 regardless of the value of y
	// Fixing this is a TODO

	n.condition = left
	n.valTrue = parser.expression(0)
	parser.consume(ArithColon)
	n.valFalse = parser.expression(0)

	if n.condition != 0 {
		return n.valTrue
	}
	return n.valFalse
}
func (n TernaryNode) lbp() int {
	return 20
}
<|endoftext|>