<commit_before>package main

import (
	"bytes"
	"container/list"
	"database/sql"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"html"
	"html/template"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"time"
)

type Import struct {
	Id         uint64 `col:"id"`
	PostId     uint64 `col:"post"`
	Source     string `col:"source"`
	Identifier string `col:"identifier"`
}

func NewImport() *Import {
	return &Import{0, 0, "", ""}
}

func ImportBySourceIdentifier(source, identifier string) (*Import, error) {
	row := db.QueryRow("SELECT id, post, source, identifier FROM import WHERE source = $1 AND identifier = $2 LIMIT 1",
		source, identifier)

	var id, postid uint64
	err := row.Scan(&id, &postid, &source, &identifier)
	if err != nil {
		return nil, err
	}

	i := &Import{id, postid, source, identifier}
	return i, nil
}

func (im *Import) Save() error {
	return db.Save(im, "import")
}

type Mutation struct {
	Start int
	End   int
	Html  string
}

type MutationList []Mutation

func (ml MutationList) Len() int {
	return len(ml)
}

func (ml MutationList) Less(i, j int) bool {
	mutI := ml[i]
	mutJ := ml[j]
	if mutI.Start < mutJ.Start {
		return true
	}
	if mutI.Start == mutJ.Start && mutI.End < mutJ.End {
		return true
	}
	return false
}

func (ml MutationList) Swap(i, j int) {
	ml[i], ml[j] = ml[j], ml[i]
}

func indicesForEntity(ent map[string]interface{}) (int, int) {
	indices := ent["indices"].([]interface{})
	s, e := indices[0].(float64), indices[1].(float64)
	return int(s), int(e)
}

