<commit_before>package fate

import "sort"

// tokset maintains a set of tokens as a sorted slice of integers.
//
// 1-byte tokens (<= 0xFF) are in buf[0:c1]
// 2-byte tokens (<= 0xFFFF) are in buf[c1:c1+2*c2]
// 3-byte tokens (<= 0xFFFFFF) are in buf[c1+2*c2:]
//
// They're stored little-endian. Adds are O(log N). Choosing a random
// token in the set is O(1).
//
// tokens greater than 0xFFFFFF are not currently supported. This is
// enough token space to handle the Web 1T corpus.
type tokset struct {
	buf []byte

	// count of 2-byte tokens, count of 1-byte tokens
	c2 uint16
	c1 uint8
}

func (t *tokset) Add(tok token) bool {
	switch {
	case tok <= 0xFF:
		return t.add1(tok)
	case tok <= 0xFFFF:
		return t.add2(tok)
	case tok <= 0xFFFFFF:
		return t.add3(tok)
	}

	panic("oops")
}

func (t *tokset) span1() []byte {
	return t.buf[0:t.c1]
}

func (t *tokset) span2() []byte {
	return t.buf[int(t.c1) : int(t.c1)+2*int(t.c2)]
}

func (t *tokset) span3() []byte {
	return t.buf[int(t.c1)+2*int(t.c2):]
}

func (t *tokset) add1(tok token) bool {
	if len(t.buf) == 0 {
		t.buf = append(t.buf, byte(tok))
		t.c1++
		return false
	}

	span := t.span1()
	loc := sort.Search(len(span), func(i int) bool {
		return token(span[i]) >= tok
	})

	if loc < len(span) && token(span[loc]) == tok {
		return true
	}

	t.buf = append(t.buf, 0)
	copy(t.buf[loc+1:], t.buf[loc:])
	t.buf[loc] = byte(tok)

	t.c1++

	return false
}

func (t *tokset) add2(tok token) bool {
	if len(t.buf) == 0 {
		t.buf = append(t.buf, byte(tok), byte(tok>>8))
		t.c2++
		return false
	}

	span := t.span2()
	idx := sort.Search(len(span)/2, func(i int) bool {
		return unpack2(span[2*i:]) >= tok
	})

	if idx < len(span)/2 && unpack2(span[2*idx:]) == tok {
		return true
	}

	t.buf = append(t.buf, 0, 0)

	loc := int(t.c1) + 2*idx
	copy(t.buf[loc+2:], t.buf[loc:])
	put2(t.buf[loc:], tok)

	t.c2++

	return false
}
func (t *tokset) add3(tok token) bool {
	if len(t.buf) == 0 {
		t.buf = append(t.buf, byte(tok), byte(tok>>8), byte(tok>>16))
		return false
	}

	span := t.span3()
	idx := sort.Search(len(span)/3, func(i int) bool {
		return unpack3(span[3*i:]) >= tok
	})

	if idx < len(span)/3 && unpack3(span[3*idx:]) == tok {
		return true
	}

	t.buf = append(t.buf, 0, 0, 0)

	loc := int(t.c1) + 2*int(t.c2) + 3*idx
	copy(t.buf[loc+3:], t.buf[loc:])
	put3(t.buf[loc:], tok)

	return false
}

func (t *tokset) Len() int {
	if t == nil {
		return 0
	}

	return int(t.c1) + int(t.c2) + len(t.span3())/3
}

func (t *tokset) Tokens() []token {
	if t == nil {
		return nil
	}

	var tokens = make([]token, 0, t.Len())
	for _, val := range t.span1() {
		tokens = append(tokens, token(val))
	}

	span2 := t.span2()
	for i := 0; i < len(span2); i += 2 {
		tokens = append(tokens, unpack2(span2[i:]))
	}

	span3 := t.span3()
	for i := 0; i < len(span3); i += 3 {
		tokens = append(tokens, unpack3(span3[i:]))
	}

	return tokens
}

func put2(buf []byte, tok token) {
	buf[0] = byte(tok)
	buf[1] = byte(tok >> 8)
}

func put3(buf []byte, tok token) {
	buf[0] = byte(tok)
	buf[1] = byte(tok >> 8)
	buf[2] = byte(tok >> 16)
}

func unpack2(buf []byte) token {
	return token(buf[0]) | token(buf[1])<<8
}

func unpack3(buf []byte) token {
	return token(buf[0]) | token(buf[1])<<8 | token(buf[2])<<16
}

func (t tokset) Choice(r Intn) token {
	index := r.Intn(t.Len())

	switch {
	case index < int(t.c1):
		return token(t.buf[index])
	case index < int(t.c1)+int(t.c2):
		span := t.span2()
		return unpack2(span[2*(index-int(t.c1)):])
	case index < t.Len():
		span := t.span3()
		return unpack3(span[3*(index-(int(t.c2)+int(t.c1))):])
	}

	panic("oops")
}

// tokset2 stores constant width tokens in a sorted slice.
type tokset2 struct {
	t []token
}

// Add inserts tok into this set, if not already present. It may
// return a new slice, so use its return value as the new set.
//
// Returns a bool signaling whether the token was already in the set
// (similar logic to map lookups).
func (t *tokset2) Add(tok token) bool {
	size := len(t.t)

	// Fast path for empty sets or brand new tokens.
	if size == 0 || tok > t.t[size-1] {
		t.t = append(t.t, tok)
		return false
	}

	loc := sort.Search(size, func(i int) bool { return t.t[i] >= tok })
	if t.t[loc] == tok {
		return true
	}

	t.t = append(t.t, 0)
	copy(t.t[loc+1:], t.t[loc:])
	t.t[loc] = tok

	return false
}

func (t *tokset2) Tokens() []token {
	if t == nil {
		return nil
	}

	return t.t
}
<commit_msg>Give tokset an Index method<commit_after>package fate

import "sort"

// tokset maintains a set of tokens as a sorted slice of integers.
//
// 1-byte tokens (<= 0xFF) are in buf[0:c1]
// 2-byte tokens (<= 0xFFFF) are in buf[c1:c1+2*c2]
// 3-byte tokens (<= 0xFFFFFF) are in buf[c1+2*c2:]
//
// They're stored little-endian. Adds are O(log N). Choosing a random
// token in the set is O(1).
//
// tokens greater than 0xFFFFFF are not currently supported. This is
// enough token space to handle the Web 1T corpus.
type tokset struct {
	buf []byte

	// count of 2-byte tokens, count of 1-byte tokens
	c2 uint16
	c1 uint8
}

func (t *tokset) Add(tok token) bool {
	switch {
	case tok <= 0xFF:
		return t.add1(tok)
	case tok <= 0xFFFF:
		return t.add2(tok)
	case tok <= 0xFFFFFF:
		return t.add3(tok)
	}

	panic("oops")
}

func (t *tokset) span1() []byte {
	return t.buf[0:t.c1]
}

func (t *tokset) span2() []byte {
	return t.buf[int(t.c1) : int(t.c1)+2*int(t.c2)]
}

func (t *tokset) span3() []byte {
	return t.buf[int(t.c1)+2*int(t.c2):]
}

func (t *tokset) add1(tok token) bool {
	if len(t.buf) == 0 {
		t.buf = append(t.buf, byte(tok))
		t.c1++
		return false
	}

	span := t.span1()
	loc := sort.Search(len(span), func(i int) bool {
		return token(span[i]) >= tok
	})

	if loc < len(span) && token(span[loc]) == tok {
		return true
	}

	t.buf = append(t.buf, 0)
	copy(t.buf[loc+1:], t.buf[loc:])
	t.buf[loc] = byte(tok)

	t.c1++

	return false
}

func (t *tokset) add2(tok token) bool {
	if len(t.buf) == 0 {
		t.buf = append(t.buf, byte(tok), byte(tok>>8))
		t.c2++
		return false
	}

	span := t.span2()
	idx := sort.Search(len(span)/2, func(i int) bool {
		return unpack2(span[2*i:]) >= tok
	})

	if idx < len(span)/2 && unpack2(span[2*idx:]) == tok {
		return true
	}

	t.buf = append(t.buf, 0, 0)

	loc := int(t.c1) + 2*idx
	copy(t.buf[loc+2:], t.buf[loc:])
	put2(t.buf[loc:], tok)

	t.c2++

	return false
}
func (t *tokset) add3(tok token) bool {
	if len(t.buf) == 0 {
		t.buf = append(t.buf, byte(tok), byte(tok>>8), byte(tok>>16))
		return false
	}

	span := t.span3()
	idx := sort.Search(len(span)/3, func(i int) bool {
		return unpack3(span[3*i:]) >= tok
	})

	if idx < len(span)/3 && unpack3(span[3*idx:]) == tok {
		return true
	}

	t.buf = append(t.buf, 0, 0, 0)

	loc := int(t.c1) + 2*int(t.c2) + 3*idx
	copy(t.buf[loc+3:], t.buf[loc:])
	put3(t.buf[loc:], tok)

	return false
}

func (t *tokset) Len() int {
	if t == nil {
		return 0
	}

	return int(t.c1) + int(t.c2) + len(t.span3())/3
}

func (t *tokset) Tokens() []token {
	if t == nil {
		return nil
	}

	var tokens = make([]token, 0, t.Len())
	for _, val := range t.span1() {
		tokens = append(tokens, token(val))
	}

	span2 := t.span2()
	for i := 0; i < len(span2); i += 2 {
		tokens = append(tokens, unpack2(span2[i:]))
	}

	span3 := t.span3()
	for i := 0; i < len(span3); i += 3 {
		tokens = append(tokens, unpack3(span3[i:]))
	}

	return tokens
}

func put2(buf []byte, tok token) {
	buf[0] = byte(tok)
	buf[1] = byte(tok >> 8)
}

func put3(buf []byte, tok token) {
	buf[0] = byte(tok)
	buf[1] = byte(tok >> 8)
	buf[2] = byte(tok >> 16)
}

func unpack2(buf []byte) token {
	return token(buf[0]) | token(buf[1])<<8
}

func unpack3(buf []byte) token {
	return token(buf[0]) | token(buf[1])<<8 | token(buf[2])<<16
}

func (t tokset) Index(n int) token {
	switch {
	case n < int(t.c1):
		return token(t.buf[n])
	case n < int(t.c1)+int(t.c2):
		span := t.span2()
		return unpack2(span[2*(n-int(t.c1)):])
	case n < t.Len():
		span := t.span3()
		return unpack3(span[3*(n-(int(t.c2)+int(t.c1))):])
	}

	panic("oops")
}

func (t tokset) Choice(r Intn) token {
	return t.Index(r.Intn(t.Len()))
}

// tokset2 stores constant width tokens in a sorted slice.
type tokset2 struct {
	t []token
}

// Add inserts tok into this set, if not already present. It may
// return a new slice, so use its return value as the new set.
//
// Returns a bool signaling whether the token was already in the set
// (similar logic to map lookups).
func (t *tokset2) Add(tok token) bool {
	size := len(t.t)

	// Fast path for empty sets or brand new tokens.
	if size == 0 || tok > t.t[size-1] {
		t.t = append(t.t, tok)
		return false
	}

	loc := sort.Search(size, func(i int) bool { return t.t[i] >= tok })
	if t.t[loc] == tok {
		return true
	}

	t.t = append(t.t, 0)
	copy(t.t[loc+1:], t.t[loc:])
	t.t[loc] = tok

	return false
}

func (t *tokset2) Tokens() []token {
	if t == nil {
		return nil
	}

	return t.t
}
<|endoftext|>


<commit_before>package libvirt

const PackerPool = `
<pool type="dir">
  <name>{{.PoolName}}</name>
  <target>
    <path>/var/lib/libvirt/images</path>
  </target>
</pool>
`

const PackerVolume = `
<volume>
  <name>{{.DiskName}}</name>
  <allocation>0</allocation>
  <capacity unit="M">{{.DiskSize}}</capacity>
  <target>
    <path>{{.DiskName}}</path>
  </target>
</volume>
`
const PackerNetwork = `
<network>
  <name>packer</name>
  <forward mode='nat'/>
  <bridge name='packer0' stp='on' delay='0'/>
  <mac address='52:54:00:2e:85:ab'/>
  <ip address='10.0.2.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='10.0.2.2' end='10.0.2.254'/>
    </dhcp>
  </ip>
</network>
`

