<commit_before>package mark

import (
	"regexp"
	"strings"
	"unicode"
	"unicode/utf8"
)

// parse holds the state of the parser.
type parse struct {
	Nodes     []Node
	lex       Lexer
	options   *Options
	tr        *parse
	output    string
	peekCount int
	token     [3]item                 // three-token lookahead for parser
	links     map[string]*DefLinkNode // Deflink parsing, used RefLinks
	renderFn  map[NodeType]RenderFn   // Custom overridden fns
}

// Return new parser
func newParse(input string, opts *Options) *parse {
	return &parse{
		lex:      lex(input),
		options:  opts,
		links:    make(map[string]*DefLinkNode),
		renderFn: make(map[NodeType]RenderFn),
	}
}

// parse convert the raw text to Nodeparse.
func (p *parse) parse() {
Loop:
	for {
		var n Node
		switch t := p.peek(); t.typ {
		case itemEOF, itemError:
			break Loop
		case itemNewLine:
			p.next()
		case itemHr:
			n = p.newHr(p.next().pos)
		case itemHTML:
			t = p.next()
			n = p.newHTML(t.pos, t.val)
		case itemDefLink:
			n = p.parseDefLink()
		case itemHeading, itemLHeading:
			n = p.parseHeading()
		case itemCodeBlock, itemGfmCodeBlock:
			n = p.parseCodeBlock()
		case itemList:
			n = p.parseList()
		case itemTable, itemLpTable:
			n = p.parseTable()
		case itemBlockQuote:
			n = p.parseBlockQuote()
		case itemIndent:
			space := p.next()
			// If it isn't followed by
			if p.peek().typ != itemText {
				continue
			}
			p.backup2(space)
			fallthrough
		// itemText
		default:
			tmp := p.newParagraph(t.pos)
			tmp.Nodes = p.parseText(p.next().val + p.scanLines())
			n = tmp
		}
		if n != nil {
			p.append(n)
		}
	}
}

// Root getter
func (p *parse) root() *parse {
	if p.tr == nil {
		return p
	}
	return p.tr.root()
}

// Render parse nodes to the wanted output
func (p *parse) render() {
	for i, node := range p.Nodes {
		// If there's a custom render function, use it instead.
		if fn, ok := p.renderFn[node.Type()]; ok {
			p.output += fn(node)
		} else {
			p.output += node.Render()
		}
		if i != len(p.Nodes)-1 {
			p.output += "\n"
		}
	}
}

// append new node to nodes-list
func (p *parse) append(n Node) {
	p.Nodes = append(p.Nodes, n)
}

// next returns the next token
func (p *parse) next() item {
	if p.peekCount > 0 {
		p.peekCount--
	} else {
		p.token[0] = p.lex.nextItem()
	}
	return p.token[p.peekCount]
}

// peek returns but does not consume the next token.
func (p *parse) peek() item {
	if p.peekCount > 0 {
		return p.token[p.peekCount-1]
	}
	p.peekCount = 1
	p.token[0] = p.lex.nextItem()
	return p.token[0]
}

// backup backs the input stream tp one token
func (p *parse) backup() {
	p.peekCount++
}

// backup2 backs the input stream up two tokens.
// The zeroth token is already there.
func (p *parse) backup2(t1 item) {
	p.token[1] = t1
	p.peekCount = 2
}

// parseText
func (p *parse) parseText(input string) (nodes []Node) {
	// Trim whitespaces that not a line-break
	input = regexp.MustCompile(`(?m)^ +| +(\n|$)`).ReplaceAllStringFunc(input, func(s string) string {
		if reBr.MatchString(s) {
			return s
		}
		return strings.Replace(s, " ", "", -1)
	})
	l := lexInline(input)
	for token := range l.items {
		var node Node
		switch token.typ {
		case itemBr:
			node = p.newBr(token.pos)
		case itemStrong, itemItalic, itemStrike, itemCode:
			node = p.parseEmphasis(token.typ, token.pos, token.val)
		case itemLink, itemAutoLink, itemGfmLink:
			var title, text, href string
			if token.typ == itemLink {
				match := reLink.FindStringSubmatch(token.val)
				text, href, title = match[1], match[2], match[3]
			} else {
				var match []string
				if token.typ == itemGfmLink {
					match = reGfmLink.FindStringSubmatch(token.val)
				} else {
					match = reAutoLink.FindStringSubmatch(token.val)
				}
				text, href = match[1], match[1]
			}
			node = p.newLink(token.pos, title, href, text)
		case itemImage:
			match := reImage.FindStringSubmatch(token.val)
			node = p.newImage(token.pos, match[3], match[2], match[1])
		case itemRefLink, itemRefImage:
			match := reRefLink.FindStringSubmatch(token.val)
			node = p.newRef(token.typ, token.pos, token.val, match[1], match[2])
		// itemText
		default:
			node = p.newText(token.pos, token.val)
		}
		nodes = append(nodes, node)
	}
	return nodes
}

// parse inline emphasis
func (p *parse) parseEmphasis(typ itemType, pos Pos, val string) *EmphasisNode {
	var re *regexp.Regexp
	switch typ {
	case itemStrike:
		re = reStrike
	case itemStrong:
		re = reStrong
	case itemCode:
		re = reCode
	case itemItalic:
		re = reItalic
	}
	node := p.newEmphasis(pos, typ)
	match := re.FindStringSubmatch(val)
	text := match[len(match)-1]
	if text == "" {
		text = match[1]
	}
	node.Nodes = p.parseText(text)
	return node
}

// parse heading block
func (p *parse) parseHeading() (node *HeadingNode) {
	token := p.next()
	level := 1
	var text string
	if token.typ == itemHeading {
		match := reHeading.FindStringSubmatch(token.val)
		level, text = len(match[1]), match[2]
	} else {
		match := reLHeading.FindStringSubmatch(token.val)
		// using equal signs for first-level, and dashes for second-level.
		text = match[1]
		if match[2] == "-" {
			level = 2
		}
	}
	node = p.newHeading(token.pos, level, text)
	node.Nodes = p.parseText(text)
	return
}

func (p *parse) parseDefLink() *DefLinkNode {
	token := p.next()
	match := reDefLink.FindStringSubmatch(token.val)
	name := strings.ToLower(match[1])
	// name(lowercase), href, title
	n := p.newDefLink(token.pos, name, match[2], match[3])
	// store in links
	links := p.root().links
	if _, ok := links[name]; !ok {
		links[name] = n
	}
	return n
}

// parse codeBlock
func (p *parse) parseCodeBlock() *CodeNode {
	var lang, text string
	token := p.next()
	if token.typ == itemGfmCodeBlock {
		codeStart := reGfmCode.FindStringSubmatch(token.val)
		lang = codeStart[3]
		text = token.val[len(codeStart[0]):]
	} else {
		text = reCodeBlock.trim(token.val, "")
	}
	return p.newCode(token.pos, lang, text)
}

func (p *parse) parseBlockQuote() (n *BlockQuoteNode) {
	token := p.next()
	// replacer
	re := regexp.MustCompile(`(?m)^ *> ?`)
	raw := re.ReplaceAllString(token.val, "")
	// TODO(Ariel): not work right now with defLink(inside the blockQuote)
	tr := &parse{lex: lex(raw), tr: p}
	tr.parse()
	n = p.newBlockQuote(token.pos)
	n.Nodes = tr.Nodes
	return
}

// parse list
func (p *parse) parseList() *ListNode {
	token := p.next()
	list := p.newList(token.pos, isDigit(token.val))
Loop:
	for {
		switch token = p.peek(); token.typ {
		case itemLooseItem, itemListItem:
			list.append(p.parseListItem())
		default:
			break Loop
		}
	}
	return list
}

// parse listItem
// Add ignore list(e.g: table should parse as a text)
func (p *parse) parseListItem() *ListItemNode {
	token := p.next()
	item := p.newListItem(token.pos)
	tr := &parse{lex: lex(strings.TrimSpace(token.val)), tr: p}
	tr.parse()
	for _, node := range tr.Nodes {
		// wrap with paragraph only when it's loose item
		if n, ok := node.(*ParagraphNode); ok && token.typ == itemListItem {
			item.Nodes = append(item.Nodes, n.Nodes...)
		} else {
			item.append(node)
		}
	}
	return item
}

// parse table
func (p *parse) parseTable() *TableNode {
	table := p.newTable(p.next().pos)
	// Align	[ None, Left, Right, ... ]
	// Header	[ Cells: [ ... ] ]
	// Data:	[ Rows: [ Cells: [ ... ] ] ]
	rows := struct {
		Align  []AlignType
		Header []item
		Cells  [][]item
	}{}
Loop:
	for i := 0; ; {
		switch token := p.next(); token.typ {
		case itemTableRow:
			i++
			if i > 2 {
				rows.Cells = append(rows.Cells, []item{})
			}
		case itemTableCell:
			// Header
			if i == 1 {
				rows.Header = append(rows.Header, token)
				// Alignment
			} else if i == 2 {
				rows.Align = append(rows.Align, parseAlign(token.val))
				// Data
			} else {
				pos := i - 3
				rows.Cells[pos] = append(rows.Cells[pos], token)
			}
		default:
			p.backup()
			break Loop
		}
	}
	// Tranform to nodes
	table.append(p.parseCells(Header, rows.Header, rows.Align))
	// Table body
	for _, row := range rows.Cells {
		table.append(p.parseCells(Data, row, rows.Align))
	}
	return table
}

// parse cells and return new row
func (p *parse) parseCells(kind int, items []item, align []AlignType) *RowNode {
	var row *RowNode
	for i, item := range items {
		if i == 0 {
			row = p.newRow(item.pos)
		}
		cell := p.newCell(item.pos, kind, align[i])
		cell.Nodes = p.parseText(item.val)
		row.append(cell)
	}
	return row
}

// Used to consume lines(itemText) for a continues paragraphs
func (p *parse) scanLines() (s string) {
	for {
		tkn := p.next()
		if tkn.typ == itemText || tkn.typ == itemIndent {
			s += tkn.val
		} else if tkn.typ == itemNewLine {
			if t := p.peek().typ; t != itemText && t != itemIndent {
				p.backup2(tkn)
				break
			}
			s += tkn.val
		} else {
			p.backup()
			break
		}
	}
	return
}

// get align-string and return the align type of it
// e.g: ":---", "---:", ":---:", "---"
func parseAlign(s string) (typ AlignType) {
	sfx, pfx := strings.HasSuffix(s, ":"), strings.HasPrefix(s, ":")
	switch {
	case sfx && pfx:
		typ = Center
	case sfx:
		typ = Right
	case pfx:
		typ = Left
	}
	return
}

// test if given string is digit
func isDigit(s string) bool {
	r, _ := utf8.DecodeRuneInString(s)
	return unicode.IsDigit(r)
}
<commit_msg>style(parser): remove comment<commit_after>package mark

import (
	"regexp"
	"strings"
	"unicode"
	"unicode/utf8"
)

// parse holds the state of the parser.
type parse struct {
	Nodes     []Node
	lex       Lexer
	options   *Options
	tr        *parse
	output    string
	peekCount int
	token     [3]item                 // three-token lookahead for parser
	links     map[string]*DefLinkNode // Deflink parsing, used RefLinks
	renderFn  map[NodeType]RenderFn   // Custom overridden fns
}

// Return new parser
func newParse(input string, opts *Options) *parse {
	return &parse{
		lex:      lex(input),
		options:  opts,
		links:    make(map[string]*DefLinkNode),
		renderFn: make(map[NodeType]RenderFn),
	}
}

// parse convert the raw text to Nodeparse.
func (p *parse) parse() {
Loop:
	for {
		var n Node
		switch t := p.peek(); t.typ {
		case itemEOF, itemError:
			break Loop
		case itemNewLine:
			p.next()
		case itemHr:
			n = p.newHr(p.next().pos)
		case itemHTML:
			t = p.next()
			n = p.newHTML(t.pos, t.val)
		case itemDefLink:
			n = p.parseDefLink()
		case itemHeading, itemLHeading:
			n = p.parseHeading()
		case itemCodeBlock, itemGfmCodeBlock:
			n = p.parseCodeBlock()
		case itemList:
			n = p.parseList()
		case itemTable, itemLpTable:
			n = p.parseTable()
		case itemBlockQuote:
			n = p.parseBlockQuote()
		case itemIndent:
			space := p.next()
			// If it isn't followed by
			if p.peek().typ != itemText {
				continue
			}
			p.backup2(space)
			fallthrough
		// itemText
		default:
			tmp := p.newParagraph(t.pos)
			tmp.Nodes = p.parseText(p.next().val + p.scanLines())
			n = tmp
		}
		if n != nil {
			p.append(n)
		}
	}
}