func makeTweetMutations(data map[string]interface{}) MutationList {
	text := data["text"].(string)
	ents := data["entities"].(map[string]interface{})

	mutations := list.New()
	for _, entIf := range ents["user_mentions"].([]interface{}) {
		ent := entIf.(map[string]interface{})
		screenName := html.EscapeString(ent["screen_name"].(string))
		html := fmt.Sprintf(`<a href="https://twitter.com/%s" title="%s">@%s</a>`,
			screenName, html.EscapeString(ent["name"].(string)), screenName)
		start, end := indicesForEntity(ent)
		mutations.PushBack(Mutation{start, end, html})
	}
	for _, entIf := range ents["hashtags"].([]interface{}) {
		ent := entIf.(map[string]interface{})
		tagText := ent["text"].(string)
		html := fmt.Sprintf(`<a href="https://twitter.com/search?q=%%23%s">#%s</a>`,
			tagText, tagText)
		start, end := indicesForEntity(ent)
		mutations.PushBack(Mutation{start, end, html})
	}
	for _, urlEnts := range []interface{}{ents["urls"], ents["media"]} {
		if urlEnts == nil {
			continue
		}
		for _, entIf := range urlEnts.([]interface{}) {
			ent := entIf.(map[string]interface{})
			url := ent["expanded_url"].(string)
			if url == "" {
				url = ent["url"].(string)
			}
			start, end := indicesForEntity(ent)
			linkText := text[start:end]
			html := fmt.Sprintf(`<a href="%s">%s</a>`, url, linkText)
			mutations.PushBack(Mutation{start, end, html})
		}
	}

	// We don't strictly need to regexp this of course but the strings package
	// won't find *all* instances of a substring, only the first or last.
	// Sadface that we can't just use lookahead assertion too.
	ampsRE, _ := regexp.Compile(`&`)
	amps := ampsRE.FindAllStringIndex(text, -1)
	for _, ampIndices := range amps {
		rest := text[ampIndices[1]:]
		matched, _ := regexp.MatchString("^(?:lt|gt|amp);", rest)
		if !matched {
			mutations.PushBack(Mutation{ampIndices[0], ampIndices[1], "&"})
		}
	}

	nlRE, _ := regexp.Compile(`\n`)
	nls := nlRE.FindAllStringIndex(text, -1)
	for _, nlIndices := range nls {
		mutations.PushBack(Mutation{nlIndices[0], nlIndices[1], "
\n"})
	}

	mutList := make(MutationList, mutations.Len())
	for i, el := 0, mutations.Front(); el != nil; i, el = i+1, el.Next() {
		mutList[i] = el.Value.(Mutation)
	}

	return mutList
}

func mutateTweetText(data map[string]interface{}) string {
	text := data["text"].(string)
	mutations := makeTweetMutations(data)
	sort.Sort(mutations)

	var buf bytes.Buffer
	i := 0
	for _, mutation := range mutations {
		if i < mutation.Start {
			buf.WriteString(text[i:mutation.Start])
		}
		buf.WriteString(mutation.Html)
		i = mutation.End
	}
	// Include any trailing plain text.
	buf.WriteString(text[i:])

	return buf.String()
}

func ImportJson(path string) {
	logr.Debugln("Importing from Twitter export", path)
	jsons, err := ioutil.ReadDir(path)
	if err != nil {
		logr.Errln("Error finding Twitter export", path, "to import:", err.Error())
		return
	}

	count := 0
	for _, fileinfo := range jsons {
		if fileinfo.IsDir() {
			continue
		}
		if !strings.HasSuffix(fileinfo.Name(), "json") {
			continue
		}

		datafilepath := filepath.Join(path, fileinfo.Name())
		datafile, err := os.Open(datafilepath)
		if err != nil {
			logr.Errln("Error opening Twitter export file", datafilepath, ":", err.Error())
			return
		}

		var data map[string]interface{}
		dec := json.NewDecoder(datafile)
		err = dec.Decode(&data)
		if err != nil {
			logr.Errln("Error unmarshaling Twitter export file", datafilepath, ":", err.Error())
			return
		}

		if replyId, ok := data["in_reply_to_status_id_str"]; ok && replyId != nil && replyId.(string) != "" {
			continue
		}
		if retweeted, ok := data["retweeted"]; ok && retweeted != nil && retweeted.(bool) {
			continue
		}

		tweetId := data["id_str"].(string)
		im, err := ImportBySourceIdentifier("twitter", tweetId)
		if err == sql.ErrNoRows {
			im = NewImport()
			im.Source = "twitter"
			im.Identifier = tweetId
		} else if err != nil {
			logr.Errln("Error searching for existing imported post (twitter,", tweetId, "):", err.Error())
			return
		}

		var post *Post
		if im.PostId != 0 {
			post, err = PostById(im.PostId)
			if err != nil {
				logr.Errln("Error loading already-imported post", im.PostId, "for twitter post", im.Identifier, ":", err.Error())
				return
			}
		} else {
			post = NewPost()
		}

		tweetDate := data["created_at"].(string)
		post.Posted, err = time.Parse(time.RubyDate, tweetDate)
		if err != nil {
			logr.Errln("Error parsing publish time", tweetDate, "for twitter post", tweetId, ":", err.Error())
			return
		}

		post.Html = mutateTweetText(data)

		// TODO: store the source?
		// TODO: store the geoplace

		err = post.Save()
		if err != nil {
			logr.Errln("Error saving imported post:", err.Error())
			return
		}

		im.PostId = post.Id
		err = im.Save()
		if err != nil {
			logr.Errln("Error saving import notation for post", im.PostId, ":", err.Error())
			return
		}

		logr.Debugln("Imported post (twitter,", im.Identifier, ")")
		count++
	}

	logr.Debugln("Imported", count, "posts")
}

func ImportThinkup(path string) {
	logr.Debugln("Importing from Thinkup export", path)
	port, err := os.Open(path)
	if err != nil {
		logr.Errln("Error opening", path, "for import:", err.Error())
		return
	}

	r := csv.NewReader(port)
	// There may be missing header columns, so turn off field count checking.
	r.FieldsPerRecord = -1

	head, err := r.Read()
	if err != nil {
		logr.Errln("Error reading from import file", path, ":", err.Error())
		return
	}

	count := 0
	for {
		record, err := r.Read()
		if err != nil {
			break
		}

		data := make(map[string]string)
		for i, field := range head {
			data[field] = record[i]
		}

		// TODO: import replies, once there's something reasonable to import them as.
		if data["in_reply_to_post_id"] != "" {
			logr.Debugln("Skipping post (twitter,", data["post_id"], ") as it is a reply")
			continue
		}
		// TODO: import repeats, once there's something reasonable to import them as.
		if data["in_retweet_of_post_id"] != "" {
			logr.Debugln("Skipping post (twitter,", data["post_id"], ") as it is a repeat")
			continue
		}

		// okay now what
		im, err := ImportBySourceIdentifier("twitter", data["post_id"])
		if err == sql.ErrNoRows {
			im = NewImport()
			im.Source = "twitter"
			im.Identifier = data["post_id"]
		} else if err != nil {
			logr.Errln("Error searching for existing imported post (twitter,", data["post_id"], "):", err.Error())
			return
		}

		var post *Post
		if im.PostId != 0 {
			post, err = PostById(im.PostId)
			if err != nil {
				logr.Errln("Error loading already-imported post", im.PostId, "for twitter post", im.Identifier, ":", err.Error())
				return
			}
		} else {
			post = NewPost()
		}

		post.Posted, err = time.Parse("2006-01-02 15:04:05", data["pub_date"])
		if err != nil {
			logr.Errln("Error parsing publish time", data["pub_date"], "for twitter post", data["post_id"], ":", err.Error())
			return
		}

		// TODO: make the links link.
		html := template.HTMLEscapeString(data["post_text"])
		html = strings.Replace(html, "\n", "
\n", -1)
		post.Html = html

		// TODO: store the source?
		// TODO: store the geoplace

		err = post.Save()
		if err != nil {
			logr.Errln("Error saving imported post:", err.Error())
			return
		}

		im.PostId = post.Id
		err = im.Save()
		if err != nil {
			logr.Errln("Error saving import notation for post", im.PostId, ":", err.Error())
			return
		}

		logr.Debugln("Imported post (twitter,", im.Identifier, ")")
		count++
	}
	if err != nil {
		logr.Errln("Error reading import records:", err.Error())
		return
	}

	logr.Debugln("Finished importing", count, "posts!")
}
<commit_msg>Use display URL for link text in imported posts<commit_after>package main

import (
	"bytes"
	"container/list"
	"database/sql"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"html"
	"html/template"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"time"
)

type Import struct {
	Id         uint64 `col:"id"`
	PostId     uint64 `col:"post"`
	Source     string `col:"source"`
	Identifier string `col:"identifier"`
}

func NewImport() *Import {
	return &Import{0, 0, "", ""}
}

func ImportBySourceIdentifier(source, identifier string) (*Import, error) {
	row := db.QueryRow("SELECT id, post, source, identifier FROM import WHERE source = $1 AND identifier = $2 LIMIT 1",
		source, identifier)

	var id, postid uint64
	err := row.Scan(&id, &postid, &source, &identifier)
	if err != nil {
		return nil, err
	}

	i := &Import{id, postid, source, identifier}
	return i, nil
}

func (im *Import) Save() error {
	return db.Save(im, "import")
}

type Mutation struct {
	Start int
	End   int
	Html  string
}

type MutationList []Mutation

func (ml MutationList) Len() int {
	return len(ml)
}

func (ml MutationList) Less(i, j int) bool {
	mutI := ml[i]
	mutJ := ml[j]
	if mutI.Start < mutJ.Start {
		return true
	}
	if mutI.Start == mutJ.Start && mutI.End < mutJ.End {
		return true
	}
	return false
}

func (ml MutationList) Swap(i, j int) {
	ml[i], ml[j] = ml[j], ml[i]
}

func indicesForEntity(ent map[string]interface{}) (int, int) {
	indices := ent["indices"].([]interface{})
	s, e := indices[0].(float64), indices[1].(float64)
	return int(s), int(e)
}

func makeTweetMutations(data map[string]interface{}) MutationList {
	text := data["text"].(string)
	ents := data["entities"].(map[string]interface{})

	mutations := list.New()
	for _, entIf := range ents["user_mentions"].([]interface{}) {
		ent := entIf.(map[string]interface{})
		screenName := html.EscapeString(ent["screen_name"].(string))
		html := fmt.Sprintf(`<a href="https://twitter.com/%s" title="%s">@%s</a>`,
			screenName, html.EscapeString(ent["name"].(string)), screenName)
		start, end := indicesForEntity(ent)
		mutations.PushBack(Mutation{start, end, html})
	}
	for _, entIf := range ents["hashtags"].([]interface{}) {
		ent := entIf.(map[string]interface{})
		tagText := ent["text"].(string)
		html := fmt.Sprintf(`<a href="https://twitter.com/search?q=%%23%s">#%s</a>`,
			tagText, tagText)
		start, end := indicesForEntity(ent)
		mutations.PushBack(Mutation{start, end, html})
	}
	for _, urlEnts := range []interface{}{ents["urls"], ents["media"]} {
		if urlEnts == nil {
			continue
		}
		for _, entIf := range urlEnts.([]interface{}) {
			ent := entIf.(map[string]interface{})
			url := ent["expanded_url"].(string)
			if url == "" {
				url = ent["url"].(string)
			}
			linkText := ent["display_url"].(string)
			html := fmt.Sprintf(`<a href="%s">%s</a>`, url, linkText)
			start, end := indicesForEntity(ent)
			mutations.PushBack(Mutation{start, end, html})
		}
	}

	// We don't strictly need to regexp this of course but the strings package
	// won't find *all* instances of a substring, only the first or last.
	// Sadface that we can't just use lookahead assertion too.
	ampsRE, _ := regexp.Compile(`&`)
	amps := ampsRE.FindAllStringIndex(text, -1)
	for _, ampIndices := range amps {
		rest := text[ampIndices[1]:]
		matched, _ := regexp.MatchString("^(?:lt|gt|amp);", rest)
		if !matched {
			mutations.PushBack(Mutation{ampIndices[0], ampIndices[1], "&"})
		}
	}

	nlRE, _ := regexp.Compile(`\n`)
	nls := nlRE.FindAllStringIndex(text, -1)
	for _, nlIndices := range nls {
		mutations.PushBack(Mutation{nlIndices[0], nlIndices[1], "
\n"})
	}

	mutList := make(MutationList, mutations.Len())
	for i, el := 0, mutations.Front(); el != nil; i, el = i+1, el.Next() {
		mutList[i] = el.Value.(Mutation)
	}

	return mutList
}

func mutateTweetText(data map[string]interface{}) string {
	text := data["text"].(string)
	mutations := makeTweetMutations(data)
	sort.Sort(mutations)

	var buf bytes.Buffer
	i := 0
	for _, mutation := range mutations {
		if i < mutation.Start {
			buf.WriteString(text[i:mutation.Start])
		}
		buf.WriteString(mutation.Html)
		i = mutation.End
	}
	// Include any trailing plain text.
	buf.WriteString(text[i:])

	return buf.String()
}

func ImportJson(path string) {
	logr.Debugln("Importing from Twitter export", path)
	jsons, err := ioutil.ReadDir(path)
	if err != nil {
		logr.Errln("Error finding Twitter export", path, "to import:", err.Error())
		return
	}

	count := 0
	for _, fileinfo := range jsons {
		if fileinfo.IsDir() {
			continue
		}
		if !strings.HasSuffix(fileinfo.Name(), "json") {
			continue
		}

		datafilepath := filepath.Join(path, fileinfo.Name())
		datafile, err := os.Open(datafilepath)
		if err != nil {
			logr.Errln("Error opening Twitter export file", datafilepath, ":", err.Error())
			return
		}

		var data map[string]interface{}
		dec := json.NewDecoder(datafile)
		err = dec.Decode(&data)
		if err != nil {
			logr.Errln("Error unmarshaling Twitter export file", datafilepath, ":", err.Error())
			return
		}

		if replyId, ok := data["in_reply_to_status_id_str"]; ok && replyId != nil && replyId.(string) != "" {
			continue
		}
		if retweeted, ok := data["retweeted"]; ok && retweeted != nil && retweeted.(bool) {
			continue
		}

		tweetId := data["id_str"].(string)
		im, err := ImportBySourceIdentifier("twitter", tweetId)
		if err == sql.ErrNoRows {
			im = NewImport()
			im.Source = "twitter"
			im.Identifier = tweetId
		} else if err != nil {
			logr.Errln("Error searching for existing imported post (twitter,", tweetId, "):", err.Error())
			return
		}

		var post *Post
		if im.PostId != 0 {
			post, err = PostById(im.PostId)
			if err != nil {
				logr.Errln("Error loading already-imported post", im.PostId, "for twitter post", im.Identifier, ":", err.Error())
				return
			}
		} else {
			post = NewPost()
		}

		tweetDate := data["created_at"].(string)
		post.Posted, err = time.Parse(time.RubyDate, tweetDate)
		if err != nil {
			logr.Errln("Error parsing publish time", tweetDate, "for twitter post", tweetId, ":", err.Error())
			return
		}

		post.Html = mutateTweetText(data)

		// TODO: store the source?
		// TODO: store the geoplace

		err = post.Save()
		if err != nil {
			logr.Errln("Error saving imported post:", err.Error())
			return
		}

		im.PostId = post.Id
		err = im.Save()
		if err != nil {
			logr.Errln("Error saving import notation for post", im.PostId, ":", err.Error())
			return
		}

		logr.Debugln("Imported post (twitter,", im.Identifier, ")")
		count++
	}

	logr.Debugln("Imported", count, "posts")
}

func ImportThinkup(path string) {
	logr.Debugln("Importing from Thinkup export", path)
	port, err := os.Open(path)
	if err != nil {
		logr.Errln("Error opening", path, "for import:", err.Error())
		return
	}

	r := csv.NewReader(port)
	// There may be missing header columns, so turn off field count checking.
	r.FieldsPerRecord = -1

	head, err := r.Read()
	if err != nil {
		logr.Errln("Error reading from import file", path, ":", err.Error())
		return
	}

	count := 0
	for {
		record, err := r.Read()
		if err != nil {
			break
		}

		data := make(map[string]string)
		for i, field := range head {
			data[field] = record[i]
		}

		// TODO: import replies, once there's something reasonable to import them as.
		if data["in_reply_to_post_id"] != "" {
			logr.Debugln("Skipping post (twitter,", data["post_id"], ") as it is a reply")
			continue
		}
		// TODO: import repeats, once there's something reasonable to import them as.
		if data["in_retweet_of_post_id"] != "" {
			logr.Debugln("Skipping post (twitter,", data["post_id"], ") as it is a repeat")
			continue
		}

		// okay now what
		im, err := ImportBySourceIdentifier("twitter", data["post_id"])
		if err == sql.ErrNoRows {
			im = NewImport()
			im.Source = "twitter"
			im.Identifier = data["post_id"]
		} else if err != nil {
			logr.Errln("Error searching for existing imported post (twitter,", data["post_id"], "):", err.Error())
			return
		}

		var post *Post
		if im.PostId != 0 {
			post, err = PostById(im.PostId)
			if err != nil {
				logr.Errln("Error loading already-imported post", im.PostId, "for twitter post", im.Identifier, ":", err.Error())
				return
			}
		} else {
			post = NewPost()
		}

		post.Posted, err = time.Parse("2006-01-02 15:04:05", data["pub_date"])
		if err != nil {
			logr.Errln("Error parsing publish time", data["pub_date"], "for twitter post", data["post_id"], ":", err.Error())
			return
		}

		// TODO: make the links link.
		html := template.HTMLEscapeString(data["post_text"])
		html = strings.Replace(html, "\n", "
\n", -1)
		post.Html = html

		// TODO: store the source?
		// TODO: store the geoplace

		err = post.Save()
		if err != nil {
			logr.Errln("Error saving imported post:", err.Error())
			return
		}

		im.PostId = post.Id
		err = im.Save()
		if err != nil {
			logr.Errln("Error saving import notation for post", im.PostId, ":", err.Error())
			return
		}

		logr.Debugln("Imported post (twitter,", im.Identifier, ")")
		count++
	}
	if err != nil {
		logr.Errln("Error reading import records:", err.Error())
		return
	}

	logr.Debugln("Finished importing", count, "posts!")
}
<|endoftext|>


<commit_before>// Package imstor enables you to create copies (or thumbnails) of your images and stores
// them along with the original image on your filesystem. The image and its
// copies are are stored in a file structure based on the (zero-prefixed, decimal)
// CRC 64 checksum of the original image. The last 2 characters of the checksum
// are used as the lvl 1 directory name.
//
// Example folder name and contents, given the checksum 08446744073709551615 and
// sizes named "small" and "large":
//
//  /configured/root/path/15/08446744073709551615/original.jpeg
//  /configured/root/path/15/08446744073709551615/small.jpeg
//  /configured/root/path/15/08446744073709551615/large.jpeg
package imstor

import (
	"errors"
	"fmt"
	"hash/crc64"
	"io/ioutil"
	"path"
	"path/filepath"
	"strings"

	"github.com/vincent-petithory/dataurl"
)

var crcTable = crc64.MakeTable(crc64.ISO)

const (
	originalImageName = "original"
)

type storage struct {
	conf    *Config
	resizer Resizer
}

// Storage is the engine that can be used to store images and retrieve their paths
type Storage interface {
	Store(mediaType string, data []byte) (string, error)
	StoreDataURL(string) (string, error)
	Checksum([]byte) string
	ChecksumDataURL(string) (string, error)
	PathFor(checksum string) (string, error)
	PathForSize(checksum, size string) (string, error)
}

// New creates a storage engine using the default Resizer
func New(conf *Config) Storage {
	return storage{
		conf:    conf,
		resizer: DefaultResizer,
	}
}

// NewWithCustomResizer creates a storage engine using a custom resizer
func NewWithCustomResizer(conf *Config, resizer Resizer) Storage {
	return storage{
		conf:    conf,
		resizer: resizer,
	}
}

func getStructuredFolderPath(checksum string) string {
	lvl1Dir := checksum[len(checksum)-3:]
	return path.Join(lvl1Dir, checksum)
}

func (s storage) ChecksumDataURL(str string) (string, error) {
	dataURL, err := dataurl.DecodeString(str)
	if err != nil {
		return "", err
	}
	return s.Checksum(dataURL.Data), nil
}

func (s storage) Checksum(data []byte) string {
	crc := crc64.Checksum(data, crcTable)
	return fmt.Sprintf("%020d", crc)
}

func (s storage) PathFor(sum string) (string, error) {
	return s.PathForSize(sum, originalImageName)
}

func (s storage) PathForSize(sum, size string) (string, error) {
	dir := getStructuredFolderPath(sum)
	absDirPath := filepath.Join(s.conf.RootPath, filepath.FromSlash(dir))
	files, err := ioutil.ReadDir(absDirPath)
	if err != nil {
		return "", err
	}
	for _, file := range files {
		if !file.IsDir() && hasNameWithoutExtension(file.Name(), size) {
			return filepath.Join(dir, file.Name()), nil
		}
	}
	return "", errors.New("File not found!")
}

func hasNameWithoutExtension(fileName, name string) bool {
	extension := path.Ext(fileName)
	nameWithoutExtension := strings.TrimSuffix(fileName, extension)
	return nameWithoutExtension == name
}
<commit_msg>Now it allows duplicate images<commit_after>// Package imstor enables you to create copies (or thumbnails) of your images and stores
// them along with the original image on your filesystem. The image and its
// copies are are stored in a file structure based on the (zero-prefixed, decimal)
// CRC 64 checksum of the original image. The last 2 characters of the checksum
// are used as the lvl 1 directory name.
//
// Example folder name and contents, given the checksum 08446744073709551615 and
// sizes named "small" and "large":
//
//  /configured/root/path/15/08446744073709551615/original.jpeg
//  /configured/root/path/15/08446744073709551615/small.jpeg
//  /configured/root/path/15/08446744073709551615/large.jpeg
package imstor

import (
	"errors"
	"fmt"
	"hash/crc64"
	"io/ioutil"
	"path"
	"path/filepath"
	"strings"
	"crypto/rand"

	"github.com/vincent-petithory/dataurl"
)

var crcTable = crc64.MakeTable(crc64.ISO)

const (
	originalImageName = "original"
)

type storage struct {
	conf    *Config
	resizer Resizer
}

// Storage is the engine that can be used to store images and retrieve their paths
type Storage interface {
	Store(mediaType string, data []byte) (string, error)
	StoreDataURL(string) (string, error)
	Checksum([]byte) string
	ChecksumDataURL(string) (string, error)
	PathFor(checksum string) (string, error)
	PathForSize(checksum, size string) (string, error)
}

// New creates a storage engine using the default Resizer
func New(conf *Config) Storage {
	return storage{
		conf:    conf,
		resizer: DefaultResizer,
	}
}

// NewWithCustomResizer creates a storage engine using a custom resizer
func NewWithCustomResizer(conf *Config, resizer Resizer) Storage {
	return storage{
		conf:    conf,
		resizer: resizer,
	}
}

func getStructuredFolderPath(checksum string) string {
	lvl1Dir := checksum[len(checksum)-3:]
	return path.Join(lvl1Dir, checksum)
}

func (s storage) ChecksumDataURL(str string) (string, error) {
	dataURL, err := dataurl.DecodeString(str)
	if err != nil {
		return "", err
	}
	return s.Checksum(dataURL.Data), nil
}

func (s storage) Checksum(data []byte) string {
	return NewUUIDv4()
}

func (s storage) PathFor(sum string) (string, error) {
	return s.PathForSize(sum, originalImageName)
}

func (s storage) PathForSize(sum, size string) (string, error) {
	dir := getStructuredFolderPath(sum)
	absDirPath := filepath.Join(s.conf.RootPath, filepath.FromSlash(dir))
	files, err := ioutil.ReadDir(absDirPath)
	if err != nil {
		return "", err
	}
	for _, file := range files {
		if !file.IsDir() && hasNameWithoutExtension(file.Name(), size) {
			return filepath.Join(dir, file.Name()), nil
		}
	}
	return "", errors.New("File not found!")
}

func hasNameWithoutExtension(fileName, name string) bool {
	extension := path.Ext(fileName)
	nameWithoutExtension := strings.TrimSuffix(fileName, extension)
	return nameWithoutExtension == name
}

func NewUUIDv4() string {
	u := [16]byte{}
	_, err := rand.Read(u[:16])
	if err != nil {
		panic(err)
	}

	u[8] = (u[8] | 0x80) & 0xBf
	u[6] = (u[6] | 0x40) & 0x4f

	return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], u[8:10], u[10:])
}
<|endoftext|>


<commit_before>package main

import ()

var scopToClms = map[string]map[string]bool{
	"profile": map[string]bool{
		"name":               true,
		"family_name":        true,
		"given_name":         true,
		"middle_name":        true,
		"nickname":           true,
		"preferred_username": true,
		"profile":            true,
		"picture":            true,
		"website":            true,
		"gender":             true,
		"birthdate":          true,
		"zoneinfo":           true,
		"locale":             true,
		"updated_at":         true,
	},
	"email": map[string]bool{
		"email":          true,
		"email_verified": true,
	},
	"address": map[string]bool{
		"address": true,
	},
	"phone": map[string]bool{
		"phone_number":          true,
		"phone_number_verified": true,
	},
}

// scope に対応するクレームを返す。
func scopesToClaims(scops map[string]bool) map[string]bool {
	clms := map[string]bool{}
	for scop, ok := range scops {
		if !ok {
			continue
		}
		for clm, ok := range scopToClms[scop] {
			if !ok {
				continue
			}
			clms[clm] = true
		}
	}
	return clms
}
<commit_msg>コメント追加<commit_after>package main

import ()

var scopToClms = map[string]map[string]bool{
	"profile": map[string]bool{
		"name":               true,
		"family_name":        true,
		"given_name":         true,
		"middle_name":        true,
		"nickname":           true,
		"preferred_username": true,
		"profile":            true,
		"picture":            true,
		"website":            true,
		"gender":             true,
		"birthdate":          true,
		"zoneinfo":           true,
		"locale":             true,
		"updated_at":         true,
	},
	"email": map[string]bool{
		"email":          true,
		"email_verified": true,
	},
	"address": map[string]bool{
		"address": true,
	},
	"phone": map[string]bool{
		"phone_number":          true,
		"phone_number_verified": true,
	},
}

// scope に対応するクレームを返す。
// 返り値は自由に書き換えて良い。
func scopesToClaims(scops map[string]bool) map[string]bool {
	clms := map[string]bool{}
	for scop, ok := range scops {
		if !ok {
			continue
		}
		for clm, ok := range scopToClms[scop] {
			if !ok {
				continue
			}
			clms[clm] = true
		}
	}
	return clms
}
<|endoftext|>


<commit_before>package recycleme

import (
	"bytes"
	"encoding/json"
	"fmt"
	eancheck "github.com/nicholassm/go-ean"
	"golang.org/x/net/html"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

type Product struct {
	EAN      string // EAN number for the Product
	Name     string // Name of the Product
	URL      string // URL where the details of the Product were found
	ImageURL string // URL where to find an image of the Product
}

func (p Product) String() string {
	s := fmt.Sprintf("%v (%v) at %v", p.Name, p.EAN, p.URL)
	if p.ImageURL != "" {
		s += fmt.Sprintf("\n\tImage: %v", p.ImageURL)
	}
	return s
}

func (p Product) Json() ([]byte, error) {
	return json.Marshal(p)
}

func fetchURL(url string) ([]byte, error) {
	resp, err := http.Get(url)
	if err != nil {
		return []byte{}, err
	}

	defer resp.Body.Close()
	switch resp.StatusCode {
	case 200:
		body, err := ioutil.ReadAll(resp.Body)
		return body, err
	case 404:
		return nil, errNotFound
	default:
		return nil, fmt.Errorf("error while processing product %v, received code %v", url, resp.StatusCode)
	}
}

// Fetcher query something (URL, database, ...) with EAN, and return the Product stored or scrapped
type Fetcher interface {
	Fetch(ean string) (Product, *ProductError)
}

// URL that can be fetched by fetchers, it must be a format string, the %s will be replaced by the EAN
type FetchableURL string

// Create a new FetchableURL, checking that it contains the correct format to place the EAN in the URL
func NewFetchableURL(url string) (FetchableURL, error) {
	if !strings.Contains(url, "%s") && !strings.Contains(url, "%v") {
		return FetchableURL(""), fmt.Errorf("URL %v does not containt format string to insert EAN", url)
	}

	return FetchableURL(url), nil
}

func (f FetchableURL) fullURL(ean string) string {
	return fmt.Sprintf(string(f), ean)
}

type upcItemDbURL struct {
	FetchableURL
}

type openFoodFactsURL struct {
	FetchableURL
}

type isbnSearchUrl struct {
	FetchableURL
}

// Fetcher for upcitemdb.com
var UpcItemDbFetcher upcItemDbURL

// Fetcher for openfoodfacts.org (using json api)
var OpenFoodFactsFetcher openFoodFactsURL

var IsbnSearchFetcher isbnSearchUrl

// Fetchers is a list of default fetchers already implemented.
// Currently supported websited:
// - upcitemdb
// - openfoodfacts
// - isbnsearch
var fetchers []Fetcher

func init() {
	fetchable, err := NewFetchableURL("http://www.upcitemdb.com/upc/%s")
	if err != nil {
		log.Fatal(err)
	}
	UpcItemDbFetcher = upcItemDbURL{fetchable}

	fetchable, err = NewFetchableURL("http://fr.openfoodfacts.org/api/v0/produit/%s.json")
	if err != nil {
		log.Fatal(err)
	}
	OpenFoodFactsFetcher = openFoodFactsURL{fetchable}

	fetchable, err = NewFetchableURL("http://www.isbnsearch.org/isbn/%s")
	if err != nil {
		log.Fatal(err)
	}
	IsbnSearchFetcher = isbnSearchUrl{fetchable}
	fetchers = []Fetcher{UpcItemDbFetcher, OpenFoodFactsFetcher, IsbnSearchFetcher}
}

func (f upcItemDbURL) Fetch(ean string) (Product, *ProductError) {
	url := f.fullURL(ean)
	body, err := fetchURL(url)
	if err != nil {
		return Product{}, NewProductError(ean, url, err)
	}
	p, err := f.parseBody(body)
	if err != nil {
		return p, NewProductError(ean, url, err)
	}
	p.EAN = ean
	p.URL = url
	return p, nil

}

func (f upcItemDbURL) parseBody(b []byte) (Product, error) {
	doc, err := html.Parse(bytes.NewReader(b))
	p := Product{}
	if err != nil {
		return p, err
	}
	var fn func(*html.Node)
	fn = func(n *html.Node) {
		//		printText = printText || (n.Type == html.ElementNode && n.Data == "b")
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			// Looking for
....$PRODUCT_NAME

			if c.Type == html.ElementNode {
				switch c.Data {
				case "p":
					if len(c.Attr) == 1 {
						classAttr := c.Attr[0]
						if classAttr.Val == "detailtitle" {
							txt := c.FirstChild.NextSibling.FirstChild
							if txt.Type == html.TextNode {
								p.Name = txt.Data
								return
							}
						}
					}
				case "img":
					for _, attr := range c.Attr {
						if attr.Key == "src" {
							p.ImageURL = attr.Val
							return
						}
					}
				default:
					fn(c)
				}
			}
		}
	}
	fn(doc)
	if p.Name == "" {
		return p, errNotFound
	}
	return p, nil
}

func (f openFoodFactsURL) Fetch(ean string) (Product, *ProductError) {
	url := f.fullURL(ean)
	p := Product{}
	body, err := fetchURL(url)
	if err != nil {
		return Product{}, NewProductError(ean, url, err)
	}
	var v interface{}
	err = json.Unmarshal(body, &v)
	if err != nil {
		return p, NewProductError(ean, url, err)
	}

	m := v.(map[string]interface{})
	if status := m["status"].(float64); status != 1. { // 1 == product found
		return p, NewProductError(ean, url, errNotFound)
	}
	productIntf, ok := m["product"]
	if !ok {
		return p, NewProductError(ean, url, fmt.Errorf("no product field found in json"))
	}
	product, ok := productIntf.(map[string]interface{})
	if !ok {
		return p, NewProductError(ean, url, fmt.Errorf("no product map found in json"))
	}
	nameIntf, ok := product["product_name"]
	if !ok {
		return p, NewProductError(ean, url, fmt.Errorf("no product_name field found in json"))
	}
	name, ok := nameIntf.(string)
	if !ok {
		return p, NewProductError(ean, url, fmt.Errorf("product_name is not a string"))
	}
	imageURLIntf, ok := product["image_front_url"]
	var imageURL string
	if !ok {
		imageURL = ""
	} else {
		imageURL, ok = imageURLIntf.(string)
		if !ok {
			return p, NewProductError(ean, url, fmt.Errorf("image_front_url is not a string"))
		}
	}

	return Product{URL: url, EAN: ean, Name: name, ImageURL: imageURL}, nil
}

func (f isbnSearchUrl) Fetch(ean string) (Product, *ProductError) {
	url := f.fullURL(ean)
	body, err := fetchURL(url)
	if err != nil {
		return Product{}, NewProductError(ean, url, err)
	}
	p, err := f.parseBody(body)
	if err != nil {
		return p, NewProductError(ean, url, fmt.Errorf("could not extract data from html page"))
	}
	p.EAN = ean
	p.URL = url
	return p, nil

}

func (f isbnSearchUrl) parseBody(b []byte) (Product, error) {
	doc, err := html.Parse(bytes.NewReader(b))
	p := Product{}
	if err != nil {
		return p, err
	}
	var fn func(*html.Node)
	fn = func(n *html.Node) {
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			// Looking for

$PRODUCT_NAME

			if c.Type == html.ElementNode {
				switch c.Data {
				case "div":
					if p.Name != "" {
						return
					}
					if len(c.Attr) == 1 {
						classAttr := c.Attr[0]
						if classAttr.Val == "bookinfo" {
							txt := c.FirstChild.NextSibling.FirstChild
							if txt.Type == html.TextNode {
								p.Name = txt.Data
								return
							}
						}
					}
				case "img":
					if p.ImageURL != "" {
						return
					}
					for _, attr := range c.Attr {
						if attr.Key == "src" {
							p.ImageURL = attr.Val
							return
						}
					}
				}
				fn(c)
			}
		}
	}
	fn(doc)
	if p.Name == "" {
		return p, errNotFound
	}
	return p, nil
}

// Scrap a Product data bases on its EAN with default Fetchers
// All Default Fetchers are executed in goroutines
// Return the Product if it is found on one site (the fastest).
func Scrap(ean string) (Product, error) {
	if !eancheck.Valid(ean) {
		return Product{}, fmt.Errorf("invalid EAN %v", ean)
	}
	type prodErr struct {
		p   Product
		err *ProductError
	}

	c := make(chan prodErr)
	q := make(chan struct{})
	for _, f := range fetchers {
		go func(f Fetcher) {
			product, err := f.Fetch(ean)
			select {
			case <-q:
				return
			case c <- prodErr{product, err}:
				return
			}
		}(f)
	}

	defer close(q)
	errors := make([]error, 0, len(fetchers))
	i := 0
	for pe := range c {
		i += 1
		if pe.err != nil {
			errors = append(errors, pe.err)
			if i == len(fetchers) {
				break
			}
		} else {
			return pe.p, nil
		}
	}

	errStr := make([]string, 1, len(errors)+1)
	errStr[0] = ""
	for _, err := range errors {
		errStr = append(errStr, err.Error())
	}
	return Product{}, fmt.Errorf("no product found because of the following errors:%v", strings.Join(errStr, "\n - "))
}
<commit_msg>Use Error instead of ProductError<commit_after>package recycleme

import (
	"bytes"
	"encoding/json"
	"fmt"
	eancheck "github.com/nicholassm/go-ean"
	"golang.org/x/net/html"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

type Product struct {
	EAN      string // EAN number for the Product
	Name     string // Name of the Product
	URL      string // URL where the details of the Product were found
	ImageURL string // URL where to find an image of the Product
}

func (p Product) String() string {
	s := fmt.Sprintf("%v (%v) at %v", p.Name, p.EAN, p.URL)
	if p.ImageURL != "" {
		s += fmt.Sprintf("\n\tImage: %v", p.ImageURL)
	}
	return s
}

func (p Product) Json() ([]byte, error) {
	return json.Marshal(p)
}

func fetchURL(url string) ([]byte, error) {
	resp, err := http.Get(url)
	if err != nil {
		return []byte{}, err
	}

	defer resp.Body.Close()
	switch resp.StatusCode {
	case 200:
		body, err := ioutil.ReadAll(resp.Body)
		return body, err
	case 404:
		return nil, errNotFound
	default:
		return nil, fmt.Errorf("error while processing product %v, received code %v", url, resp.StatusCode)
	}
}

// Fetcher query something (URL, database, ...) with EAN, and return the Product stored or scrapped
type Fetcher interface {
	Fetch(ean string) (Product, error)
}

// URL that can be fetched by fetchers, it must be a format string, the %s will be replaced by the EAN
type FetchableURL string

// Create a new FetchableURL, checking that it contains the correct format to place the EAN in the URL
func NewFetchableURL(url string) (FetchableURL, error) {
	if !strings.Contains(url, "%s") && !strings.Contains(url, "%v") {
		return FetchableURL(""), fmt.Errorf("URL %v does not containt format string to insert EAN", url)
	}

	return FetchableURL(url), nil
}

func (f FetchableURL) fullURL(ean string) string {
	return fmt.Sprintf(string(f), ean)
}

type upcItemDbURL struct {
	FetchableURL
}

type openFoodFactsURL struct {
	FetchableURL
}

type isbnSearchUrl struct {
	FetchableURL
}

// Fetcher for upcitemdb.com
var UpcItemDbFetcher upcItemDbURL

// Fetcher for openfoodfacts.org (using json api)
var OpenFoodFactsFetcher openFoodFactsURL

var IsbnSearchFetcher isbnSearchUrl

// Fetchers is a list of default fetchers already implemented.
// Currently supported websited:
// - upcitemdb
// - openfoodfacts
// - isbnsearch
var fetchers []Fetcher

func init() {
	fetchable, err := NewFetchableURL("http://www.upcitemdb.com/upc/%s")
	if err != nil {
		log.Fatal(err)
	}
	UpcItemDbFetcher = upcItemDbURL{fetchable}

	fetchable, err = NewFetchableURL("http://fr.openfoodfacts.org/api/v0/produit/%s.json")
	if err != nil {
		log.Fatal(err)
	}
	OpenFoodFactsFetcher = openFoodFactsURL{fetchable}

	fetchable, err = NewFetchableURL("http://www.isbnsearch.org/isbn/%s")
	if err != nil {
		log.Fatal(err)
	}
	IsbnSearchFetcher = isbnSearchUrl{fetchable}
	fetchers = []Fetcher{UpcItemDbFetcher, OpenFoodFactsFetcher, IsbnSearchFetcher}
}

func (f upcItemDbURL) Fetch(ean string) (Product, error) {
	url := f.fullURL(ean)
	body, err := fetchURL(url)
	if err != nil {
		return Product{}, NewProductError(ean, url, err)
	}
	p, err := f.parseBody(body)
	if err != nil {
		return p, NewProductError(ean, url, err)
	}
	p.EAN = ean
	p.URL = url
	return p, nil

}

func (f upcItemDbURL) parseBody(b []byte) (Product, error) {
	doc, err := html.Parse(bytes.NewReader(b))
	p := Product{}
	if err != nil {
		return p, err
	}
	var fn func(*html.Node)
	fn = func(n *html.Node) {
		//		printText = printText || (n.Type == html.ElementNode && n.Data == "b")
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			// Looking for
....$PRODUCT_NAME

			if c.Type == html.ElementNode {
				switch c.Data {
				case "p":
					if len(c.Attr) == 1 {
						classAttr := c.Attr[0]
						if classAttr.Val == "detailtitle" {
							txt := c.FirstChild.NextSibling.FirstChild
							if txt.Type == html.TextNode {
								p.Name = txt.Data
								return
							}
						}
					}
				case "img":
					for _, attr := range c.Attr {
						if attr.Key == "src" {
							p.ImageURL = attr.Val
							return
						}
					}
				default:
					fn(c)
				}
			}
		}
	}
	fn(doc)
	if p.Name == "" {
		return p, errNotFound
	}
	return p, nil
}

func (f openFoodFactsURL) Fetch(ean string) (Product, error) {
	url := f.fullURL(ean)
	p := Product{}
	body, err := fetchURL(url)
	if err != nil {
		return Product{}, NewProductError(ean, url, err)
	}
	var v interface{}
	err = json.Unmarshal(body, &v)
	if err != nil {
		return p, NewProductError(ean, url, err)
	}

	m := v.(map[string]interface{})
	if status := m["status"].(float64); status != 1. { // 1 == product found
		return p, NewProductError(ean, url, errNotFound)
	}
	productIntf, ok := m["product"]
	if !ok {
		return p, NewProductError(ean, url, fmt.Errorf("no product field found in json"))
	}
	product, ok := productIntf.(map[string]interface{})
	if !ok {
		return p, NewProductError(ean, url, fmt.Errorf("no product map found in json"))
	}
	nameIntf, ok := product["product_name"]
	if !ok {
		return p, NewProductError(ean, url, fmt.Errorf("no product_name field found in json"))
	}
	name, ok := nameIntf.(string)
	if !ok {
		return p, NewProductError(ean, url, fmt.Errorf("product_name is not a string"))
	}
	imageURLIntf, ok := product["image_front_url"]
	var imageURL string
	if !ok {
		imageURL = ""
	} else {
		imageURL, ok = imageURLIntf.(string)
		if !ok {
			return p, NewProductError(ean, url, fmt.Errorf("image_front_url is not a string"))
		}
	}

	return Product{URL: url, EAN: ean, Name: name, ImageURL: imageURL}, nil
}

func (f isbnSearchUrl) Fetch(ean string) (Product, error) {
	url := f.fullURL(ean)
	body, err := fetchURL(url)
	if err != nil {
		return Product{}, NewProductError(ean, url, err)
	}
	p, err := f.parseBody(body)
	if err != nil {
		return p, NewProductError(ean, url, fmt.Errorf("could not extract data from html page"))
	}
	p.EAN = ean
	p.URL = url
	return p, nil

}

func (f isbnSearchUrl) parseBody(b []byte) (Product, error) {
	doc, err := html.Parse(bytes.NewReader(b))
	p := Product{}
	if err != nil {
		return p, err
	}
	var fn func(*html.Node)
	fn = func(n *html.Node) {
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			// Looking for

$PRODUCT_NAME

			if c.Type == html.ElementNode {
				switch c.Data {
				case "div":
					if p.Name != "" {
						return
					}
					if len(c.Attr) == 1 {
						classAttr := c.Attr[0]
						if classAttr.Val == "bookinfo" {
							txt := c.FirstChild.NextSibling.FirstChild
							if txt.Type == html.TextNode {
								p.Name = txt.Data
								return
							}
						}
					}
				case "img":
					if p.ImageURL != "" {
						return
					}
					for _, attr := range c.Attr {
						if attr.Key == "src" {
							p.ImageURL = attr.Val
							return
						}
					}
				}
				fn(c)
			}
		}
	}
	fn(doc)
	if p.Name == "" {
		return p, errNotFound
	}
	return p, nil
}

// Scrap a Product data bases on its EAN with default Fetchers
// All Default Fetchers are executed in goroutines
// Return the Product if it is found on one site (the fastest).
func Scrap(ean string) (Product, error) {
	if !eancheck.Valid(ean) {
		return Product{}, fmt.Errorf("invalid EAN %v", ean)
	}
	type prodErr struct {
		p   Product
		err error
	}

	c := make(chan prodErr)
	q := make(chan struct{})
	for _, f := range fetchers {
		go func(f Fetcher) {
			product, err := f.Fetch(ean)
			select {
			case <-q:
				return
			case c <- prodErr{product, err}:
				return
			}
		}(f)
	}

	defer close(q)
	errors := make([]error, 0, len(fetchers))
	i := 0
	for pe := range c {
		i += 1
		if pe.err != nil {
			errors = append(errors, pe.err)
			if i == len(fetchers) {
				break
			}
		} else {
			return pe.p, nil
		}
	}

	errStr := make([]string, 1, len(errors)+1)
	errStr[0] = ""
	for _, err := range errors {
		errStr = append(errStr, err.Error())
	}
	return Product{}, fmt.Errorf("no product found because of the following errors:%v", strings.Join(errStr, "\n - "))
}
<|endoftext|>


<commit_before>package object

import (
	"bytes"
	"fmt"
	"hash/fnv"
	"strconv"
	"strings"

	"github.com/skatsuta/monkey-interpreter/ast"
)

// Type is a type of objects.
type Type string

const (
	// IntegerType represents a type of integers.
	IntegerType Type = "Integer"
	// FloatType represents a type of floating point numbers.
	FloatType = "Float"
	// BooleanType represents a type of booleans.
	BooleanType = "Boolean"
	// NilType represents a type of nil.
	NilType = "Nil"
	// ReturnValueType represents a type of return values.
	ReturnValueType = "ReturnValue"
	// ErrorType represents a type of errors.
	ErrorType = "Error"
	// FunctionType represents a type of functions.
	FunctionType = "Function"
	// StringType represents a type of strings.
	StringType = "String"
	// BuiltinType represents a type of builtin functions.
	BuiltinType = "Builtin"
	// ArrayType represents a type of arrays.
	ArrayType = "Array"
	// HashType represents a type of hashes.
	HashType = "Hash"
	// QuoteType represents a type of quotes used for macros.
	QuoteType = "Quote"
)

// Object represents an object of Monkey language.
type Object interface {
	Type() Type
	Inspect() string
}

// HashKey represents a key of a hash.
type HashKey struct {
	Type  Type
	Value uint64
}

// Hashable is the interface that is able to become a hash key.
type Hashable interface {
	HashKey() HashKey
}

// Integer represents an integer.
type Integer struct {
	Value int64
}

// Type returns the type of the Integer.
func (i *Integer) Type() Type {
	return IntegerType
}

// Inspect returns a string representation of the Integer.
func (i *Integer) Inspect() string {
	return strconv.FormatInt(i.Value, 10)
}

// HashKey returns a hash key object for i.
func (i *Integer) HashKey() HashKey {
	return HashKey{
		Type:  i.Type(),
		Value: uint64(i.Value),
	}
}

// Float represents an integer.
type Float struct {
	Value float64
}

// Type returns the type of f.
func (f *Float) Type() Type {
	return FloatType
}

// Inspect returns a string representation of f.
func (f *Float) Inspect() string {
	return strconv.FormatFloat(f.Value, 'f', -1, 64)
}

// HashKey returns a hash key object for f.
func (f *Float) HashKey() HashKey {
	s := strconv.FormatFloat(f.Value, 'f', -1, 64)
	h := fnv.New64a()
	h.Write([]byte(s))

	return HashKey{
		Type:  f.Type(),
		Value: h.Sum64(),
	}
}

// Boolean represents a boolean.
type Boolean struct {
	Value bool
}

// Type returns the type of the Boolean.
func (b *Boolean) Type() Type {
	return BooleanType
}

// Inspect returns a string representation of the Boolean.
func (b *Boolean) Inspect() string {
	return strconv.FormatBool(b.Value)
}

// HashKey returns a hash key object for b.
func (b *Boolean) HashKey() HashKey {
	key := HashKey{Type: b.Type()}
	if b.Value {
		key.Value = 1
	}
	return key
}

// Nil represents the absence of any value.
type Nil struct{}

// Type returns the type of the Nil.
func (n *Nil) Type() Type {
	return NilType
}

// Inspect returns a string representation of the Nil.
func (n *Nil) Inspect() string {
	return "nil"
}

// ReturnValue represents a return value.
type ReturnValue struct {
	Value Object
}

// Type returns the type of the ReturnValue.
func (rv *ReturnValue) Type() Type {
	return ReturnValueType
}

// Inspect returns a string representation of the ReturnValue.
func (rv *ReturnValue) Inspect() string {
	return rv.Value.Inspect()
}

// Error represents an error.
type Error struct {
	Message string
}

// Type returns the type of the Error.
func (e *Error) Type() Type {
	return ErrorType
}

// Inspect returns a string representation of the Error.
func (e *Error) Inspect() string {
	return "Error: " + e.Message
}

// Function represents a function.
type Function struct {
	Parameters []*ast.Ident
	Body       *ast.BlockStatement
	Env        Environment
}

// Type returns the type of the Function.
func (f *Function) Type() Type {
	return FunctionType
}

// Inspect returns a string representation of the Function.
func (f *Function) Inspect() string {
	var out bytes.Buffer

	params := make([]string, 0, len(f.Parameters))
	for _, p := range f.Parameters {
		params = append(params, p.String())
	}

	out.WriteString("fn(")
	out.WriteString(strings.Join(params, ", "))
	out.WriteString(") {\n")
	out.WriteString(f.Body.String())
	out.WriteString("\n}")

	return out.String()
}

// String represents a string.
type String struct {
	Value string
}

// Type returns the type of the String.
func (s *String) Type() Type {
	return StringType
}

// Inspect returns a string representation of the String.
func (s *String) Inspect() string {
	return s.Value
}

// HashKey returns a hash key object for s.
func (s *String) HashKey() HashKey {
	h := fnv.New64a()
	h.Write([]byte(s.Value))

	return HashKey{
		Type:  s.Type(),
		Value: h.Sum64(),
	}
}

// BuiltinFunction represents a function signature of builtin functions.
type BuiltinFunction func(args ...Object) Object

// Builtin represents a builtin function.
type Builtin struct {
	Fn BuiltinFunction
}

// Type returns the type of the Builtin.
func (b *Builtin) Type() Type {
	return BuiltinType
}

// Inspect returns a string representation of the Builtin.
func (b *Builtin) Inspect() string {
	return "builtin function"
}

// Array represents an array.
type Array struct {
	Elements []Object
}

// Type returns the type of the Array.
func (*Array) Type() Type {
	return ArrayType
}

// Inspect returns a string representation of the Array.
func (a *Array) Inspect() string {
	if a == nil {
		return ""
	}

	elements := make([]string, 0, len(a.Elements))
	for _, e := range a.Elements {
		elements = append(elements, e.Inspect())
	}

	var out bytes.Buffer
	out.WriteString("[")
	out.WriteString(strings.Join(elements, ", "))
	out.WriteString("]")
	return out.String()
}

// HashPair represents a key-value pair in a hash.
type HashPair struct {
	Key   Object
	Value Object
}

// Hash represents a hash.
type Hash struct {
	Pairs map[HashKey]HashPair
}

// Type returns the type of the Hash.
func (*Hash) Type() Type {
	return HashType
}

// Inspect returns a string representation of the Hash.
func (h *Hash) Inspect() string {
	if h == nil {
		return ""
	}

	pairs := make([]string, 0, len(h.Pairs))
	for _, pair := range h.Pairs {
		pairs = append(pairs, pair.Key.Inspect()+": "+pair.Value.Inspect())
	}

	var out bytes.Buffer
	out.WriteString("{")
	out.WriteString(strings.Join(pairs, ", "))
	out.WriteString("}")
	return out.String()
}

// Quote represents a quote, i.e. an unevaluated expression.
type Quote struct {
	ast.Node
}

// Type returns the type of `q`.
func (q *Quote) Type() Type {
	return QuoteType
}

// Inspect returns a string representation of `q`.
func (q *Quote) Inspect() string {
	return fmt.Sprintf("%s(%s)", QuoteType, q.Node.String())
}
<commit_msg>object: add Macro struct which represents a macro<commit_after>package object

import (
	"bytes"
	"fmt"
	"hash/fnv"
	"strconv"
	"strings"

	"github.com/skatsuta/monkey-interpreter/ast"
)

// Type is a type of objects.
type Type string

const (
	// IntegerType represents a type of integers.
	IntegerType Type = "Integer"
	// FloatType represents a type of floating point numbers.
	FloatType = "Float"
	// BooleanType represents a type of booleans.
	BooleanType = "Boolean"
	// NilType represents a type of nil.
	NilType = "Nil"
	// ReturnValueType represents a type of return values.
	ReturnValueType = "ReturnValue"
	// ErrorType represents a type of errors.
	ErrorType = "Error"
	// FunctionType represents a type of functions.
	FunctionType = "Function"
	// StringType represents a type of strings.
	StringType = "String"
	// BuiltinType represents a type of builtin functions.
	BuiltinType = "Builtin"
	// ArrayType represents a type of arrays.
	ArrayType = "Array"
	// HashType represents a type of hashes.
	HashType = "Hash"
	// QuoteType represents a type of quotes used for macros.
	QuoteType = "Quote"
	// MacroType represents a type of macros.
	MacroType = "Macro"
)

// Object represents an object of Monkey language.
type Object interface {
	Type() Type
	Inspect() string
}

// HashKey represents a key of a hash.
type HashKey struct {
	Type  Type
	Value uint64
}

// Hashable is the interface that is able to become a hash key.
type Hashable interface {
	HashKey() HashKey
}

// Integer represents an integer.
type Integer struct {
	Value int64
}

// Type returns the type of the Integer.
func (i *Integer) Type() Type {
	return IntegerType
}

// Inspect returns a string representation of the Integer.
func (i *Integer) Inspect() string {
	return strconv.FormatInt(i.Value, 10)
}

// HashKey returns a hash key object for i.
func (i *Integer) HashKey() HashKey {
	return HashKey{
		Type:  i.Type(),
		Value: uint64(i.Value),
	}
}

// Float represents an integer.
type Float struct {
	Value float64
}

// Type returns the type of f.
func (f *Float) Type() Type {
	return FloatType
}

// Inspect returns a string representation of f.
func (f *Float) Inspect() string {
	return strconv.FormatFloat(f.Value, 'f', -1, 64)
}

// HashKey returns a hash key object for f.
func (f *Float) HashKey() HashKey {
	s := strconv.FormatFloat(f.Value, 'f', -1, 64)
	h := fnv.New64a()
	h.Write([]byte(s))

	return HashKey{
		Type:  f.Type(),
		Value: h.Sum64(),
	}
}

// Boolean represents a boolean.
type Boolean struct {
	Value bool
}

// Type returns the type of the Boolean.
func (b *Boolean) Type() Type {
	return BooleanType
}

// Inspect returns a string representation of the Boolean.
func (b *Boolean) Inspect() string {
	return strconv.FormatBool(b.Value)
}

// HashKey returns a hash key object for b.
func (b *Boolean) HashKey() HashKey {
	key := HashKey{Type: b.Type()}
	if b.Value {
		key.Value = 1
	}
	return key
}

// Nil represents the absence of any value.
type Nil struct{}

// Type returns the type of the Nil.
func (n *Nil) Type() Type {
	return NilType
}

// Inspect returns a string representation of the Nil.
func (n *Nil) Inspect() string {
	return "nil"
}

// ReturnValue represents a return value.
type ReturnValue struct {
	Value Object
}

// Type returns the type of the ReturnValue.
func (rv *ReturnValue) Type() Type {
	return ReturnValueType
}

// Inspect returns a string representation of the ReturnValue.
func (rv *ReturnValue) Inspect() string {
	return rv.Value.Inspect()
}

// Error represents an error.
type Error struct {
	Message string
}

// Type returns the type of the Error.
func (e *Error) Type() Type {
	return ErrorType
}

// Inspect returns a string representation of the Error.
func (e *Error) Inspect() string {
	return "Error: " + e.Message
}

// Function represents a function.
type Function struct {
	Parameters []*ast.Ident
	Body       *ast.BlockStatement
	Env        Environment
}

// Type returns the type of the Function.
func (f *Function) Type() Type {
	return FunctionType
}

// Inspect returns a string representation of the Function.
func (f *Function) Inspect() string {
	var out bytes.Buffer

	params := make([]string, 0, len(f.Parameters))
	for _, p := range f.Parameters {
		params = append(params, p.String())
	}

	out.WriteString("fn(")
	out.WriteString(strings.Join(params, ", "))
	out.WriteString(") {\n")
	out.WriteString(f.Body.String())
	out.WriteString("\n}")

	return out.String()
}

// String represents a string.
type String struct {
	Value string
}

// Type returns the type of the String.
func (s *String) Type() Type {
	return StringType
}

// Inspect returns a string representation of the String.
func (s *String) Inspect() string {
	return s.Value
}

// HashKey returns a hash key object for s.
func (s *String) HashKey() HashKey {
	h := fnv.New64a()
	h.Write([]byte(s.Value))

	return HashKey{
		Type:  s.Type(),
		Value: h.Sum64(),
	}
}

// BuiltinFunction represents a function signature of builtin functions.
type BuiltinFunction func(args ...Object) Object

// Builtin represents a builtin function.
type Builtin struct {
	Fn BuiltinFunction
}

// Type returns the type of the Builtin.
func (b *Builtin) Type() Type {
	return BuiltinType
}

// Inspect returns a string representation of the Builtin.
func (b *Builtin) Inspect() string {
	return "builtin function"
}

// Array represents an array.
type Array struct {
	Elements []Object
}

// Type returns the type of the Array.
func (*Array) Type() Type {
	return ArrayType
}

// Inspect returns a string representation of the Array.
func (a *Array) Inspect() string {
	if a == nil {
		return ""
	}

	elements := make([]string, 0, len(a.Elements))
	for _, e := range a.Elements {
		elements = append(elements, e.Inspect())
	}

	var out bytes.Buffer
	out.WriteString("[")
	out.WriteString(strings.Join(elements, ", "))
	out.WriteString("]")
	return out.String()
}

// HashPair represents a key-value pair in a hash.
type HashPair struct {
	Key   Object
	Value Object
}

// Hash represents a hash.
type Hash struct {
	Pairs map[HashKey]HashPair
}

// Type returns the type of the Hash.
func (*Hash) Type() Type {
	return HashType
}

// Inspect returns a string representation of the Hash.
func (h *Hash) Inspect() string {
	if h == nil {
		return ""
	}

	pairs := make([]string, 0, len(h.Pairs))
	for _, pair := range h.Pairs {
		pairs = append(pairs, pair.Key.Inspect()+": "+pair.Value.Inspect())
	}

	var out bytes.Buffer
	out.WriteString("{")
	out.WriteString(strings.Join(pairs, ", "))
	out.WriteString("}")
	return out.String()
}

// Quote represents a quote, i.e. an unevaluated expression.
type Quote struct {
	ast.Node
}

// Type returns the type of `q`.
func (q *Quote) Type() Type {
	return QuoteType
}

// Inspect returns a string representation of `q`.
func (q *Quote) Inspect() string {
	return fmt.Sprintf("%s(%s)", QuoteType, q.Node.String())
}

// Macro represents a macro.
type Macro struct {
	Parameters []*ast.Ident
	Body       *ast.BlockStatement
	Env        Environment
}

// Type returns the type of `m`.
func (m *Macro) Type() Type {
	return MacroType
}

// Inspect returns a string representation of `m`.
func (m *Macro) Inspect() string {
	var out bytes.Buffer

	params := make([]string, 0, len(m.Parameters))
	for _, p := range m.Parameters {
		params = append(params, p.String())
	}

	out.WriteString("macro(")
	out.WriteString(strings.Join(params, ", "))
	out.WriteString(") {\n")
	out.WriteString(m.Body.String())
	out.WriteString("\n}")

	return out.String()
}
<|endoftext|>


<commit_before>package object

import "fmt"

// Object is the base object interface, which
// every object implements
type Object interface {
	fmt.Stringer
	Equals(Object) bool
	Type() Type
}

// Collection is a child interface of Object,
// which represents an object which can be
// thought of as a list of items
type Collection interface {
	Object
	Elements() []Object
	GetIndex(int) Object
	SetIndex(int, Object)
}

// Container is a child interface of Object,
// which can be accessed by keys - like a map
type Container interface {
	Object
	Get(Object) Object
	Set(Object, Object)
}

// Hasher is any object which can be a key
// in a map
type Hasher interface {
	Object
	Hash() string
}

// Numeric is any object which can be
// represented as a float64 value
type Numeric interface {
	Float64() float64
}
<commit_msg>Make Numeric an Object<commit_after>package object

import "fmt"

// Object is the base object interface, which
// every object implements
type Object interface {
	fmt.Stringer
	Equals(Object) bool
	Type() Type
}

// Collection is a child interface of Object,
// which represents an object which can be
// thought of as a list of items
type Collection interface {
	Object
	Elements() []Object
	GetIndex(int) Object
	SetIndex(int, Object)
}

// Container is a child interface of Object,
// which can be accessed by keys - like a map
type Container interface {
	Object
	Get(Object) Object
	Set(Object, Object)
}

// Hasher is any object which can be a key
// in a map
type Hasher interface {
	Object
	Hash() string
}

// Numeric is any object which can be
// represented as a float64 value
type Numeric interface {
	Object
	Float64() float64
}
<|endoftext|>


<commit_before>/**
 * 模板扩展
 * @author swh <swh@admpub.com>
 */
package tplex

import (
	"bytes"
	"fmt"
	htmlTpl "html/template"
	"io/ioutil"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/coscms/webx/lib/log"
)

func New(logger *log.Logger, templateDir string, cached ...bool) *TemplateEx {
	t := &TemplateEx{
		CachedRelation: make(map[string]*CcRel),
		TemplateDir:    templateDir,
		TemplateMgr:    new(TemplateMgr),
		DelimLeft:      "{{",
		DelimRight:     "}}",
		IncludeTag:     "Include",
		ExtendTag:      "Extend",
		BlockTag:       "Block",
		SuperTag:       "Super",
		Ext:            ".html",
	}
	mgrCtlLen := len(cached)
	if mgrCtlLen > 0 && cached[0] {
		reloadTemplates := true
		if mgrCtlLen > 1 {
			reloadTemplates = cached[1]
		}
		t.TemplateMgr.OnChangeCallback = func(name, typ, event string) {
			switch event {
			case "create":
			case "delete", "modify", "rename":
				if typ == "dir" {
					return
				}
				if cs, ok := t.CachedRelation[name]; ok {
					for key, _ := range cs.Rel {
						if name == key {
							logger.Infof("remove cached template object: %v", key)
							continue
						}
						if _, ok := t.CachedRelation[key]; ok {
							logger.Infof("remove cached template object: %v", key)
							delete(t.CachedRelation, key)
						}
					}
					delete(t.CachedRelation, name)
				}
			}
		}
		t.TemplateMgr.Init(logger, templateDir, reloadTemplates)
	}
	t.InitRegexp()
	return t
}

type CcRel struct {
	Rel map[string]uint8
	Tpl [2]*htmlTpl.Template //0是独立模板；1是子模板
}

type TemplateEx struct {
	CachedRelation     map[string]*CcRel
	TemplateDir        string
	TemplateMgr        *TemplateMgr
	BeforeRender       func(*string)
	DelimLeft          string
	DelimRight         string
	incTagRegex        *regexp.Regexp
	extTagRegex        *regexp.Regexp
	blkTagRegex        *regexp.Regexp
	cachedRegexIdent   string
	IncludeTag         string
	ExtendTag          string
	BlockTag           string
	SuperTag           string
	Ext                string
	TemplatePathParser func(string) string
	Debug              bool
}

func (self *TemplateEx) TemplatePath(p string) string {
	if self.TemplatePathParser == nil {
		return p
	}
	return self.TemplatePathParser(p)
}

func (self *TemplateEx) echo(messages ...string) {
	if self.Debug {
		var message string
		for _, v := range messages {
			message += v + ` `
		}
		fmt.Println(`[tplex]`, message)
	}
}

func (self *TemplateEx) InitRegexp() {
	left := regexp.QuoteMeta(self.DelimLeft)
	right := regexp.QuoteMeta(self.DelimRight)
	rfirst := regexp.QuoteMeta(self.DelimRight[0:1])
	self.incTagRegex = regexp.MustCompile(left + self.IncludeTag + `[\s]+"([^"]+)"(?:[\s]+([^` + rfirst + `]+))?[\s]*` + right)
	self.extTagRegex = regexp.MustCompile(left + self.ExtendTag + `[\s]+"([^"]+)"(?:[\s]+([^` + rfirst + `]+))?[\s]*` + right)
	self.blkTagRegex = regexp.MustCompile(`(?s)` + left + self.BlockTag + `[\s]+"([^"]+)"[\s]*` + right + `(.*?)` + left + `\/` + self.BlockTag + right)
}

func (self *TemplateEx) Fetch(tmplName string, fn func() htmlTpl.FuncMap, values interface{}) string {
	tmplName = tmplName + self.Ext
	var tmpl *htmlTpl.Template
	var funcMap htmlTpl.FuncMap
	if fn != nil {
		funcMap = fn()
	}
	tmplName = self.TemplatePath(tmplName)
	cv, ok := self.CachedRelation[tmplName]
	if !ok || cv.Tpl[0] == nil {
		self.echo(`Read not cached template content:`, tmplName)
		b, err := self.RawContent(tmplName)
		if err != nil {
			return fmt.Sprintf("RenderTemplate %v read err: %s", tmplName, err)
		}

		content := string(b)
		if self.BeforeRender != nil {
			self.BeforeRender(&content)
		}
		subcs := make(map[string]string, 0) //子模板内容
		extcs := make(map[string]string, 0) //母板内容

		ident := self.DelimLeft + self.IncludeTag + self.DelimRight
		if self.cachedRegexIdent != ident || self.incTagRegex == nil {
			InitRegexp()
		}
		m := self.extTagRegex.FindAllStringSubmatch(content, 1)
		if len(m) > 0 {
			self.ParseBlock(content, &subcs, &extcs)
			extFile := m[0][1] + self.Ext
			passObject := m[0][2]
			extFile = self.TemplatePath(extFile)
			self.echo(`Read layout template content:`, extFile)
			b, err = self.RawContent(extFile)
			if err != nil {
				return fmt.Sprintf("RenderTemplate %v read err: %s", extFile, err)
			}
			content = string(b)
			content = self.ParseExtend(content, &extcs, passObject)
		}
		content = self.ContainsSubTpl(content, &subcs)
		t := htmlTpl.New(tmplName)
		t.Delims(self.DelimLeft, self.DelimRight)
		t.Funcs(funcMap)
		//self.echo(`The template content:`, content)
		tmpl, err = t.Parse(content)
		if err != nil {
			return fmt.Sprintf("Parse %v err: %v", tmplName, err)
		}
		for name, subc := range subcs {
			v, ok := self.CachedRelation[name]
			if ok && v.Tpl[1] != nil {
				self.CachedRelation[name].Rel[tmplName] = 0
				tmpl.AddParseTree(name, self.CachedRelation[name].Tpl[1].Tree)
				continue
			}
			var t *htmlTpl.Template
			if name == tmpl.Name() {
				t = tmpl
			} else {
				t = tmpl.New(name)
			}
			if self.BeforeRender != nil {
				self.BeforeRender(&subc)
			}
			_, err = t.Parse(subc)
			if err != nil {
				return fmt.Sprintf("Parse File %v err: %v", name, err)
			}

			if ok {
				self.CachedRelation[name].Rel[tmplName] = 0
				self.CachedRelation[name].Tpl[1] = t
			} else {
				self.CachedRelation[name] = &CcRel{
					Rel: map[string]uint8{tmplName: 0},
					Tpl: [2]*htmlTpl.Template{nil, t},
				}
			}

		}
		for name, extc := range extcs {
			var t *htmlTpl.Template
			if name == tmpl.Name() {
				t = tmpl
			} else {
				t = tmpl.New(name)
			}
			if self.BeforeRender != nil {
				self.BeforeRender(&extc)
			}
			_, err = t.Parse(extc)
			if err != nil {
				return fmt.Sprintf("Parse Block %v err: %v", name, err)
			}
		}

		self.CachedRelation[tmplName] = &CcRel{
			Rel: map[string]uint8{tmplName: 0},
			Tpl: [2]*htmlTpl.Template{tmpl, nil},
		}

	} else {
		tmpl = cv.Tpl[0]
		tmpl.Funcs(funcMap)
		if self.Debug {
			fmt.Println(`Using the template object to be cached:`, tmplName)
			fmt.Println("_________________________________________")
			fmt.Println("")
			for k, v := range tmpl.Templates() {
				fmt.Printf("%v. %#v\n", k, v.Name())
			}
			fmt.Println("_________________________________________")
			fmt.Println("")
		}
	}
	return self.Parse(tmpl, values)
}

func (self *TemplateEx) ParseBlock(content string, subcs *map[string]string, extcs *map[string]string) {
	matches := self.blkTagRegex.FindAllStringSubmatch(content, -1)
	for _, v := range matches {
		blockName := v[1]
		content := v[2]
		(*extcs)[blockName] = self.Tag(`define "`+blockName+`"`) + self.ContainsSubTpl(content, subcs) + self.Tag(`end`)
	}
}

func (self *TemplateEx) ParseExtend(content string, extcs *map[string]string, passObject string) string {
	if passObject == "" {
		passObject = "."
	}
	matches := self.blkTagRegex.FindAllStringSubmatch(content, -1)
	var superTag string
	if self.SuperTag != "" {
		superTag = self.Tag(self.SuperTag)
	}
	var rec map[string]uint8 = make(map[string]uint8)
	for _, v := range matches {
		matched := v[0]
		blockName := v[1]
		innerStr := v[2]
		if v, ok := (*extcs)[blockName]; ok {
			rec[blockName] = 0
			if superTag != "" && strings.Contains(v, superTag) {
				(*extcs)[blockName] = strings.Replace(v, superTag, innerStr, 1)
			}
			content = strings.Replace(content, matched, self.Tag(`template "`+blockName+`" `+passObject), -1)
		} else {
			content = strings.Replace(content, matched, innerStr, -1)
		}
	}
	for k, _ := range *extcs {
		if _, ok := rec[k]; !ok {
			delete(*extcs, k)
		}
	}
	return content
}

func (self *TemplateEx) ContainsSubTpl(content string, subcs *map[string]string) string {
	matches := self.incTagRegex.FindAllStringSubmatch(content, -1)
	for _, v := range matches {
		matched := v[0]
		tmplFile := v[1]
		passObject := v[2]
		tmplFile += self.Ext
		tmplFile = self.TemplatePath(tmplFile)
		if _, ok := (*subcs)[tmplFile]; !ok {
			if v, ok := self.CachedRelation[tmplFile]; ok && v.Tpl[1] != nil {
				(*subcs)[tmplFile] = ""
			} else {
				b, err := self.RawContent(tmplFile)
				if err != nil {
					return fmt.Sprintf("RenderTemplate %v read err: %s", tmplFile, err)
				}
				str := string(b)
				(*subcs)[tmplFile] = "" //先登记，避免死循环
				str = self.ContainsSubTpl(str, subcs)
				(*subcs)[tmplFile] = self.Tag(`define "`+tmplFile+`"`) + str + self.Tag(`end`)
			}
		}
		if passObject == "" {
			passObject = "."
		}
		content = strings.Replace(content, matched, self.Tag(`template "`+tmplFile+`" `+passObject), -1)
	}
	return content
}

func (self *TemplateEx) Tag(content string) string {
	return self.DelimLeft + content + self.DelimRight
}

// Include method provide to template for {{include "about"}}
func (self *TemplateEx) Include(tmplName string, fn func() htmlTpl.FuncMap, values interface{}) interface{} {
	return htmlTpl.HTML(self.Fetch(tmplName, fn, values))
}

func (self *TemplateEx) Parse(tmpl *htmlTpl.Template, values interface{}) string {
	newbytes := bytes.NewBufferString("")
	err := tmpl.Execute(newbytes, values)
	if err != nil {
		return fmt.Sprintf("Parse %v err: %v", tmpl.Name(), err)
	}

	b, err := ioutil.ReadAll(newbytes)
	if err != nil {
		return fmt.Sprintf("Parse %v err: %v", tmpl.Name(), err)
	}
	return string(b)
}

func (self *TemplateEx) RawContent(tmpl string) ([]byte, error) {
	if self.TemplateMgr != nil && self.TemplateMgr.Caches != nil {
		return self.TemplateMgr.GetTemplate(tmpl)
	}
	return ioutil.ReadFile(filepath.Join(self.TemplateDir, tmpl))
}
<commit_msg>update<commit_after>/**
 * 模板扩展
 * @author swh <swh@admpub.com>
 */
package tplex

import (
	"bytes"
	"fmt"
	htmlTpl "html/template"
	"io/ioutil"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/coscms/webx/lib/log"
)

func New(logger *log.Logger, templateDir string, cached ...bool) *TemplateEx {
	t := &TemplateEx{
		CachedRelation: make(map[string]*CcRel),
		TemplateDir:    templateDir,
		TemplateMgr:    new(TemplateMgr),
		DelimLeft:      "{{",
		DelimRight:     "}}",
		IncludeTag:     "Include",
		ExtendTag:      "Extend",
		BlockTag:       "Block",
		SuperTag:       "Super",
		Ext:            ".html",
	}
	mgrCtlLen := len(cached)
	if mgrCtlLen > 0 && cached[0] {
		reloadTemplates := true
		if mgrCtlLen > 1 {
			reloadTemplates = cached[1]
		}
		t.TemplateMgr.OnChangeCallback = func(name, typ, event string) {
			switch event {
			case "create":
			case "delete", "modify", "rename":
				if typ == "dir" {
					return
				}
				if cs, ok := t.CachedRelation[name]; ok {
					for key, _ := range cs.Rel {
						if name == key {
							logger.Infof("remove cached template object: %v", key)
							continue
						}
						if _, ok := t.CachedRelation[key]; ok {
							logger.Infof("remove cached template object: %v", key)
							delete(t.CachedRelation, key)
						}
					}
					delete(t.CachedRelation, name)
				}
			}
		}
		t.TemplateMgr.Init(logger, templateDir, reloadTemplates)
	}
	t.InitRegexp()
	return t
}

type CcRel struct {
	Rel map[string]uint8
	Tpl [2]*htmlTpl.Template //0是独立模板；1是子模板
}

type TemplateEx struct {
	CachedRelation     map[string]*CcRel
	TemplateDir        string
	TemplateMgr        *TemplateMgr
	BeforeRender       func(*string)
	DelimLeft          string
	DelimRight         string
	incTagRegex        *regexp.Regexp
	extTagRegex        *regexp.Regexp
	blkTagRegex        *regexp.Regexp
	cachedRegexIdent   string
	IncludeTag         string
	ExtendTag          string
	BlockTag           string
	SuperTag           string
	Ext                string
	TemplatePathParser func(string) string
	Debug              bool
}

func (self *TemplateEx) TemplatePath(p string) string {
	if self.TemplatePathParser == nil {
		return p
	}
	return self.TemplatePathParser(p)
}

func (self *TemplateEx) echo(messages ...string) {
	if self.Debug {
		var message string
		for _, v := range messages {
			message += v + ` `
		}
		fmt.Println(`[tplex]`, message)
	}
}

func (self *TemplateEx) InitRegexp() {
	left := regexp.QuoteMeta(self.DelimLeft)
	right := regexp.QuoteMeta(self.DelimRight)
	rfirst := regexp.QuoteMeta(self.DelimRight[0:1])
	self.incTagRegex = regexp.MustCompile(left + self.IncludeTag + `[\s]+"([^"]+)"(?:[\s]+([^` + rfirst + `]+))?[\s]*` + right)
	self.extTagRegex = regexp.MustCompile(left + self.ExtendTag + `[\s]+"([^"]+)"(?:[\s]+([^` + rfirst + `]+))?[\s]*` + right)
	self.blkTagRegex = regexp.MustCompile(`(?s)` + left + self.BlockTag + `[\s]+"([^"]+)"[\s]*` + right + `(.*?)` + left + `\/` + self.BlockTag + right)
}

func (self *TemplateEx) Fetch(tmplName string, fn func() htmlTpl.FuncMap, values interface{}) string {
	tmplName = tmplName + self.Ext
	var tmpl *htmlTpl.Template
	var funcMap htmlTpl.FuncMap
	if fn != nil {
		funcMap = fn()
	}
	tmplName = self.TemplatePath(tmplName)
	cv, ok := self.CachedRelation[tmplName]
	if !ok || cv.Tpl[0] == nil {
		self.echo(`Read not cached template content:`, tmplName)
		b, err := self.RawContent(tmplName)
		if err != nil {
			return fmt.Sprintf("RenderTemplate %v read err: %s", tmplName, err)
		}

		content := string(b)
		if self.BeforeRender != nil {
			self.BeforeRender(&content)
		}
		subcs := make(map[string]string, 0) //子模板内容
		extcs := make(map[string]string, 0) //母板内容

		ident := self.DelimLeft + self.IncludeTag + self.DelimRight
		if self.cachedRegexIdent != ident || self.incTagRegex == nil {
			self.InitRegexp()
		}
		m := self.extTagRegex.FindAllStringSubmatch(content, 1)
		if len(m) > 0 {
			self.ParseBlock(content, &subcs, &extcs)
			extFile := m[0][1] + self.Ext
			passObject := m[0][2]
			extFile = self.TemplatePath(extFile)
			self.echo(`Read layout template content:`, extFile)
			b, err = self.RawContent(extFile)
			if err != nil {
				return fmt.Sprintf("RenderTemplate %v read err: %s", extFile, err)
			}
			content = string(b)
			content = self.ParseExtend(content, &extcs, passObject)
		}
		content = self.ContainsSubTpl(content, &subcs)
		t := htmlTpl.New(tmplName)
		t.Delims(self.DelimLeft, self.DelimRight)
		t.Funcs(funcMap)
		//self.echo(`The template content:`, content)
		tmpl, err = t.Parse(content)
		if err != nil {
			return fmt.Sprintf("Parse %v err: %v", tmplName, err)
		}
		for name, subc := range subcs {
			v, ok := self.CachedRelation[name]
			if ok && v.Tpl[1] != nil {
				self.CachedRelation[name].Rel[tmplName] = 0
				tmpl.AddParseTree(name, self.CachedRelation[name].Tpl[1].Tree)
				continue
			}
			var t *htmlTpl.Template
			if name == tmpl.Name() {
				t = tmpl
			} else {
				t = tmpl.New(name)
			}
			if self.BeforeRender != nil {
				self.BeforeRender(&subc)
			}
			_, err = t.Parse(subc)
			if err != nil {
				return fmt.Sprintf("Parse File %v err: %v", name, err)
			}

			if ok {
				self.CachedRelation[name].Rel[tmplName] = 0
				self.CachedRelation[name].Tpl[1] = t
			} else {
				self.CachedRelation[name] = &CcRel{
					Rel: map[string]uint8{tmplName: 0},
					Tpl: [2]*htmlTpl.Template{nil, t},
				}
			}

		}
		for name, extc := range extcs {
			var t *htmlTpl.Template
			if name == tmpl.Name() {
				t = tmpl
			} else {
				t = tmpl.New(name)
			}
			if self.BeforeRender != nil {
				self.BeforeRender(&extc)
			}
			_, err = t.Parse(extc)
			if err != nil {
				return fmt.Sprintf("Parse Block %v err: %v", name, err)
			}
		}

		self.CachedRelation[tmplName] = &CcRel{
			Rel: map[string]uint8{tmplName: 0},
			Tpl: [2]*htmlTpl.Template{tmpl, nil},
		}

	} else {
		tmpl = cv.Tpl[0]
		tmpl.Funcs(funcMap)
		if self.Debug {
			fmt.Println(`Using the template object to be cached:`, tmplName)
			fmt.Println("_________________________________________")
			fmt.Println("")
			for k, v := range tmpl.Templates() {
				fmt.Printf("%v. %#v\n", k, v.Name())
			}
			fmt.Println("_________________________________________")
			fmt.Println("")
		}
	}
	return self.Parse(tmpl, values)
}

func (self *TemplateEx) ParseBlock(content string, subcs *map[string]string, extcs *map[string]string) {
	matches := self.blkTagRegex.FindAllStringSubmatch(content, -1)
	for _, v := range matches {
		blockName := v[1]
		content := v[2]
		(*extcs)[blockName] = self.Tag(`define "`+blockName+`"`) + self.ContainsSubTpl(content, subcs) + self.Tag(`end`)
	}
}

func (self *TemplateEx) ParseExtend(content string, extcs *map[string]string, passObject string) string {
	if passObject == "" {
		passObject = "."
	}
	matches := self.blkTagRegex.FindAllStringSubmatch(content, -1)
	var superTag string
	if self.SuperTag != "" {
		superTag = self.Tag(self.SuperTag)
	}
	var rec map[string]uint8 = make(map[string]uint8)
	for _, v := range matches {
		matched := v[0]
		blockName := v[1]
		innerStr := v[2]
		if v, ok := (*extcs)[blockName]; ok {
			rec[blockName] = 0
			if superTag != "" && strings.Contains(v, superTag) {
				(*extcs)[blockName] = strings.Replace(v, superTag, innerStr, 1)
			}
			content = strings.Replace(content, matched, self.Tag(`template "`+blockName+`" `+passObject), -1)
		} else {
			content = strings.Replace(content, matched, innerStr, -1)
		}
	}
	for k, _ := range *extcs {
		if _, ok := rec[k]; !ok {
			delete(*extcs, k)
		}
	}
	return content
}

func (self *TemplateEx) ContainsSubTpl(content string, subcs *map[string]string) string {
	matches := self.incTagRegex.FindAllStringSubmatch(content, -1)
	for _, v := range matches {
		matched := v[0]
		tmplFile := v[1]
		passObject := v[2]
		tmplFile += self.Ext
		tmplFile = self.TemplatePath(tmplFile)
		if _, ok := (*subcs)[tmplFile]; !ok {
			if v, ok := self.CachedRelation[tmplFile]; ok && v.Tpl[1] != nil {
				(*subcs)[tmplFile] = ""
			} else {
				b, err := self.RawContent(tmplFile)
				if err != nil {
					return fmt.Sprintf("RenderTemplate %v read err: %s", tmplFile, err)
				}
				str := string(b)
				(*subcs)[tmplFile] = "" //先登记，避免死循环
				str = self.ContainsSubTpl(str, subcs)
				(*subcs)[tmplFile] = self.Tag(`define "`+tmplFile+`"`) + str + self.Tag(`end`)
			}
		}
		if passObject == "" {
			passObject = "."
		}
		content = strings.Replace(content, matched, self.Tag(`template "`+tmplFile+`" `+passObject), -1)
	}
	return content
}

func (self *TemplateEx) Tag(content string) string {
	return self.DelimLeft + content + self.DelimRight
}

// Include method provide to template for {{include "about"}}
func (self *TemplateEx) Include(tmplName string, fn func() htmlTpl.FuncMap, values interface{}) interface{} {
	return htmlTpl.HTML(self.Fetch(tmplName, fn, values))
}

func (self *TemplateEx) Parse(tmpl *htmlTpl.Template, values interface{}) string {
	newbytes := bytes.NewBufferString("")
	err := tmpl.Execute(newbytes, values)
	if err != nil {
		return fmt.Sprintf("Parse %v err: %v", tmpl.Name(), err)
	}

	b, err := ioutil.ReadAll(newbytes)
	if err != nil {
		return fmt.Sprintf("Parse %v err: %v", tmpl.Name(), err)
	}
	return string(b)
}

func (self *TemplateEx) RawContent(tmpl string) ([]byte, error) {
	if self.TemplateMgr != nil && self.TemplateMgr.Caches != nil {
		return self.TemplateMgr.GetTemplate(tmpl)
	}
	return ioutil.ReadFile(filepath.Join(self.TemplateDir, tmpl))
}
<|endoftext|>


<commit_before>package vinxi

import (
	"gopkg.in/vinxi/forward.v0"
	"gopkg.in/vinxi/layer.v0"
	"gopkg.in/vinxi/router.v0"
	"net/http"
)

// DefaultForwarder stores the default http.Handler to be used to forward the traffic.
// By default the proxy will reply with 502 Bad Gateway if no custom forwarder is defined.
var DefaultForwarder = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	fwd, _ := forward.New(forward.PassHostHeader(true))
	fwd.ServeHTTP(w, r)
})