const PackerQemuXML = `
<domain type='kvm'>
  <name>{{.VMName}}</name>

  <memory unit='M'>{{.MemorySize}}</memory>
  <type arch='x86_64' machine='pc-i440fx-1.5'>hvm</type>

  <features>
    <acpi/>
    <apic/>
    <pae/>
    <viridian/>
  </features>

  <clock offset='utc'/>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>destroy</on_crash>

  <devices>
  <emulator>/usr/bin/qemu-system-x86_64</emulator>
    <disk type='volume' device='disk'>
      <driver name='qemu' type='{{.DiskType}}' cache='none' io='native' discard='unmap'/>
      <source pool='{{.PoolName}}' volume='{{.DiskName}}'/>
      <alias name='scsi-disk0'/>
      <target dev='sda' bus='scsi'/>
      <boot order='1'/>
      <address type='drive' controller='0' bus='0' unit='0'/>
    </disk>

    <disk type='network' device='cdrom'>
      <driver name='qemu' type='raw'/>
      <target dev='sdb' bus='scsi'/>
      <source protocol="{{.ISOUrlProto}}" name="{{.ISOUrlPath}}">
        <host name="{{.ISOUrlHost}}" port="{{.ISOUrlPort}}"/>
      </source>
      <boot order='2'/>
      <address type='drive' controller='0' target='1' bus='0' unit='0'/>
      <readonly/>
    </disk>

    <controller type='scsi' model='virtio-scsi' index='0'>
      <alias name='scsi0'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
    </controller>

    <controller type='usb' index='0'>
      <alias name='usb0'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
    </controller>

    <interface type='user'>
      <alias name='net0'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
      <rom bar='off'/>
      <model type='virtio'/>
    </interface>

    <serial type='pty'>
      <source path='/dev/pts/4'/>
      <target port='0'/>
      <alias name='serial0'/>
    </serial>

    <console type='pty' tty='/dev/pts/4'>
      <source path='/dev/pts/4'/>
      <target type='serial' port='0'/>
      <alias name='serial0'/>
     </console>

     <input type='mouse' bus='usb'/>

     <graphics type='vnc' port='-1' autoport='yes'>
       <listen type='address' address='::'/>
     </graphics>

     <video>
       <model type='vga' vram='9216' heads='1'/>
       <alias name='video0'/>
       <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
     </video>

     <memballoon model='virtio'>
       <alias name='balloon0'/>
       <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
       <stats period='30'/>
     </memballoon>

     <rng model='virtio'>
       <rate period="1000" bytes="1024"/>
       <backend model='random'>/dev/random</backend>
     </rng>
  </devices>
  <qemu:commandline xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
    <qemu:arg value='-redir'/>
    <qemu:arg value='tcp:{{.SSHPort}}::22'/>
  </qemu:commandline>
</domain>
`
<commit_msg>fix<commit_after>package libvirt

const PackerPool = `
<pool type="dir">
  <name>{{.PoolName}}</name>
  <target>
    <path>/var/lib/libvirt/images</path>
  </target>
</pool>
`

const PackerVolume = `
<volume>
  <name>{{.DiskName}}</name>
  <allocation>0</allocation>
  <capacity unit="M">{{.DiskSize}}</capacity>
  <target>
    <path>{{.DiskName}}</path>
  </target>
</volume>
`
const PackerNetwork = `
<network>
  <name>packer</name>
  <forward mode='nat'/>
  <bridge name='packer0' stp='on' delay='0'/>
  <mac address='52:54:00:2e:85:ab'/>
  <ip address='10.0.2.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='10.0.2.2' end='10.0.2.254'/>
    </dhcp>
  </ip>
</network>
`

const PackerQemuXML = `
<domain type='kvm'>
  <name>{{.VMName}}</name>

  <memory unit='M'>{{.MemorySize}}</memory>
  <os>
    <type arch='x86_64' machine='pc-i440fx-1.5'>hvm</type>
  </os>

  <features>
    <acpi/>
    <apic/>
    <pae/>
    <viridian/>
  </features>

  <clock offset='utc'/>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>destroy</on_crash>

  <devices>
  <emulator>/usr/bin/qemu-system-x86_64</emulator>
    <disk type='volume' device='disk'>
      <driver name='qemu' type='{{.DiskType}}' cache='none' io='native' discard='unmap'/>
      <source pool='{{.PoolName}}' volume='{{.DiskName}}'/>
      <alias name='scsi-disk0'/>
      <target dev='sda' bus='scsi'/>
      <boot order='1'/>
      <address type='drive' controller='0' bus='0' unit='0'/>
    </disk>

    <disk type='network' device='cdrom'>
      <driver name='qemu' type='raw'/>
      <target dev='sdb' bus='scsi'/>
      <source protocol="{{.ISOUrlProto}}" name="{{.ISOUrlPath}}">
        <host name="{{.ISOUrlHost}}" port="{{.ISOUrlPort}}"/>
      </source>
      <boot order='2'/>
      <address type='drive' controller='0' target='1' bus='0' unit='0'/>
      <readonly/>
    </disk>

    <controller type='scsi' model='virtio-scsi' index='0'>
      <alias name='scsi0'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
    </controller>

    <controller type='usb' index='0'>
      <alias name='usb0'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
    </controller>

    <interface type='user'>
      <alias name='net0'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
      <rom bar='off'/>
      <model type='virtio'/>
    </interface>

    <serial type='pty'>
      <source path='/dev/pts/4'/>
      <target port='0'/>
      <alias name='serial0'/>
    </serial>

    <console type='pty' tty='/dev/pts/4'>
      <source path='/dev/pts/4'/>
      <target type='serial' port='0'/>
      <alias name='serial0'/>
     </console>

     <input type='mouse' bus='usb'/>

     <graphics type='vnc' port='-1' autoport='yes'>
       <listen type='address' address='::'/>
     </graphics>

     <video>
       <model type='vga' vram='9216' heads='1'/>
       <alias name='video0'/>
       <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
     </video>

     <memballoon model='virtio'>
       <alias name='balloon0'/>
       <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
       <stats period='30'/>
     </memballoon>

     <rng model='virtio'>
       <rate period="1000" bytes="1024"/>
       <backend model='random'>/dev/random</backend>
     </rng>
  </devices>
  <qemu:commandline xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
    <qemu:arg value='-redir'/>
    <qemu:arg value='tcp:{{.SSHPort}}::22'/>
  </qemu:commandline>
</domain>
`
<|endoftext|>


<commit_before>package operators

import (
	"fmt"
	"os"
	"strings"
	"testing"

	exutil "github.com/openshift/origin/test/extended/util"
	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
)

func Test_checkCRDs(t *testing.T) {

	crdClient := setupLocalAPIClientset()
	t.Run("check_crds test", func(t *testing.T) {
		crdList, err := getCRDItemList(*crdClient)
		if err != nil {
			t.Errorf("Fail: %s", err)
		}
		failures := checkSubresourceStatus(crdList)
		if len(failures) > 0 {
			t.Error("There should be no failures")
			for _, i := range failures {
				if strings.Contains(i, "has no 'status' element in its schema") {
					fmt.Println(i)
				}
			}
			for _, i := range failures {
				if !strings.Contains(i, "has no 'status' element in its schema") {
					fmt.Println(i)
				}
			}
		}
	})
}

func setupLocalAPIClientset() *apiextensionsclientset.Clientset {
	// Get the kubeconfig by creating an Openshift cluster with cluster-bot, downloading it,
	// and using the filename for KUBECONFIG.
	home_dir := os.Getenv("HOME")
	err := os.Setenv("KUBECONFIG", fmt.Sprintf("%s/Downloads/cluster-bot-2022-04-07-164806.kubeconfig.txt", home_dir))
	kube_dir := os.Getenv("KUBECONFIG")
	fmt.Println(kube_dir)
	if err != nil {
		fmt.Printf("Error setting KUBECONFIG: %s", err)
	}
	oc := exutil.NewCLI("default")
	local_client := apiextensionsclientset.NewForConfigOrDie(oc.AdminConfig())
	return local_client
}
<commit_msg>update unit test<commit_after>package operators

import (
	"fmt"
	"os"
	"testing"

	exutil "github.com/openshift/origin/test/extended/util"
	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
)

func Test_checkSubresourceStatus(t *testing.T) {

	crdClient := setupLocalAPIClientset()
	t.Run("Test_checkSubresourceStatus test", func(t *testing.T) {
		crdList, err := getCRDItemList(*crdClient)
		if err != nil {
			t.Errorf("Fail: %s", err)
		}
		failures := checkSubresourceStatus(crdList)
		if len(failures) > 0 {
			t.Error("There should be no failures")
			for _, i := range failures {
				fmt.Println(i)
			}
		}
	})
}

func Test_checkStatusInSchema(t *testing.T) {

	crdClient := setupLocalAPIClientset()
	t.Run("Test_checkStatusInSchema test", func(t *testing.T) {
		crdList, err := getCRDItemList(*crdClient)
		if err != nil {
			t.Errorf("Fail: %s", err)
		}
		failures := checkStatusInSchema(crdList)
		if len(failures) > 0 {
			t.Error("There should be no failures")
			for _, i := range failures {
				fmt.Println(i)
			}
		}
	})
}
func setupLocalAPIClientset() *apiextensionsclientset.Clientset {
	// Get the kubeconfig by creating an Openshift cluster with cluster-bot, downloading it,
	// and using the filename for KUBECONFIG.
	home_dir := os.Getenv("HOME")
	err := os.Setenv("KUBECONFIG", fmt.Sprintf("%s/Downloads/cluster-bot-2022-05-10-100029.kubeconfig.txt", home_dir))
	kube_dir := os.Getenv("KUBECONFIG")
	fmt.Println(kube_dir)
	if err != nil {
		fmt.Printf("Error setting KUBECONFIG: %s", err)
	}
	oc := exutil.NewCLI("default")
	local_client := apiextensionsclientset.NewForConfigOrDie(oc.AdminConfig())
	return local_client
}
<|endoftext|>


<commit_before>/*
The executable "parse" takes one or more file names as arguments and
calls the template processing function, processTemplate, on each one.
*/

package main

import (
  "os"
  "fmt"
  "io"
  "bufio"
  "strings"
  "strconv"
  "path"
  "path/filepath"
  "errors"
  "bytes"
  "go/token"
  "go/parser"
  "go/printer"
  "golang.org/x/tools/go/ast/astutil"
)

var verbose bool = false
var log *os.File = os.Stderr

type Section struct {
  Kind uint
  Text string
}
const (
  StaticSection uint = iota
  CodeSection
)

var sections []*Section

//--- Linear pattern matcher

type Pattern struct {
  Text []rune
  Length, Pos int
}

func newPattern(s string) Pattern {
  runes := []rune(s)
  return Pattern{ Text: runes, Length: len(runes) }
}

// Next returns true if Pos advances past the last character of Text.
func (pattern *Pattern) Next(ch rune) bool {
  // If Pos is past the end of Text, reset it to the beginning.
  if pattern.Pos == pattern.Length {
    pattern.Pos = 0
  }
  // Try to match the current rune in Text.
  if ch == pattern.Text[pattern.Pos] {
    pattern.Pos++
  }
  // Check for a complete match.
  return pattern.Pos == pattern.Length
}

//--- Template parsing and output generation

type TemplateEntry struct {
  SitePath, HardPath string
  FileInfo os.FileInfo
  InsertionLine int
}

func (entry TemplateEntry) String() string {
  if entry.InsertionLine == 0 {
    return entry.SitePath
  }
  return fmt.Sprintf("-> line %d: %s", entry.InsertionLine, entry.SitePath)
}

func makeTemplateEntry(siteRoot, startDir, sitePath string,
    insertionLine int) (*TemplateEntry, error) {
  hardPath := makeHardPath(siteRoot, startDir, sitePath)
  fileInfo, error := os.Stat(hardPath)
  if error != nil {
    return nil, error
  }
  entry := TemplateEntry{
      SitePath: sitePath,
      HardPath: hardPath,
      FileInfo: fileInfo,
      InsertionLine: insertionLine,
    }
  return &entry, nil
}

func makeHardPath(siteRoot, startDir, sitePath string) string {
  // A hard path names a location in the physical file system rather than
  //  in the website's directory structure. It is either an absolute path
  //  or a relative path with respect to the directory containing the
  //  top-level template that is being parsed.
  var dir string
  if filepath.IsAbs(sitePath) {
    dir = siteRoot
  } else {
    dir = startDir
  }
  // Note that filepath.Join automatically performs filepath.Clean, thus
  //  returning a lexically unique form of the path. However, the path
  //  does not uniquely identify a file if it includes a symbolic link.
  //  Therefore, we cannot rely on string comparison to prevent cycles.
  hardPath := filepath.Join(dir, sitePath)
  return hardPath
}