// Root getter
func (p *parse) root() *parse {
	if p.tr == nil {
		return p
	}
	return p.tr.root()
}

// Render parse nodes to the wanted output
func (p *parse) render() {
	for i, node := range p.Nodes {
		// If there's a custom render function, use it instead.
		if fn, ok := p.renderFn[node.Type()]; ok {
			p.output += fn(node)
		} else {
			p.output += node.Render()
		}
		if i != len(p.Nodes)-1 {
			p.output += "\n"
		}
	}
}

// append new node to nodes-list
func (p *parse) append(n Node) {
	p.Nodes = append(p.Nodes, n)
}

// next returns the next token
func (p *parse) next() item {
	if p.peekCount > 0 {
		p.peekCount--
	} else {
		p.token[0] = p.lex.nextItem()
	}
	return p.token[p.peekCount]
}

// peek returns but does not consume the next token.
func (p *parse) peek() item {
	if p.peekCount > 0 {
		return p.token[p.peekCount-1]
	}
	p.peekCount = 1
	p.token[0] = p.lex.nextItem()
	return p.token[0]
}

// backup backs the input stream tp one token
func (p *parse) backup() {
	p.peekCount++
}

// backup2 backs the input stream up two tokens.
// The zeroth token is already there.
func (p *parse) backup2(t1 item) {
	p.token[1] = t1
	p.peekCount = 2
}

// parseText
func (p *parse) parseText(input string) (nodes []Node) {
	// Trim whitespaces that not a line-break
	input = regexp.MustCompile(`(?m)^ +| +(\n|$)`).ReplaceAllStringFunc(input, func(s string) string {
		if reBr.MatchString(s) {
			return s
		}
		return strings.Replace(s, " ", "", -1)
	})
	l := lexInline(input)
	for token := range l.items {
		var node Node
		switch token.typ {
		case itemBr:
			node = p.newBr(token.pos)
		case itemStrong, itemItalic, itemStrike, itemCode:
			node = p.parseEmphasis(token.typ, token.pos, token.val)
		case itemLink, itemAutoLink, itemGfmLink:
			var title, text, href string
			if token.typ == itemLink {
				match := reLink.FindStringSubmatch(token.val)
				text, href, title = match[1], match[2], match[3]
			} else {
				var match []string
				if token.typ == itemGfmLink {
					match = reGfmLink.FindStringSubmatch(token.val)
				} else {
					match = reAutoLink.FindStringSubmatch(token.val)
				}
				text, href = match[1], match[1]
			}
			node = p.newLink(token.pos, title, href, text)
		case itemImage:
			match := reImage.FindStringSubmatch(token.val)
			node = p.newImage(token.pos, match[3], match[2], match[1])
		case itemRefLink, itemRefImage:
			match := reRefLink.FindStringSubmatch(token.val)
			node = p.newRef(token.typ, token.pos, token.val, match[1], match[2])
		// itemText
		default:
			node = p.newText(token.pos, token.val)
		}
		nodes = append(nodes, node)
	}
	return nodes
}

// parse inline emphasis
func (p *parse) parseEmphasis(typ itemType, pos Pos, val string) *EmphasisNode {
	var re *regexp.Regexp
	switch typ {
	case itemStrike:
		re = reStrike
	case itemStrong:
		re = reStrong
	case itemCode:
		re = reCode
	case itemItalic:
		re = reItalic
	}
	node := p.newEmphasis(pos, typ)
	match := re.FindStringSubmatch(val)
	text := match[len(match)-1]
	if text == "" {
		text = match[1]
	}
	node.Nodes = p.parseText(text)
	return node
}

// parse heading block
func (p *parse) parseHeading() (node *HeadingNode) {
	token := p.next()
	level := 1
	var text string
	if token.typ == itemHeading {
		match := reHeading.FindStringSubmatch(token.val)
		level, text = len(match[1]), match[2]
	} else {
		match := reLHeading.FindStringSubmatch(token.val)
		// using equal signs for first-level, and dashes for second-level.
		text = match[1]
		if match[2] == "-" {
			level = 2
		}
	}
	node = p.newHeading(token.pos, level, text)
	node.Nodes = p.parseText(text)
	return
}

func (p *parse) parseDefLink() *DefLinkNode {
	token := p.next()
	match := reDefLink.FindStringSubmatch(token.val)
	name := strings.ToLower(match[1])
	// name(lowercase), href, title
	n := p.newDefLink(token.pos, name, match[2], match[3])
	// store in links
	links := p.root().links
	if _, ok := links[name]; !ok {
		links[name] = n
	}
	return n
}

// parse codeBlock
func (p *parse) parseCodeBlock() *CodeNode {
	var lang, text string
	token := p.next()
	if token.typ == itemGfmCodeBlock {
		codeStart := reGfmCode.FindStringSubmatch(token.val)
		lang = codeStart[3]
		text = token.val[len(codeStart[0]):]
	} else {
		text = reCodeBlock.trim(token.val, "")
	}
	return p.newCode(token.pos, lang, text)
}

func (p *parse) parseBlockQuote() (n *BlockQuoteNode) {
	token := p.next()
	// replacer
	re := regexp.MustCompile(`(?m)^ *> ?`)
	raw := re.ReplaceAllString(token.val, "")
	// TODO(Ariel): not work right now with defLink(inside the blockQuote)
	tr := &parse{lex: lex(raw), tr: p}
	tr.parse()
	n = p.newBlockQuote(token.pos)
	n.Nodes = tr.Nodes
	return
}

// parse list
func (p *parse) parseList() *ListNode {
	token := p.next()
	list := p.newList(token.pos, isDigit(token.val))
Loop:
	for {
		switch token = p.peek(); token.typ {
		case itemLooseItem, itemListItem:
			list.append(p.parseListItem())
		default:
			break Loop
		}
	}
	return list
}

// parse listItem
// Add ignore list(e.g: table should parse as a text)
func (p *parse) parseListItem() *ListItemNode {
	token := p.next()
	item := p.newListItem(token.pos)
	tr := &parse{lex: lex(strings.TrimSpace(token.val)), tr: p}
	tr.parse()
	for _, node := range tr.Nodes {
		// wrap with paragraph only when it's loose item
		if n, ok := node.(*ParagraphNode); ok && token.typ == itemListItem {
			item.Nodes = append(item.Nodes, n.Nodes...)
		} else {
			item.append(node)
		}
	}
	return item
}

// parse table
func (p *parse) parseTable() *TableNode {
	table := p.newTable(p.next().pos)
	// Align	[ None, Left, Right, ... ]
	// Header	[ Cells: [ ... ] ]
	// Data:	[ Rows: [ Cells: [ ... ] ] ]
	rows := struct {
		Align  []AlignType
		Header []item
		Cells  [][]item
	}{}
Loop:
	for i := 0; ; {
		switch token := p.next(); token.typ {
		case itemTableRow:
			i++
			if i > 2 {
				rows.Cells = append(rows.Cells, []item{})
			}
		case itemTableCell:
			// Header
			if i == 1 {
				rows.Header = append(rows.Header, token)
				// Alignment
			} else if i == 2 {
				rows.Align = append(rows.Align, parseAlign(token.val))
				// Data
			} else {
				pos := i - 3
				rows.Cells[pos] = append(rows.Cells[pos], token)
			}
		default:
			p.backup()
			break Loop
		}
	}
	// Tranform to nodes
	table.append(p.parseCells(Header, rows.Header, rows.Align))
	// Table body
	for _, row := range rows.Cells {
		table.append(p.parseCells(Data, row, rows.Align))
	}
	return table
}

// parse cells and return new row
func (p *parse) parseCells(kind int, items []item, align []AlignType) *RowNode {
	var row *RowNode
	for i, item := range items {
		if i == 0 {
			row = p.newRow(item.pos)
		}
		cell := p.newCell(item.pos, kind, align[i])
		cell.Nodes = p.parseText(item.val)
		row.append(cell)
	}
	return row
}

// Used to consume lines(itemText) for a continues paragraphs
func (p *parse) scanLines() (s string) {
	for {
		tkn := p.next()
		if tkn.typ == itemText || tkn.typ == itemIndent {
			s += tkn.val
		} else if tkn.typ == itemNewLine {
			if t := p.peek().typ; t != itemText && t != itemIndent {
				p.backup2(tkn)
				break
			}
			s += tkn.val
		} else {
			p.backup()
			break
		}
	}
	return
}

// get align-string and return the align type of it
func parseAlign(s string) (typ AlignType) {
	sfx, pfx := strings.HasSuffix(s, ":"), strings.HasPrefix(s, ":")
	switch {
	case sfx && pfx:
		typ = Center
	case sfx:
		typ = Right
	case pfx:
		typ = Left
	}
	return
}

// test if given string is digit
func isDigit(s string) bool {
	r, _ := utf8.DecodeRuneInString(s)
	return unicode.IsDigit(r)
}
<|endoftext|>


<commit_before>package main

import (
    "log"
    "strings"
    "net/url"
    "github.com/PuerkitoBio/goquery"
)

const LSE_URL = "http://www.lse.ac.uk"
var PROGRAMMES_URL = map[string]string {
    "Undergraduate":    LSE_URL + "/resources/calendar/courseGuides/undergraduate.htm",
    "Graduate":         LSE_URL + "/resources/calendar/courseGuides/graduate.htm",
    "Research":         LSE_URL + "/resources/calendar/courseGuides/research.htm",
}

type Course struct {
    Code string
    Title string
    URL string
    Department string
    Students int
    Class int
    Value int
    Program int
}

func main() {
    var program *goquery.Document
    var e error

    if program, e = goquery.NewDocument(PROGRAMMES_URL["Undergraduate"]); e != nil {
        log.Fatal(e)
    }

    var courses []interface{}
    program.Find("table tr td p a").Each(func(i int, s *goquery.Selection) {
        course_item := strings.Split(s.Text(), " ")
        course_item_url, _ := s.Attr("href")
        parsed_url, _ := url.Parse(PROGRAMMES_URL["Undergraduate"])
        parsed_relative, _ := url.Parse(course_item_url)

        course_object := new(Course)
        course_object.Code = course_item[0]
        course_object.Title = course_item[1]
        course_object.URL = parsed_url.ResolveReference(parsed_relative).String()
        course_object.Program = 0
        courses = append(courses, course_object)
    })
}<commit_msg>Compartmentalizing functions<commit_after>package main

import (
    "log"
    "strings"
    "net/url"
    "github.com/PuerkitoBio/goquery"
)

const LSE_URL = "http://www.lse.ac.uk"
var PROGRAMMES_URL = map[string]string {
    "Undergraduate":    LSE_URL + "/resources/calendar/courseGuides/undergraduate.htm",
    "Graduate":         LSE_URL + "/resources/calendar/courseGuides/graduate.htm",
    "Research":         LSE_URL + "/resources/calendar/courseGuides/research.htm",
}

type Course struct {
    Code string
    Title string
    URL string
    Department string
    Students int
    Class int
    Value int
    Program int
}

func Parse(Type string) (program_courses []interface{}) {
    var program *goquery.Document
    var e error

    if program, e = goquery.NewDocument(PROGRAMMES_URL[Type]); e != nil {
        log.Fatal(e)
    }

    program.Find("table tr td p a").Each(func(i int, s *goquery.Selection) {
        course_item := strings.Split(s.Text(), " ")
        course_item_url, _ := s.Attr("href")
        parsed_url, _ := url.Parse(PROGRAMMES_URL[Type])
        parsed_relative, _ := url.Parse(course_item_url)

        course_object := new(Course)
        course_object.Code = course_item[0]
        course_object.Title = course_item[1]
        course_object.URL = parsed_url.ResolveReference(parsed_relative).String()
        course_object.Program = 0
        program_courses = append(program_courses, course_object)
    })
    return
}