// Middleware defines the required interface implemented
// by public middleware capable entities in the vinxi ecosystem.
type Middleware interface {
	// Use is used to register one or multiple middleware handlers.
	Use(...interface{}) Middleware

	// UsePhase is used to register one or multiple middleware
	// handlers for a specific middleware phase.
	UsePhase(string, ...interface{}) Middleware

	// UseFinalHandler is used to register the final request handler
	// usually to define the error or forward handlers.
	UseFinalHandler(http.Handler) Middleware
}

// Route represents the required route capable interface
type Route interface {
	Middleware
	http.Handler
	Forward(string) Route
	Handle(http.HandlerFunc)
}

// Router represents the router capable interface.
type Router interface {
	Route(string, string) Route
	Match(string string) (Route, error)
}

// Vinxi represents the vinxi proxy structure.
type Vinxi struct {
	// Layer stores the proxy top-level middleware layer.
	Layer *layer.Layer

	// Router stores the built-in router.
	Router *router.Router
}

// New creates a new vinxi proxy layer.
func New() *Vinxi {
	v := &Vinxi{Layer: layer.New(), Router: router.New()}
	v.Layer.UsePriority("request", layer.Tail, v.Router)
	v.UseFinalHandler(DefaultForwarder)
	return v
}

// Get will register a pattern for GET requests.
// It also registers pat for HEAD requests. If this needs to be overridden, use
// Head before Get with pat.
func (r *Vinxi) Get(path string) *router.Route {
	return r.Route("GET", path)
}