func parse(templatePath string) error {
  sections = []*Section{}

  // We resolve absolute paths with the website root.
  siteRoot := "/var/www/dd1"  // Stub. We'll get the real value from Apache.
  // We resolve relative paths using the starting directory.
  startDir := filepath.Dir(templatePath)
  entryPoint := filepath.Base(templatePath)

  // Make an insertion stack with a top-level entry.
  entry, error := makeTemplateEntry(siteRoot, startDir, entryPoint, 0)
  if error != nil {
    return error
  }
  stack := []*TemplateEntry{ entry }
  return doParse(siteRoot, startDir, stack)
}

func doParse(siteRoot, startDir string, stack []*TemplateEntry) error {
  current := stack[len(stack)-1]
  if verbose {
    fmt.Fprintf(log, "// start \"%s\"\n", current.SitePath)
  }
  var topLevel bool
  if len(stack) == 1 {
    topLevel = true
  }

  // Check for an insertion cycle.
  for i := len(stack)-2; i >= 0; i-- {
    ancestor := stack[i]
    if os.SameFile(ancestor.FileInfo, current.FileInfo) {
      lines := []string{ "doParse: insertion cycle" }
      for j := i; j < len(stack); j++ {
        lines = append(lines, stack[j].String())
      }
      message := fmt.Sprintf(strings.Join(lines, "\n  "))
      return errors.New(message)
    }
  }

  var error error
  var file *os.File

  file, error = os.Open(current.HardPath)
  if error != nil {
    return error
  }

  reader := bufio.NewReader(file)
  writer := bufio.NewWriter(os.Stdout)
  defer writer.Flush()

  codePattern := newPattern("<?code")
  insertPattern := newPattern("<?insert")
  openPatterns := []*Pattern{ &codePattern, &insertPattern }
  var open *Pattern
  close := newPattern("?>")

  var buffer []rune
  var ch rune
  var size int
  countBytes, countRunes := 0, 0
  lineIndex := 1
  prefix := true

  for {
    ch, size, error = reader.ReadRune()
    if error == nil {
      buffer = append(buffer, ch)
      countBytes += size
      countRunes += 1
      if ch == '\n' {
        lineIndex += 1
      }
    } else {
      content := string(buffer)
      if topLevel {
        content = strings.TrimSpace(content)
      }
      emitStatic(content)
      break
    }

    if open == nil {
      for _, pattern := range openPatterns {
        if pattern.Next(ch) {
          open = pattern
          content := string(buffer[0:len(buffer)-open.Length])
          if prefix {
            if topLevel {
              content = strings.TrimSpace(content)
            }
            prefix = false
          }
          emitStatic(content)
          buffer = []rune{}
        }
      }
    } else {
      if close.Next(ch) {
        content := buffer[0:len(buffer)-close.Length]
        if open == &codePattern {
          emitCode(string(content))
        } else if open == &insertPattern {
          childPath := strings.TrimSpace(string(content))
          entry, error := makeTemplateEntry(siteRoot, startDir, childPath,
              lineIndex)
          if error != nil {
            return error
          }
          stack = append(stack, entry)
          error = doParse(siteRoot, startDir, stack)
          if error != nil {
            return error
          }
          stack = stack[0:len(stack)-1]
        }
        open = nil
        buffer = []rune{}
      }
    }
  }
  if verbose {
    fmt.Fprintf(log, "// finish \"%s\"\n", current.SitePath)
    fmt.Fprintf(log, "// read %d bytes, %d runes\n", countBytes, countRunes)
    fmt.Fprintf(log, "// finished on line %d\n", lineIndex)
  }
  if error == io.EOF {
    return nil
  }
  return error
}

func emitCode(content string) {
  sections = append(sections, &Section{ Kind: CodeSection, Text: content })
}

func emitStatic(content string) {
  if len(content) == 0 {
    return
  }
  from := 0
  for pos, ch := range content {
    if ch == '`' {
      if pos != from {
        raw := fmt.Sprintf("`%s`", content[from:pos])
        emitStaticChunk(raw)
      }
      emitStaticChunk("'`'")
      from = pos+1
    }
  }
  if from != len(content) {
    raw := fmt.Sprintf("`%s`", content[from:len(content)])
    emitStaticChunk(raw)
  }
}
func emitStaticChunk(chunk string) {
  sections = append(sections, &Section{ Kind: StaticSection, Text: chunk })
}

func processTemplate(templatePath string, writer *bufio.Writer) {
  // We parse the template to obtain code sections and static sections.
  error := parse(templatePath)
  if error != nil {
    writer.WriteString(fmt.Sprintf("Template parsing error: %s\n", error))
    return
  }

  // Concatenate only the code sections.
  output := bytes.Buffer{}
  for _, section := range sections {
    if section.Kind == CodeSection {
      fmt.Fprintf(&output, section.Text)
    }
  }
  fileSet := token.NewFileSet()
  fileNode, error := parser.ParseFile(fileSet, "output", output.Bytes(),
      parser.ParseComments)
  if error != nil {
    writer.Write(output.Bytes())
    writer.WriteString(fmt.Sprintf(
        "\n---\nError parsing code sections: %s\n", error))
    return
  }

  seekPath := "fmt"
  seekName := path.Base(seekPath)
  printCall := "Print"

  //  Has the package been imported? Is the name available?
  isImported := false
  var importedAs string          // use this if the path has been imported
  seenName := map[string]bool{}  // consult this if we have to import

  for _, importSpec := range fileNode.Imports {
    importPath, _ := strconv.Unquote(importSpec.Path.Value)
    var importName string
    if importSpec.Name == nil {
      importName = path.Base(importPath)
    } else {
      importName = importSpec.Name.Name
    }
    seenName[importName] = true
    if !isImported && importPath == seekPath && importName != "_" {
      isImported = true
      importedAs = importName
    }
  }

  var importAs, printPrefix string  // NB: these are "" by default
  if isImported {
    if importedAs != "." {  // no prefix is needed with a dot import
      printPrefix = importedAs+"."
    }
  } else {
    if !seenName[seekName] {
      importAs = seekName
    } else {
      for i := 0; ; i++ {
        importAs = fmt.Sprintf("%s_%d", seekName, i)
        _, found := seenName[importAs]
        if !found {
          break
        }
      }
    }
    printPrefix = importAs+"."
  }

  // Concatenate the code sections and static sections.
  output.Reset()
  for _, section := range sections {
    if section.Kind == CodeSection {
      fmt.Fprintf(&output, section.Text)
    } else {
      s := fmt.Sprintf(";%s%s(%s);", printPrefix, printCall, section.Text)
      fmt.Fprintf(&output, s)
    }
  }
  // Have Go parse the entire template output.
  fileSet = token.NewFileSet()
  fileNode, error = parser.ParseFile(fileSet, "output", output.Bytes(),
      parser.ParseComments)
  if error != nil {
    writer.Write(output.Bytes())
    writer.WriteString(fmt.Sprintf(
        "\n---\nError parsing entire template output: %s\n", error))
    return
  }
  // Finally, inject an import statement if necessary.
  if !isImported {
    if importAs == seekName {
      astutil.AddImport(fileSet, fileNode, seekPath)
    } else {
      astutil.AddNamedImport(fileSet, fileNode, importAs, seekPath)
    }
  }

  // Print with a custom configuration: soft tabs of two spaces each.
  config := printer.Config{ Mode: printer.UseSpaces, Tabwidth: 2 }
  (&config).Fprint(writer, fileSet, fileNode)
}

func main() {
  writer := bufio.NewWriter(os.Stdout)
  defer writer.Flush()

  numFiles := len(os.Args)-1
  if numFiles == 0 {
    writer.WriteString("No files specified.\n")
    return
  }
  for argIx := 1; argIx <= numFiles; argIx++ {
    // Parse a top-level template.
    processTemplate(os.Args[argIx], writer)
  }
}
<commit_msg>Write comments and refactor the parsing stack<commit_after>// The executable "parse" takes one or more file names as arguments and
//  calls the template processing function, processTemplate, on each one.

package main

import (
  "os"
  "fmt"
  "io"
  "bufio"
  "strings"
  "strconv"
  "path"
  "path/filepath"
  "errors"
  "bytes"
  "go/token"
  "go/parser"
  "go/printer"
  "golang.org/x/tools/go/ast/astutil"
)

var verbose bool = false
var log *os.File = os.Stderr

var sections []*Section     // stores output sections during template parsing
var stack []*TemplateEntry  // used to prevent template insertion cycles

// Section contains the text of a code section or static section.
type Section struct {
  Kind uint
  Text string
}
const (
  StaticSection uint = iota
  CodeSection
)

//--- Linear pattern matching

// Pattern helps us keep track of progress in matching a string.
type Pattern struct {
  Text []rune
  Length, Pos int
}

// NewPattern initializes a Pattern for a given string.
func NewPattern(s string) Pattern {
  runes := []rune(s)
  return Pattern{ Text: runes, Length: len(runes) }
}

// Next returns true when Pos advances past the last character of Text.
func (pattern *Pattern) Next(ch rune) bool {
  // If Pos is past the end of Text, reset it to the beginning.
  if pattern.Pos == pattern.Length {
    pattern.Pos = 0
  }
  // Try to match the current rune in Text.
  if ch == pattern.Text[pattern.Pos] {
    pattern.Pos++
  }
  // Check for a complete match.
  return pattern.Pos == pattern.Length
}

//--- Template parsing and output generation

// TemplateEntry contains path and file information about a template.
type TemplateEntry struct {
  SitePath, HardPath string  // The site path is relative to the site root,
  FileInfo os.FileInfo       //  while the hard path is a physical path in
  InsertionLine int          //  the file system. A child template begins
}                            //  at an insertion line of a parent template.

// String implements the fmt.Stringer interface for TemplateEntry.
func (entry TemplateEntry) String() string {
  if entry.InsertionLine == 0 {
    return entry.SitePath
  }
  return fmt.Sprintf("-> line %d: %s", entry.InsertionLine, entry.SitePath)
}

// MakeTemplateEntry fills in every field of a TemplateEntry, generating
//  the hard path and file info based on the details of the site path.
func MakeTemplateEntry(siteRoot, startDir, sitePath string,
    insertionLine int) (*TemplateEntry, error) {
  hardPath := MakeHardPath(siteRoot, startDir, sitePath)
  fileInfo, error := os.Stat(hardPath)
  if error != nil {
    return nil, error
  }
  entry := TemplateEntry{
      SitePath: sitePath,
      HardPath: hardPath,
      FileInfo: fileInfo,
      InsertionLine: insertionLine,
    }
  return &entry, nil
}

// MakeHardPath uses the details of the site path to make a hard path.
// A hard path names a location in the physical file system rather than
//  in the website's directory structure. It is either an absolute path
//  or a relative path with respect to the starting directory, which is
//  where the top-level template is located.
func MakeHardPath(siteRoot, startDir, sitePath string) string {
  var dir string
  if filepath.IsAbs(sitePath) {
    dir = siteRoot
  } else {
    dir = startDir
  }
  hardPath := filepath.Join(dir, sitePath)
  return hardPath
}

// parse makes an entry for the top-level template, initializes the section
//  list and the parsing stack, and calls doParse.
func parse(siteRoot, templatePath string) error {
  // We resolve relative paths using the starting directory.
  startDir := filepath.Dir(templatePath)
  entryPoint := filepath.Base(templatePath)
  // Make an insertion stack with a top-level entry.
  entry, error := MakeTemplateEntry(siteRoot, startDir, entryPoint, 0)
  if error != nil {
    return error
  }
  sections = []*Section{}
  stack = []*TemplateEntry{ entry }
  return doParse(siteRoot, startDir)
}