func main() {
    var courses []interface{}
    for program, _ := range PROGRAMMES_URL {
        courses = append(courses, Parse(program)...)
        log.Printf("%s courses: %d", program, len(courses))
    }
}<|endoftext|>


<commit_before>package sexp

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"strconv"
)

// Convinience shortcut function for loading and unmarshaling an S-exp file
// into an arbitrary type.
func Load(filename string, out interface{}) error {
	f, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer f.Close()

	ast, err := Parse(f, "", -1, nil)
	if err != nil {
		return err
	}
	return ast.Unmarshal(out)
}

// Parse an S-exp stream from a given io.Reader.
//
// Filename is used for informative purposes only. Length must reflect the length
// of a stream or -1 if unknown. Source context is optional as well, it will be
// used to encode source location information. If no source context was provided,
// the one will be created in-place, meaning you will not be able to decode
// source locations. Returns the root node of an AST tree if there were no errors.
func Parse(r io.Reader, filename string, length int, ctx *SourceContext) (*Node, error) {
	if ctx == nil {
		ctx = &SourceContext{}
	}
	f := ctx.AddFile(filename, length)
	return ParseFile(r, f)
}

// Same as Parse, except it takes source file created by SourceContext.AddFile
// directly. It's here for advanced purposes such as parallel parsing. You can
// add multiple files to the source context at once and then parse these files
// in parallel. However, keep in mind that all the lengths of the streams
// must be known ahead of time.
func ParseFile(r io.Reader, f *SourceFile) (*Node, error) {
	var p parser

	// avoid unnecessary double buffering
	if br, ok := r.(*bufio.Reader); ok {
		p.r = br
	} else {
		p.r = bufio.NewReader(r)
	}

	p.f = f
	p.line = 1
	p.last_seq = seq{offset: -1}
	p.expect_eof = true
	return p.parse()
}

var seq_delims = map[rune]rune{
	'(': ')',
	'`': '`',
	'"': '"',
}

func is_hex(r rune) bool {
	return (r >= '0' && r <= '9') ||
		(r >= 'a' && r <= 'f') ||
		(r >= 'A' && r <= 'F')
}

func is_space(r rune) bool {
	return r == ' ' || r == '\t' || r == '\n' || r == '\r'
}

func is_delimiter(r rune) bool {
	return is_space(r) || r == ')' || r == ';' || r == 0
}

type seq struct {
	offset int
	rune   rune
}

type delim_state struct {
	last_seq   seq
	expect_eof bool
}

type parser struct {
	r      *bufio.Reader
	f      *SourceFile
	buf    bytes.Buffer
	line   int
	offset int
	cur    rune
	curlen int
	delim_state
}

func (p *parser) advance_delim_state() delim_state {
	s := p.delim_state
	p.last_seq = seq{p.offset, p.cur}
	p.expect_eof = false
	return s
}

func (p *parser) restore_delim_state(s delim_state) {
	p.delim_state = s
}

func (p *parser) error(loc SourceLoc, format string, args ...interface{}) {
	panic(&Error{
		Location: loc,
		message:  fmt.Sprintf(format, args...),
	})
}

func (p *parser) next() {
	p.offset += p.curlen
	r, s, err := p.r.ReadRune()
	if err != nil {
		if err == io.EOF {
			if p.expect_eof {
				p.cur = 0
				p.curlen = 0
				return
			}
			p.error(p.f.Encode(p.last_seq.offset),
				"missing matching sequence delimiter '%c'",
				seq_delims[p.last_seq.rune])
		}
		p.error(p.f.Encode(p.offset),
			"unexpected read error: %s", err)
	}

	p.cur = r
	p.curlen = s
	if r == '\n' {
		p.f.AddLine(p.offset + p.curlen)
	}
}

func (p *parser) skip_spaces() {
	for {
		if is_space(p.cur) {
			p.next()
		} else {
			return
		}
	}
	panic("unreachable")
}

func (p *parser) skip_comment() {
	for {
		// there was an EOF, return
		if p.cur == 0 {
			return
		}

		// read until '\n'
		if p.cur != '\n' {
			p.next()
		} else {
			// skip '\n' and return
			p.next()
			return
		}
	}
	panic("unreachable")
}

func (p *parser) parse_node() *Node {
again:
	// the convention is that this function is called on a non-space `p.cur`
	switch p.cur {
	case ')':
		return nil
	case '(':
		return p.parse_list()
	case '"':
		return p.parse_string()
	case '`':
		return p.parse_raw_string()
	case ';':
		// skip comment
		p.skip_comment()
		p.skip_spaces()
		goto again
	case 0:
		// delayed expected EOF
		panic(io.EOF)
	default:
		return p.parse_ident()
	}
	panic("unreachable")
}

func (p *parser) parse_list() *Node {
	loc := p.f.Encode(p.offset)
	save := p.advance_delim_state()

	head := &Node{Location: loc}
	p.next() // skip opening '('

	var lastchild *Node
	for {
		p.skip_spaces()
		if p.cur == ')' {
			// skip enclosing ')', but it could be EOF also
			p.restore_delim_state(save)
			p.next()
			return head
		}

		node := p.parse_node()
		if node == nil {
			continue
		}
		if head.Children == nil {
			head.Children = node
		} else {
			lastchild.Next = node
		}
		lastchild = node
	}
	panic("unreachable")
}

func (p *parser) parse_esc_seq() {
	loc := p.f.Encode(p.offset)

	p.next() // skip '\\'
	switch p.cur {
	case 'a':
		p.next()
		p.buf.WriteByte('\a')
	case 'b':
		p.next()
		p.buf.WriteByte('\b')
	case 'f':
		p.next()
		p.buf.WriteByte('\f')
	case 'n':
		p.next()
		p.buf.WriteByte('\n')
	case 'r':
		p.next()
		p.buf.WriteByte('\r')
	case 't':
		p.next()
		p.buf.WriteByte('\t')
	case 'v':
		p.next()
		p.buf.WriteByte('\v')
	case '\\':
		p.next()
		p.buf.WriteByte('\\')
	case '"':
		p.next()
		p.buf.WriteByte('"')
	default:
		switch p.cur {
		case 'x':
			p.next() // skip 'x'
			p.parse_hex_rune(2)
		case 'u':
			p.next() // skip 'u'
			p.parse_hex_rune(4)
		case 'U':
			p.next() // skip 'U'
			p.parse_hex_rune(8)
		default:
			p.error(loc, `unrecognized escape sequence within '"' string`)
		}
	}
}

func (p *parser) parse_hex_rune(n int) {
	if n > 8 {
		panic("hex rune is too large")
	}

	var hex [8]byte
	p.next_hex(hex[:n])
	r, err := strconv.ParseUint(string(hex[:n]), 16, n*4) // 4 bits per hex digit
	panic_if_error(err)
	if n == 2 {
		p.buf.WriteByte(byte(r))
	} else {
		p.buf.WriteRune(rune(r))
	}
}

func (p *parser) next_hex(s []byte) {
	for i, n := 0, len(s); i < n; i++ {
		if !is_hex(p.cur) {
			loc := p.f.Encode(p.offset)
			p.error(loc, `'%c' is not a hex digit`, p.cur)
		}
		s[i] = byte(p.cur)
		p.next()
	}
}

func (p *parser) parse_string() *Node {
	loc := p.f.Encode(p.offset)
	save := p.advance_delim_state()

	p.next() // skip opening '"'
	for {
		switch p.cur {
		case '\n':
			p.error(loc, `newline is not allowed within '"' strings`)
		case '\\':
			p.parse_esc_seq()
		case '"':
			node := &Node{
				Location: loc,
				Value:    p.buf.String(),
			}
			p.buf.Reset()

			// consume enclosing '"', could be EOF
			p.restore_delim_state(save)
			p.next()
			return node
		default:
			p.buf.WriteRune(p.cur)
			p.next()
		}
	}
	panic("unreachable")
}

func (p *parser) parse_raw_string() *Node {
	loc := p.f.Encode(p.offset)
	save := p.advance_delim_state()

	p.next() // skip opening '`'
	for {
		if p.cur == '`' {
			node := &Node{
				Location: loc,
				Value:    p.buf.String(),
			}
			p.buf.Reset()
			// consume enclosing '`', could be EOF
			p.restore_delim_state(save)
			p.next()
			return node
		} else {
			p.buf.WriteRune(p.cur)
			p.next()
		}
	}
	panic("unreachable")
}

func (p *parser) parse_ident() *Node {
	loc := p.f.Encode(p.offset)
	for {
		if is_delimiter(p.cur) {
			node := &Node{
				Location: loc,
				Value:    p.buf.String(),
			}
			p.buf.Reset()
			return node
		} else {
			p.buf.WriteRune(p.cur)
			p.next()
		}
	}
	panic("unreachable")
}

func (p *parser) parse() (root *Node, err error) {
	defer func() {
		if e := recover(); e != nil {
			p.f.Finalize(p.offset)
			if e == io.EOF {
				return
			}
			if sexperr, ok := e.(*Error); ok {
				root = nil
				err = sexperr
				return
			}
			panic(e)
		}
	}()

	root = new(Node)
	p.next()

	// don't worry, will eventually panic with io.EOF :D
	var lastchild *Node
	for {
		p.skip_spaces()
		node := p.parse_node()
		if node == nil {
			p.error(p.f.Encode(p.offset),
				"unexpected ')' at the top level")
		}
		if root.Children == nil {
			root.Children = node
		} else {
			lastchild.Next = node
		}
		lastchild = node
	}
	panic("unreachable")
}
<commit_msg>Slightly rephrase description of the 'Load' function.<commit_after>package sexp

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"strconv"
)

// Convinience shortcut function for loading and unmarshaling an S-exp file
// into a value of an arbitrary type.
func Load(filename string, out interface{}) error {
	f, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer f.Close()

	ast, err := Parse(f, "", -1, nil)
	if err != nil {
		return err
	}
	return ast.Unmarshal(out)
}

// Parse an S-exp stream from a given io.Reader.
//
// Filename is used for informative purposes only. Length must reflect the length
// of a stream or -1 if unknown. Source context is optional as well, it will be
// used to encode source location information. If no source context was provided,
// the one will be created in-place, meaning you will not be able to decode
// source locations. Returns the root node of an AST tree if there were no errors.
func Parse(r io.Reader, filename string, length int, ctx *SourceContext) (*Node, error) {
	if ctx == nil {
		ctx = &SourceContext{}
	}
	f := ctx.AddFile(filename, length)
	return ParseFile(r, f)
}

// Same as Parse, except it takes source file created by SourceContext.AddFile
// directly. It's here for advanced purposes such as parallel parsing. You can
// add multiple files to the source context at once and then parse these files
// in parallel. However, keep in mind that all the lengths of the streams
// must be known ahead of time.
func ParseFile(r io.Reader, f *SourceFile) (*Node, error) {
	var p parser

	// avoid unnecessary double buffering
	if br, ok := r.(*bufio.Reader); ok {
		p.r = br
	} else {
		p.r = bufio.NewReader(r)
	}

	p.f = f
	p.line = 1
	p.last_seq = seq{offset: -1}
	p.expect_eof = true
	return p.parse()
}

var seq_delims = map[rune]rune{
	'(': ')',
	'`': '`',
	'"': '"',
}

func is_hex(r rune) bool {
	return (r >= '0' && r <= '9') ||
		(r >= 'a' && r <= 'f') ||
		(r >= 'A' && r <= 'F')
}

func is_space(r rune) bool {
	return r == ' ' || r == '\t' || r == '\n' || r == '\r'
}

func is_delimiter(r rune) bool {
	return is_space(r) || r == ')' || r == ';' || r == 0
}

type seq struct {
	offset int
	rune   rune
}

type delim_state struct {
	last_seq   seq
	expect_eof bool
}

type parser struct {
	r      *bufio.Reader
	f      *SourceFile
	buf    bytes.Buffer
	line   int
	offset int
	cur    rune
	curlen int
	delim_state
}

func (p *parser) advance_delim_state() delim_state {
	s := p.delim_state
	p.last_seq = seq{p.offset, p.cur}
	p.expect_eof = false
	return s
}

func (p *parser) restore_delim_state(s delim_state) {
	p.delim_state = s
}

func (p *parser) error(loc SourceLoc, format string, args ...interface{}) {
	panic(&Error{
		Location: loc,
		message:  fmt.Sprintf(format, args...),
	})
}