// Post will register a pattern for POST requests.
func (r *Vinxi) Post(path string) *router.Route {
	return r.Route("POST", path)
}

// Put will register a pattern for PUT requests.
func (r *Vinxi) Put(path string) *router.Route {
	return r.Route("PUT", path)
}

// Delete will register a pattern for DELETE requests.
func (r *Vinxi) Delete(path string) *router.Route {
	return r.Route("DELETE", path)
}

// Options will register a pattern for OPTIONS requests.
func (r *Vinxi) Options(path string) *router.Route {
	return r.Route("OPTIONS", path)
}

// Patch will register a pattern for PATCH requests.
func (r *Vinxi) Patch(path string) *router.Route {
	return r.Route("PATCH", path)
}

// All will register a pattern for any HTTP method.
func (r *Vinxi) All(path string) *router.Route {
	return r.Route("*", path)
}

// Route will register a new route for the given pattern and HTTP method.
func (r *Vinxi) Route(method, path string) *router.Route {
	return r.Router.Route(method, path)
}

// Forward defines the default URL to forward incoming traffic.
func (v *Vinxi) Forward(uri string) *Vinxi {
	return v.UseFinalHandler(http.HandlerFunc(forward.To(uri)))
}

// Use attaches a new middleware handler for incoming HTTP traffic.
func (v *Vinxi) Use(handler ...interface{}) *Vinxi {
	v.Layer.Use(layer.RequestPhase, handler...)
	return v
}