// doParse recursively parses a template and its children.
func doParse(siteRoot, startDir string) error {
  current := stack[len(stack)-1]
  if verbose {
    fmt.Fprintf(log, "// start \"%s\"\n", current.SitePath)
  }
  var topLevel bool
  if len(stack) == 1 {
    topLevel = true
  }

  // Check for an insertion cycle.
  for i := len(stack)-2; i >= 0; i-- {
    ancestor := stack[i]
    if os.SameFile(ancestor.FileInfo, current.FileInfo) {
      lines := []string{ "doParse: insertion cycle" }
      for j := i; j < len(stack); j++ {           //  In the event of a cycle,
        lines = append(lines, stack[j].String())  //   generate a stack trace.
      }
      message := fmt.Sprintf(strings.Join(lines, "\n  "))
      return errors.New(message)
    }
  }

  // Open the template file and make a reader.
  var error error
  var file *os.File
  file, error = os.Open(current.HardPath)
  if error != nil {
    return error
  }
  reader := bufio.NewReader(file)

  // There are two opening patterns but only one closing pattern. There is
  //  no need to check tag depth because nested tags are not allowed.
  codePattern := NewPattern("<?code")
  insertPattern := NewPattern("<?insert")
  openPatterns := []*Pattern{ &codePattern, &insertPattern }
  var open *Pattern
  close := NewPattern("?>")

  // Each character goes into the buffer, which we empty whenever we match
  //  an opening or closing tag. In the former case the buffer must contain
  //  static text, while the latter case is code or a template insertion.
  var buffer []rune
  var ch rune
  var size int
  countBytes, countRunes := 0, 0  // Byte and rune counts only appear in log
  lineIndex := 1                  // messages. We store the line index in
  prefix := true                  //  template entries for debugging purposes.

  for {
    ch, size, error = reader.ReadRune()
    if error == nil {
      buffer = append(buffer, ch)
      countBytes += size
      countRunes += 1
      if ch == '\n' {
        lineIndex += 1
      }
    } else {               // We assume that the read failed due to EOF.
      content := string(buffer)
      if topLevel {        // Trim the end of the top-level template.
        content = strings.TrimSpace(content)
      }
      emitStatic(content)
      break
    }

    // Once a tag has been opened, we ignore further opening tags until
    //  we have come across the closing tag. Nesting is not allowed.
    if open == nil {
      for _, pattern := range openPatterns {
        if pattern.Next(ch) {
          open = pattern
          content := string(buffer[0:len(buffer)-open.Length])  // remove tag
          if prefix {
            if topLevel {  // Trim the start of the top-level template.
              content = strings.TrimSpace(content)
            }
            prefix = false
          }
          emitStatic(content)  // Text before an opening tag must be static.
          buffer = []rune{}
        }
      }
    } else {
      if close.Next(ch) {
        content := buffer[0:len(buffer)-close.Length]  // remove tag
        if open == &codePattern {           // Code sections are just text.
          emitCode(string(content))
        } else if open == &insertPattern {  // Insertion requires more work.
          childPath := strings.TrimSpace(string(content))
          entry, error := MakeTemplateEntry(siteRoot, startDir, childPath,
              lineIndex)                    // We have to push a new template
          if error != nil {                 //  entry onto the stack and make
            return error                    //  a recursive call.
          }
          stack = append(stack, entry)
          error = doParse(siteRoot, startDir)
          if error != nil {
            return error
          }
          stack = stack[0:len(stack)-1]
        }
        open = nil
        buffer = []rune{}
      }
    }
  }
  if verbose {
    fmt.Fprintf(log, "// finish \"%s\"\n", current.SitePath)
    fmt.Fprintf(log, "// read %d bytes, %d runes\n", countBytes, countRunes)
    fmt.Fprintf(log, "// finished on line %d\n", lineIndex)
  }
  if error == io.EOF {
    return nil
  }
  return error
}

// emitCode makes a code section and adds it to the global sections.
func emitCode(content string) {
  sections = append(sections, &Section{ Kind: CodeSection, Text: content })
}

// emitStatic breaks a string into back-quoted strings and back quotes,
//  calling emitStaticChunk for each one.
func emitStatic(content string) {
  if len(content) == 0 {
    return
  }
  from := 0
  for pos, ch := range content {
    if ch == '`' {
      if pos != from {
        raw := fmt.Sprintf("`%s`", content[from:pos])
        emitStaticChunk(raw)
      }
      emitStaticChunk("'`'")
      from = pos+1
    }
  }
  if from != len(content) {
    raw := fmt.Sprintf("`%s`", content[from:len(content)])
    emitStaticChunk(raw)
  }
}
// emitStaticChunk makes a static section and adds it to the global sections.
func emitStaticChunk(chunk string) {
  sections = append(sections, &Section{ Kind: StaticSection, Text: chunk })
}

// ProcessTemplate is the top-level template parsing function. It calls
//  parse, then glues the sections together and injects an import statement
//  as needed. The final result is printed to the global writer.
func ProcessTemplate(siteRoot, templatePath string, writer *bufio.Writer) {
  // We parse the template to obtain code sections and static sections.
  error := parse(siteRoot, templatePath)
  if error != nil {
    writer.WriteString(fmt.Sprintf("Template parsing error: %s\n", error))
    return
  }

  // Concatenate only the code sections. We're not adding print statements yet
  //  because we don't know what the print command is going to look like. We
  //  do want to parse the user's code in order to scan the imports.
  output := bytes.Buffer{}
  for _, section := range sections {
    if section.Kind == CodeSection {
      fmt.Fprintf(&output, section.Text)
    }
  }
  fileSet := token.NewFileSet()
  fileNode, error := parser.ParseFile(fileSet, "output", output.Bytes(),
      parser.ParseComments)
  if error != nil {
    writer.Write(output.Bytes())
    writer.WriteString(fmt.Sprintf(
        "\n---\nError parsing code sections: %s\n", error))
    return
  }

  seekPath := "fmt"  // The print command is to be found in this package.
  seekName := path.Base(seekPath)
  printCall := "Print"

  //  Has the desired package been imported? Is the name available?
  isImported := false
  var importedAs string          // Use this if the path has been imported.
  seenName := map[string]bool{}  // Consult this if we have to import.

  for _, importSpec := range fileNode.Imports {
    importPath, _ := strconv.Unquote(importSpec.Path.Value)
    var importName string
    if importSpec.Name == nil {
      importName = path.Base(importPath)
    } else {
      importName = importSpec.Name.Name
    }
    seenName[importName] = true  // NB: underscore imports only run a package.
    if !isImported && importPath == seekPath && importName != "_" {
      isImported = true          // If the package is imported several times,
      importedAs = importName    //  we use the name in the first occurrence.
    }
  }

  var importAs, printPrefix string  // NB: these are "" by default
  if isImported {
    if importedAs != "." {  // No prefix is needed with a dot import.
      printPrefix = importedAs+"."
    }
  } else {
    if !seenName[seekName] {
      importAs = seekName
    } else {                // Look for a name that hasn't been used yet.
      for i := 0; ; i++ {
        importAs = fmt.Sprintf("%s_%d", seekName, i)
        _, found := seenName[importAs]
        if !found {
          break
        }
      }
    }
    printPrefix = importAs+"."
  }

  // Concatenate the code with static sections wrapped in print statements.
  output.Reset()
  for _, section := range sections {
    if section.Kind == CodeSection {
      fmt.Fprintf(&output, section.Text)
    } else {
      s := fmt.Sprintf(";%s%s(%s);", printPrefix, printCall, section.Text)
      fmt.Fprintf(&output, s)
    }
  }
  // Have Go parse the whole output in preparation for import injection
  //  and formatted code output.
  fileSet = token.NewFileSet()
  fileNode, error = parser.ParseFile(fileSet, "output", output.Bytes(),
      parser.ParseComments)
  if error != nil {
    writer.Write(output.Bytes())
    writer.WriteString(fmt.Sprintf(
        "\n---\nError parsing entire template output: %s\n", error))
    return
  }
  // Inject an import statement if necessary.
  if !isImported {
    if importAs == seekName {  // to get 'import "fmt"', not 'import fmt "fmt"'
      astutil.AddImport(fileSet, fileNode, seekPath)
    } else {                   // AddNamedImport would make 'import fmt "fmt"'
      astutil.AddNamedImport(fileSet, fileNode, importAs, seekPath)
    }
  }

  // Print with a custom configuration: soft tabs of two spaces each.
  config := printer.Config{ Mode: printer.UseSpaces, Tabwidth: 2 }
  (&config).Fprint(writer, fileSet, fileNode)
}

func main() {
  writer := bufio.NewWriter(os.Stdout)
  defer writer.Flush()

  // We resolve absolute paths by consulting the website root.
  siteRoot := "/var/www/dd1"  // Stub. We'll get the real value from Apache.

  numFiles := len(os.Args)-1
  if numFiles == 0 {
    writer.WriteString("No files specified.\n")
    return
  }
  for argIx := 1; argIx <= numFiles; argIx++ {
    // Parse a top-level template.
    ProcessTemplate(siteRoot, os.Args[argIx], writer)
  }
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"plaid/check"
	"plaid/codegen"
	"plaid/libs"
	"plaid/parser"
	"plaid/vm"
)

func main() {
	showAST := flag.Bool("ast", false, "output abstract syntax tree")
	showCheck := flag.Bool("check", false, "output type checker results")
	showIR := flag.Bool("ir", false, "output intermediate representation")
	showBC := flag.Bool("bytecode", false, "output bytecode")
	showOut := flag.Bool("out", false, "run program and print output")
	flag.Parse()

	for _, filename := range flag.Args() {
		processFile(filename, *showAST, *showCheck, *showIR, *showBC, *showOut)
	}
}

func processFile(filename string, showAST bool, showCheck bool, showIR bool, showBC bool, showOut bool) {
	buf, err := ioutil.ReadFile(filename)
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}

	src := string(buf)
	ast, err := parser.Parse(src)
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}

	if showAST {
		fmt.Println(ast.String())
	}

	if showCheck || showIR || showBC || showOut {
		scope := check.Check(ast, libs.IO, libs.Conv)
		if len(scope.Errors()) > 0 {
			for i, err := range scope.Errors() {
				fmt.Printf("%4d %s\n", i, err)
			}
			os.Exit(1)
		} else if showCheck {
			fmt.Println(scope)
		}

		if showIR || showBC || showOut {
			ir := codegen.Transform(ast, libs.IO, libs.Conv)

			if showIR {
				fmt.Println(ir.String())
			}

			if showBC || showOut {
				mod := codegen.Generate(ir)

				if showBC {
					fmt.Println(mod.Main.String())
				}

				if showOut {
					vm.Run(mod.Main)
				}
			}
		}
	}
}
<commit_msg>display scope and bytecode as pretty printed trees<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"plaid/check"
	"plaid/codegen"
	"plaid/debug"
	"plaid/libs"
	"plaid/parser"
	"plaid/vm"
)

func main() {
	showAST := flag.Bool("ast", false, "output abstract syntax tree")
	showCheck := flag.Bool("check", false, "output type checker results")
	showIR := flag.Bool("ir", false, "output intermediate representation")
	showBC := flag.Bool("bytecode", false, "output bytecode")
	showOut := flag.Bool("out", false, "run program and print output")
	flag.Parse()

	for _, filename := range flag.Args() {
		processFile(filename, *showAST, *showCheck, *showIR, *showBC, *showOut)
	}
}