func (p *parser) next() {
	p.offset += p.curlen
	r, s, err := p.r.ReadRune()
	if err != nil {
		if err == io.EOF {
			if p.expect_eof {
				p.cur = 0
				p.curlen = 0
				return
			}
			p.error(p.f.Encode(p.last_seq.offset),
				"missing matching sequence delimiter '%c'",
				seq_delims[p.last_seq.rune])
		}
		p.error(p.f.Encode(p.offset),
			"unexpected read error: %s", err)
	}

	p.cur = r
	p.curlen = s
	if r == '\n' {
		p.f.AddLine(p.offset + p.curlen)
	}
}

func (p *parser) skip_spaces() {
	for {
		if is_space(p.cur) {
			p.next()
		} else {
			return
		}
	}
	panic("unreachable")
}

func (p *parser) skip_comment() {
	for {
		// there was an EOF, return
		if p.cur == 0 {
			return
		}

		// read until '\n'
		if p.cur != '\n' {
			p.next()
		} else {
			// skip '\n' and return
			p.next()
			return
		}
	}
	panic("unreachable")
}

func (p *parser) parse_node() *Node {
again:
	// the convention is that this function is called on a non-space `p.cur`
	switch p.cur {
	case ')':
		return nil
	case '(':
		return p.parse_list()
	case '"':
		return p.parse_string()
	case '`':
		return p.parse_raw_string()
	case ';':
		// skip comment
		p.skip_comment()
		p.skip_spaces()
		goto again
	case 0:
		// delayed expected EOF
		panic(io.EOF)
	default:
		return p.parse_ident()
	}
	panic("unreachable")
}

func (p *parser) parse_list() *Node {
	loc := p.f.Encode(p.offset)
	save := p.advance_delim_state()

	head := &Node{Location: loc}
	p.next() // skip opening '('

	var lastchild *Node
	for {
		p.skip_spaces()
		if p.cur == ')' {
			// skip enclosing ')', but it could be EOF also
			p.restore_delim_state(save)
			p.next()
			return head
		}

		node := p.parse_node()
		if node == nil {
			continue
		}
		if head.Children == nil {
			head.Children = node
		} else {
			lastchild.Next = node
		}
		lastchild = node
	}
	panic("unreachable")
}

func (p *parser) parse_esc_seq() {
	loc := p.f.Encode(p.offset)

	p.next() // skip '\\'
	switch p.cur {
	case 'a':
		p.next()
		p.buf.WriteByte('\a')
	case 'b':
		p.next()
		p.buf.WriteByte('\b')
	case 'f':
		p.next()
		p.buf.WriteByte('\f')
	case 'n':
		p.next()
		p.buf.WriteByte('\n')
	case 'r':
		p.next()
		p.buf.WriteByte('\r')
	case 't':
		p.next()
		p.buf.WriteByte('\t')
	case 'v':
		p.next()
		p.buf.WriteByte('\v')
	case '\\':
		p.next()
		p.buf.WriteByte('\\')
	case '"':
		p.next()
		p.buf.WriteByte('"')
	default:
		switch p.cur {
		case 'x':
			p.next() // skip 'x'
			p.parse_hex_rune(2)
		case 'u':
			p.next() // skip 'u'
			p.parse_hex_rune(4)
		case 'U':
			p.next() // skip 'U'
			p.parse_hex_rune(8)
		default:
			p.error(loc, `unrecognized escape sequence within '"' string`)
		}
	}
}

func (p *parser) parse_hex_rune(n int) {
	if n > 8 {
		panic("hex rune is too large")
	}

	var hex [8]byte
	p.next_hex(hex[:n])
	r, err := strconv.ParseUint(string(hex[:n]), 16, n*4) // 4 bits per hex digit
	panic_if_error(err)
	if n == 2 {
		p.buf.WriteByte(byte(r))
	} else {
		p.buf.WriteRune(rune(r))
	}
}

func (p *parser) next_hex(s []byte) {
	for i, n := 0, len(s); i < n; i++ {
		if !is_hex(p.cur) {
			loc := p.f.Encode(p.offset)
			p.error(loc, `'%c' is not a hex digit`, p.cur)
		}
		s[i] = byte(p.cur)
		p.next()
	}
}

func (p *parser) parse_string() *Node {
	loc := p.f.Encode(p.offset)
	save := p.advance_delim_state()

	p.next() // skip opening '"'
	for {
		switch p.cur {
		case '\n':
			p.error(loc, `newline is not allowed within '"' strings`)
		case '\\':
			p.parse_esc_seq()
		case '"':
			node := &Node{
				Location: loc,
				Value:    p.buf.String(),
			}
			p.buf.Reset()

			// consume enclosing '"', could be EOF
			p.restore_delim_state(save)
			p.next()
			return node
		default:
			p.buf.WriteRune(p.cur)
			p.next()
		}
	}
	panic("unreachable")
}

func (p *parser) parse_raw_string() *Node {
	loc := p.f.Encode(p.offset)
	save := p.advance_delim_state()

	p.next() // skip opening '`'
	for {
		if p.cur == '`' {
			node := &Node{
				Location: loc,
				Value:    p.buf.String(),
			}
			p.buf.Reset()
			// consume enclosing '`', could be EOF
			p.restore_delim_state(save)
			p.next()
			return node
		} else {
			p.buf.WriteRune(p.cur)
			p.next()
		}
	}
	panic("unreachable")
}

func (p *parser) parse_ident() *Node {
	loc := p.f.Encode(p.offset)
	for {
		if is_delimiter(p.cur) {
			node := &Node{
				Location: loc,
				Value:    p.buf.String(),
			}
			p.buf.Reset()
			return node
		} else {
			p.buf.WriteRune(p.cur)
			p.next()
		}
	}
	panic("unreachable")
}

func (p *parser) parse() (root *Node, err error) {
	defer func() {
		if e := recover(); e != nil {
			p.f.Finalize(p.offset)
			if e == io.EOF {
				return
			}
			if sexperr, ok := e.(*Error); ok {
				root = nil
				err = sexperr
				return
			}
			panic(e)
		}
	}()

	root = new(Node)
	p.next()

	// don't worry, will eventually panic with io.EOF :D
	var lastchild *Node
	for {
		p.skip_spaces()
		node := p.parse_node()
		if node == nil {
			p.error(p.f.Encode(p.offset),
				"unexpected ')' at the top level")
		}
		if root.Children == nil {
			root.Children = node
		} else {
			lastchild.Next = node
		}
		lastchild = node
	}
	panic("unreachable")
}
<|endoftext|>


<commit_before>package lang

import (
	"bufio"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"github.com/dockpit/pit/contract"
)

var CaseEX = regexp.MustCompile(`^'(.*)'$`)
var ResourceEX = regexp.MustCompile(`^- (.*)`)
var VariableEX = regexp.MustCompile(`(\(.*?\))`)
var ValidHTTPMethods = []string{"GET", "POST", "PUT"} //@todo add more

func UnexpectedDirError(fi os.FileInfo) error {
	return fmt.Errorf("Parser encountered an unexpected directory: %s, expected a resource directory (starting with `- `), or a case directory formatted as `'case name'`", fi.Name())
}

func UnexpectedFileError(fi os.FileInfo) error {
	return fmt.Errorf("Parser encountered a file without an extension: '%s', only 'given', 'when', 'then' or 'while' is allowed", fi.Name())
}

func UnexpectedStateLineError(fpath, line string) error {
	return fmt.Errorf("Parser encountered a 'given' file '%s', with an invalid line: \n%s\n expected format \"<state provider name> '<state name>'\"", fpath, line)
}

func UnexpectedLinkLineError(fpath, line string) error {
	return fmt.Errorf("Parser encountered a 'while' file '%s' with an unexpected line: %s, expected format \"<service id> '<case name>'\"", fpath, line)
}

func UnexpectedLinkLineCaseNameError(fpath, line string) error {
	return fmt.Errorf("Parser encountered a 'while' file '%s' with a invalid casename: \"%s\", expected single-quoted name: e.g 'name of the case'", fpath, line)
}

func UnexpectedHeaderLineError(fpath, giv string) error {
	return fmt.Errorf("File '%s' has an unexpected header line: '%s', expected format 'Header-Key: Value'", fpath, giv)
}

func UnexpectedResponseLineError(fpath, line string) error {
	return fmt.Errorf("Parser encountered a 'then' file '%s' with an unexpected first line: %s, expected format '<HTTP Status Code> <Status Text>'", fpath, line)
}

func UnexpectedResponseLineCodeError(fpath, giv string, err error) error {
	return fmt.Errorf("Parser encountered a 'then' file '%s' with an unexpected status code: %s, expected a number. (%s)", fpath, giv, err)
}

func UnexpectedRequestLineError(fpath, line string) error {
	return fmt.Errorf("Parser encountered a 'when' file '%s' with an unexpected first line: %s, expected format '<HTTP method> <path>'", fpath, line)
}

func UnexpectedRequestLineMethodError(fpath, giv string) error {
	return fmt.Errorf("Parser encountered a 'when' file '%s' with an unexpected HTTP Method in the first line: '%s', expected one of: %s", fpath, giv, ValidHTTPMethods)
}

func UnexpectedRequestLinePathError(fpath, giv string) error {
	return fmt.Errorf("Parser encountered a 'when' file '%s' with an unexpected path in the first line: '%s', expected absolute path (starting with '/')", fpath, giv)
}

type Node struct {
	Pattern  string
	Children []*Node
}

func NewNode() *Node {
	return &Node{
		Pattern:  "/",
		Children: []*Node{},
	}
}

func (n *Node) Append(nn *Node, part string) {
	n.Children = append(n.Children, nn)

	nn.Pattern = path.Join(n.Pattern, part)
}

//
//
//
type Parser struct {
	Dir string

	data  *contract.ContractData
	nodes map[string]*Node
	cases map[string]string

	currentNode     *Node
	currentResource *contract.ResourceData
	currentCase     *contract.CaseData
}

func NewParser(dir string) *Parser {
	p := &Parser{
		Dir: dir,
	}
	p.reset()
	return p
}

func (p *Parser) reset() {
	root := NewNode()
	p.data = &contract.ContractData{}
	p.nodes = map[string]*Node{".": root}
	p.cases = map[string]string{}
	p.currentNode = root
	p.currentResource = nil
	p.currentCase = nil
}

func (p *Parser) ParseHTTPMessage(r io.ReadCloser, fpath string) (string, http.Header, string, error) {

	inbody := false
	rline := ""
	hlines := []string{}
	body := ""
	headers := make(http.Header)

	s := bufio.NewScanner(r)
	for s.Scan() {
		if !inbody {
			//first line should be the request line
			if rline == "" {
				rline = s.Text()
				continue
			}

			//empty line indicates that we will be parsing the body
			if s.Text() == "" {
				inbody = true
				continue
			}

			//add as headers
			hlines = append(hlines, s.Text())
		} else {
			//res is assumed to be body
			body += s.Text()
		}
	}

	//check/parse header format
	for _, h := range hlines {
		hp := strings.SplitN(h, ":", 2)
		if len(hp) != 2 {
			return rline, headers, body, UnexpectedHeaderLineError(fpath, h)
		}

		headers.Add(http.CanonicalHeaderKey(hp[0]), strings.TrimSpace(hp[1]))
	}

	return rline, headers, body, nil
}

// parses a when file loosely based on the http request spec
func (p *Parser) ParseWhen(r io.ReadCloser, fpath string) (*contract.When, error) {
	w := &contract.When{}

	rline, headers, body, err := p.ParseHTTPMessage(r, fpath)
	if err != nil {
		return nil, err
	}

	//request line should be 2 parts seperated by a space
	rlinep := strings.SplitN(rline, " ", 2)
	if len(rlinep) != 2 {
		return nil, UnexpectedRequestLineError(fpath, rline)
	}

	//check method format
	method := ""
	for _, m := range ValidHTTPMethods {
		if m == rlinep[0] {
			method = m
			break
		}
	}

	if method == "" {
		return nil, UnexpectedRequestLineMethodError(fpath, rlinep[0])
	}
	w.Method = method

	//check path
	if !path.IsAbs(rlinep[1]) {
		return nil, UnexpectedRequestLinePathError(fpath, rlinep[1])
	}
	w.Path = rlinep[1]
	w.Headers = headers
	w.Body = body

	return w, nil
}