// UsePhase attaches a new middleware handler to a specific phase.
func (v *Vinxi) UsePhase(phase string, handler ...interface{}) *Vinxi {
	v.Layer.Use(phase, handler...)
	return v
}

// UseFinalHandler uses a new middleware handler function as final handler.
func (v *Vinxi) UseFinalHandler(fn http.Handler) *Vinxi {
	v.Layer.UseFinalHandler(fn)
	return v
}

// Flush flushes all the middleware stack.
func (v *Vinxi) Flush() {
	v.Layer.Flush()
}

// BindServer binds the vinxi HTTP handler to the given http.Server.
func (v *Vinxi) BindServer(server *http.Server) {
	server.Handler = v
}

// ServeHTTP implements the required http.Handler interface.
func (v *Vinxi) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	v.Layer.Run("request", w, req, nil)
}
<commit_msg>fix(lint): code style<commit_after>package vinxi

import (
	"gopkg.in/vinxi/forward.v0"
	"gopkg.in/vinxi/layer.v0"
	"gopkg.in/vinxi/router.v0"
	"net/http"
)

// DefaultForwarder stores the default http.Handler to be used to forward the traffic.
// By default the proxy will reply with 502 Bad Gateway if no custom forwarder is defined.
var DefaultForwarder = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	fwd, _ := forward.New(forward.PassHostHeader(true))
	fwd.ServeHTTP(w, r)
})