func processFile(filename string, showAST bool, showCheck bool, showIR bool, showBC bool, showOut bool) {
	buf, err := ioutil.ReadFile(filename)
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}

	src := string(buf)
	ast, err := parser.Parse(src)
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}

	if showAST {
		fmt.Println(ast.String())
	}

	if showCheck || showIR || showBC || showOut {
		scope := check.Check(ast, libs.IO, libs.Conv)
		if len(scope.Errors()) > 0 {
			for i, err := range scope.Errors() {
				fmt.Printf("%4d %s\n", i, err)
			}
			os.Exit(1)
		} else if showCheck {
			fmt.Println(debug.PrettyTree(scope))
		}

		if showIR || showBC || showOut {
			ir := codegen.Transform(ast, libs.IO, libs.Conv)

			if showIR {
				fmt.Println(ir.String())
			}

			if showBC || showOut {
				mod := codegen.Generate(ir)

				if showBC {
					fmt.Println(debug.PrettyTree(mod.Main))
				}

				if showOut {
					vm.Run(mod.Main)
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package lich

import (
	"fmt"
	"strconv"
)

type UnparseableError struct {
	Parsestring string
	Location    int
	Problem     string
}

const errformat = "Couldn't parse string %q...\nProblem at index %d was %q."

func (u UnparseableError) Error() string {
	return fmt.Sprintf(errformat, u.Parsestring[:10], u.Location, u.Problem)
}

func Parse(s string) (Element, error) {
	return topLevel(s, 0, len(s))
}

func isdigit(r uint8) bool {
	return r >= '0' && r <= '9'
}

func topLevel(s string, start, stop int) (Element, error) {
	if len(s) < 1 {
		return nil, UnparseableError{s, 0, "Empty string!"}
	}

	current := start
	for isdigit(s[current]) {
		current++
	}

	size, err := strconv.Atoi(s[start:current])

	if err != nil {
		return nil, UnparseableError{s, current, "Non-digit start"}
	}

	//If this doesn't match, the reported size is screwed up.
	//Doing this check helps make sure we don't try to read too far.
	if current+size+2 != stop {
		return nil, UnparseableError{s, current, "Data payload is too short"}
	}

	switch s[current] {
	case '<':
		if s[stop-1] != '>' {
			return nil, UnparseableError{s, stop - 1, "No matching >"}
		}
		return Data(s[current+1 : stop-1]), nil

	case '[':
		if s[stop-1] != ']' {
			return nil, UnparseableError{s, stop - 1, "No matching ]"}
		}

		return getArray(s, current+1, stop-1), nil

	}
	return nil, UnparseableError{s, current, "Invalid separator"}
}

func getArray(s string, start, stop int) Element {
	if s[stop] != ']' {
		return nil
	}
	return Data(s[start:stop])
}
<commit_msg>Inline errors, prefix function.<commit_after>package lich

import (
	"fmt"
	"strconv"
)

type UnparseableError struct {
	Parsestring string
	Location    int
	Problem     string
}

const errformat = "Couldn't parse string %q...\nProblem at index %d was %q."

func (u UnparseableError) Error() string {
	return fmt.Sprintf(errformat, u.Parsestring[:10], u.Location, u.Problem)
}

func Parse(s string) (Element, error) {
	return topLevel(s, 0, len(s))
}

func topLevel(s string, start, stop int) (Element, error) {
	if len(s) < 1 {
		return nil, UnparseableError{s, 0, "Empty string!"}
	}

	size, current, err := sizePrefix(s, start)
	if err != nil {
		return nil, err
	}

	//If this doesn't match, the reported size is screwed up.
	//Doing this check helps make sure we don't try to read too far.
	if current+size+2 != stop {
		return nil, UnparseableError{s, current, "Data payload is too short"}
	}

	switch s[current] {
	case '<':
		if s[stop-1] != '>' {
			return nil, UnparseableError{s, stop - 1, "No matching >"}
		}
		return Data(s[current+1 : stop-1]), nil

	case '[':
		if s[stop-1] != ']' {
			return nil, UnparseableError{s, stop - 1, "No matching ]"}
		}

		return getArrayBody(s, current+1, stop-1)

	}
	return nil, UnparseableError{s, current, "Invalid separator"}
}

func sizePrefix(s string, start int) (size, current int, err error) {
	current = start + 1
	for isdigit(s[current]) {
		current++
	}

	size, err = strconv.Atoi(s[start:current])

	if err != nil {
		return -1, -1, UnparseableError{s, current,
			"Non-digit at start of Element"}
	}

	return size, current, nil
}

func isdigit(r uint8) bool {
	return r >= '0' && r <= '9'
}

func getArrayBody(s string, start, stop int) (Element, error) {
	return nil, UnparseableError{s, stop - 1, "Arrays are hard."}
}
<|endoftext|>


<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package client

import (
	"encoding/json"
	"path"

	"golang.org/x/net/context"

	"github.com/keybase/cli"
	"github.com/keybase/client/go/install"
	"github.com/keybase/client/go/libcmdline"
	"github.com/keybase/client/go/libkb"
)

func NewCmdNStatus(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command {
	return cli.Command{
		Name:  "nstatus",
		Usage: "Show information about current user",
		Action: func(c *cli.Context) {
			cl.ChooseCommand(&CmdNStatus{Contextified: libkb.NewContextified(g)}, "nstatus", c)
		},
		Flags: []cli.Flag{
			cli.BoolFlag{
				Name:  "j, json",
				Usage: "Output status as JSON",
			},
		},
	}
}

type CmdNStatus struct {
	libkb.Contextified
	json bool
}

func (c *CmdNStatus) ParseArgv(ctx *cli.Context) error {
	if len(ctx.Args()) > 0 {
		return UnexpectedArgsError("status")
	}
	c.json = ctx.Bool("json")
	return nil
}

type fstatus struct {
	Username               string
	UserID                 string
	DeviceID               string
	DeviceName             string
	DeviceStatus           string
	LoggedInProvisioned    bool `json:"LoggedIn"`
	PassphraseStreamCached bool `json:"KeychainUnlocked"`
	ConfigPath             string

	Client struct {
		Version string
	}
	Service struct {
		Version string
		Running bool
		Pid     string
		Log     string
	}
	KBFS struct {
		Version string
		Running bool
		Pid     string
		Log     string
	}
	Desktop struct {
		Running bool
	}
}

func (c *CmdNStatus) Run() error {
	status, err := c.load()
	if err != nil {
		return err
	}

	return c.output(status)
}

func (c *CmdNStatus) load() (*fstatus, error) {
	var status fstatus

	status.Client.Version = libkb.VersionString()

	cli, err := GetConfigClient(c.G())
	if err != nil {
		return nil, err
	}

	curStatus, err := cli.GetCurrentStatus(context.TODO(), 0)
	if err != nil {
		return nil, err
	}

	status.LoggedInProvisioned = curStatus.LoggedIn
	if curStatus.User != nil {
		status.Username = curStatus.User.Username
		status.UserID = curStatus.User.Uid.String()
	}

	extStatus, err := cli.GetExtendedStatus(context.TODO(), 0)
	if err != nil {
		return nil, err
	}

	config, err := cli.GetConfig(context.TODO(), 0)
	if err != nil {
		return nil, err
	}

	status.ConfigPath = config.ConfigPath
	status.Service.Version = config.Version

	status.DeviceID = extStatus.DeviceID.String()
	status.DeviceName = extStatus.DeviceName
	status.DeviceStatus = extStatus.DeviceStatus

	if extStatus.Standalone {
		status.Service.Running = false
	} else {
		status.Service.Running = true
		status.Service.Log = path.Join(extStatus.LogDir, c.serviceLogFilename())
	}

	status.PassphraseStreamCached = extStatus.PassphraseStreamCached

	kbfsVersion, err := install.KBFSBundleVersion(c.G(), "")
	if err == nil {
		status.KBFS.Version = kbfsVersion
	}
	status.KBFS.Log = path.Join(extStatus.LogDir, c.kbfsLogFilename())

	status.Desktop.Running = extStatus.DesktopUIConnected

	// set anything os-specific:
	if err := c.osSpecific(&status); err != nil {
		return nil, err
	}

	return &status, nil
}

func (c *CmdNStatus) output(status *fstatus) error {
	if c.json {
		return c.outputJSON(status)
	}

	return c.outputTerminal(status)
}

func (c *CmdNStatus) outputJSON(status *fstatus) error {
	b, err := json.MarshalIndent(status, "", "    ")
	if err != nil {
		return err
	}
	dui := c.G().UI.GetDumbOutputUI()
	_, err = dui.Printf(string(b) + "\n")
	return err
}

/*

Username: chris
Device name: ubuntu-work-vm
User ID: 23260c2ce19420f97b58d7d95b68ca00
Device ID: 829493463c83fd2560d1964948a6df18
Local Keybase Keychain:  unlocked
KBFS:
   status: running, connected, mounted
   version: 1.0.8-1123123213
   log:  /home/chris/.cache/keybase/keybase.kbfs.log
Service:
   status: running, connected
   version: 1.0.8-1123123213
   log:  /home/chris/.cache/keybase/keybase.log
Client:
   version: 1.0.8-12312321312
Electron:
   status: running

*/

/*

   logged out:

   available users:
    chris
        - whatever info
    max
        - whatever info

*/

func (c *CmdNStatus) outputTerminal(status *fstatus) error {
	dui := c.G().UI.GetDumbOutputUI()
	dui.Printf("Username:      %s\n", status.Username)
	dui.Printf("Logged in:     %s\n\n", c.boolString(status.LoggedInProvisioned, "yes", "no"))
	dui.Printf("Device name:   %s\n", status.DeviceName)
	dui.Printf("Device ID:     %s\n", status.DeviceID)
	dui.Printf("Device status: %s\n\n", status.DeviceStatus)
	dui.Printf("Local Keybase Keychain: %s\n", c.boolString(status.PassphraseStreamCached, "unlocked", "locked"))
	dui.Printf("\nKBFS:\n")
	dui.Printf("    status:    %s\n", c.boolString(status.KBFS.Running, "running", "not running"))
	dui.Printf("    version:   %s\n", status.KBFS.Version)
	dui.Printf("    log:       %s\n", status.KBFS.Log)
	dui.Printf("\nService:\n")
	dui.Printf("    status:    %s\n", c.boolString(status.Service.Running, "running", "not running"))
	dui.Printf("    version:   %s\n", status.Service.Version)
	dui.Printf("    log:       %s\n", status.Service.Log)
	dui.Printf("\nClient:\n")
	dui.Printf("    version:   %s\n", status.Client.Version)
	dui.Printf("\nDesktop App:\n")
	dui.Printf("    status:    %s\n", c.boolString(status.Desktop.Running, "running", "not running"))
	return nil
}

func (c *CmdNStatus) boolString(b bool, t, f string) string {
	if b {
		return t
	}
	return f
}

func (c *CmdNStatus) client() {
	dui := c.G().UI.GetDumbOutputUI()
	dui.Printf("Client:\n")
	dui.Printf("\tversion:\t%s\n", libkb.VersionString())
}

func (c *CmdNStatus) GetUsage() libkb.Usage {
	return libkb.Usage{
		Config: true,
		API:    true,
	}
}
<commit_msg>Tweak terminal output formatting<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package client

import (
	"encoding/json"
	"path"

	"golang.org/x/net/context"

	"github.com/keybase/cli"
	"github.com/keybase/client/go/install"
	"github.com/keybase/client/go/libcmdline"
	"github.com/keybase/client/go/libkb"
)

func NewCmdNStatus(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command {
	return cli.Command{
		Name:  "nstatus",
		Usage: "Show information about current user",
		Action: func(c *cli.Context) {
			cl.ChooseCommand(&CmdNStatus{Contextified: libkb.NewContextified(g)}, "nstatus", c)
		},
		Flags: []cli.Flag{
			cli.BoolFlag{
				Name:  "j, json",
				Usage: "Output status as JSON",
			},
		},
	}
}

type CmdNStatus struct {
	libkb.Contextified
	json bool
}

func (c *CmdNStatus) ParseArgv(ctx *cli.Context) error {
	if len(ctx.Args()) > 0 {
		return UnexpectedArgsError("status")
	}
	c.json = ctx.Bool("json")
	return nil
}

type fstatus struct {
	Username               string
	UserID                 string
	DeviceID               string
	DeviceName             string
	DeviceStatus           string
	LoggedInProvisioned    bool `json:"LoggedIn"`
	PassphraseStreamCached bool `json:"KeychainUnlocked"`
	ConfigPath             string

	Client struct {
		Version string
	}
	Service struct {
		Version string
		Running bool
		Pid     string
		Log     string
	}
	KBFS struct {
		Version string
		Running bool
		Pid     string
		Log     string
	}
	Desktop struct {
		Running bool
	}
}

func (c *CmdNStatus) Run() error {
	status, err := c.load()
	if err != nil {
		return err
	}

	return c.output(status)
}

func (c *CmdNStatus) load() (*fstatus, error) {
	var status fstatus

	status.Client.Version = libkb.VersionString()

	cli, err := GetConfigClient(c.G())
	if err != nil {
		return nil, err
	}

	curStatus, err := cli.GetCurrentStatus(context.TODO(), 0)
	if err != nil {
		return nil, err
	}

	status.LoggedInProvisioned = curStatus.LoggedIn
	if curStatus.User != nil {
		status.Username = curStatus.User.Username
		status.UserID = curStatus.User.Uid.String()
	}

	extStatus, err := cli.GetExtendedStatus(context.TODO(), 0)
	if err != nil {
		return nil, err
	}

	config, err := cli.GetConfig(context.TODO(), 0)
	if err != nil {
		return nil, err
	}

	status.ConfigPath = config.ConfigPath
	status.Service.Version = config.Version

	status.DeviceID = extStatus.DeviceID.String()
	status.DeviceName = extStatus.DeviceName
	status.DeviceStatus = extStatus.DeviceStatus

	if extStatus.Standalone {
		status.Service.Running = false
	} else {
		status.Service.Running = true
		status.Service.Log = path.Join(extStatus.LogDir, c.serviceLogFilename())
	}

	status.PassphraseStreamCached = extStatus.PassphraseStreamCached

	kbfsVersion, err := install.KBFSBundleVersion(c.G(), "")
	if err == nil {
		status.KBFS.Version = kbfsVersion
	}
	status.KBFS.Log = path.Join(extStatus.LogDir, c.kbfsLogFilename())

	status.Desktop.Running = extStatus.DesktopUIConnected

	// set anything os-specific:
	if err := c.osSpecific(&status); err != nil {
		return nil, err
	}

	return &status, nil
}

func (c *CmdNStatus) output(status *fstatus) error {
	if c.json {
		return c.outputJSON(status)
	}

	return c.outputTerminal(status)
}

func (c *CmdNStatus) outputJSON(status *fstatus) error {
	b, err := json.MarshalIndent(status, "", "    ")
	if err != nil {
		return err
	}
	dui := c.G().UI.GetDumbOutputUI()
	_, err = dui.Printf(string(b) + "\n")
	return err
}

/*

Username: chris
Device name: ubuntu-work-vm
User ID: 23260c2ce19420f97b58d7d95b68ca00
Device ID: 829493463c83fd2560d1964948a6df18
Local Keybase Keychain:  unlocked
KBFS:
   status: running, connected, mounted
   version: 1.0.8-1123123213
   log:  /home/chris/.cache/keybase/keybase.kbfs.log
Service:
   status: running, connected
   version: 1.0.8-1123123213
   log:  /home/chris/.cache/keybase/keybase.log
Client:
   version: 1.0.8-12312321312
Electron:
   status: running

*/

/*

   logged out:

   available users:
    chris
        - whatever info
    max
        - whatever info

*/

func (c *CmdNStatus) outputTerminal(status *fstatus) error {
	dui := c.G().UI.GetDumbOutputUI()
	dui.Printf("Username:      %s\n", status.Username)
	dui.Printf("Logged in:     %s\n\n", c.boolString(status.LoggedInProvisioned, "yes", "no"))
	dui.Printf("Device name:   %s\n", status.DeviceName)
	dui.Printf("Device ID:     %s\n", status.DeviceID)
	dui.Printf("Device status: %s\n\n", status.DeviceStatus)
	dui.Printf("Local Keybase Keychain: %s\n", c.boolString(status.PassphraseStreamCached, "unlocked", "locked"))
	dui.Printf("\nKBFS:\n")
	dui.Printf("    status:    %s\n", c.boolString(status.KBFS.Running, "running", "not running"))
	dui.Printf("    version:   %s\n", status.KBFS.Version)
	dui.Printf("    log:       %s\n", status.KBFS.Log)
	dui.Printf("\nService:\n")
	dui.Printf("    status:    %s\n", c.boolString(status.Service.Running, "running", "not running"))
	dui.Printf("    version:   %s\n", status.Service.Version)
	dui.Printf("    log:       %s\n", status.Service.Log)
	dui.Printf("\nClient:\n")
	dui.Printf("    version:   %s\n", status.Client.Version)
	dui.Printf("\nDesktop App:\n")
	dui.Printf("    status:    %s\n\n", c.boolString(status.Desktop.Running, "running", "not running"))
	dui.Printf("Config path:   %s\n", status.ConfigPath)
	return nil
}

func (c *CmdNStatus) boolString(b bool, t, f string) string {
	if b {
		return t
	}
	return f
}

func (c *CmdNStatus) client() {
	dui := c.G().UI.GetDumbOutputUI()
	dui.Printf("Client:\n")
	dui.Printf("\tversion:\t%s\n", libkb.VersionString())
}

func (c *CmdNStatus) GetUsage() libkb.Usage {
	return libkb.Usage{
		Config: true,
		API:    true,
	}
}
<|endoftext|>


<commit_before>package goparse

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"reflect"
	"strconv"
	"strings"
)

// StructDesc contains description of parsed struct
type StructDesc struct {
	Name  string
	Field []StructField
}

// StructField describes field itself
type StructField struct {
	Name      string
	Type      string
	Tags      []string
	TagParams map[string]string
	TagGt     map[string]int
	TagLt     map[string]int
}

func getTypeName(t ast.Expr) string {
	switch e := t.(type) {
	case *ast.Ident:
		return e.Name
	case *ast.ArrayType:
		return "[]" + getTypeName(e.Elt)
	case *ast.StarExpr:
		return "*" + getTypeName(e.X)
	case *ast.MapType:
		return fmt.Sprintf("map[%s]%s", getTypeName(e.Key), getTypeName(e.Value))
	}
	return "unknown"
}

// GetFileStructs returns structs descriptions from parsed go file
func GetFileStructs(filename string, prefix string, tag string) ([]StructDesc, error) {
	result := make([]StructDesc, 0, 5)

	fset := token.NewFileSet()

	f, err := parser.ParseFile(fset, filename, nil, 0)
	if nil != err {
		return result, err
	}

	for i := range f.Decls {
		if g, ok := f.Decls[i].(*ast.GenDecl); ok {
			for _, s := range g.Specs {
				if ts, ok := s.(*ast.TypeSpec); ok {
					if "" == prefix || strings.HasPrefix(ts.Name.String(), prefix) {
						if tt, ok := ts.Type.(*ast.StructType); ok {
							newStruct := StructDesc{Name: ts.Name.String(), Field: make([]StructField, 0, len(tt.Fields.List))}
							for _, field := range tt.Fields.List {
								newField := StructField{}
								if len(field.Names) < 1 {
									continue
								}
								newField.Name = field.Names[0].Name
								newField.Type = getTypeName(field.Type)
								newField.Tags = []string{}
								newField.TagParams = map[string]string{}
								newField.TagGt = map[string]int{}
								newField.TagLt = map[string]int{}

								if nil != field.Tag {
									tags := strings.Split(reflect.StructTag(strings.Trim(field.Tag.Value, "`")).Get(tag), ",")

									for _, tag := range tags {
										ts := strings.SplitN(tag, "=", 2)
										if len(ts) == 2 {
											newField.TagParams[ts[0]] = ts[1]
										}
										if len(ts) == 1 {
											ts = strings.SplitN(tag, ">", 2)
											if len(ts) == 2 {
												newField.TagGt[ts[0]], _ = strconv.Atoi(ts[1])
											}
										}
										if len(ts) == 1 {
											ts = strings.SplitN(tag, "<", 2)
											if len(ts) == 2 {
												newField.TagLt[ts[0]], _ = strconv.Atoi(ts[1])
											}
										}
										if len(ts) == 1 {
											newField.Tags = append(newField.Tags, ts[0])
										}

									}
								}
								newStruct.Field = append(newStruct.Field, newField)
							}
							result = append(result, newStruct)
						}
					}
				}
			}
		}
	}

	return result, nil
}
<commit_msg>Support types like "time.Time"<commit_after>package goparse

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"reflect"
	"strconv"
	"strings"
)

// StructDesc contains description of parsed struct
type StructDesc struct {
	Name  string
	Field []StructField
}

// StructField describes field itself
type StructField struct {
	Name      string
	Type      string
	Tags      []string
	TagParams map[string]string
	TagGt     map[string]int
	TagLt     map[string]int
}

func getTypeName(t ast.Expr) string {
	switch e := t.(type) {
	case *ast.Ident:
		return e.Name
	case *ast.ArrayType:
		return "[]" + getTypeName(e.Elt)
	case *ast.StarExpr:
		return "*" + getTypeName(e.X)
	case *ast.MapType:
		return fmt.Sprintf("map[%s]%s", getTypeName(e.Key), getTypeName(e.Value))
	case *ast.SelectorExpr:
		return fmt.Sprintf("%s.%s", e.X.(*ast.Ident).Name, e.Sel.Name)
	}
	return "unknown"
}

// GetFileStructs returns structs descriptions from parsed go file
func GetFileStructs(filename string, prefix string, tag string) ([]StructDesc, error) {
	result := make([]StructDesc, 0, 5)

	fset := token.NewFileSet()

	f, err := parser.ParseFile(fset, filename, nil, 0)
	if nil != err {
		return result, err
	}

	for i := range f.Decls {
		if g, ok := f.Decls[i].(*ast.GenDecl); ok {
			for _, s := range g.Specs {
				if ts, ok := s.(*ast.TypeSpec); ok {
					if "" == prefix || strings.HasPrefix(ts.Name.String(), prefix) {
						if tt, ok := ts.Type.(*ast.StructType); ok {
							newStruct := StructDesc{Name: ts.Name.String(), Field: make([]StructField, 0, len(tt.Fields.List))}
							for _, field := range tt.Fields.List {
								newField := StructField{}
								if len(field.Names) < 1 {
									continue
								}

								newField.Name = field.Names[0].Name
								newField.Type = getTypeName(field.Type)
								newField.Tags = []string{}
								newField.TagParams = map[string]string{}
								newField.TagGt = map[string]int{}
								newField.TagLt = map[string]int{}

								if nil != field.Tag {
									tags := strings.Split(reflect.StructTag(strings.Trim(field.Tag.Value, "`")).Get(tag), ",")

									for _, tag := range tags {
										ts := strings.SplitN(tag, "=", 2)
										if len(ts) == 2 {
											newField.TagParams[ts[0]] = ts[1]
										}
										if len(ts) == 1 {
											ts = strings.SplitN(tag, ">", 2)
											if len(ts) == 2 {
												newField.TagGt[ts[0]], _ = strconv.Atoi(ts[1])
											}
										}
										if len(ts) == 1 {
											ts = strings.SplitN(tag, "<", 2)
											if len(ts) == 2 {
												newField.TagLt[ts[0]], _ = strconv.Atoi(ts[1])
											}
										}
										if len(ts) == 1 {
											newField.Tags = append(newField.Tags, ts[0])
										}

									}
								}
								newStruct.Field = append(newStruct.Field, newField)
							}
							result = append(result, newStruct)
						}
					}
				}
			}
		}
	}

	return result, nil
}
<|endoftext|>


<commit_before>package ase

import (
	"bytes"
	"encoding/binary"
	"io"
	"unicode/utf16"
)

type Group struct {
	nameLen uint16
	Name    string
	Colors  []Color
}

func (group *Group) read(r io.Reader) (err error) {
	if err = group.readNameLen(r); err != nil {
		return
	}

	return group.readName(r)
}

func (group *Group) readNameLen(r io.Reader) error {
	return binary.Read(r, binary.BigEndian, &group.nameLen)
}

func (group *Group) readName(r io.Reader) (err error) {
	//	make array for our color name based on block length
	name := make([]uint16, group.nameLen)
	if err = binary.Read(r, binary.BigEndian, &name); err != nil {
		return
	}

	//	decode our name. we trim off the last byte since it's zero terminated
	group.Name = string(utf16.Decode(name[:len(name)-1]))

	return
}

func (group *Group) write(w io.Writer) (err error) {

	// Write group start headers (block entry, block length,  nameLen, name)
	if err = group.writeBlockStart(w); err != nil {
		return
	}

	if err = group.writeBlockLength(w); err != nil {
		return
	}

	if err = group.writeNameLen(w); err != nil {
		return
	}
	if err = group.writeName(w); err != nil {
		return
	}

	// Write group's colors
	for _, color := range group.Colors {
		if err = color.write(w); err != nil {
			return err
		}
	}

	// Write the group's closing headers
	if err = group.writeBlockEnd(w); err != nil {
		return
	}

	return nil
}

func (group *Group) writeBlockStart(w io.Writer) (err error) {
	return binary.Write(w, binary.BigEndian, groupStart)
}

func (group *Group) writeBlockEnd(w io.Writer) (err error) {
	return binary.Write(w, binary.BigEndian, groupEnd)
}

// Encode the color's name length.
func (group *Group) writeNameLen(w io.Writer) (err error) {
	// Adding one to the name length accounts for the zero-terminated character.
	return binary.Write(w, binary.BigEndian, group.NameLen()+1)
}

// Encode the group's name.
func (group *Group) writeName(w io.Writer) (err error) {
	name := utf16.Encode([]rune(group.Name))
	name = append(name, uint16(0))
	return binary.Write(w, binary.BigEndian, name)
}

// Helper function that returns the length of a group's name.
func (group *Group) NameLen() uint16 {
	return uint16(len(group.Name))
}

// Write color's block length as a part of the ASE encoding.
func (group *Group) writeBlockLength(w io.Writer) (err error) {
	blockLength := group.calculateBlockLength()
	if err = binary.Write(w, binary.BigEndian, blockLength); err != nil {
		return err
	}
	return
}

// Calculates the block length to be written based on the color's attributes.
func (group *Group) calculateBlockLength() int32 {
	buf := new(bytes.Buffer)
	group.writeNameLen(buf)
	group.writeName(buf)
	return int32(buf.Len())
}
<commit_msg>(encode)include zero termination for writing a group end block header<commit_after>package ase

import (
	"bytes"
	"encoding/binary"
	"io"
	"unicode/utf16"
)

type Group struct {
	nameLen uint16
	Name    string
	Colors  []Color
}

func (group *Group) read(r io.Reader) (err error) {
	if err = group.readNameLen(r); err != nil {
		return
	}

	return group.readName(r)
}

func (group *Group) readNameLen(r io.Reader) error {
	return binary.Read(r, binary.BigEndian, &group.nameLen)
}

func (group *Group) readName(r io.Reader) (err error) {
	//	make array for our color name based on block length
	name := make([]uint16, group.nameLen)
	if err = binary.Read(r, binary.BigEndian, &name); err != nil {
		return
	}

	//	decode our name. we trim off the last byte since it's zero terminated
	group.Name = string(utf16.Decode(name[:len(name)-1]))

	return
}

func (group *Group) write(w io.Writer) (err error) {

	// Write group start headers (block entry, block length,  nameLen, name)
	if err = group.writeBlockStart(w); err != nil {
		return
	}

	if err = group.writeBlockLength(w); err != nil {
		return
	}

	if err = group.writeNameLen(w); err != nil {
		return
	}
	if err = group.writeName(w); err != nil {
		return
	}

	// Write group's colors
	for _, color := range group.Colors {
		if err = color.write(w); err != nil {
			return err
		}
	}

	// Write the group's closing headers
	if err = group.writeBlockEnd(w); err != nil {
		return
	}

	return nil
}

// Wrapper around writing a group start header.
func (group *Group) writeBlockStart(w io.Writer) (err error) {
	return binary.Write(w, binary.BigEndian, groupStart)
}

// Wrapper around writing a group end header.
func (group *Group) writeBlockEnd(w io.Writer) (err error) {
	// First writes the groupEnd block followed by a terminating zero.
	binary.Write(w, binary.BigEndian, groupEnd)
	return binary.Write(w, binary.BigEndian, uint16(0x0000))
}

// Encode the color's name length.
func (group *Group) writeNameLen(w io.Writer) (err error) {
	// Adding one to the name length accounts for the zero-terminated character.
	return binary.Write(w, binary.BigEndian, group.NameLen()+1)
}

// Encode the group's name.
func (group *Group) writeName(w io.Writer) (err error) {
	name := utf16.Encode([]rune(group.Name))
	name = append(name, uint16(0))
	return binary.Write(w, binary.BigEndian, name)
}

// Helper function that returns the length of a group's name.
func (group *Group) NameLen() uint16 {
	return uint16(len(group.Name))
}

// Write color's block length as a part of the ASE encoding.
func (group *Group) writeBlockLength(w io.Writer) (err error) {
	blockLength := group.calculateBlockLength()
	if err = binary.Write(w, binary.BigEndian, blockLength); err != nil {
		return err
	}
	return
}

// Calculates the block length to be written based on the color's attributes.
func (group *Group) calculateBlockLength() int32 {
	buf := new(bytes.Buffer)
	group.writeNameLen(buf)
	group.writeName(buf)
	return int32(buf.Len())
}
<|endoftext|>


<commit_before>// Copyright 2015 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package mux

import (
	"net/http"
)

// 封装ServeMux，使所有添加的路由项的匹配模式都带上指定的前缀。
//  p := srv.Prefix("/api")
//  p.Get("/users")  // 相当于 srv.Get("/api/users")
//  p.Get("/user/1") // 相当于 srv.Get("/api/user/1")
//
// 可以直接使用ServeMux.Prefix()方法获取一个Prefix实例。
type Prefix struct {
	mux    *ServeMux
	prefix string
}

// Add相当于ServeMux.Add(prefix+pattern, h, "POST"...)的简易写法
func (p *Prefix) Add(pattern string, h http.Handler, methods ...string) *Prefix {
	p.mux.Add(p.prefix+pattern, h, methods...)
	return p
}

// Get相当于ServeMux.Get(prefix+pattern, h)的简易写法
func (p *Prefix) Get(pattern string, h http.Handler) *Prefix {
	p.mux.Get(p.prefix+pattern, h)
	return p
}

// Post相当于ServeMux.Post(prefix+pattern, h)的简易写法
func (p *Prefix) Post(pattern string, h http.Handler) *Prefix {
	p.mux.Post(p.prefix+pattern, h)
	return p
}

// Delete相当于ServeMux.Delete(prefix+pattern, h)的简易写法
func (p *Prefix) Delete(pattern string, h http.Handler) *Prefix {
	p.mux.Delete(p.prefix+pattern, h)
	return p
}

// Put相当于ServeMux.Put(prefix+pattern, h)的简易写法
func (p *Prefix) Put(pattern string, h http.Handler) *Prefix {
	p.mux.Put(p.prefix+pattern, h)
	return p
}

// Any相当于ServeMux.Any(prefix+pattern, h)的简易写法
func (p *Prefix) Any(pattern string, h http.Handler) *Prefix {
	p.mux.Any(p.prefix+pattern, h)
	return p
}

// AddFunc功能同ServeMux.AddFunc(prefix+pattern, fun, ...)
func (p *Prefix) AddFunc(pattern string, fun func(http.ResponseWriter, *http.Request), methods ...string) *Prefix {
	p.mux.AddFunc(p.prefix+pattern, fun, methods...)
	return p
}

// GetFunc相当于ServeMux.GetFunc(prefix+pattern, func)的简易写法
func (p *Prefix) GetFunc(pattern string, fun func(http.ResponseWriter, *http.Request)) *Prefix {
	p.mux.GetFunc(p.prefix+pattern, fun)
	return p
}

// PutFunc相当于ServeMux.PutFunc(prefix+pattern, func)的简易写法
func (p *Prefix) PutFunc(pattern string, fun func(http.ResponseWriter, *http.Request)) *Prefix {
	p.mux.PutFunc(p.prefix+pattern, fun)
	return p
}

// PostFunc相当于ServeMux.PostFunc(prefix+pattern, func)的简易写法
func (p *Prefix) PostFunc(pattern string, fun func(http.ResponseWriter, *http.Request)) *Prefix {
	p.mux.PostFunc(p.prefix+pattern, fun)
	return p
}

// DeleteFunc相当于ServeMux.DeleteFunc(prefix+pattern, func)的简易写法
func (p *Prefix) DeleteFunc(pattern string, fun func(http.ResponseWriter, *http.Request)) *Prefix {
	p.mux.DeleteFunc(p.prefix+pattern, fun)
	return p
}

// AnyFunc相当于ServeMux.AnyFunc(prefix+pattern, func)的简易写法
func (p *Prefix) AnyFunc(pattern string, fun func(http.ResponseWriter, *http.Request)) *Prefix {
	p.mux.AnyFunc(p.prefix+pattern, fun)
	return p
}

// AnyFunc相当于ServeMux.Remove(prefix+pattern, methods...)的简易写法
func (p *Prefix) Remove(pattern string, methods ...string) {
	p.mux.Remove(p.prefix+pattern, methods...)
}
<commit_msg>mv group.go prefix.go<commit_after><|endoftext|>


<commit_before>package kubernetes

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"math"
)

type logReadCloser struct {
	labels       []string
	labelLength  int
	readClosers  []io.ReadCloser
	eof          []bool
	buffer       bytes.Buffer
	dataChannel  chan []byte
	stopChannels []chan struct{}
	eofChannel   chan int
}

// NewLogReadCloser reads from multiple io.ReadCloser, where data is available,
// and annotates each line with the reader's label
func NewLogReadCloser(readClosersWithLabel map[io.ReadCloser]string) io.ReadCloser {
	stopChannels := make([]chan struct{}, len(readClosersWithLabel))
	labels := make([]string, len(readClosersWithLabel))
	readClosers := make([]io.ReadCloser, len(readClosersWithLabel))

	i := 0
	labelLength := 0
	for readCloser, label := range readClosersWithLabel {
		stopChannels[i] = make(chan struct{})
		readClosers[i] = readCloser
		labels[i] = label
		labelLength = int(math.Max(float64(labelLength), float64(len(label))))
		i++
	}

	l := logReadCloser{
		readClosers:  readClosers,
		labels:       labels,
		labelLength:  labelLength,
		dataChannel:  make(chan []byte),
		stopChannels: stopChannels,
		eofChannel:   make(chan int),
		eof:          make([]bool, len(readClosers)),
	}

	for idx := range l.readClosers {
		go l.readInput(idx)
	}

	return &l
}

func (l *logReadCloser) Read(p []byte) (int, error) {
	if len(p) <= l.buffer.Len() {
		return l.readInternalBuffer(p)
	}

	// if there's data available to read, read it,
	// otherwise block
	byteCount := 0
	if l.buffer.Len() > 0 {
		n, err := l.readInternalBuffer(p)
		if err != nil {
			return n, err
		}
		byteCount += n
	} else {
		// block on read or EOF
		received := false
		for !received && !l.isEOF() {
			select {
			case data := <-l.dataChannel:
				l.buffer.Write(data)
				received = true
			case idx := <-l.eofChannel:
				l.eof[idx] = true
			}
		}
	}

	// check if there's more data to read, without blocking
	empty := false
	for !empty && l.buffer.Len() < len(p) {
		select {
		case data := <-l.dataChannel:
			l.buffer.Write(data)
		case idx := <-l.eofChannel:
			l.eof[idx] = true
		default:
			empty = true
		}
	}

	return l.readInternalBuffer(p[byteCount:])
}

func (l *logReadCloser) Close() error {
	for i, rc := range l.readClosers {
		err := rc.Close()
		if err != nil {
			return err
		}

		// synchronous stop:
		// the routines write to dataChannel which will be closed by this thread
		select {
		case <-l.stopChannels[i]:
			break
		}
		close(l.stopChannels[i])
	}

	close(l.dataChannel)
	close(l.eofChannel)
	return nil
}

func (l *logReadCloser) readInternalBuffer(p []byte) (int, error) {
	n, err := l.buffer.Read(p)
	if err == io.EOF && !l.isEOF() {
		return n, nil
	}
	return n, err
}

func (l *logReadCloser) readInput(idx int) {
	reader := bufio.NewReader(l.readClosers[idx])
	for {
		line, err := reader.ReadBytes('\n')
		if err == io.EOF {
			if len(line) > 0 {
				l.dataChannel <- l.annotateLine(idx, line)
			}
			l.eofChannel <- idx
			break
		}
		if err != nil {
			// error, exit
			break
		}
		l.dataChannel <- l.annotateLine(idx, line)
	}

	// signal the routine won't write to dataChannel
	l.stopChannels[idx] <- struct{}{}
}

func (l *logReadCloser) annotateLine(idx int, line []byte) []byte {
	// do not annotate if it's the only reader
	if len(l.labels) == 1 {
		return line
	}
	return []byte(fmt.Sprintf("[%-*s] %v", l.labelLength, l.labels[idx], string(line)))
}

func (l *logReadCloser) isEOF() bool {
	for _, e := range l.eof {
		if !e {
			return false
		}
	}
	return true
}
<commit_msg>logReaderCloser: remove `stopChannels`<commit_after>package kubernetes

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"math"
	"sync"
)

type logReadCloser struct {
	labels      []string
	labelLength int
	readClosers []io.ReadCloser
	eof         []bool
	buffer      bytes.Buffer
	dataChannel chan []byte
	eofChannel  chan int
	wg          sync.WaitGroup
}

// NewLogReadCloser reads from multiple io.ReadCloser, where data is available,
// and annotates each line with the reader's label
func NewLogReadCloser(readClosersWithLabel map[io.ReadCloser]string) io.ReadCloser {
	n := len(readClosersWithLabel)
	labels := make([]string, n)
	readClosers := make([]io.ReadCloser, n)

	i := 0
	labelLength := 0
	for readCloser, label := range readClosersWithLabel {
		readClosers[i] = readCloser
		labels[i] = label
		labelLength = int(math.Max(float64(labelLength), float64(len(label))))
		i++
	}

	l := logReadCloser{
		readClosers: readClosers,
		labels:      labels,
		labelLength: labelLength,
		dataChannel: make(chan []byte),
		eofChannel:  make(chan int),
		eof:         make([]bool, len(readClosers)),
	}

	l.wg.Add(n)

	for idx := range l.readClosers {
		go l.readInput(idx)
	}

	return &l
}

func (l *logReadCloser) Read(p []byte) (int, error) {
	if len(p) <= l.buffer.Len() {
		return l.readInternalBuffer(p)
	}

	// if there's data available to read, read it,
	// otherwise block
	byteCount := 0
	if l.buffer.Len() > 0 {
		n, err := l.readInternalBuffer(p)
		if err != nil {
			return n, err
		}
		byteCount += n
	} else {
		// block on read or EOF
		received := false
		for !received && !l.isEOF() {
			select {
			case data := <-l.dataChannel:
				l.buffer.Write(data)
				received = true
			case idx := <-l.eofChannel:
				l.eof[idx] = true
			}
		}
	}

	// check if there's more data to read, without blocking
	empty := false
	for !empty && l.buffer.Len() < len(p) {
		select {
		case data := <-l.dataChannel:
			l.buffer.Write(data)
		case idx := <-l.eofChannel:
			l.eof[idx] = true
		default:
			empty = true
		}
	}

	return l.readInternalBuffer(p[byteCount:])
}

func (l *logReadCloser) Close() error {
	for _, rc := range l.readClosers {
		err := rc.Close()
		if err != nil {
			return err
		}
	}

	l.wg.Wait()

	close(l.dataChannel)
	close(l.eofChannel)
	return nil
}

func (l *logReadCloser) readInternalBuffer(p []byte) (int, error) {
	n, err := l.buffer.Read(p)
	if err == io.EOF && !l.isEOF() {
		return n, nil
	}
	return n, err
}

func (l *logReadCloser) readInput(idx int) {
	defer l.wg.Done()
	reader := bufio.NewReader(l.readClosers[idx])
	for {
		line, err := reader.ReadBytes('\n')
		if err == io.EOF {
			if len(line) > 0 {
				l.dataChannel <- l.annotateLine(idx, line)
			}
			l.eofChannel <- idx
			break
		}
		if err != nil {
			// error, exit
			break
		}
		l.dataChannel <- l.annotateLine(idx, line)
	}
}

func (l *logReadCloser) annotateLine(idx int, line []byte) []byte {
	// do not annotate if it's the only reader
	if len(l.labels) == 1 {
		return line
	}
	return []byte(fmt.Sprintf("[%-*s] %v", l.labelLength, l.labels[idx], string(line)))
}

func (l *logReadCloser) isEOF() bool {
	for _, e := range l.eof {
		if !e {
			return false
		}
	}
	return true
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"time"
)

type Group struct {
	*EventLoop
	createdAt       time.Time
	proto           *Prototype
	targetAccepting int
	targetProcesses int
	startingSet     processSet
	acceptingSet    processSet
	notAcceptingSet processSet
	stoppingSet     processSet
}

type processSet map[*Process]bool

func (self processSet) Add(p *Process) {
	self[p] = true
}

func (self processSet) Rem(p *Process) {
	delete(self, p)
}

// GetRand returns random element due to random ordering of range
func (self processSet) GetRand() *Process {
	var p *Process
	for p, _ = range self {
		break
	}
	return p
}

func (self processSet) Size() int {
	return len(self)
}

func NewGroup(proto *Prototype, n int) *Group {
	return &Group{
		EventLoop:       NewEventLoop(),
		createdAt:       time.Now(),
		proto:           proto,
		targetAccepting: n,
		targetProcesses: n,
		startingSet:     make(processSet),
		acceptingSet:    make(processSet),
		notAcceptingSet: make(processSet),
		stoppingSet:     make(processSet),
	}
}

func (self *Group) String() string {
	const layout = "2006-01-02@15:04:05"
	return fmt.Sprintf("[group %v] ", self.createdAt.Format(layout))
}

func (self *Group) Run() {
	self.scheduleThink()
	self.EventLoop.Run()
	// TODO: Stop event loop when process exits
}

func (self *Group) Stop() {
	self.EventLoop.Stop()
	log.Print(self, "Group terminated")
}

// Reduce reduces the number of running processes in the group by 1
func (self *Group) Reduce() {
	// Reduce the target count by 1
	self.targetAccepting = self.targetAccepting - 1
	self.targetProcesses = self.targetProcesses - 1

	self.scheduleThink()
}

func (self *Group) Increase() {
	self.targetAccepting = self.targetAccepting + 1
	self.targetProcesses = self.targetProcesses + 1

	self.scheduleThink()
}

// func (self *Group) IncrementAccept(i int) {
// 	self.targetAccepting = self.targetAccepting + i
// 	self.scheduleThink()
// }
//
// func (self *Group) IncrementProcesses(i int) {
// 	self.targetProcesses = self.targetProcesses + i
// 	self.scheduleThink()
// }

func (self *Group) scheduleThink() {
	self.NextTick(func() {
		self.think()
	})
}

func (self *Group) think() {
	if self.acceptingSet.Size() == self.targetAccepting && self.nonStoppingCount() == self.targetProcesses {
		if self.totalCount() == 0 {
			log.Print(self, "Terminating group (last process has exited)")
			self.Stop()
		}
		return
	}

	// Aim here is to do one operation per tick
	if self.acceptingSet.Size() < self.targetAccepting && self.notAcceptingSet.Size() > 0 {
		log.Print(self, "Transitioning a non-accepting process to accepting")
		p := self.notAcceptingSet.GetRand()
		p.StartAccepting()
		self.notAcceptingSet.Rem(p)
		self.acceptingSet.Add(p)
	} else if self.acceptingSet.Size() > self.targetAccepting {
		log.Print(self, "Transitioning an accepting process to non-accepting")
		p := self.acceptingSet.GetRand()
		p.StopAccepting()
		self.acceptingSet.Rem(p)
		self.notAcceptingSet.Add(p)
	} else if self.totalCount() < self.targetProcesses {
		log.Print(self, "Starting a new process")
		self.startProcess()
	} else if self.nonStoppingCount() > self.targetProcesses {
		if self.notAcceptingSet.Size() > 0 {
			log.Print(self, "Stopping a non-accepting process")
			p := self.notAcceptingSet.GetRand()
			p.Stop()
			self.notAcceptingSet.Rem(p)
			self.stoppingSet.Add(p)
		} else if self.acceptingSet.Size() > 0 {
			log.Print(self, "Stopping an accepting process, non non-accepting ones available")
			p := self.acceptingSet.GetRand()
			p.Stop()
			self.acceptingSet.Rem(p)
			self.stoppingSet.Add(p)
		}
	}

	// Schedule next check in 1s
	self.AddTimer(time.Second, func() {
		self.think()
	})
}

func (self *Group) nonStoppingCount() int {
	return (self.startingSet.Size() + self.acceptingSet.Size() + self.notAcceptingSet.Size())
}

func (self *Group) totalCount() int {
	return (self.startingSet.Size() + self.acceptingSet.Size() + self.notAcceptingSet.Size() + self.stoppingSet.Size())
}

func (self *Group) startProcess() {
	onStarted := make(chan bool)

	process := NewProcess(self.proto, onStarted)

	// Process is initally placed in the starting set
	self.startingSet.Add(process)

	// Processes notify on startup, but should not start accepting automatically
	go func() {
		<-onStarted
		self.NextTick(func() {
			self.startingSet.Rem(process)
			self.notAcceptingSet.Add(process)
		})
	}()

	// Remove process from all sets on exit
	process.OnExit(func() {
		self.NextTick(func() {
			self.startingSet.Rem(process)
			self.acceptingSet.Rem(process)
			self.notAcceptingSet.Rem(process)
			self.stoppingSet.Rem(process)
		})
		self.scheduleThink()
	})

	process.Start()
}
<commit_msg>Log state breakdown on each group tick, e.g.:<commit_after>package main

import (
	"fmt"
	"log"
	"time"
)

type Group struct {
	*EventLoop
	createdAt       time.Time
	proto           *Prototype
	targetAccepting int
	targetProcesses int
	startingSet     processSet
	acceptingSet    processSet
	notAcceptingSet processSet
	stoppingSet     processSet
}

type processSet map[*Process]bool

func (self processSet) Add(p *Process) {
	self[p] = true
}

func (self processSet) Rem(p *Process) {
	delete(self, p)
}

// GetRand returns random element due to random ordering of range
func (self processSet) GetRand() *Process {
	var p *Process
	for p, _ = range self {
		break
	}
	return p
}

func (self processSet) Size() int {
	return len(self)
}

func NewGroup(proto *Prototype, n int) *Group {
	return &Group{
		EventLoop:       NewEventLoop(),
		createdAt:       time.Now(),
		proto:           proto,
		targetAccepting: n,
		targetProcesses: n,
		startingSet:     make(processSet),
		acceptingSet:    make(processSet),
		notAcceptingSet: make(processSet),
		stoppingSet:     make(processSet),
	}
}

func (self *Group) String() string {
	const layout = "2006-01-02@15:04:05"
	return fmt.Sprintf("[group %v] ", self.createdAt.Format(layout))
}

func (self *Group) stateReport() string {
	return fmt.Sprintf("Processes: %v (Accepting: %v [target:%v], Not-accepting: %v, Starting: %v, Stopping: %v)", self.totalCount(), self.acceptingSet.Size(), self.targetAccepting, self.notAcceptingSet.Size(), self.startingSet.Size(), self.stoppingSet.Size())
}

func (self *Group) Run() {
	self.scheduleThink()
	self.EventLoop.Run()
	// TODO: Stop event loop when process exits
}

func (self *Group) Stop() {
	self.EventLoop.Stop()
	log.Print(self, "Group terminated")
}

// Reduce reduces the number of running processes in the group by 1
func (self *Group) Reduce() {
	// Reduce the target count by 1
	self.targetAccepting = self.targetAccepting - 1
	self.targetProcesses = self.targetProcesses - 1

	self.scheduleThink()
}

func (self *Group) Increase() {
	self.targetAccepting = self.targetAccepting + 1
	self.targetProcesses = self.targetProcesses + 1

	self.scheduleThink()
}

// func (self *Group) IncrementAccept(i int) {
// 	self.targetAccepting = self.targetAccepting + i
// 	self.scheduleThink()
// }
//
// func (self *Group) IncrementProcesses(i int) {
// 	self.targetProcesses = self.targetProcesses + i
// 	self.scheduleThink()
// }

func (self *Group) scheduleThink() {
	self.NextTick(func() {
		self.think()
	})
}

func (self *Group) think() {
	log.Print(self, self.stateReport())

	if self.acceptingSet.Size() == self.targetAccepting && self.nonStoppingCount() == self.targetProcesses {
		if self.totalCount() == 0 {
			log.Print(self, "Terminating group (last process has exited)")
			self.Stop()
		}
		return
	}

	// Aim here is to do one operation per tick
	if self.acceptingSet.Size() < self.targetAccepting && self.notAcceptingSet.Size() > 0 {
		log.Print(self, "Transitioning a non-accepting process to accepting")
		p := self.notAcceptingSet.GetRand()
		p.StartAccepting()
		self.notAcceptingSet.Rem(p)
		self.acceptingSet.Add(p)
	} else if self.acceptingSet.Size() > self.targetAccepting {
		log.Print(self, "Transitioning an accepting process to non-accepting")
		p := self.acceptingSet.GetRand()
		p.StopAccepting()
		self.acceptingSet.Rem(p)
		self.notAcceptingSet.Add(p)
	} else if self.totalCount() < self.targetProcesses {
		log.Print(self, "Starting a new process")
		self.startProcess()
	} else if self.nonStoppingCount() > self.targetProcesses {
		if self.notAcceptingSet.Size() > 0 {
			log.Print(self, "Stopping a non-accepting process")
			p := self.notAcceptingSet.GetRand()
			p.Stop()
			self.notAcceptingSet.Rem(p)
			self.stoppingSet.Add(p)
		} else if self.acceptingSet.Size() > 0 {
			log.Print(self, "Stopping an accepting process, non non-accepting ones available")
			p := self.acceptingSet.GetRand()
			p.Stop()
			self.acceptingSet.Rem(p)
			self.stoppingSet.Add(p)
		}
	}

	// Schedule next check in 1s
	self.AddTimer(time.Second, func() {
		self.think()
	})
}

func (self *Group) nonStoppingCount() int {
	return (self.startingSet.Size() + self.acceptingSet.Size() + self.notAcceptingSet.Size())
}

func (self *Group) totalCount() int {
	return (self.startingSet.Size() + self.acceptingSet.Size() + self.notAcceptingSet.Size() + self.stoppingSet.Size())
}

func (self *Group) startProcess() {
	onStarted := make(chan bool)

	process := NewProcess(self.proto, onStarted)

	// Process is initally placed in the starting set
	self.startingSet.Add(process)

	// Processes notify on startup, but should not start accepting automatically
	go func() {
		<-onStarted
		self.NextTick(func() {
			self.startingSet.Rem(process)
			self.notAcceptingSet.Add(process)
		})
	}()

	// Remove process from all sets on exit
	process.OnExit(func() {
		self.NextTick(func() {
			self.startingSet.Rem(process)
			self.acceptingSet.Rem(process)
			self.notAcceptingSet.Rem(process)
			self.stoppingSet.Rem(process)
		})
		self.scheduleThink()
	})

	process.Start()
}
<|endoftext|>