// parses a then file loosely based on the format of a standard http message
func (p *Parser) ParseThen(r io.ReadCloser, fpath string) (*contract.Then, error) {
	t := &contract.Then{}

	//parse as a standard http message
	rline, headers, body, err := p.ParseHTTPMessage(r, fpath)
	if err != nil {
		return nil, err
	}

	//response line should be 2 parts seperated by a space
	rlinep := strings.SplitN(rline, " ", 2)
	if len(rlinep) != 2 {
		return nil, UnexpectedResponseLineError(fpath, rline)
	}

	//first one should be parseable as int
	code, err := strconv.Atoi(rlinep[0])
	if err != nil {
		return nil, UnexpectedResponseLineCodeError(fpath, rlinep[0], err)
	}

	t.StatusCode = code
	t.Status = rlinep[1]
	t.Headers = headers
	t.Body = body
	return t, nil
}

// parses a 'while' file
func (p *Parser) ParseWhile(r io.ReadCloser, fpath string) ([]contract.While, error) {
	ws := []contract.While{}

	s := bufio.NewScanner(r)
	for s.Scan() {

		//dont mind empty lines
		if strings.TrimSpace(s.Text()) == "" {
			continue
		}

		//every non-empty line should have space seperated link
		wp := strings.SplitN(s.Text(), " ", 2)
		if len(wp) != 2 {
			return ws, UnexpectedLinkLineError(fpath, s.Text())
		}

		cname := p.ToCaseName(wp[1])
		if cname == "" {
			return ws, UnexpectedLinkLineCaseNameError(fpath, wp[1])
		}

		//create while for case
		ws = append(ws, contract.While{
			ID:       wp[0],
			CaseName: cname,
		})
	}

	return ws, nil
}

// parses a 'given' file
func (p *Parser) ParseGiven(r io.ReadCloser, fpath string) (map[string]contract.Given, error) {
	gs := make(map[string]contract.Given)

	s := bufio.NewScanner(r)
	for s.Scan() {

		//dont mind empty lines
		if strings.TrimSpace(s.Text()) == "" {
			continue
		}

		//every non-empty line should have space seperated link
		gp := strings.SplitN(s.Text(), ":", 2)
		if len(gp) != 2 {
			return gs, UnexpectedStateLineError(fpath, s.Text())
		}

		//extract provider name
		pname := strings.TrimSpace(gp[0])
		if pname == "" {
			return gs, UnexpectedStateLineError(fpath, s.Text())
		}

		//extract state name as case name
		sname := p.ToCaseName(strings.TrimSpace(gp[1]))
		if sname == "" {
			return gs, UnexpectedStateLineError(fpath, s.Text())
		}

		//set given
		gs[pname] = contract.Given{
			Name: sname,
		}
	}

	return gs, nil
}

//
// Returns wether a given basename of a file path denotes a resource
func (p *Parser) ToResourcePatternPart(basename string) string {
	m := ResourceEX.FindStringSubmatch(basename)
	if m == nil {
		return ""
	}

	//replace variables by sinatra style vars
	res := VariableEX.ReplaceAllFunc([]byte(m[1]), func(in []byte) []byte {
		v := in[1 : len(in)-1]
		return append([]byte(":"), v...)
	})

	return string(res)
}

//
// Returns wether a given basename of a file path denotes a case example
func (p *Parser) ToCaseName(basename string) string {
	m := CaseEX.FindStringSubmatch(basename)
	if m == nil {
		return ""
	}

	return m[1]
}

func (p *Parser) visit(fpath string, fi os.FileInfo, err error) error {

	//cancel walk if something went wrong
	if err != nil {
		return err
	}

	//skip root
	if fpath == p.Dir {
		return nil
	}

	//care only about relative path
	rel, err := filepath.Rel(p.Dir, fpath)
	if err != nil {
		return err
	}

	//directories are expected to be either resources or cases
	if fi.IsDir() {
		if part := p.ToResourcePatternPart(filepath.Base(rel)); part != "" {
			var parent *Node
			var ok bool

			//create and add new resource node
			p.currentNode = NewNode()
			p.nodes[rel] = p.currentNode

			//get parent node
			if parent, ok = p.nodes[filepath.Dir(rel)]; !ok {
				return fmt.Errorf("No parent node found for '%s'", fpath)
			}

			//apprent to parent
			parent.Append(p.currentNode, part)

			//use node structure to create and append resource to contract
			p.currentResource = &contract.ResourceData{
				Pattern: p.currentNode.Pattern,
				Cases:   []*contract.CaseData{},
			}

			// add to contract data
			p.data.Resources = append(p.data.Resources, p.currentResource)

		} else if cname := p.ToCaseName(filepath.Base(rel)); cname != "" {

			//get current resource (if any)
			res := p.currentResource
			if res == nil {
				return fmt.Errorf("Case folder '%s' (%s) is outside a resource", cname, fpath)
			}

			//case name must be unique
			if ex, ok := p.cases[cname]; ok {
				return fmt.Errorf("Case with name '%s' (%s) already exists in '%s'", cname, fpath, filepath.Dir(ex))
			}

			//create the case from available data
			p.currentCase = &contract.CaseData{
				Name: cname,
				When: contract.When{},
				Then: contract.Then{},
			}

			//and append to resource
			res.Cases = append(res.Cases, p.currentCase)
			p.cases[cname] = fpath
		} else {
			return UnexpectedDirError(fi)
		}
	} else {

		//files without extension have to be either when/then/given/while
		if filepath.Ext(fpath) == "" {

			f, err := os.Open(fpath)
			if err != nil {
				return err
			}
			defer f.Close()

			//'keywords;
			if filepath.Base(fpath) == "given" {
				given, err := p.ParseGiven(f, fpath)
				if err != nil {
					return err
				}

				p.currentCase.Given = given
			} else if filepath.Base(fpath) == "when" {
				when, err := p.ParseWhen(f, fpath)
				if err != nil {
					return err
				}

				p.currentCase.When = *when
			} else if filepath.Base(fpath) == "then" {
				then, err := p.ParseThen(f, fpath)
				if err != nil {
					return err
				}

				if p.currentCase == nil {
					return fmt.Errorf("Race condition? cancelling:", p.currentCase)
				}

				p.currentCase.Then = *then
			} else if filepath.Base(fpath) == "while" {
				whiles, err := p.ParseWhile(f, fpath)
				if err != nil {
					return err
				}

				p.currentCase.While = whiles
			} else {
				return UnexpectedFileError(fi)
			}

		} else {
			//@todo parse data files
		}

	}

	return nil
}

func (p *Parser) Parse() (*contract.ContractData, error) {

	//reset parser afterwards
	defer p.reset()

	//walk nodes
	err := filepath.Walk(p.Dir, p.visit)
	if err != nil {
		return nil, err
	}

	//return result
	return p.data, nil
}
<commit_msg>debugging...<commit_after>package lang

import (
	"bufio"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"github.com/dockpit/pit/contract"
)

var CaseEX = regexp.MustCompile(`^'(.*)'$`)
var ResourceEX = regexp.MustCompile(`^- (.*)`)
var VariableEX = regexp.MustCompile(`(\(.*?\))`)
var ValidHTTPMethods = []string{"GET", "POST", "PUT"} //@todo add more

func UnexpectedDirError(fi os.FileInfo) error {
	return fmt.Errorf("Parser encountered an unexpected directory: %s, expected a resource directory (starting with `- `), or a case directory formatted as `'case name'`", fi.Name())
}

func UnexpectedFileError(fi os.FileInfo) error {
	return fmt.Errorf("Parser encountered a file without an extension: '%s', only 'given', 'when', 'then' or 'while' is allowed", fi.Name())
}

func UnexpectedStateLineError(fpath, line string) error {
	return fmt.Errorf("Parser encountered a 'given' file '%s', with an invalid line: \n%s\n expected format \"<state provider name> '<state name>'\"", fpath, line)
}

func UnexpectedLinkLineError(fpath, line string) error {
	return fmt.Errorf("Parser encountered a 'while' file '%s' with an unexpected line: %s, expected format \"<service id> '<case name>'\"", fpath, line)
}

func UnexpectedLinkLineCaseNameError(fpath, line string) error {
	return fmt.Errorf("Parser encountered a 'while' file '%s' with a invalid casename: \"%s\", expected single-quoted name: e.g 'name of the case'", fpath, line)
}

func UnexpectedHeaderLineError(fpath, giv string) error {
	return fmt.Errorf("File '%s' has an unexpected header line: '%s', expected format 'Header-Key: Value'", fpath, giv)
}

func UnexpectedResponseLineError(fpath, line string) error {
	return fmt.Errorf("Parser encountered a 'then' file '%s' with an unexpected first line: %s, expected format '<HTTP Status Code> <Status Text>'", fpath, line)
}

func UnexpectedResponseLineCodeError(fpath, giv string, err error) error {
	return fmt.Errorf("Parser encountered a 'then' file '%s' with an unexpected status code: %s, expected a number. (%s)", fpath, giv, err)
}

func UnexpectedRequestLineError(fpath, line string) error {
	return fmt.Errorf("Parser encountered a 'when' file '%s' with an unexpected first line: %s, expected format '<HTTP method> <path>'", fpath, line)
}

func UnexpectedRequestLineMethodError(fpath, giv string) error {
	return fmt.Errorf("Parser encountered a 'when' file '%s' with an unexpected HTTP Method in the first line: '%s', expected one of: %s", fpath, giv, ValidHTTPMethods)
}

func UnexpectedRequestLinePathError(fpath, giv string) error {
	return fmt.Errorf("Parser encountered a 'when' file '%s' with an unexpected path in the first line: '%s', expected absolute path (starting with '/')", fpath, giv)
}

type Node struct {
	Pattern  string
	Children []*Node
}

func NewNode() *Node {
	return &Node{
		Pattern:  "/",
		Children: []*Node{},
	}
}

func (n *Node) Append(nn *Node, part string) {
	n.Children = append(n.Children, nn)

	nn.Pattern = path.Join(n.Pattern, part)
}

//
//
//
type Parser struct {
	Dir string

	data  *contract.ContractData
	nodes map[string]*Node
	cases map[string]string

	currentNode     *Node
	currentResource *contract.ResourceData
	currentCase     *contract.CaseData
}

func NewParser(dir string) *Parser {
	p := &Parser{
		Dir: dir,
	}
	p.reset()
	return p
}

func (p *Parser) reset() {
	root := NewNode()
	p.data = &contract.ContractData{}
	p.nodes = map[string]*Node{".": root}
	p.cases = map[string]string{}
	p.currentNode = root
	p.currentResource = nil
	p.currentCase = nil
}

func (p *Parser) ParseHTTPMessage(r io.ReadCloser, fpath string) (string, http.Header, string, error) {

	inbody := false
	rline := ""
	hlines := []string{}
	body := ""
	headers := make(http.Header)

	s := bufio.NewScanner(r)
	for s.Scan() {
		if !inbody {
			//first line should be the request line
			if rline == "" {
				rline = s.Text()
				continue
			}

			//empty line indicates that we will be parsing the body
			if s.Text() == "" {
				inbody = true
				continue
			}

			//add as headers
			hlines = append(hlines, s.Text())
		} else {
			//res is assumed to be body
			body += s.Text()
		}
	}

	//check/parse header format
	for _, h := range hlines {
		hp := strings.SplitN(h, ":", 2)
		if len(hp) != 2 {
			return rline, headers, body, UnexpectedHeaderLineError(fpath, h)
		}

		headers.Add(http.CanonicalHeaderKey(hp[0]), strings.TrimSpace(hp[1]))
	}

	return rline, headers, body, nil
}

// parses a when file loosely based on the http request spec
func (p *Parser) ParseWhen(r io.ReadCloser, fpath string) (*contract.When, error) {
	w := &contract.When{}

	rline, headers, body, err := p.ParseHTTPMessage(r, fpath)
	if err != nil {
		return nil, err
	}

	//request line should be 2 parts seperated by a space
	rlinep := strings.SplitN(rline, " ", 2)
	if len(rlinep) != 2 {
		return nil, UnexpectedRequestLineError(fpath, rline)
	}

	//check method format
	method := ""
	for _, m := range ValidHTTPMethods {
		if m == rlinep[0] {
			method = m
			break
		}
	}

	if method == "" {
		return nil, UnexpectedRequestLineMethodError(fpath, rlinep[0])
	}
	w.Method = method

	//check path
	if !path.IsAbs(rlinep[1]) {
		return nil, UnexpectedRequestLinePathError(fpath, rlinep[1])
	}
	w.Path = rlinep[1]
	w.Headers = headers
	w.Body = body

	return w, nil
}