// Middleware defines the required interface implemented
// by public middleware capable entities in the vinxi ecosystem.
type Middleware interface {
	// Use is used to register one or multiple middleware handlers.
	Use(...interface{}) Middleware

	// UsePhase is used to register one or multiple middleware
	// handlers for a specific middleware phase.
	UsePhase(string, ...interface{}) Middleware

	// UseFinalHandler is used to register the final request handler
	// usually to define the error or forward handlers.
	UseFinalHandler(http.Handler) Middleware
}

// Route represents the required route capable interface
type Route interface {
	Middleware
	http.Handler
	Forward(string) Route
	Handle(http.HandlerFunc)
}

// Router represents the router capable interface.
type Router interface {
	Route(string, string) Route
	Match(string string) (Route, error)
}

// Vinxi represents the vinxi proxy structure.
type Vinxi struct {
	// Layer stores the proxy top-level middleware layer.
	Layer *layer.Layer

	// Router stores the built-in router.
	Router *router.Router
}

// New creates a new vinxi proxy layer.
func New() *Vinxi {
	v := &Vinxi{Layer: layer.New(), Router: router.New()}
	v.Layer.UsePriority("request", layer.Tail, v.Router)
	v.UseFinalHandler(DefaultForwarder)
	return v
}

// Get will register a pattern for GET requests.
// It also registers pat for HEAD requests. If this needs to be overridden, use
// Head before Get with pat.
func (v *Vinxi) Get(path string) *router.Route {
	return r.Route("GET", path)
}