// parses a then file loosely based on the format of a standard http message
func (p *Parser) ParseThen(r io.ReadCloser, fpath string) (*contract.Then, error) {
	t := &contract.Then{}

	//parse as a standard http message
	rline, headers, body, err := p.ParseHTTPMessage(r, fpath)
	if err != nil {
		return nil, err
	}

	//response line should be 2 parts seperated by a space
	rlinep := strings.SplitN(rline, " ", 2)
	if len(rlinep) != 2 {
		return nil, UnexpectedResponseLineError(fpath, rline)
	}

	//first one should be parseable as int
	code, err := strconv.Atoi(rlinep[0])
	if err != nil {
		return nil, UnexpectedResponseLineCodeError(fpath, rlinep[0], err)
	}

	t.StatusCode = code
	t.Status = rlinep[1]
	t.Headers = headers
	t.Body = body
	return t, nil
}

// parses a 'while' file
func (p *Parser) ParseWhile(r io.ReadCloser, fpath string) ([]contract.While, error) {
	ws := []contract.While{}

	s := bufio.NewScanner(r)
	for s.Scan() {

		//dont mind empty lines
		if strings.TrimSpace(s.Text()) == "" {
			continue
		}

		//every non-empty line should have space seperated link
		wp := strings.SplitN(s.Text(), " ", 2)
		if len(wp) != 2 {
			return ws, UnexpectedLinkLineError(fpath, s.Text())
		}

		cname := p.ToCaseName(wp[1])
		if cname == "" {
			return ws, UnexpectedLinkLineCaseNameError(fpath, wp[1])
		}

		//create while for case
		ws = append(ws, contract.While{
			ID:       wp[0],
			CaseName: cname,
		})
	}

	return ws, nil
}

// parses a 'given' file
func (p *Parser) ParseGiven(r io.ReadCloser, fpath string) (map[string]contract.Given, error) {
	gs := make(map[string]contract.Given)

	s := bufio.NewScanner(r)
	for s.Scan() {

		//dont mind empty lines
		if strings.TrimSpace(s.Text()) == "" {
			continue
		}

		//every non-empty line should have space seperated link
		gp := strings.SplitN(s.Text(), ":", 2)
		if len(gp) != 2 {
			return gs, UnexpectedStateLineError(fpath, s.Text())
		}

		//extract provider name
		pname := strings.TrimSpace(gp[0])
		if pname == "" {
			return gs, UnexpectedStateLineError(fpath, s.Text())
		}

		//extract state name as case name
		sname := p.ToCaseName(strings.TrimSpace(gp[1]))
		if sname == "" {
			return gs, UnexpectedStateLineError(fpath, s.Text())
		}

		//set given
		gs[pname] = contract.Given{
			Name: sname,
		}
	}

	return gs, nil
}

//
// Returns wether a given basename of a file path denotes a resource
func (p *Parser) ToResourcePatternPart(basename string) string {
	m := ResourceEX.FindStringSubmatch(basename)
	if m == nil {
		return ""
	}

	//replace variables by sinatra style vars
	res := VariableEX.ReplaceAllFunc([]byte(m[1]), func(in []byte) []byte {
		v := in[1 : len(in)-1]
		return append([]byte(":"), v...)
	})

	return string(res)
}

//
// Returns wether a given basename of a file path denotes a case example
func (p *Parser) ToCaseName(basename string) string {
	m := CaseEX.FindStringSubmatch(basename)
	if m == nil {
		return ""
	}

	return m[1]
}

func (p *Parser) visit(fpath string, fi os.FileInfo, err error) error {

	//cancel walk if something went wrong
	if err != nil {
		return err
	}

	//skip root
	if fpath == p.Dir {
		return nil
	}

	//care only about relative path
	rel, err := filepath.Rel(p.Dir, fpath)
	if err != nil {
		return err
	}

	//directories are expected to be either resources or cases
	if fi.IsDir() {
		if part := p.ToResourcePatternPart(filepath.Base(rel)); part != "" {
			var parent *Node
			var ok bool

			//create and add new resource node
			p.currentNode = NewNode()
			p.nodes[rel] = p.currentNode

			//get parent node
			if parent, ok = p.nodes[filepath.Dir(rel)]; !ok {
				return fmt.Errorf("No parent node found for '%s'", fpath)
			}

			//apprent to parent
			parent.Append(p.currentNode, part)

			//use node structure to create and append resource to contract
			p.currentResource = &contract.ResourceData{
				Pattern: p.currentNode.Pattern,
				Cases:   []*contract.CaseData{},
			}

			// add to contract data
			p.data.Resources = append(p.data.Resources, p.currentResource)

		} else if cname := p.ToCaseName(filepath.Base(rel)); cname != "" {

			//get current resource (if any)
			res := p.currentResource
			if res == nil {
				return fmt.Errorf("Case folder '%s' (%s) is outside a resource", cname, fpath)
			}

			//case name must be unique
			if ex, ok := p.cases[cname]; ok {
				return fmt.Errorf("Case with name '%s' (%s) already exists in '%s'", cname, fpath, filepath.Dir(ex))
			}

			//create the case from available data
			p.currentCase = &contract.CaseData{
				Name: cname,
				When: contract.When{},
				Then: contract.Then{},
			}

			//and append to resource
			res.Cases = append(res.Cases, p.currentCase)
			p.cases[cname] = fpath
		} else {
			return UnexpectedDirError(fi)
		}
	} else {

		//files without extension have to be either when/then/given/while
		if filepath.Ext(fpath) == "" {

			f, err := os.Open(fpath)
			if err != nil {
				return err
			}
			defer f.Close()

			//'keywords;
			if filepath.Base(fpath) == "given" {
				given, err := p.ParseGiven(f, fpath)
				if err != nil {
					return err
				}

				p.currentCase.Given = given
			} else if filepath.Base(fpath) == "when" {
				when, err := p.ParseWhen(f, fpath)
				if err != nil {
					return err
				}

				p.currentCase.When = *when
			} else if filepath.Base(fpath) == "then" {
				then, err := p.ParseThen(f, fpath)
				if err != nil {
					return err
				}

				if p.currentCase == nil {
					return fmt.Errorf("When encountered without current case: %s", fpath)
				}

				p.currentCase.Then = *then
			} else if filepath.Base(fpath) == "while" {
				whiles, err := p.ParseWhile(f, fpath)
				if err != nil {
					return err
				}

				p.currentCase.While = whiles
			} else {
				return UnexpectedFileError(fi)
			}

		} else {
			//@todo parse data files
		}

	}

	return nil
}

func (p *Parser) Parse() (*contract.ContractData, error) {

	//reset parser afterwards
	defer p.reset()

	//walk nodes
	err := filepath.Walk(p.Dir, p.visit)
	if err != nil {
		return nil, err
	}

	//return result
	return p.data, nil
}
<|endoftext|>


<commit_before>package demoinfocs

import (
	bs "github.com/markus-wa/demoinfocs-golang/bitread"
	"github.com/markus-wa/demoinfocs-golang/common"
	"github.com/markus-wa/demoinfocs-golang/msg"
	"github.com/markus-wa/demoinfocs-golang/st"
	dp "github.com/markus-wa/godispatch"
	"io"
)

// TODO?: create struct GameState for all game-state relevant stuff

// Parser can parse a CS:GO demo.
// Creating a Parser is done via NewParser().
// To start off use Parser.ParseHeader() to parse the demo header.
// After parsing the header Parser.ParseNextTick() and Parser.ParseToEnd() can be used to parse the demo.
// Use Parser.RegisterEventHandler() to receive notifications about events.
type Parser struct {
	bitReader             *bs.BitReader
	stParser              st.Parser
	msgDispatcher         dp.Dispatcher
	eventDispatcher       dp.Dispatcher
	msgQueue              chan interface{}
	currentTick           int
	ingameTick            int
	header                *common.DemoHeader // Pointer so we can check for nil
	equipmentMapping      map[*st.ServerClass]common.EquipmentElement
	rawPlayers            [maxPlayers]*common.PlayerInfo
	players               map[int]*common.Player
	connectedPlayers      map[int]*common.Player
	additionalPlayerInfo  [maxPlayers]common.AdditionalPlayerInformation
	entities              map[int]*st.Entity
	modelPreCache         []string                      // Used to find out whether a weapon is a p250 or cz for example (same id)
	weapons               [maxEntities]common.Equipment // Used to remember what a weapon is (p250 / cz etc.)
	tState                TeamState
	ctState               TeamState
	bombsiteA             bombsiteInfo
	bombsiteB             bombsiteInfo
	triggers              []*boundingBoxInformation
	instanceBaselines     map[int][]byte
	preprocessedBaselines map[int]map[int]st.PropValue
	gehDescriptors        map[int32]*msg.CSVCMsg_GameEventListDescriptorT
	stringTables          []*msg.CSVCMsg_CreateStringTable
	cancelChan            chan struct{}
}

// Map returns the map name. E.g. de_dust2 or de_inferno.
func (p *Parser) Map() string {
	return p.header.MapName
}

// Participants returns all connected players.
// This includes spectators.
func (p *Parser) Participants() []*common.Player {
	r := make([]*common.Player, 0, len(p.connectedPlayers))
	for _, ptcp := range p.connectedPlayers {
		r = append(r, ptcp)
	}
	return r
}

// PlayingParticipants returns all players that aren't spectating.
func (p *Parser) PlayingParticipants() []*common.Player {
	r := make([]*common.Player, 0, len(p.connectedPlayers))
	for _, ptcp := range p.connectedPlayers {
		// FIXME: Why do we have to check for nil here???
		if ptcp != nil && ptcp.Team != common.Team_Spectators {
			r = append(r, ptcp)
		}
	}
	return r
}

// TickRate returns the tick rate of the demo.
// VolvoPlx128BitKTnxBye
func (p *Parser) TickRate() float32 {
	return float32(p.header.PlaybackFrames) / p.header.PlaybackTime
}

// TickTime returns the time a single tick takes in seconds.
func (p *Parser) TickTime() float32 {
	return p.header.PlaybackTime / float32(p.header.PlaybackFrames)
}

// Progress returns the parsing progress from 0 to 1.
// Where 0 means nothing has been parsed yet and 1 means the demo has been parsed to the end.
// Might not actually be reliable since it's just based on the reported tick count of the header.
func (p *Parser) Progress() float32 {
	return float32(p.currentTick) / float32(p.header.PlaybackFrames)
}

// CurrentTick return the number of the current tick.
// Starts with tick 0.
func (p *Parser) CurrentTick() int {
	return p.currentTick
}

func (p *Parser) IngameTick() int {
	return p.ingameTick
}

// CurrentTime returns the ingame time in seconds since the start of the demo.
func (p *Parser) CurrentTime() float32 {
	return float32(p.currentTick) * p.TickTime()
}

// RegisterEventHandler registers a handler for game events.
// Must be of type func(<EventType>) where EventType is the kind of event that is handled.
// To catch all events func(interface{}) can be used.
// Parameter handler has to be of type interface{} because lolnogenerics.
func (p *Parser) RegisterEventHandler(handler interface{}) {
	p.eventDispatcher.RegisterHandler(handler)
}

// CTState returns the TeamState of the CT team.
// Make sure you handle swapping sides propperly if you keep the reference.
func (p *Parser) CTState() *TeamState {
	return &p.ctState
}

// TState returns the TeamState of the T team.
// Make sure you handle swapping sides propperly if you keep the reference.
func (p *Parser) TState() *TeamState {
	return &p.tState
}