// Post will register a pattern for POST requests.
func (v *Vinxi) Post(path string) *router.Route {
	return r.Route("POST", path)
}

// Put will register a pattern for PUT requests.
func (v *Vinxi) Put(path string) *router.Route {
	return r.Route("PUT", path)
}

// Delete will register a pattern for DELETE requests.
func (v *Vinxi) Delete(path string) *router.Route {
	return r.Route("DELETE", path)
}

// Options will register a pattern for OPTIONS requests.
func (v *Vinxi) Options(path string) *router.Route {
	return r.Route("OPTIONS", path)
}

// Patch will register a pattern for PATCH requests.
func (v *Vinxi) Patch(path string) *router.Route {
	return r.Route("PATCH", path)
}

// All will register a pattern for any HTTP method.
func (v *Vinxi) All(path string) *router.Route {
	return r.Route("*", path)
}

// Route will register a new route for the given pattern and HTTP method.
func (v *Vinxi) Route(method, path string) *router.Route {
	return r.Router.Route(method, path)
}

// Forward defines the default URL to forward incoming traffic.
func (v *Vinxi) Forward(uri string) *Vinxi {
	return v.UseFinalHandler(http.HandlerFunc(forward.To(uri)))
}

// Use attaches a new middleware handler for incoming HTTP traffic.
func (v *Vinxi) Use(handler ...interface{}) *Vinxi {
	v.Layer.Use(layer.RequestPhase, handler...)
	return v
}

// UsePhase attaches a new middleware handler to a specific phase.
func (v *Vinxi) UsePhase(phase string, handler ...interface{}) *Vinxi {
	v.Layer.Use(phase, handler...)
	return v
}

// UseFinalHandler uses a new middleware handler function as final handler.
func (v *Vinxi) UseFinalHandler(fn http.Handler) *Vinxi {
	v.Layer.UseFinalHandler(fn)
	return v
}

// Flush flushes all the middleware stack.
func (v *Vinxi) Flush() {
	v.Layer.Flush()
}

// BindServer binds the vinxi HTTP handler to the given http.Server.
func (v *Vinxi) BindServer(server *http.Server) {
	server.Handler = v
}

// ServeHTTP implements the required http.Handler interface.
func (v *Vinxi) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	v.Layer.Run("request", w, req, nil)
}
<|endoftext|>


<commit_before>package vm

import (
	"bytes"
	"fmt"
	"strings"
)

type VM struct {
	CallFrameStack *CallFrameStack
	Stack          *Stack
	SP             int
	CFP            int
	Constants      map[string]Object
	LabelTable     map[LabelType]map[string][]*InstructionSet
	MethodISTable  *ISIndexTable
	ClassISTable   *ISIndexTable
	BlockList      *ISIndexTable
}

type ISIndexTable struct {
	Data map[string]int
}

type Stack struct {
	Data []Object
	VM   *VM
}

func New() *VM {
	s := &Stack{}
	cfs := &CallFrameStack{CallFrames: []*CallFrame{}}
	vm := &VM{Stack: s, CallFrameStack: cfs, SP: 0, CFP: 0}
	s.VM = vm
	cfs.VM = vm

	vm.initConstants()
	vm.MethodISTable = &ISIndexTable{Data: make(map[string]int)}
	vm.ClassISTable = &ISIndexTable{Data: make(map[string]int)}
	vm.BlockList = &ISIndexTable{Data: make(map[string]int)}
	vm.LabelTable = map[LabelType]map[string][]*InstructionSet{
		LABEL_DEF:      make(map[string][]*InstructionSet),
		LABEL_DEFCLASS: make(map[string][]*InstructionSet),
		BLOCK:          make(map[string][]*InstructionSet),
		PROGRAM:        make(map[string][]*InstructionSet),
	}

	return vm
}

func (vm *VM) EvalCallFrame(cf *CallFrame) {
	for cf.PC < len(cf.InstructionSet.Instructions) {
		i := cf.InstructionSet.Instructions[cf.PC]
		vm.execInstruction(cf, i)
	}
}

func (vm *VM) Exec() {
	cf := vm.CallFrameStack.Top()
	vm.EvalCallFrame(cf)
}

func (vm *VM) initConstants() {
	constants := make(map[string]Object)

	builtInClasses := []Class{
		IntegerClass,
		StringClass,
		BooleanClass,
		NullClass,
		ArrayClass,
		HashClass,
		ClassClass,
		ObjectClass,
	}

	for _, c := range builtInClasses {
		constants[c.ReturnName()] = c
	}

	vm.Constants = constants
}

func (vm *VM) execInstruction(cf *CallFrame, i *Instruction) {
	cf.PC += 1
	fmt.Println(i.Inspect())
	i.Action.Operation(vm, cf, i.Params...)
	fmt.Println(vm.Stack.inspect())
}

func (vm *VM) getBlock() (*InstructionSet, bool) {
	iss, ok := vm.LabelTable[BLOCK]["Block"]

	if !ok {
		return nil, false
	}

	is := iss[vm.MethodISTable.Data["Block"]]

	vm.MethodISTable.Data["Block"] += 1
	return is, ok
}

func (vm *VM) getMethodIS(name string) (*InstructionSet, bool) {
	iss, ok := vm.LabelTable[LABEL_DEF][name]

	if !ok {
		return nil, false
	}

	is := iss[vm.MethodISTable.Data[name]]

	vm.MethodISTable.Data[name] += 1
	return is, ok
}

func (vm *VM) getClassIS(name string) (*InstructionSet, bool) {
	iss, ok := vm.LabelTable[LABEL_DEFCLASS][name]

	if !ok {
		return nil, false
	}

	is := iss[vm.ClassISTable.Data[name]]

	vm.ClassISTable.Data[name] += 1
	return is, ok
}