// NewParser creates a new Parser on the basis of an io.Reader
// - like os.File or bytes.Reader - that reads demo data.
func NewParser(demostream io.Reader) *Parser {
	var p Parser
	// Init parser
	p.bitReader = bs.NewLargeBitReader(demostream)
	p.msgQueue = make(chan interface{}, 8)
	p.instanceBaselines = make(map[int][]byte)
	p.preprocessedBaselines = make(map[int]map[int]st.PropValue)
	p.equipmentMapping = make(map[*st.ServerClass]common.EquipmentElement)
	p.players = make(map[int]*common.Player)
	p.connectedPlayers = make(map[int]*common.Player)
	p.entities = make(map[int]*st.Entity)
	p.cancelChan = make(chan struct{}, 1)

	// Attach proto msg handlers
	p.msgDispatcher.RegisterHandler(p.handlePacketEntities)
	p.msgDispatcher.RegisterHandler(p.handleGameEventList)
	p.msgDispatcher.RegisterHandler(p.handleGameEvent)
	p.msgDispatcher.RegisterHandler(p.handleCreateStringTable)
	p.msgDispatcher.RegisterHandler(p.handleUpdateStringTable)

	p.msgDispatcher.AddQueues(p.msgQueue)
	return &p
}
<commit_msg>Fixed misspellings of 'properly'<commit_after>package demoinfocs

import (
	bs "github.com/markus-wa/demoinfocs-golang/bitread"
	"github.com/markus-wa/demoinfocs-golang/common"
	"github.com/markus-wa/demoinfocs-golang/msg"
	"github.com/markus-wa/demoinfocs-golang/st"
	dp "github.com/markus-wa/godispatch"
	"io"
)

// TODO?: create struct GameState for all game-state relevant stuff

// Parser can parse a CS:GO demo.
// Creating a Parser is done via NewParser().
// To start off use Parser.ParseHeader() to parse the demo header.
// After parsing the header Parser.ParseNextTick() and Parser.ParseToEnd() can be used to parse the demo.
// Use Parser.RegisterEventHandler() to receive notifications about events.
type Parser struct {
	bitReader             *bs.BitReader
	stParser              st.Parser
	msgDispatcher         dp.Dispatcher
	eventDispatcher       dp.Dispatcher
	msgQueue              chan interface{}
	currentTick           int
	ingameTick            int
	header                *common.DemoHeader // Pointer so we can check for nil
	equipmentMapping      map[*st.ServerClass]common.EquipmentElement
	rawPlayers            [maxPlayers]*common.PlayerInfo
	players               map[int]*common.Player
	connectedPlayers      map[int]*common.Player
	additionalPlayerInfo  [maxPlayers]common.AdditionalPlayerInformation
	entities              map[int]*st.Entity
	modelPreCache         []string                      // Used to find out whether a weapon is a p250 or cz for example (same id)
	weapons               [maxEntities]common.Equipment // Used to remember what a weapon is (p250 / cz etc.)
	tState                TeamState
	ctState               TeamState
	bombsiteA             bombsiteInfo
	bombsiteB             bombsiteInfo
	triggers              []*boundingBoxInformation
	instanceBaselines     map[int][]byte
	preprocessedBaselines map[int]map[int]st.PropValue
	gehDescriptors        map[int32]*msg.CSVCMsg_GameEventListDescriptorT
	stringTables          []*msg.CSVCMsg_CreateStringTable
	cancelChan            chan struct{}
}

// Map returns the map name. E.g. de_dust2 or de_inferno.
func (p *Parser) Map() string {
	return p.header.MapName
}

// Participants returns all connected players.
// This includes spectators.
func (p *Parser) Participants() []*common.Player {
	r := make([]*common.Player, 0, len(p.connectedPlayers))
	for _, ptcp := range p.connectedPlayers {
		r = append(r, ptcp)
	}
	return r
}

// PlayingParticipants returns all players that aren't spectating.
func (p *Parser) PlayingParticipants() []*common.Player {
	r := make([]*common.Player, 0, len(p.connectedPlayers))
	for _, ptcp := range p.connectedPlayers {
		// FIXME: Why do we have to check for nil here???
		if ptcp != nil && ptcp.Team != common.Team_Spectators {
			r = append(r, ptcp)
		}
	}
	return r
}

// TickRate returns the tick rate of the demo.
// VolvoPlx128BitKTnxBye
func (p *Parser) TickRate() float32 {
	return float32(p.header.PlaybackFrames) / p.header.PlaybackTime
}

// TickTime returns the time a single tick takes in seconds.
func (p *Parser) TickTime() float32 {
	return p.header.PlaybackTime / float32(p.header.PlaybackFrames)
}

// Progress returns the parsing progress from 0 to 1.
// Where 0 means nothing has been parsed yet and 1 means the demo has been parsed to the end.
// Might not actually be reliable since it's just based on the reported tick count of the header.
func (p *Parser) Progress() float32 {
	return float32(p.currentTick) / float32(p.header.PlaybackFrames)
}

// CurrentTick return the number of the current tick.
// Starts with tick 0.
func (p *Parser) CurrentTick() int {
	return p.currentTick
}

func (p *Parser) IngameTick() int {
	return p.ingameTick
}

// CurrentTime returns the ingame time in seconds since the start of the demo.
func (p *Parser) CurrentTime() float32 {
	return float32(p.currentTick) * p.TickTime()
}

// RegisterEventHandler registers a handler for game events.
// Must be of type func(<EventType>) where EventType is the kind of event that is handled.
// To catch all events func(interface{}) can be used.
// Parameter handler has to be of type interface{} because lolnogenerics.
func (p *Parser) RegisterEventHandler(handler interface{}) {
	p.eventDispatcher.RegisterHandler(handler)
}

// CTState returns the TeamState of the CT team.
// Make sure you handle swapping sides properly if you keep the reference.
func (p *Parser) CTState() *TeamState {
	return &p.ctState
}

// TState returns the TeamState of the T team.
// Make sure you handle swapping sides properly if you keep the reference.
func (p *Parser) TState() *TeamState {
	return &p.tState
}

// NewParser creates a new Parser on the basis of an io.Reader
// - like os.File or bytes.Reader - that reads demo data.
func NewParser(demostream io.Reader) *Parser {
	var p Parser
	// Init parser
	p.bitReader = bs.NewLargeBitReader(demostream)
	p.msgQueue = make(chan interface{}, 8)
	p.instanceBaselines = make(map[int][]byte)
	p.preprocessedBaselines = make(map[int]map[int]st.PropValue)
	p.equipmentMapping = make(map[*st.ServerClass]common.EquipmentElement)
	p.players = make(map[int]*common.Player)
	p.connectedPlayers = make(map[int]*common.Player)
	p.entities = make(map[int]*st.Entity)
	p.cancelChan = make(chan struct{}, 1)

	// Attach proto msg handlers
	p.msgDispatcher.RegisterHandler(p.handlePacketEntities)
	p.msgDispatcher.RegisterHandler(p.handleGameEventList)
	p.msgDispatcher.RegisterHandler(p.handleGameEvent)
	p.msgDispatcher.RegisterHandler(p.handleCreateStringTable)
	p.msgDispatcher.RegisterHandler(p.handleUpdateStringTable)

	p.msgDispatcher.AddQueues(p.msgQueue)
	return &p
}
<|endoftext|>


<commit_before>package gold

import (
	"errors"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"strings"

	"github.com/yosssi/gohtml"
)

const (
	unicodeTab            = 9
	unicodeSpace          = 32
	unicodeDoubleQuote    = 34
	indentTop             = 0
	extendsBlockTokensLen = 2
	goldExtension         = ".gold"
)

// A generator represents an HTML generator.
type Generator struct {
	cache       bool
	templates   map[string]*template.Template
	gtemplates  map[string]*Template
	helperFuncs template.FuncMap
	baseDir     string
	prettyPrint bool
	debugWriter io.Writer
}

// ParseFile parses a Gold template file and returns an HTML template.
func (g *Generator) ParseFile(path string) (*template.Template, error) {
	return g.generateTemplate(path, nil)
}

// SetHelpers sets the helperFuncs to the generator.
func (g *Generator) SetHelpers(helperFuncs template.FuncMap) *Generator {
	g.helperFuncs = helperFuncs
	return g
}

// SetBaseDir sets the base directory to the generator.
func (g *Generator) SetBaseDir(baseDir string) *Generator {
	g.baseDir = baseDir
	return g
}

// SetPrettyPrint sets the prettyPrint to the generator.
func (g *Generator) SetPrettyPrint(prettyPrint bool) *Generator {
	g.prettyPrint = prettyPrint
	return g
}

// SetDebugWriter sets a debugWriter to the generator.
func (g *Generator) SetDebugWriter(debugWriter io.Writer) *Generator {
	g.debugWriter = debugWriter
	return g
}

// ParseString parses a Gold template string and returns an HTML template.
func (g *Generator) ParseString(stringTemplates map[string]string, name string) (*template.Template, error) {
	return g.generateTemplate(name, stringTemplates)
}

// generateTemplate parses a Gold template and returns an HTML template.
func (g *Generator) generateTemplate(path string, stringTemplates map[string]string) (*template.Template, error) {
	if g.cache {
		if tpl, prs := g.templates[path]; prs {
			return tpl, nil
		}
	}
	gtpl, err := g.parse(path, stringTemplates)
	if err != nil {
		return nil, err
	}
	html, err := gtpl.Html(stringTemplates, nil)
	if err != nil {
		return nil, err
	}
	if g.prettyPrint {
		html = gohtml.Format(html)
	}
	if g.debugWriter != nil {
		debugStr := gohtml.AddLineNo(html)
		g.debugWriter.Write([]byte(debugStr + "\n"))
	}
	tpl := template.New(path)
	tpl.Funcs(g.helperFuncs)
	_, err = tpl.Parse(html)
	if err != nil {
		return nil, err
	}
	if g.cache {
		g.templates[path] = tpl
	}
	return tpl, nil
}

// parse parses a Gold template file and returns a Gold template.
func (g *Generator) parse(path string, stringTemplates map[string]string) (*Template, error) {
	path = Path(g.baseDir, path)
	if g.cache {
		if tpl, prs := g.gtemplates[path]; prs {
			return tpl, nil
		}
	}
	var s string
	if stringTemplates == nil {
		b, err := ioutil.ReadFile(path)
		if err != nil {
			return nil, err
		}
		s = string(b)
	} else {
		s = stringTemplates[path]
	}
	lines := strings.Split(formatLf(s), "\n")
	i, l := 0, len(lines)
	tpl := NewTemplate(path, g)
	for i < l {
		line := lines[i]
		i++
		if empty(line) {
			continue
		}
		if topElement(line) {
			switch {
			case isExtends(line):
				tokens := strings.Split(strings.TrimSpace(line), " ")
				if l := len(tokens); l != extendsBlockTokensLen {
					return nil, errors.New(fmt.Sprintf("The line tokens length is invalid. (expected: %d, actual: %d, line no: %d, template: %s, line: %s)", extendsBlockTokensLen, l, i, tpl.Path, strings.TrimSpace(line)))
				}
				superTplPath := tokens[1]
				var superTpl *Template
				var err error
				if stringTemplates == nil {
					if CurrentDirectoryBasedPath(superTplPath) {
						superTplPath = tpl.Dir() + superTplPath + goldExtension
					} else {
						superTplPath = Path(g.baseDir, superTplPath+goldExtension)
					}
					superTpl, err = g.parse(superTplPath, nil)
				} else {
					superTpl, err = g.parse(superTplPath, stringTemplates)
				}
				if err != nil {
					return nil, err
				}
				superTpl.Sub = tpl
				tpl.Super = superTpl
			case tpl.Super != nil && isBlock(line):
				tokens := strings.Split(strings.TrimSpace(line), " ")
				if l := len(tokens); l != extendsBlockTokensLen {
					return nil, errors.New(fmt.Sprintf("The line tokens length is invalid. (expected: %d, actual: %d, line no: %d, template: %s, line: %s)", extendsBlockTokensLen, l, i, tpl.Path, strings.TrimSpace(line)))
				}
				block := &Block{Name: tokens[1], Template: tpl}
				tpl.AddBlock(block.Name, block)
				if err := appendChildren(block, lines, &i, &l, indentTop, false, "", tpl); err != nil {
					return nil, err
				}
			default:
				e, err := NewElement(line, i, indentTop, nil, tpl, nil)
				if err != nil {
					return nil, err
				}
				tpl.AppendElement(e)
				if err := appendChildren(e, lines, &i, &l, indentTop, e.RawContent, e.Type, tpl); err != nil {
					return nil, err
				}
			}
		}
	}
	if g.cache {
		g.gtemplates[path] = tpl
	}
	return tpl, nil
}