func (vm *VM) setLabel(is *InstructionSet, name string) {
	var l *Label
	var labelName string
	var labelType LabelType

	if name == "ProgramStart" {
		labelName = name
		labelType = PROGRAM

	} else if name == "Block" {
		labelName = name
		labelType = BLOCK
	} else {
		labelName = strings.Split(name, ":")[1]
		labelType = labelTypes[strings.Split(name, ":")[0]]
	}

	l = &Label{Name: name, Type: labelType}
	is.Label = l
	vm.LabelTable[labelType][labelName] = append(vm.LabelTable[labelType][labelName], is)
}

func (s *Stack) push(v Object) {
	if len(s.Data) <= s.VM.SP {
		s.Data = append(s.Data, v)
	} else {
		s.Data[s.VM.SP] = v
	}

	s.VM.SP += 1
}

func (s *Stack) pop() Object {
	if len(s.Data) < 1 {
		panic("Nothing to pop!")
	}

	s.VM.SP -= 1

	v := s.Data[s.VM.SP]
	s.Data[s.VM.SP] = nil
	return v
}

func (s *Stack) Top() Object {

	if s.VM.SP > 0 {
		return s.Data[s.VM.SP-1]
	}

	return s.Data[0]
}

func (s *Stack) inspect() string {
	var out bytes.Buffer
	datas := []string{}

	for i, o := range s.Data {
		if o != nil {
			if i == s.VM.SP {
				datas = append(datas, fmt.Sprintf("%s (%T) %d <----", o.Inspect(), o, i))
			} else {
				datas = append(datas, fmt.Sprintf("%s (%T) %d", o.Inspect(), o, i))
			}

		} else {
			if i == s.VM.SP {
				datas = append(datas, "nil <----")
			} else {
				datas = append(datas, "nil")
			}

		}

	}

	out.WriteString("-----------\n")
	out.WriteString(strings.Join(datas, "\n"))
	out.WriteString("\n---------\n")

	return out.String()
}

func newError(format string, args ...interface{}) *Error {
	return &Error{Message: fmt.Sprintf(format, args...)}
}
<commit_msg>Disable vm's debug info output.<commit_after>package vm

import (
	"bytes"
	"fmt"
	"strings"
)

type VM struct {
	CallFrameStack *CallFrameStack
	Stack          *Stack
	SP             int
	CFP            int
	Constants      map[string]Object
	LabelTable     map[LabelType]map[string][]*InstructionSet
	MethodISTable  *ISIndexTable
	ClassISTable   *ISIndexTable
	BlockList      *ISIndexTable
}

type ISIndexTable struct {
	Data map[string]int
}

type Stack struct {
	Data []Object
	VM   *VM
}

func New() *VM {
	s := &Stack{}
	cfs := &CallFrameStack{CallFrames: []*CallFrame{}}
	vm := &VM{Stack: s, CallFrameStack: cfs, SP: 0, CFP: 0}
	s.VM = vm
	cfs.VM = vm

	vm.initConstants()
	vm.MethodISTable = &ISIndexTable{Data: make(map[string]int)}
	vm.ClassISTable = &ISIndexTable{Data: make(map[string]int)}
	vm.BlockList = &ISIndexTable{Data: make(map[string]int)}
	vm.LabelTable = map[LabelType]map[string][]*InstructionSet{
		LABEL_DEF:      make(map[string][]*InstructionSet),
		LABEL_DEFCLASS: make(map[string][]*InstructionSet),
		BLOCK:          make(map[string][]*InstructionSet),
		PROGRAM:        make(map[string][]*InstructionSet),
	}

	return vm
}

func (vm *VM) EvalCallFrame(cf *CallFrame) {
	for cf.PC < len(cf.InstructionSet.Instructions) {
		i := cf.InstructionSet.Instructions[cf.PC]
		vm.execInstruction(cf, i)
	}
}

func (vm *VM) Exec() {
	cf := vm.CallFrameStack.Top()
	vm.EvalCallFrame(cf)
}

func (vm *VM) initConstants() {
	constants := make(map[string]Object)

	builtInClasses := []Class{
		IntegerClass,
		StringClass,
		BooleanClass,
		NullClass,
		ArrayClass,
		HashClass,
		ClassClass,
		ObjectClass,
	}

	for _, c := range builtInClasses {
		constants[c.ReturnName()] = c
	}

	vm.Constants = constants
}

func (vm *VM) execInstruction(cf *CallFrame, i *Instruction) {
	cf.PC += 1
	//fmt.Println(i.Inspect())
	i.Action.Operation(vm, cf, i.Params...)
	//fmt.Println(vm.Stack.inspect())
}

func (vm *VM) getBlock() (*InstructionSet, bool) {
	iss, ok := vm.LabelTable[BLOCK]["Block"]

	if !ok {
		return nil, false
	}

	is := iss[vm.MethodISTable.Data["Block"]]

	vm.MethodISTable.Data["Block"] += 1
	return is, ok
}

func (vm *VM) getMethodIS(name string) (*InstructionSet, bool) {
	iss, ok := vm.LabelTable[LABEL_DEF][name]

	if !ok {
		return nil, false
	}

	is := iss[vm.MethodISTable.Data[name]]

	vm.MethodISTable.Data[name] += 1
	return is, ok
}

func (vm *VM) getClassIS(name string) (*InstructionSet, bool) {
	iss, ok := vm.LabelTable[LABEL_DEFCLASS][name]

	if !ok {
		return nil, false
	}

	is := iss[vm.ClassISTable.Data[name]]

	vm.ClassISTable.Data[name] += 1
	return is, ok
}

func (vm *VM) setLabel(is *InstructionSet, name string) {
	var l *Label
	var labelName string
	var labelType LabelType

	if name == "ProgramStart" {
		labelName = name
		labelType = PROGRAM

	} else if name == "Block" {
		labelName = name
		labelType = BLOCK
	} else {
		labelName = strings.Split(name, ":")[1]
		labelType = labelTypes[strings.Split(name, ":")[0]]
	}

	l = &Label{Name: name, Type: labelType}
	is.Label = l
	vm.LabelTable[labelType][labelName] = append(vm.LabelTable[labelType][labelName], is)
}

func (s *Stack) push(v Object) {
	if len(s.Data) <= s.VM.SP {
		s.Data = append(s.Data, v)
	} else {
		s.Data[s.VM.SP] = v
	}

	s.VM.SP += 1
}

func (s *Stack) pop() Object {
	if len(s.Data) < 1 {
		panic("Nothing to pop!")
	}

	s.VM.SP -= 1

	v := s.Data[s.VM.SP]
	s.Data[s.VM.SP] = nil
	return v
}

func (s *Stack) Top() Object {

	if s.VM.SP > 0 {
		return s.Data[s.VM.SP-1]
	}

	return s.Data[0]
}

func (s *Stack) inspect() string {
	var out bytes.Buffer
	datas := []string{}

	for i, o := range s.Data {
		if o != nil {
			if i == s.VM.SP {
				datas = append(datas, fmt.Sprintf("%s (%T) %d <----", o.Inspect(), o, i))
			} else {
				datas = append(datas, fmt.Sprintf("%s (%T) %d", o.Inspect(), o, i))
			}

		} else {
			if i == s.VM.SP {
				datas = append(datas, "nil <----")
			} else {
				datas = append(datas, "nil")
			}

		}

	}

	out.WriteString("-----------\n")
	out.WriteString(strings.Join(datas, "\n"))
	out.WriteString("\n---------\n")

	return out.String()
}

func newError(format string, args ...interface{}) *Error {
	return &Error{Message: fmt.Sprintf(format, args...)}
}
<|endoftext|>


<commit_before>package vm

import (
	"fmt"
)

const (
	HALT  int32 = iota // Halt
	NOP                // Nop
	PUSH               // Push to Top of Stack
	POP                // Remove from Top of Stack
	ADD                // Add
	PRINT              // Print
	JMPEQ              // Jump If Equal
	JMPNE              // Jump If Not Equal
	J                  // Jump Inconditionally
	SLL                // Shift Left Logical
	SRL                // Shift Right Logical
)

type op struct {
	name  string
	nargs int32
}

var ops = map[int32]op{
	NOP:   op{"nop", 0},
	PUSH:  op{"push", 1},
	POP:   op{"pop", 0},
	ADD:   op{"add", 0},
	PRINT: op{"print", 0},
	HALT:  op{"halt", 0},
	JMPEQ: op{"jmpeq", 2},
	JMPNE: op{"jmpne", 2},
	J:     op{"j", 1},
	SLL:   op{"sll", 2},
	SRL:   op{"srl", 2},
}

type VM struct {
	code []int32
	pc   int32

	stack []int32
	sp    int32

	trace bool
}

func New(code []int32) *VM {
	return &VM{
		stack: make([]int32, 128),
		sp:    -1,
		code:  code,
		pc:    0,
		trace: false,
	}
}

func (v *VM) maybeTrace() {
	if !v.trace {
		return
	}
	addr := v.pc
	op := ops[v.code[v.pc]]
	args := v.code[v.pc+1 : v.pc+op.nargs+1]
	stack := v.stack[0 : v.sp+1]

	fmt.Printf("%04d: %s %v \t%v\n", addr, op.name, args, stack)
}

func (v *VM) Run() {
	for {
		v.maybeTrace()

		// Fetch
		op := v.code[v.pc]
		v.pc++

		// Decode
		switch op {
		case NOP:
			// Derp
		case PUSH:
			val := v.code[v.pc]
			v.pc++

			v.sp++
			v.stack[v.sp] = val
		case POP:
			v.sp--
		case ADD:
			a := v.stack[v.sp]
			v.sp--
			b := v.stack[v.sp]
			v.sp--

			v.sp++
			v.stack[v.sp] = a + b
		case PRINT:
			val := v.stack[v.sp]
			v.sp--
			fmt.Println(val)
		case JMPEQ:
			eq := v.code[v.pc]
			v.pc++
			addr := v.code[v.pc]
			v.pc++

			if v.stack[v.sp] == eq {
				v.pc = addr
			}
		case JMPNE:
			ne := v.code[v.pc]
			v.pc++
			addr := v.code[v.pc]
			v.pc++

			if v.stack[v.sp] != ne {
				v.pc = addr
			}
		case J:
			addr := v.code[v.pc]
			// v.pc++
			v.pc = addr
		case SLL:
			a := uint(v.stack[v.sp])
			v.sp--
			b := uint(v.stack[v.sp])
			v.sp--

			v.sp++
			v.stack[v.sp] = int32(a << b)
		case SRL:
			a := uint(v.stack[v.sp])
			v.sp--
			b := uint(v.stack[v.sp])
			v.sp--

			v.sp++
			v.stack[v.sp] = int32(a >> b)
		case HALT:
			return
		}
	}
}
<commit_msg>vm: Add 32 registers.<commit_after>package vm

import (
	"fmt"
)

const (
	HALT  int32 = iota // Halt
	NOP                // Nop
	PUSH               // Push to Top of Stack
	POP                // Remove from Top of Stack
	ADD                // Add
	PRINT              // Print
	JMPEQ              // Jump If Equal
	JMPNE              // Jump If Not Equal
	J                  // Jump Inconditionally
	SLL                // Shift Left Logical
	SRL                // Shift Right Logical
)

const (
	ZERO = iota
	LO
	HI
)

type op struct {
	name  string
	nargs int32
}

var ops = map[int32]op{
	NOP:   op{"nop", 0},
	PUSH:  op{"push", 1},
	POP:   op{"pop", 0},
	ADD:   op{"add", 0},
	PRINT: op{"print", 0},
	HALT:  op{"halt", 0},
	JMPEQ: op{"jmpeq", 2},
	JMPNE: op{"jmpne", 2},
	J:     op{"j", 1},
	SLL:   op{"sll", 2},
	SRL:   op{"srl", 2},
}

type VM struct {
	r [32]int32 // registers

	code []int32
	pc   int32

	stack []int32
	sp    int32

	trace bool
}

func New(code []int32) *VM {
	return &VM{
		stack: make([]int32, 128),
		sp:    -1,
		code:  code,
		pc:    0,
		trace: false,
	}
}

func (v *VM) maybeTrace() {
	if !v.trace {
		return
	}
	addr := v.pc
	op := ops[v.code[v.pc]]
	args := v.code[v.pc+1 : v.pc+op.nargs+1]
	stack := v.stack[0 : v.sp+1]

	fmt.Printf("%04d: %s %v \t%v\n", addr, op.name, args, stack)
}

func (v *VM) Run() {
	for {
		v.maybeTrace()

		// Fetch
		op := v.code[v.pc]
		v.pc++

		// Decode
		switch op {
		case NOP:
			// Derp
		case PUSH:
			val := v.code[v.pc]
			v.pc++

			v.sp++
			v.stack[v.sp] = val
		case POP:
			v.sp--
		case ADD:
			a := v.stack[v.sp]
			v.sp--
			b := v.stack[v.sp]
			v.sp--

			v.sp++
			v.stack[v.sp] = a + b
		case PRINT:
			val := v.stack[v.sp]
			v.sp--
			fmt.Println(val)
		case JMPEQ:
			eq := v.code[v.pc]
			v.pc++
			addr := v.code[v.pc]
			v.pc++

			if v.stack[v.sp] == eq {
				v.pc = addr
			}
		case JMPNE:
			ne := v.code[v.pc]
			v.pc++
			addr := v.code[v.pc]
			v.pc++

			if v.stack[v.sp] != ne {
				v.pc = addr
			}
		case J:
			addr := v.code[v.pc]
			// v.pc++
			v.pc = addr
		case SLL:
			a := uint(v.stack[v.sp])
			v.sp--
			b := uint(v.stack[v.sp])
			v.sp--

			v.sp++
			v.stack[v.sp] = int32(a << b)
		case SRL:
			a := uint(v.stack[v.sp])
			v.sp--
			b := uint(v.stack[v.sp])
			v.sp--

			v.sp++
			v.stack[v.sp] = int32(a >> b)
		case HALT:
			return
		}
	}
}
<|endoftext|>