// NewGenerator generages a generator and returns it.
func NewGenerator(cache bool) *Generator {
	return &Generator{cache: cache, templates: make(map[string]*template.Template), gtemplates: make(map[string]*Template)}
}

// formatLf returns a string whose line feed codes are replaced with LF.
func formatLf(s string) string {
	return strings.Replace(strings.Replace(s, "\r\n", "\n", -1), "\r", "\n", -1)
}

// indent returns the string's indent.
func indent(s string) int {
	i := 0
	space := false
indentLoop:
	for _, b := range s {
		switch b {
		case unicodeTab:
			i++
		case unicodeSpace:
			if space {
				i++
				space = false
			} else {
				space = true
			}
		default:
			break indentLoop
		}
	}
	return i
}

// empty returns if the string is empty or not.
func empty(s string) bool {
	return strings.TrimSpace(s) == ""
}

// topElement returns if the string is the top element.
func topElement(s string) bool {
	return indent(s) == indentTop
}

// appendChildren fetches the lines and appends child elements to the element.
func appendChildren(parent Container, lines []string, i *int, l *int, parentIndent int, parentRawContent bool, parentType string, tpl *Template) error {
	for *i < *l {
		line := lines[*i]
		if empty(line) {
			*i++
			continue
		}
		indent := indent(line)
		switch {
		case parentRawContent || parentType == TypeContent:
			switch {
			case indent < parentIndent+1:
				return nil
			default:
				if err := appendChild(parent, &line, &indent, lines, i, l, tpl); err != nil {
					return err
				}
			}
		default:
			switch {
			case indent < parentIndent+1:
				return nil
			case indent == parentIndent+1:
				if err := appendChild(parent, &line, &indent, lines, i, l, tpl); err != nil {
					return err
				}
			case indent > parentIndent+1:
				return errors.New(fmt.Sprintf("The indent of the line %d is invalid. [template: %s][lineno: %d][line: %s]", *i+1, tpl.Path, *i+1, strings.TrimSpace(line)))
			}
		}
	}
	return nil
}

// appendChild appends the child element to the parent element.
func appendChild(parent Container, line *string, indent *int, lines []string, i *int, l *int, tpl *Template) error {
	var child *Element
	var err error
	switch p := parent.(type) {
	case *Block:
		child, err = NewElement(*line, *i+1, *indent, nil, nil, p)
	case *Element:
		child, err = NewElement(*line, *i+1, *indent, p, nil, nil)
	}
	if err != nil {
		return err
	}
	parent.AppendChild(child)
	*i++
	err = appendChildren(child, lines, i, l, child.Indent, child.RawContent, child.Type, tpl)
	if err != nil {
		return err
	}
	return nil
}

// isExtends returns if the line's prefix is "extends" or not.
func isExtends(line string) bool {
	return strings.HasPrefix(line, "extends ") || line == "extends"
}

// isBlock returns if the line's prefix is "block" or not.
func isBlock(line string) bool {
	return strings.HasPrefix(line, "block ") || line == "block"
}
<commit_msg>Updated generator.go.<commit_after>package gold

import (
	"errors"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"strings"

	"github.com/yosssi/gohtml"
)

const (
	unicodeTab            = 9
	unicodeSpace          = 32
	unicodeDoubleQuote    = 34
	indentTop             = 0
	extendsBlockTokensLen = 2
	goldExtension         = ".gold"
)

// A generator represents an HTML generator.
type Generator struct {
	cache       bool
	templates   map[string]*template.Template
	htmls       map[string]string
	gtemplates  map[string]*Template
	helperFuncs template.FuncMap
	baseDir     string
	prettyPrint bool
	debugWriter io.Writer
}

// ParseFile parses a Gold template file and returns an HTML template.
func (g *Generator) ParseFile(path string) (*template.Template, error) {
	tpl, _, err := g.generateTemplate(path, nil)
	return tpl, err
}

// ParseFileWithHTML parses a Gold template file and returns an HTML template and HTML source codes.
func (g *Generator) ParseFileWithHTML(path string) (*template.Template, string, error) {
	return g.generateTemplate(path, nil)
}

// SetHelpers sets the helperFuncs to the generator.
func (g *Generator) SetHelpers(helperFuncs template.FuncMap) *Generator {
	g.helperFuncs = helperFuncs
	return g
}

// SetBaseDir sets the base directory to the generator.
func (g *Generator) SetBaseDir(baseDir string) *Generator {
	g.baseDir = baseDir
	return g
}

// SetPrettyPrint sets the prettyPrint to the generator.
func (g *Generator) SetPrettyPrint(prettyPrint bool) *Generator {
	g.prettyPrint = prettyPrint
	return g
}

// SetDebugWriter sets a debugWriter to the generator.
func (g *Generator) SetDebugWriter(debugWriter io.Writer) *Generator {
	g.debugWriter = debugWriter
	return g
}

// ParseString parses a Gold template string and returns an HTML template.
func (g *Generator) ParseString(stringTemplates map[string]string, name string) (*template.Template, error) {
	tpl, _, err := g.generateTemplate(name, stringTemplates)
	return tpl, err
}

// ParseStringWithHTML parses a Gold template string and returns an HTML template and HTML source codes.
func (g *Generator) ParseStringWithHTML(stringTemplates map[string]string, name string) (*template.Template, string, error) {
	return g.generateTemplate(name, stringTemplates)
}

// generateTemplate parses a Gold template and returns an HTML template.
func (g *Generator) generateTemplate(path string, stringTemplates map[string]string) (*template.Template, string, error) {
	if g.cache {
		if tpl, prs := g.templates[path]; prs {
			html := g.htmls[path]
			return tpl, html, nil
		}
	}
	gtpl, err := g.parse(path, stringTemplates)
	if err != nil {
		return nil, "", err
	}
	html, err := gtpl.Html(stringTemplates, nil)
	if err != nil {
		return nil, "", err
	}
	if g.prettyPrint {
		html = gohtml.Format(html)
	}
	if g.debugWriter != nil {
		debugStr := gohtml.AddLineNo(html)
		g.debugWriter.Write([]byte(debugStr + "\n"))
	}
	tpl := template.New(path)
	tpl.Funcs(g.helperFuncs)
	_, err = tpl.Parse(html)
	if err != nil {
		return nil, "", err
	}
	if g.cache {
		g.templates[path] = tpl
		g.htmls[path] = html
	}
	return tpl, html, nil
}

// parse parses a Gold template file and returns a Gold template.
func (g *Generator) parse(path string, stringTemplates map[string]string) (*Template, error) {
	path = Path(g.baseDir, path)
	if g.cache {
		if tpl, prs := g.gtemplates[path]; prs {
			return tpl, nil
		}
	}
	var s string
	if stringTemplates == nil {
		b, err := ioutil.ReadFile(path)
		if err != nil {
			return nil, err
		}
		s = string(b)
	} else {
		s = stringTemplates[path]
	}
	lines := strings.Split(formatLf(s), "\n")
	i, l := 0, len(lines)
	tpl := NewTemplate(path, g)
	for i < l {
		line := lines[i]
		i++
		if empty(line) {
			continue
		}
		if topElement(line) {
			switch {
			case isExtends(line):
				tokens := strings.Split(strings.TrimSpace(line), " ")
				if l := len(tokens); l != extendsBlockTokensLen {
					return nil, errors.New(fmt.Sprintf("The line tokens length is invalid. (expected: %d, actual: %d, line no: %d, template: %s, line: %s)", extendsBlockTokensLen, l, i, tpl.Path, strings.TrimSpace(line)))
				}
				superTplPath := tokens[1]
				var superTpl *Template
				var err error
				if stringTemplates == nil {
					if CurrentDirectoryBasedPath(superTplPath) {
						superTplPath = tpl.Dir() + superTplPath + goldExtension
					} else {
						superTplPath = Path(g.baseDir, superTplPath+goldExtension)
					}
					superTpl, err = g.parse(superTplPath, nil)
				} else {
					superTpl, err = g.parse(superTplPath, stringTemplates)
				}
				if err != nil {
					return nil, err
				}
				superTpl.Sub = tpl
				tpl.Super = superTpl
			case tpl.Super != nil && isBlock(line):
				tokens := strings.Split(strings.TrimSpace(line), " ")
				if l := len(tokens); l != extendsBlockTokensLen {
					return nil, errors.New(fmt.Sprintf("The line tokens length is invalid. (expected: %d, actual: %d, line no: %d, template: %s, line: %s)", extendsBlockTokensLen, l, i, tpl.Path, strings.TrimSpace(line)))
				}
				block := &Block{Name: tokens[1], Template: tpl}
				tpl.AddBlock(block.Name, block)
				if err := appendChildren(block, lines, &i, &l, indentTop, false, "", tpl); err != nil {
					return nil, err
				}
			default:
				e, err := NewElement(line, i, indentTop, nil, tpl, nil)
				if err != nil {
					return nil, err
				}
				tpl.AppendElement(e)
				if err := appendChildren(e, lines, &i, &l, indentTop, e.RawContent, e.Type, tpl); err != nil {
					return nil, err
				}
			}
		}
	}
	if g.cache {
		g.gtemplates[path] = tpl
	}
	return tpl, nil
}

// NewGenerator generages a generator and returns it.
func NewGenerator(cache bool) *Generator {
	return &Generator{cache: cache, templates: make(map[string]*template.Template), gtemplates: make(map[string]*Template), htmls: make(map[string]string)}
}

// formatLf returns a string whose line feed codes are replaced with LF.
func formatLf(s string) string {
	return strings.Replace(strings.Replace(s, "\r\n", "\n", -1), "\r", "\n", -1)
}

// indent returns the string's indent.
func indent(s string) int {
	i := 0
	space := false
indentLoop:
	for _, b := range s {
		switch b {
		case unicodeTab:
			i++
		case unicodeSpace:
			if space {
				i++
				space = false
			} else {
				space = true
			}
		default:
			break indentLoop
		}
	}
	return i
}

// empty returns if the string is empty or not.
func empty(s string) bool {
	return strings.TrimSpace(s) == ""
}

// topElement returns if the string is the top element.
func topElement(s string) bool {
	return indent(s) == indentTop
}

// appendChildren fetches the lines and appends child elements to the element.
func appendChildren(parent Container, lines []string, i *int, l *int, parentIndent int, parentRawContent bool, parentType string, tpl *Template) error {
	for *i < *l {
		line := lines[*i]
		if empty(line) {
			*i++
			continue
		}
		indent := indent(line)
		switch {
		case parentRawContent || parentType == TypeContent:
			switch {
			case indent < parentIndent+1:
				return nil
			default:
				if err := appendChild(parent, &line, &indent, lines, i, l, tpl); err != nil {
					return err
				}
			}
		default:
			switch {
			case indent < parentIndent+1:
				return nil
			case indent == parentIndent+1:
				if err := appendChild(parent, &line, &indent, lines, i, l, tpl); err != nil {
					return err
				}
			case indent > parentIndent+1:
				return errors.New(fmt.Sprintf("The indent of the line %d is invalid. [template: %s][lineno: %d][line: %s]", *i+1, tpl.Path, *i+1, strings.TrimSpace(line)))
			}
		}
	}
	return nil
}

// appendChild appends the child element to the parent element.
func appendChild(parent Container, line *string, indent *int, lines []string, i *int, l *int, tpl *Template) error {
	var child *Element
	var err error
	switch p := parent.(type) {
	case *Block:
		child, err = NewElement(*line, *i+1, *indent, nil, nil, p)
	case *Element:
		child, err = NewElement(*line, *i+1, *indent, p, nil, nil)
	}
	if err != nil {
		return err
	}
	parent.AppendChild(child)
	*i++
	err = appendChildren(child, lines, i, l, child.Indent, child.RawContent, child.Type, tpl)
	if err != nil {
		return err
	}
	return nil
}

// isExtends returns if the line's prefix is "extends" or not.
func isExtends(line string) bool {
	return strings.HasPrefix(line, "extends ") || line == "extends"
}

// isBlock returns if the line's prefix is "block" or not.
func isBlock(line string) bool {
	return strings.HasPrefix(line, "block ") || line == "block"
}
<|endoftext|>